[Zope-dev] A summary of "Interfaces vs ZCA concepts"
Martijn Faassen
faassen at startifact.com
Thu Dec 17 11:48:04 EST 2009
Hey,
Thomas Lotze wrote:
[snip]
>> * We want to implement .adapter(), .utility() and .__call__() in
>> zope.component as much as possible.
>
> The method's name is `adapt`, JFTR.
Whoops, yes, I prefer 'adapt' actually anyway. :)
>> * we want a similar mechanism for each of them to plug in.
>
> Agreed, even though (AFAICT) we haven't been talking about moving the
> implementation of __call__ to zope.component so far.
I figured we'd want to treat each of them in the same way.
>> * It'd be nice if __call__ came back with a LookupError instead of a
>> TypeError, but how to get from A to B without breakage?
>
> It's not possible without breakage.
Unless we create a zope.interface specific LookupError which subclasses
both the built-in LookupError and TypeError. zope.component's
ComponentLookupError should subclass this special LookupError then.
> One thing I start questioning is an adapter registry being implemented by
> zope.interface. Moving it to zope.component seems to me to be related to
> keeping the implementations of the new method within zope.component.
I'm not sure where that stuff should be. I'll defer some of this to Gary
again, who is interested in working on this topic. :)
[snip]
>> * the methods can be on zope.interface even if zope.component isn't
>> installed. They will behave as if the component registry is empty.
>
> This isn't covered by the consensus you mentioned above as far as I'm
> concerned.
Yeah, I put that in so we can reach consensus on it. I thought Tres had
a good idea going on there that makes the plugin behavior a lot cleaner.
>> Their behavior should be:
>>
>> IFoo.adapt(context) raises LookupError, unless the context provides IFoo,
>> in which case it returns context.
>>
>> IFoo.adapt(context, default=default) returns default unless context
>> provides IFoo, in which case it returns context.
>>
>> IFoo.utility() raises LookupError.
>>
>> IFoo.utility(default=default) returns default
>
> I think looking at that API explains why we have trouble with having stub
> methods defined by zope.interface: these methods contain enough
> information about component concepts to blur the distinction between
> zope.interface and zope.component, but they still lie about the actual
> method signature.
I don't understand you: why do you say they lie about their method
signature? They should have the same signature and have a well-defined
behavior if zope.component is not installed: there is nothing registered
at all. zope.interface provides a plugin point that allows one to plug
lookup behavior into it.
> In that sense, these stubs would be worse than
> zope.interface not documenting the methods at all.
I strongly disagree. We want to define a bunch of methods on Interface
that we want everybody to have access to. We can't then turn around and
say we really actually don't want to implement those methods on Interface.
That Interface *delegates* the implementation to something else is fine,
but the methods are conceptually on Interface, and delegation is
normally implemented by just calling the code we delegate to.
> In my and Wolfgang's opinion, we can either have zope.interface implement
> methods with the real contract, which would mean defining the full
> concepts of the ZCA within zope.interface (if not their implementation),
> or not even have method stubs in zope.interface and leave the whole
> business of defining specialised uses of interfaces to other packages such
> as zope.component.
In that case, I want the real contract to be in zope.interface. That's
where the methods are, after all. We need to talk about the concept of
an adapter and a utility briefly in zope.interface and defer to
zope.component as the most common implementation. We already have this
kind of behavior going on anyway with __call__() (even though not
documented!).
>> What's the behavior of __call__ now if zope.component isn't around?
>
> Similar to what you've just described of your `adapt` method, up to the
> name of the `default` parameter and the exception raised.
So if no registry is available (zope.component not installed), you can
still call it and it'll just behave as if the registry is empty? That's
good..
>> * Tres brought up that we can come up with a clean plugin interface
>> instead, and now I'm tempted to go for that instead of monkey-ing around.
> [...]
>
> I'd have to think about that some more, but while reading it the first
> time, it feels quite wrong to me.
You'll have to go into more detail. Why does it feel wrong to you? It's
the way plugin APIs generally tend to work. You could even look up this
API as a utility - but that's probably a chicken and egg problem. :)
[see below for a possible improvement on this API]
>> I realize that the proposal for a plugin API gives zope.interface some
>> knowledge about adaption and utility lookups, which is what Thomas and
>> Wolfgang had trouble with. But after all that's what we're doing anyway by
>> putting those methods on the API, one way or another.
>
> No: the zope.interface package doesn't have any knowledge about the
> particulars of any of the uses of interfaces. One might claim that
> interfaces do after they've been patched by other code,
That's what I claim. :)
Why is it a problem that the zope.interface package gains knowledge
about adaptation (which it always had, anyway) and utility lookup?
Because to the user of those methods on Interface, it looks exactly like
the package does have such knowledge. We shouldn't lie.
> but then, that's
> after some application has made its choice about plugin certain packages
> together.
Generally when we have patterns like this we *do* explicitly define
plugin points on the thing we plug into. It's a lot more clear when
there's an explicit plugin API available.
> It's not baked into zope.interface, and that's what we're trying
> so hard to achieve.
But why are you trying so hard? What's the point of trying to do this?
>> [Philosophically from my own perspective I think we'd have much less
>> conceptual difficulty with this if we just made __call__ do all lookups,
>> as that hides the whole set of concepts of utility versus adapter a bit,
>> but we can't get consensus for that unfortunately. But I'm biding my
>> time..]
>
> Implementing __call__ within zope.interface in that way would get the
> package rid of the method names `adapt` and `utility`,
In my mind you do more than just getting rid of names if you just had a
multi-functional __call__. You introduce the following notion into
zope.interface:
"please look up an object that provides this interface, somehow. (with
zero or more context objects)"
How that task is fulfilled, by adapters, utilities, null-adapters or
contextual utilities doesn't matter to zope.interface. That's up to
zope.component. To me putting those notions in zope.interface is a bit
of conceptual leakage we could have avoided, but we've already concluded
that discussion for now and we're going to go with 'adapt' and 'utility'.
> but the technical
> problem with the method signatures and the more philosophical one about
> whether zope.interface should really make adaptation stand out as a use of
> interfaces remains.
We've had this behavior for *years*. We're extending it with one new
concept, utility lookup (multi adaptation being an extension of the
existing concept).
> Which brings me back to the question I asked earlier, and which nobody has
> replied to so far:
I think by now I have. :)
> Do we want to make zope.interface completely unaware of
> any particular uses of interfaces, or do we want to treat component lookup
> and in particular the ZCA's way of looking up components as a special case?
Since zope.interface is already aware of particular uses of interfaces,
such as adapter lookup (and classes implementing interfaces, and so on),
I'd say it's all right if we teach zope.interface about looking up
utilities and multi adapters by interface too, in its own API.
Having thought about it, I think the right way to do this is by a
well-defined plugin point in zope.interface.
In fact, let me propose the following plugin point instead, reducing it
to a single method:
class ILookupPlugin(Interface):
def lookup(interface, contexts):
"""Look up an object that provides interface, given contexts.
contexts may be an empty list, or contain one or more entries.
Raises LookupError if an object providing the interface cannot
be found.
"""
Then __call__ and adapt and utility can be implemented by using
"lookup()". Anything plugging into zope.interface would then have to
only plug in a single method.
[I am getting closer to my preferred API here, but it's only an
implementation detail, people.. nothing to see here! :)]
Regards,
Martijn
More information about the Zope-Dev
mailing list