[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