Schema-by-interface options
Martin Aspeli <[email protected]> Fri, 8 Sep 2006 08:14:53 -0700 (PDT)
| Newsgroups | gmane.comp.web.zope.plone.archetypes.devel |
|---|---|
| Message-ID | <[email protected]> |
Hi guys, I'm still quite serious about wanting to make AT schemas more adaptable (pun intended). This is in reference to the basically-working solution I put in b-org, at http://svn.plone.org/svn/collective/borg/trunk/content/schema.py, and to the whitmo-schema-by-iface branch of Archetypes. One question right up front - the whitmo-schema-by-iface branch was created from the whitmo-iface-ageddon branch. This in turn was copied from 1.4. I'm planning to look at this for 1.5, but what is the status of the iface-ageddon branch? Has it been merged? Does it need to be merged? Secondly, to take a step back, I see two fundamental use cases that I'd like to support: 1) Replace the schema of an existing content type (most likely by extending it or changing parts of it, e.g. plugging in a new widget) 2) Add a particular schema field to a number of content types (e.g. add a "tags" field to all or some content types when a tagging solution is installed) There is one main challenge in making this work, which any solution will need to deal with: - Various bits of AT depend on there being ClassGen'd methods (getFoo() and setFoo() for field 'foo') for each field. Those methods get created only when the class is ClassGen'd. This in turn always checks the class attribute 'schema'. Class attributes cannot be made dynamic in the same way instance attributes can with @property. Therefore, you must run ClassGen each time the schema changes. This is the reason why VariableSchemaSupport is so slow, because it re-calculates on every access (mainly due to a bug/poor design). In b-org, I use a cache so that it only calls ClassGen the first time Schema() is called (or when Zope is restarted, it's not persistent), or after an ISchemaInvalidatedEvent is received. - Since this is basically infrastructure that is needed to work around AT suck, and since it needs to happen exactly once each time a schema is changed (not each time it's looked up!) I'd suggest that we make the Schema() method in BaseObject responsible for doing this, in the same way that Schema() in ExtensibleSchemaSupport in b-org does. I think it's important to meet both the original use cases with the design. The schema-by-interface branch is concerned really only with case (1) above (wholesale replacement of the schema, e.g. by registering a more specific adapter). The original discussion ended by saying that the adapter from context -> ISchema (which is what the Schema() method does - in fact, it's just a lambda self: ISchema(self)) could be replaced by one that knew about aggregation. That means each type that the hypothetical tagging solution could plug into would need to explicitly mark their ability to be schema-extended. At least, I think the default position should be extensibility, and I think that AT needs to come with the necessary machinery rather than leave it up to each component to invent it like b-org did. I also take some issue with this - http://dev.plone.org/archetypes/browser/Archetypes/branches/whitmo-schema-by-iface/Schema/adapters.py?rev=6267, which is the default adapter from IBaseObject to ISchema that is the result of Schema = labmda self: ISchema(self). This manipulates the __class__ and __dict__ of the adapter, which seems quite hacky (perhaps consistently with the rest of AT and ClassGen, but ...) I could imagine something like this would work, which I think would be quite easily doable based on the experience I gained from b-org (I could even adapt the tests....): o BaseObject's Schema() takes care of the schema cache like ExtensibleSchemaSupport in http://svn.plone.org/svn/collective/borg/trunk/content/schema.py. The ISchemaInvalidatedEvent etc. works the same way. As far as I can tell, this approach has negligible performance overhead (after the first schema fetch, which is slower), and third party code that needs schema changes to be reflected at run time (e.g. some install script for a new service that doesn't want to require a restart) can fire this event. o Schema() adapts self to ISchemaComposer, which is responsible for finding schemas and returning a single, aggregated schema. This schema is then cached (so that the composer is only invoked once unless the event is fired) and returned in an aq wrapper. o The schema composer looks up adapters from the context to ISchemaSource. The ISchemaSource is a callable that can return a schema. That way, you would write small adapters that simply had __call__(self): return Schema ( ... ) to plug in a new schema. Note that you could, for example, have an ITaggable marker interface and register the ISchemaSource for ITaggable, so that only ITaggable-marked objects get the 'tags' field. If you want a completely global version, you could register an adapter for Interface, and if you want a site-local (only-when-installed) schema, then you can register a local adapter (yay for Zope 2.10 - you can even do it with GenericSetup) o A default ISchemaSource component would look at klass.schema to find the schema, so as to provide full backwards compatability support. Simple cases (i.e. most cases) can keep doing schema = MySchema in the class definition. The ISchemaComposer, however, needs to know how to put the schemas together, and in which order. Obviously, if you have specific needs, you could register a specific ISchemaComposer adapter that may even ignore the ISchemaSource components (i.e. fi you just wanted a particular schema every time). However, there must be some generic version. One option (similar to how menus work in Zope 3) is to let each ISchemaSource specify an ordering, which is just an int. The composer could use that - it looks up all applicable adapters to ISchemaSource, orders them and then appends the schemas they produce in that order. An alternative (or additional step), which is what I do in b-org, is to let the ISchemaSoruce's __call__() method take the current schema as a parameter so that it can examine it and make decisions. Obviously, not every schema source will know about every other schema source though. Other patterns gratefully accepted! In fact, there are some problems that could arise, for example of two schema sources define fields with the same name but different types/meanings. AT has no good way of dealing with that (you'd need namespaces on fields; the whole ClassGen thing would have to go away, which would mean refactoring a lot of things that depend on it). I expect there will be a small number of general schema plug-ins, though, and these would need to be coded quite defensively. Specific schema sources for specific content types would be more common, since it gives you a way of doing a per-use-case modification of a type's schema without subclassing or modifying that type directly. In either case, I think the potential benefits are quite big. This is what I'd like to do, anyway - in time for Plone 3.0. It fits in well with the other generalisability-work we've been doing on the content menu and with viewlets. And I've already done it in b-org, so I know it can work and I know where the pitfalls are. I even have tests! :) Martin -- View this message in context: http://www.nabble.com/Schema-by-interface-options-tf2239709.html#a6210974 Sent from the Archetypes - devel forum at Nabble.com. ------------------------------------------------------------------------- Using Tomcat but need to do more? Need to support web services, security? Get stuff done quickly with pre-integrated technology to make your job easier Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642