Another long post, where below I take apart zope.formlib.namedtemplate, hoping it can be used as a source of insight into how to provide flexible extensibility (like in this example AnnotationsAdapter concept) without needing yet-another-ZCML-directive.
On 3/15/06, Martijn Faassen <[EMAIL PROTECTED]> wrote: > Jim Fulton wrote: > > This may not be documented as well as it should be, but not for lack of > > trying. > > > > 1. You can always provide the interface provided by an adapter > > when you register it. > > I realize this, but I was trying to implement the example as suggested, > also because it's clear that the tendency is away from specifying 'for' > and 'implements' in ZCML. I know I wouldn't have a problem if I use > 'for' and 'implements' in ZCML. I don't think there's anything wrong with specifying 'for' and 'implements' in ZCML. It serves a couple of purposes: 1. It is where you can override \ augment an existing configuration. 2. It's where you can specify those arguments for objects and situations that aren't easy to specify in Python. I prefer to see 'implements' and 'adapts' in Python code as much as possible, but there are certainly situations where specifying it in ZCML is fine. It's also allowable to provide those arguments in zope.component.provideAdapter / zapi.provideAdapter. > > 2. If a factory declares that it implements a single interface, > > then you can omit the interface when registering the adapter. > > > > If a factory is a class, it typically declares that it implements > > an interface via the implements call in the class statement. > > Well, I found this out by reading the code in zope.app.component. > > > If a factory is not a class, and if it allows attributes to be > > set on it, then the interface.implementor function can be used to make > > declarations for it. This is documeted in zope/interfaces/README.txt > > and zope/component/README.txt. > > This is one bit I was missing, thanks. > > Unfortunately I read in zope/interface/README.txt that the 'implementer' > function cannot be used for classes yet, so this will change the design > somewhat (I was using __call__, looks like I'll have to exploit lexical > scoping and generate a function on the fly). > > Using implements() on the class seems like lying, as the class is just > implementing a factory, not the functionality itself. Besides, I'd have > to change the class each time it gets called (it's using implementedBy > to check). When you say 'implements() on the class', do you mean:: class FooAdapter(...): implements(IFoo) I believe that the interfaces documentation says that this statement means 'instances of this class provide the IFoo interface'. zope.interface.classProvides is what you use to make declarations about the class itself. class FooAdapter(...): implements(IFoo) classProvides(IFooFactory) > Then that leaves convincing 'adaptedBy()'. One hack is to write the > 'for_' argument that's passed to the AnnotationAdapter() constructor > directly to an attribute called __component_adapts__. I don't think I > can use 'adapts()' on a lexically scoped function... > > I stand by my conclusions on this approach sounding simple in theory, > but still being a bit harder than it should be in practice. :) I feel the same way. I may have missed some obvious piece of documentation. Or maybe it's just not an obvious piece of documentation. But there do seem to be certain rules about what can register as an adapter. I believe this is why so many of the browser: directives generate new classes dynamically. There's a good possibility that you may be over-complicating the situation. Since I haven't tried implementing my suggestion myself (it was mostly wishful thinking on what I'd prefer to see), I can't say for sure. But NamedTemplates in formlib seem to be doing something similar... I think. In zope/formlib/form.py:: class Action(...) implements(interfaces.IAction) ... render = namedtemplate.NamedTemplate('render') @namedtemplate.implementation(interfaces.IAction) def render_submit_button(self): if not self.available(): return '' ... In zope/formlib/configure.zcml:: <adapter factory=".form.render_submit_button" name="render" /> The implementation of named templates (zope/formlib/namedtemplate.py) looks rather small. It uses zope.interface.implementer and zope.component.adapts. It's probably a good example of .... something. It's taken me a little while to figure it out from staring at it, but I think I finally get it now. I'll document my interpretation of it. Anyone's free to correct me if I'm wrong. I think this may help in providing ways of keeping the number of ZCML directives down. Looking at ``render = namedtemplate.NamedTemplate('render')``. That creates an instance of this class:: class NamedTemplate(object): def __init__(self, name): self.__name__ = name def __get__(self, instance, type=None): if instance is None: return self return component.getAdapter(instance, INamedTemplate, self.__name__) def __call__(self, instance, *args, **kw): self.__get__(instance)(*args, **kw) I don't think this has any benefits to an Annotations helper, but I thought it would help to start at the source. Basically, the Action class has an attribute, 'render', that when used on an instance will get an adapter with the name 'render' that provides INamedTemplate for the action instance. The __call__ method here allows it to be used in situations like: ``Action.render(action, ...)`` (going through the class instead of the instance to call the thing). Next we have [EMAIL PROTECTED](interfaces.IAction)`` which decorates (in this case) a function. It uses the following class:: class implementation: def __init__(self, view_type=None): self.view_type = view_type def __call__(self, descriptor): return NamedTemplateImplementation(descriptor, self.view_type) It's basically doing ``render_submit_button = NamedTemplateImplementation(render_submit_button, interfaces.IAction)``. This is where things get interesting:: class NamedTemplateImplementation: def __init__(self, descriptor, view_type=None): try: descriptor.__get__ except AttributeError: raise TypeError( "NamedTemplateImplementation must be passed a descriptor." ) self.descriptor = descriptor interface.implementer(INamedTemplate)(self) if view_type is not None: component.adapter(view_type)(self) def __call__(self, instance): return self.descriptor.__get__(instance, instance.__class__) In __init__, this particular instance calls interface.implementer and component.adapter on itself, which ultimately sets up the attributes ``__implemented__`` and ``__component_adapts__``. And its this instance that gets registered as an adapter factory, like:: <adapter factory=".form.render_submit_button" name="render" /> So now, ``.form.render_submit_button`` is a callable object (an instance) that has the __implemented__ and __component_adapts__ attributes set. The ZCML/zope.component machinery can see that and figure out that it can call ``render_submit_button`` to get the INamedTemplate adapter named 'render' for all IAction objects. **NamedTemplateImplementation instances are adapter factories.** If 'instance' in the __call__ were 'context', it might have been a bit more obvious that this is what was going on. But maybe this helps? This NamedTemplate stuff isn't exactly clear, and it is a bit magical on its own. Some of the special magical-looking parts have to do with making NamedTemplates be attributes (so that the action class can call 'self.render()'). Don't let that aspect get in the way of what can be learned here. This is a feature that allows specialized views/renderings to be provided. And it accomplishes all of this: * No new ZCML Directives - only adapters are registered. formlib's configure.zcml registers three adapters that provide INamedTemplate - one for rendering IAction implementations, two for forms (one for full page forms, one for sub-forms). It registers one other adapter to provide a TALES namespace. And that's it. For such a comprehensive and extensible package, that's awfully nice and light on the ZCML. * Works with both ViewPageTemplateFiles and functions. * Using overrides or sub-interfaces, you can provide other implementations (I'm using some base interfaces for an application I have now, based on IForm, with a replacement form template provided), without having an impact on the base software. * In setting up test harnesses or other situations where ZCML may not be loaded / loadable, it should be a little more obvious that provideAdapter(...) should work as expected. This is not obvious in many situations where a ZCML directive like 'vocabulary', 'renderer', and more are nice masks over provideAdapter()/provideUtility() - but those masks are unavailable outside of the ZCML run time (leaving you to your own devices to figure out how to get a vocabulary registered in just the right way to test a zope.schema.Choice field). * You can provide INamedTemplate implementations in the more classic way if you'd like:: class CancelButton(object): implements(INamedTemplate) adapts(ICancelAction) def __init__(self, context): self.context = context def __call__(self): if not self.context.available(): return u'' ... <adapter factory=".CancelButton" name="render" /> That last bullet is important. This is the "yeah, but how do I grow up beyond ___?" answer. With custom ZCML directives, there often comes a point where you hit a wall where you need to do something different than what the directive allows. NamedTemplateImplementation does a lot of what the specialized directives do: automation of a common registration. But by NOT having it as a custom directive, hopefully it makes it just a little more obvious that you can actually do an INamedTemplate implementation of your own and it would work exactly the same. (Not that the namedtemplate.py code goes out of its way to make this clear, but it is still easier to understand than the discriminator/handler stuff of ZCML). -- Jeff Shell _______________________________________________ Zope3-dev mailing list Zope3-dev@zope.org Unsub: http://mail.zope.org/mailman/options/zope3-dev/archive%40mail-archive.com