So we've decided to let interfaces grow `adapt` and `utility` methods. I've written a simple and straight-forward implementation of them (see the tlotze-component-API branches of zope.interface and zope.component) that is closely modelled on the exisiting `__call__`. In particular, the new methods use component hooks which are like adapter hooks but with a richer set of call parameters. There are a few tests for the new methods as well, so everything should be fine. Except that I don't like the implications now that I have actually written down the code. I'll describe the problem I see and then suggest an idea that I don't think we've been considering in the discussion two weeks ago: We're intentionally leaking the concept of utilities to zope.interface. Assuming we're entirely fine with this, we still need to decide how much of the particulars of the ZCA we want to bring along: named components, lookup contexts, the ComponentLookupError. My current implementation tries to introduce enough generic behaviour into the `adapt` and `utility` methods so that they don't cause too obvious (conceptual) dependencies of zope.interface on zope.component: * `adapt` and `utility` don't define particular optional arguments but pass all keyword parameters except for `default` to the component hook which, being implemented by zope.component, keeps the knowledge about named adapters and lookup contexts within the latter package. * The hook invokes the `query*` functions to play nice with any other component hooks and the interface methods raise a TypeError if all of them fail to find a component. However, the generic behaviour gets in our way: the method signatures become useless and hooks lose the possibility of raising useful exceptions. I've tried some variations but as long as the `adapt` and `utility` methods are actually implemented by zope.interface, it will always come down to a compromise that either renders the new methods unusable with anything that's not very much like zope.component, or makes for a half-hearted copy of the functionality we currently have in the zope.component API. I discussed this a bit with Wolfgang as we both don't like this kind of compromise in such core functionality. We came up with the idea that a clean solution would be to keep any implementation of the two methods out of zope.interface and rather inject them into the interface API by code kept entirely within zope.component. We do realise how close to the concept of monkey-patching this comes, but maybe it wouldn't be so bad if we could do it in a more structured way (being intentionally vague here yet). In particular, keeping the concrete `adapt` and `utility` methods out of the core implementation of interfaces would address the concern raised by somebody on this list that we were going to tailor zope.interface too much to the needs of the Zope ecosystem. Uses of interfaces other than adaptation and component lookup could get convenience methods registered by the same mechanism zope.component would end up employing, which is a big conceptual advantage from my point of view. What do people think of this? -- Thomas
Am Dienstag 15 Dezember 2009 17:16:12 schrieb Thomas Lotze:
So we've decided to let interfaces grow `adapt` and `utility` methods. I've written a simple and straight-forward implementation of them (see the tlotze-component-API branches of zope.interface and zope.component) that is closely modelled on the exisiting `__call__`. In particular, the new methods use component hooks which are like adapter hooks but with a richer set of call parameters. There are a few tests for the new methods as well, so everything should be fine.
Except that I don't like the implications now that I have actually written down the code. I'll describe the problem I see and then suggest an idea that I don't think we've been considering in the discussion two weeks ago:
We're intentionally leaking the concept of utilities to zope.interface. Assuming we're entirely fine with this, we still need to decide how much of the particulars of the ZCA we want to bring along: named components, lookup contexts, the ComponentLookupError. My current implementation tries to introduce enough generic behaviour into the `adapt` and `utility` methods so that they don't cause too obvious (conceptual) dependencies of zope.interface on zope.component:
* `adapt` and `utility` don't define particular optional arguments but pass all keyword parameters except for `default` to the component hook which, being implemented by zope.component, keeps the knowledge about named adapters and lookup contexts within the latter package.
* The hook invokes the `query*` functions to play nice with any other component hooks and the interface methods raise a TypeError if all of them fail to find a component.
However, the generic behaviour gets in our way: the method signatures become useless and hooks lose the possibility of raising useful exceptions.
I've tried some variations but as long as the `adapt` and `utility` methods are actually implemented by zope.interface, it will always come down to a compromise that either renders the new methods unusable with anything that's not very much like zope.component, or makes for a half-hearted copy of the functionality we currently have in the zope.component API.
I discussed this a bit with Wolfgang as we both don't like this kind of compromise in such core functionality. We came up with the idea that a clean solution would be to keep any implementation of the two methods out of zope.interface and rather inject them into the interface API by code kept entirely within zope.component. We do realise how close to the concept of monkey-patching this comes, but maybe it wouldn't be so bad if we could do it in a more structured way (being intentionally vague here yet).
In particular, keeping the concrete `adapt` and `utility` methods out of the core implementation of interfaces would address the concern raised by somebody on this list that we were going to tailor zope.interface too much to the needs of the Zope ecosystem. Uses of interfaces other than adaptation and component lookup could get convenience methods registered by the same mechanism zope.component would end up employing, which is a big conceptual advantage from my point of view.
What do people think of this?
Conceptually, this sounds a lot like what I was thinking when I read the discussion, a week or two ago. So I am very much like the idea - to let an interface be an interface without bringing it into tight coupling to other zope-concepts. So the remaining question is, how to implement this in a clean way and still keeping the usage simple and readable. Something like IFoo.do.adapt(x,y) and IFoo.do.get_utility() (as well as IFoo.do.something_completely_different() ) would be possible, but somewhat unusual ... Mat
Funny you should mention this, I've got this idea bugging for a few weeks now, after the last thread: Have the zope.interface package expose a single overridable hook: def getInterfaceAttribute(iface, name, default): This method would be called by any attempt to look up an interface attribute that isn't provided by zope.interface itself. For example: IFoo.adapter(bar, default=baz) Would be the almost the same as: getInterfaceAttribute(IFoo, 'adapter', default=somemarker)(bar, default=baz) That is unless getInterfaceAttribute(IFoo, 'adapter') returned the passed-in marker which would instruct zope.interface to raise an AttributeError instead. In turn, "zope.component" could then provide a getInterfaceAttribute() implementation that called: getAdapter(IFoo, interface=IInterfaceMethod, name='adapter') Yes, the first parameter above is the original interface. The IInterfaceMethod is an interface defining __call__(*args, **kw) This idea would also allow one to define named adapters for IInterfaceMethod to, for instance, "__call__" or "__add__", such that the semantics of "IFoo(...)" or "IFoo + IBar" could also be changed. Perhaps entry points could be used to provide getInterfaceAttribute implementations, and if multiple entry points are present, each would be called in turn to attempt to provide the attributes of an interface, but maybe this is too much flexibility... Just an idea... Cheers, Leo On Tue, Dec 15, 2009 at 14:16, Thomas Lotze <tl@gocept.com> wrote:
So we've decided to let interfaces grow `adapt` and `utility` methods. I've written a simple and straight-forward implementation of them (see the tlotze-component-API branches of zope.interface and zope.component) that is closely modelled on the exisiting `__call__`. In particular, the new methods use component hooks which are like adapter hooks but with a richer set of call parameters. There are a few tests for the new methods as well, so everything should be fine.
Except that I don't like the implications now that I have actually written down the code. I'll describe the problem I see and then suggest an idea that I don't think we've been considering in the discussion two weeks ago:
We're intentionally leaking the concept of utilities to zope.interface. Assuming we're entirely fine with this, we still need to decide how much of the particulars of the ZCA we want to bring along: named components, lookup contexts, the ComponentLookupError. My current implementation tries to introduce enough generic behaviour into the `adapt` and `utility` methods so that they don't cause too obvious (conceptual) dependencies of zope.interface on zope.component:
* `adapt` and `utility` don't define particular optional arguments but pass all keyword parameters except for `default` to the component hook which, being implemented by zope.component, keeps the knowledge about named adapters and lookup contexts within the latter package.
* The hook invokes the `query*` functions to play nice with any other component hooks and the interface methods raise a TypeError if all of them fail to find a component.
However, the generic behaviour gets in our way: the method signatures become useless and hooks lose the possibility of raising useful exceptions.
I've tried some variations but as long as the `adapt` and `utility` methods are actually implemented by zope.interface, it will always come down to a compromise that either renders the new methods unusable with anything that's not very much like zope.component, or makes for a half-hearted copy of the functionality we currently have in the zope.component API.
I discussed this a bit with Wolfgang as we both don't like this kind of compromise in such core functionality. We came up with the idea that a clean solution would be to keep any implementation of the two methods out of zope.interface and rather inject them into the interface API by code kept entirely within zope.component. We do realise how close to the concept of monkey-patching this comes, but maybe it wouldn't be so bad if we could do it in a more structured way (being intentionally vague here yet).
In particular, keeping the concrete `adapt` and `utility` methods out of the core implementation of interfaces would address the concern raised by somebody on this list that we were going to tailor zope.interface too much to the needs of the Zope ecosystem. Uses of interfaces other than adaptation and component lookup could get convenience methods registered by the same mechanism zope.component would end up employing, which is a big conceptual advantage from my point of view.
What do people think of this?
-- Thomas
_______________________________________________ Zope-Dev maillist - Zope-Dev@zope.org https://mail.zope.org/mailman/listinfo/zope-dev ** No cross posts or HTML encoding! ** (Related lists - https://mail.zope.org/mailman/listinfo/zope-announce https://mail.zope.org/mailman/listinfo/zope )
I wonder if maybe something like (notionally): class InterfaceBase(object): @classmethod def add_more_api(cls, **kw): cls.__dict__.update(kw) - C Leonardo Rochael Almeida wrote:
Funny you should mention this, I've got this idea bugging for a few weeks now, after the last thread:
Have the zope.interface package expose a single overridable hook:
def getInterfaceAttribute(iface, name, default):
This method would be called by any attempt to look up an interface attribute that isn't provided by zope.interface itself. For example:
IFoo.adapter(bar, default=baz)
Would be the almost the same as:
getInterfaceAttribute(IFoo, 'adapter', default=somemarker)(bar, default=baz)
That is unless getInterfaceAttribute(IFoo, 'adapter') returned the passed-in marker which would instruct zope.interface to raise an AttributeError instead.
In turn, "zope.component" could then provide a getInterfaceAttribute() implementation that called:
getAdapter(IFoo, interface=IInterfaceMethod, name='adapter')
Yes, the first parameter above is the original interface. The IInterfaceMethod is an interface defining __call__(*args, **kw)
This idea would also allow one to define named adapters for IInterfaceMethod to, for instance, "__call__" or "__add__", such that the semantics of "IFoo(...)" or "IFoo + IBar" could also be changed.
Perhaps entry points could be used to provide getInterfaceAttribute implementations, and if multiple entry points are present, each would be called in turn to attempt to provide the attributes of an interface, but maybe this is too much flexibility...
Just an idea...
Cheers, Leo
On Tue, Dec 15, 2009 at 14:16, Thomas Lotze <tl@gocept.com> wrote:
So we've decided to let interfaces grow `adapt` and `utility` methods. I've written a simple and straight-forward implementation of them (see the tlotze-component-API branches of zope.interface and zope.component) that is closely modelled on the exisiting `__call__`. In particular, the new methods use component hooks which are like adapter hooks but with a richer set of call parameters. There are a few tests for the new methods as well, so everything should be fine.
Except that I don't like the implications now that I have actually written down the code. I'll describe the problem I see and then suggest an idea that I don't think we've been considering in the discussion two weeks ago:
We're intentionally leaking the concept of utilities to zope.interface. Assuming we're entirely fine with this, we still need to decide how much of the particulars of the ZCA we want to bring along: named components, lookup contexts, the ComponentLookupError. My current implementation tries to introduce enough generic behaviour into the `adapt` and `utility` methods so that they don't cause too obvious (conceptual) dependencies of zope.interface on zope.component:
* `adapt` and `utility` don't define particular optional arguments but pass all keyword parameters except for `default` to the component hook which, being implemented by zope.component, keeps the knowledge about named adapters and lookup contexts within the latter package.
* The hook invokes the `query*` functions to play nice with any other component hooks and the interface methods raise a TypeError if all of them fail to find a component.
However, the generic behaviour gets in our way: the method signatures become useless and hooks lose the possibility of raising useful exceptions.
I've tried some variations but as long as the `adapt` and `utility` methods are actually implemented by zope.interface, it will always come down to a compromise that either renders the new methods unusable with anything that's not very much like zope.component, or makes for a half-hearted copy of the functionality we currently have in the zope.component API.
I discussed this a bit with Wolfgang as we both don't like this kind of compromise in such core functionality. We came up with the idea that a clean solution would be to keep any implementation of the two methods out of zope.interface and rather inject them into the interface API by code kept entirely within zope.component. We do realise how close to the concept of monkey-patching this comes, but maybe it wouldn't be so bad if we could do it in a more structured way (being intentionally vague here yet).
In particular, keeping the concrete `adapt` and `utility` methods out of the core implementation of interfaces would address the concern raised by somebody on this list that we were going to tailor zope.interface too much to the needs of the Zope ecosystem. Uses of interfaces other than adaptation and component lookup could get convenience methods registered by the same mechanism zope.component would end up employing, which is a big conceptual advantage from my point of view.
What do people think of this?
-- Thomas
_______________________________________________ Zope-Dev maillist - Zope-Dev@zope.org https://mail.zope.org/mailman/listinfo/zope-dev ** No cross posts or HTML encoding! ** (Related lists - https://mail.zope.org/mailman/listinfo/zope-announce https://mail.zope.org/mailman/listinfo/zope )
_______________________________________________ Zope-Dev maillist - Zope-Dev@zope.org https://mail.zope.org/mailman/listinfo/zope-dev ** No cross posts or HTML encoding! ** (Related lists - https://mail.zope.org/mailman/listinfo/zope-announce https://mail.zope.org/mailman/listinfo/zope )
On Tue, Dec 15, 2009 at 05:32:15PM -0500, Chris McDonough wrote:
I wonder if maybe something like (notionally):
class InterfaceBase(object): @classmethod def add_more_api(cls, **kw): cls.__dict__.update(kw)
I like this as a clear point to put a pdb when things do not go according to plan.
- C
Leonardo Rochael Almeida wrote:
Funny you should mention this, I've got this idea bugging for a few weeks now, after the last thread:
Have the zope.interface package expose a single overridable hook:
def getInterfaceAttribute(iface, name, default):
This method would be called by any attempt to look up an interface attribute that isn't provided by zope.interface itself. For example:
IFoo.adapter(bar, default=baz)
Would be the almost the same as:
getInterfaceAttribute(IFoo, 'adapter', default=somemarker)(bar, default=baz)
That is unless getInterfaceAttribute(IFoo, 'adapter') returned the passed-in marker which would instruct zope.interface to raise an AttributeError instead.
In turn, "zope.component" could then provide a getInterfaceAttribute() implementation that called:
getAdapter(IFoo, interface=IInterfaceMethod, name='adapter')
Yes, the first parameter above is the original interface. The IInterfaceMethod is an interface defining __call__(*args, **kw)
This idea would also allow one to define named adapters for IInterfaceMethod to, for instance, "__call__" or "__add__", such that the semantics of "IFoo(...)" or "IFoo + IBar" could also be changed.
Perhaps entry points could be used to provide getInterfaceAttribute implementations, and if multiple entry points are present, each would be called in turn to attempt to provide the attributes of an interface, but maybe this is too much flexibility...
Just an idea...
Cheers, Leo
On Tue, Dec 15, 2009 at 14:16, Thomas Lotze <tl@gocept.com> wrote:
So we've decided to let interfaces grow `adapt` and `utility` methods. I've written a simple and straight-forward implementation of them (see the tlotze-component-API branches of zope.interface and zope.component) that is closely modelled on the exisiting `__call__`. In particular, the new methods use component hooks which are like adapter hooks but with a richer set of call parameters. There are a few tests for the new methods as well, so everything should be fine.
Except that I don't like the implications now that I have actually written down the code. I'll describe the problem I see and then suggest an idea that I don't think we've been considering in the discussion two weeks ago:
We're intentionally leaking the concept of utilities to zope.interface. Assuming we're entirely fine with this, we still need to decide how much of the particulars of the ZCA we want to bring along: named components, lookup contexts, the ComponentLookupError. My current implementation tries to introduce enough generic behaviour into the `adapt` and `utility` methods so that they don't cause too obvious (conceptual) dependencies of zope.interface on zope.component:
* `adapt` and `utility` don't define particular optional arguments but pass all keyword parameters except for `default` to the component hook which, being implemented by zope.component, keeps the knowledge about named adapters and lookup contexts within the latter package.
* The hook invokes the `query*` functions to play nice with any other component hooks and the interface methods raise a TypeError if all of them fail to find a component.
However, the generic behaviour gets in our way: the method signatures become useless and hooks lose the possibility of raising useful exceptions.
I've tried some variations but as long as the `adapt` and `utility` methods are actually implemented by zope.interface, it will always come down to a compromise that either renders the new methods unusable with anything that's not very much like zope.component, or makes for a half-hearted copy of the functionality we currently have in the zope.component API.
I discussed this a bit with Wolfgang as we both don't like this kind of compromise in such core functionality. We came up with the idea that a clean solution would be to keep any implementation of the two methods out of zope.interface and rather inject them into the interface API by code kept entirely within zope.component. We do realise how close to the concept of monkey-patching this comes, but maybe it wouldn't be so bad if we could do it in a more structured way (being intentionally vague here yet).
In particular, keeping the concrete `adapt` and `utility` methods out of the core implementation of interfaces would address the concern raised by somebody on this list that we were going to tailor zope.interface too much to the needs of the Zope ecosystem. Uses of interfaces other than adaptation and component lookup could get convenience methods registered by the same mechanism zope.component would end up employing, which is a big conceptual advantage from my point of view.
What do people think of this?
-- Thomas
_______________________________________________ Zope-Dev maillist - Zope-Dev@zope.org https://mail.zope.org/mailman/listinfo/zope-dev ** No cross posts or HTML encoding! ** (Related lists - https://mail.zope.org/mailman/listinfo/zope-announce https://mail.zope.org/mailman/listinfo/zope )
_______________________________________________ Zope-Dev maillist - Zope-Dev@zope.org https://mail.zope.org/mailman/listinfo/zope-dev ** No cross posts or HTML encoding! ** (Related lists - https://mail.zope.org/mailman/listinfo/zope-announce https://mail.zope.org/mailman/listinfo/zope )
_______________________________________________ Zope-Dev maillist - Zope-Dev@zope.org https://mail.zope.org/mailman/listinfo/zope-dev ** No cross posts or HTML encoding! ** (Related lists - https://mail.zope.org/mailman/listinfo/zope-announce https://mail.zope.org/mailman/listinfo/zope )
-- Brian Sutherland
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Leonardo Rochael Almeida wrote:
Funny you should mention this, I've got this idea bugging for a few weeks now, after the last thread:
Have the zope.interface package expose a single overridable hook:
def getInterfaceAttribute(iface, name, default):
This method would be called by any attempt to look up an interface attribute that isn't provided by zope.interface itself. For example:
IFoo.adapter(bar, default=baz)
Would be the almost the same as:
getInterfaceAttribute(IFoo, 'adapter', default=somemarker)(bar, default=baz)
That is unless getInterfaceAttribute(IFoo, 'adapter') returned the passed-in marker which would instruct zope.interface to raise an AttributeError instead.
In turn, "zope.component" could then provide a getInterfaceAttribute() implementation that called:
getAdapter(IFoo, interface=IInterfaceMethod, name='adapter')
Yes, the first parameter above is the original interface. The IInterfaceMethod is an interface defining __call__(*args, **kw)
This idea would also allow one to define named adapters for IInterfaceMethod to, for instance, "__call__" or "__add__", such that the semantics of "IFoo(...)" or "IFoo + IBar" could also be changed.
Perhaps entry points could be used to provide getInterfaceAttribute implementations, and if multiple entry points are present, each would be called in turn to attempt to provide the attributes of an interface, but maybe this is too much flexibility...
Just an idea...
I had originally contemplated replacing the current adapter hook with a more generic hook: the __call__ of interface would delegate to this hook for its semantics. Given that the choice to use __call__ is not widely enough accepted, I think something like your solution makes sense. I'm not sure that hooking __getattr__ is going to be performant enough, but a similar mechanism probably could be made fast enough. Tres. - -- =================================================================== Tres Seaver +1 540-429-0999 tseaver@palladion.com Palladion Software "Excellence by Design" http://palladion.com -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iEYEARECAAYFAksoDpQACgkQ+gerLs4ltQ4LkACgxb9GzzGU7RAnj9rTEhJMVnJ9 iVwAniefCS8HXGyEiSxG7V7fYWJISsrZ =bmx9 -----END PGP SIGNATURE-----
On 12/15/2009 11:32 PM, Tres Seaver wrote:
Given that the choice to use __call__ is not widely enough accepted, I think something like your solution makes sense. I'm not sure that hooking __getattr__ is going to be performant enough, but a similar mechanism probably could be made fast enough.
That sounds a lot like what the SQLAlchemy guys did with their attrgetter (well, that was 2007 IIRC so they might be doing something even more fun by now. Just in case someone goes for hunting down a fast pluggable __getattr__-alike. -- Christian Theune · ct@gocept.com gocept gmbh & co. kg · forsterstraße 29 · 06112 halle (saale) · germany http://gocept.com · tel +49 345 1229889 0 · fax +49 345 1229889 1 Zope and Plone consulting and development
Hey, Good to see there's progress! Thomas Lotze wrote: [snip]
Except that I don't like the implications now that I have actually written down the code. I'll describe the problem I see and then suggest an idea that I don't think we've been considering in the discussion two weeks ago:
We're intentionally leaking the concept of utilities to zope.interface. Assuming we're entirely fine with this, we still need to decide how much of the particulars of the ZCA we want to bring along: named components, lookup contexts, the ComponentLookupError. My current implementation tries to introduce enough generic behaviour into the `adapt` and `utility` methods so that they don't cause too obvious (conceptual) dependencies of zope.interface on zope.component:
* `adapt` and `utility` don't define particular optional arguments but pass all keyword parameters except for `default` to the component hook which, being implemented by zope.component, keeps the knowledge about named adapters and lookup contexts within the latter package.
* The hook invokes the `query*` functions to play nice with any other component hooks and the interface methods raise a TypeError if all of them fail to find a component.
A TypeError instead of a ComponentLookupError? I was thinking we should keep the behavior as close to zope.component as we can, including ComponentLookupError. Don't you get a ComponentLookupError with the classic adapter hook too? So I'm -1 to making this a TypeError.
However, the generic behaviour gets in our way: the method signatures become useless and hooks lose the possibility of raising useful exceptions.
I've tried some variations but as long as the `adapt` and `utility` methods are actually implemented by zope.interface, it will always come down to a compromise that either renders the new methods unusable with anything that's not very much like zope.component, or makes for a half-hearted copy of the functionality we currently have in the zope.component API.
I discussed this a bit with Wolfgang as we both don't like this kind of compromise in such core functionality. We came up with the idea that a clean solution would be to keep any implementation of the two methods out of zope.interface and rather inject them into the interface API by code kept entirely within zope.component. We do realise how close to the concept of monkey-patching this comes, but maybe it wouldn't be so bad if we could do it in a more structured way (being intentionally vague here yet).
I think a monkey patch would be fine. Opinions about making it more structured: * have dummy implementations in zope.interface that raise NotImplementedError * have that NotImplementedError say to look for implementations in zope.component * have the docstring of these methods also talk about zope.component * have the docstring on the methods that are injected be clear that they are being injected into zope.interface That way someone reading the zope.interface code can at least find out that this functionality is injected from zope.component. [as an aside it'd be interesting to find out how much of of zope.component functionality could sensibly make it into zope.interface, but that's another project; Gary Poster has some ideas in relation to this]
In particular, keeping the concrete `adapt` and `utility` methods out of the core implementation of interfaces would address the concern raised by somebody on this list that we were going to tailor zope.interface too much to the needs of the Zope ecosystem. Uses of interfaces other than adaptation and component lookup could get convenience methods registered by the same mechanism zope.component would end up employing, which is a big conceptual advantage from my point of view.
What do people think of this?
I'm +1 on this general approach. Implementation in zope.component, and inject it into zope.interface during import time. I'm still sneakily hoping that in a few years we'll be able to get calling the interface be the one true way of looking up implementations of that interface (utilities and adapters and whatnots), but we'll see. Did you make an implicit 'default' deprecated on __call__ yet by the way? Regards, Martijn
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Martijn Faassen wrote:
Hey,
Good to see there's progress!
Thomas Lotze wrote: [snip]
Except that I don't like the implications now that I have actually written down the code. I'll describe the problem I see and then suggest an idea that I don't think we've been considering in the discussion two weeks ago:
We're intentionally leaking the concept of utilities to zope.interface. Assuming we're entirely fine with this, we still need to decide how much of the particulars of the ZCA we want to bring along: named components, lookup contexts, the ComponentLookupError. My current implementation tries to introduce enough generic behaviour into the `adapt` and `utility` methods so that they don't cause too obvious (conceptual) dependencies of zope.interface on zope.component:
* `adapt` and `utility` don't define particular optional arguments but pass all keyword parameters except for `default` to the component hook which, being implemented by zope.component, keeps the knowledge about named adapters and lookup contexts within the latter package.
* The hook invokes the `query*` functions to play nice with any other component hooks and the interface methods raise a TypeError if all of them fail to find a component.
A TypeError instead of a ComponentLookupError?
I was thinking we should keep the behavior as close to zope.component as we can, including ComponentLookupError. Don't you get a ComponentLookupError with the classic adapter hook too? So I'm -1 to making this a TypeError.
+1 to TypeError: nobody really cares about the type of the error: code that wants to be robust about a failure uses the 'query' methods. As long as the message is informative enough (which ComponentLookupError isn't, really) we should be fine. If we made CLE derive from TypeError, we could even still be satisfying the contract.
However, the generic behaviour gets in our way: the method signatures become useless and hooks lose the possibility of raising useful exceptions.
I've tried some variations but as long as the `adapt` and `utility` methods are actually implemented by zope.interface, it will always come down to a compromise that either renders the new methods unusable with anything that's not very much like zope.component, or makes for a half-hearted copy of the functionality we currently have in the zope.component API.
I discussed this a bit with Wolfgang as we both don't like this kind of compromise in such core functionality. We came up with the idea that a clean solution would be to keep any implementation of the two methods out of zope.interface and rather inject them into the interface API by code kept entirely within zope.component. We do realise how close to the concept of monkey-patching this comes, but maybe it wouldn't be so bad if we could do it in a more structured way (being intentionally vague here yet).
I think a monkey patch would be fine. Opinions about making it more structured:
Setting a hook using an API is *not* a "monkey patch": that term implies munging a module / class without its knowledge or consent.
* have dummy implementations in zope.interface that raise NotImplementedError
* have that NotImplementedError say to look for implementations in zope.component
* have the docstring of these methods also talk about zope.component
* have the docstring on the methods that are injected be clear that they are being injected into zope.interface
That way someone reading the zope.interface code can at least find out that this functionality is injected from zope.component.
- -1 to monkey patching: I would favor exposing an API in zope.interface which allowed this kind of pluggability.
[as an aside it'd be interesting to find out how much of of zope.component functionality could sensibly make it into zope.interface, but that's another project; Gary Poster has some ideas in relation to this]
In particular, keeping the concrete `adapt` and `utility` methods out of the core implementation of interfaces would address the concern raised by somebody on this list that we were going to tailor zope.interface too much to the needs of the Zope ecosystem. Uses of interfaces other than adaptation and component lookup could get convenience methods registered by the same mechanism zope.component would end up employing, which is a big conceptual advantage from my point of view.
What do people think of this?
I'm +1 on this general approach. Implementation in zope.component, and inject it into zope.interface during import time.
Not at import time, please: better to have this happen during startup (e.g., just before bootstrapping configuration from ZCML or martian).
I'm still sneakily hoping that in a few years we'll be able to get calling the interface be the one true way of looking up implementations of that interface (utilities and adapters and whatnots), but we'll see.
Me too. Tres. - -- =================================================================== Tres Seaver +1 540-429-0999 tseaver@palladion.com Palladion Software "Excellence by Design" http://palladion.com -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iEYEARECAAYFAksoEE0ACgkQ+gerLs4ltQ4cnwCg2LVFDkFJTAujLljTIQDRydWW sUgAnjJtoGDbmOpOfH3SXy0gisGGn6dp =nLjA -----END PGP SIGNATURE-----
Tres Seaver wrote:
* The hook invokes the `query*` functions to play nice with any other component hooks and the interface methods raise a TypeError if all of them fail to find a component. A TypeError instead of a ComponentLookupError?
I was thinking we should keep the behavior as close to zope.component as we can, including ComponentLookupError. Don't you get a ComponentLookupError with the classic adapter hook too? So I'm -1 to making this a TypeError.
+1 to TypeError: nobody really cares about the type of the error: code that wants to be robust about a failure uses the 'query' methods. As long as the message is informative enough (which ComponentLookupError isn't, really) we should be fine. If we made CLE derive from TypeError, we could even still be satisfying the contract.
zope.component raises TypeError if you can't adapt. It raises ComponentLookupError it can't find a utility. Let's keep exceptions the same. People do catch specific errors, so those who've done 'except TypeError' now aren't going to be happy if we change that to something else when they try to move to use the "new" API.
- -1 to monkey patching: I would favor exposing an API in zope.interface which allowed this kind of pluggability.
I agree. Martin -- Author of `Professional Plone Development`, a book for developers who want to work with Plone. See http://martinaspeli.net/plone-book
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Martin Aspeli wrote:
Tres Seaver wrote:
* The hook invokes the `query*` functions to play nice with any other component hooks and the interface methods raise a TypeError if all of them fail to find a component. A TypeError instead of a ComponentLookupError?
I was thinking we should keep the behavior as close to zope.component as we can, including ComponentLookupError. Don't you get a ComponentLookupError with the classic adapter hook too? So I'm -1 to making this a TypeError. +1 to TypeError: nobody really cares about the type of the error: code that wants to be robust about a failure uses the 'query' methods. As long as the message is informative enough (which ComponentLookupError isn't, really) we should be fine. If we made CLE derive from TypeError, we could even still be satisfying the contract.
zope.component raises TypeError if you can't adapt. It raises ComponentLookupError it can't find a utility.
Not so: see $ZSVN/zope.component/trunk/src/zope/component/registry.py: class Components: ... def getUtility(self, provided, name=u''): utility = self.utilities.lookup((), provided, name) if utility is None: raise ComponentLookupError(provided, name) return utility ... def getAdapter(self, object, interface, name=u''): adapter = self.adapters.queryAdapter(object, interface, name) if adapter is None: raise ComponentLookupError(object, interface, name) return adapter which matches the contract spelled out in the docstrings for IComponent. That class raises TypeError only for invalid values passed to the various registration functions. At any rate, we are talking about errors raised from zope.itnerface APIs, which nowhere mention or use CLE:: $ svn info . | grep URL URL: svn+ssh://svn.zope.org/repos/main/zope.interface/trunk $ svn up At revision 106615. $ find . -name "*.py" | xargs grep ComponentLookupError $ Nobody calling an interface today has any *defined* behavior to expect in the case of failure (in fact, '__call__' is not even part of IInterface!)
Let's keep exceptions the same. People do catch specific errors, so those who've done 'except TypeError' now aren't going to be happy if we change that to something else when they try to move to use the "new" API.
Please point to existing code which calls 'IFoo.utility(name="bar")' and catches a CLE. Since this is a new API we are talkign about, there can't be any BBB concerns. Any code today which wants a utility is calling 'getUtilty' (if it *knows* the utility must be registered) or 'queryUtility' (if it thinks it might not be). Less facetiously than my first challenge: please point to actual code in the wild which looks like:: try: foo = getUtilty(IFoo, name='bar') except ComponentLookupError: # do something instead of:: foo = queryUtility(IFoo, name='bar') if foo is None: # do something I will argue that any code doing the first, outside of maybe tests of the ZCA itself is plain broken. Tres. - -- =================================================================== Tres Seaver +1 540-429-0999 tseaver@palladion.com Palladion Software "Excellence by Design" http://palladion.com -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iEYEARECAAYFAksoWyUACgkQ+gerLs4ltQ4dCQCfeF5a1xYdWNIJtnh/fVoeBbHt g1cAoKRzg8utmIYpK5skXhk2qhhJ/qR0 =BKnt -----END PGP SIGNATURE-----
Tres Seaver wrote:
+1 to TypeError: nobody really cares about the type of the error: code that wants to be robust about a failure uses the 'query' methods. As long as the message is informative enough (which ComponentLookupError isn't, really) we should be fine. If we made CLE derive from TypeError, we could even still be satisfying the contract. zope.component raises TypeError if you can't adapt. It raises ComponentLookupError it can't find a utility.
Not so: see $ZSVN/zope.component/trunk/src/zope/component/registry.py:
I'm pretty sure you get a TypeError when Zope can't adapt. I'm not sure where it's thrown from, though. Stupid example:
from plone.portlets.interfaces import IPortletType class X(object): pass ... x = () IPortletType(x) Traceback (most recent call last): File "<console>", line 1, in <module> TypeError: ('Could not adapt', (), <InterfaceClass plone.portlets.interfaces.IPortletType>)
class Components: ... def getUtility(self, provided, name=u''): utility = self.utilities.lookup((), provided, name) if utility is None: raise ComponentLookupError(provided, name) return utility ... def getAdapter(self, object, interface, name=u''): adapter = self.adapters.queryAdapter(object, interface, name) if adapter is None: raise ComponentLookupError(object, interface, name) return adapter
getAdapter() is different, though:
from zope.component import getAdapter getAdapter(x, IPortletType) Traceback (most recent call last): File "<console>", line 1, in <module> File "/Users/optilude/.buildout/eggs/zope.component-3.7.1-py2.6.egg/zope/component/_api.py", line 98, in getAdapter raise ComponentLookupError(object, interface, name) ComponentLookupError: ((), <InterfaceClass plone.portlets.interfaces.IPortletType>, u'')
which matches the contract spelled out in the docstrings for IComponent. That class raises TypeError only for invalid values passed to the various registration functions.
Something else is raising TypeError then. :)
At any rate, we are talking about errors raised from zope.itnerface APIs, which nowhere mention or use CLE::
Ok.
$ svn info . | grep URL URL: svn+ssh://svn.zope.org/repos/main/zope.interface/trunk $ svn up At revision 106615. $ find . -name "*.py" | xargs grep ComponentLookupError $
Nobody calling an interface today has any *defined* behavior to expect in the case of failure (in fact, '__call__' is not even part of IInterface!)
Let's keep exceptions the same. People do catch specific errors, so those who've done 'except TypeError' now aren't going to be happy if we change that to something else when they try to move to use the "new" API.
Please point to existing code which calls 'IFoo.utility(name="bar")' and catches a CLE. Since this is a new API we are talkign about, there can't be any BBB concerns.
True, but we're also going to ask people to change their use of getUtility(IFoo) to IFoo.utility and ditto for adaption. If I have existing code that looks like this: try: foo = IFoo(bar) except TypeError: pass I would like to be able to move that "mechanically" to: try: foo = IFoo.adapt(bar) except TypeError: pass If I have to change the exception type in any of the scenarios (utility lookup would be similar) then that would be another change to detect, and possibly a harder one to spot in less contrived code.
Any code today which wants a utility is calling 'getUtilty' (if it *knows* the utility must be registered) or 'queryUtility' (if it thinks it might not be). Less facetiously than my first challenge: please point to actual code in the wild which looks like::
try: foo = getUtilty(IFoo, name='bar') except ComponentLookupError: # do something
instead of::
foo = queryUtility(IFoo, name='bar') if foo is None: # do something
I will argue that any code doing the first, outside of maybe tests of the ZCA itself is plain broken.
Perhaps, but I'm pretty sure people have done this. They may also have done it in tests. I'm not religious about it, but I think that if we're only changing the API, it'd be preferable not to change the exceptions we throw unless semantics also change. Martin -- Author of `Professional Plone Development`, a book for developers who want to work with Plone. See http://martinaspeli.net/plone-book
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Martin Aspeli wrote:
Tres Seaver wrote:
Martin Aspeli wrote:
zope.component raises TypeError if you can't adapt. It raises ComponentLookupError it can't find a utility. Not so: see $ZSVN/zope.component/trunk/src/zope/component/registry.py:
I'm pretty sure you get a TypeError when Zope can't adapt. I'm not sure where it's thrown from, though.
Stupid example:
from plone.portlets.interfaces import IPortletType class X(object): pass ... x = () IPortletType(x) Traceback (most recent call last): File "<console>", line 1, in <module> TypeError: ('Could not adapt', (), <InterfaceClass plone.portlets.interfaces.IPortletType>)
The "Could not adapt" traceback tells you that it isnt The ZCA doing that: it is the C code inside zope.interface. One might argue that a better exception would be LookupError: we haven't "violted the contract" here.
class Components: ... def getUtility(self, provided, name=u''): utility = self.utilities.lookup((), provided, name) if utility is None: raise ComponentLookupError(provided, name) return utility ... def getAdapter(self, object, interface, name=u''): adapter = self.adapters.queryAdapter(object, interface, name) if adapter is None: raise ComponentLookupError(object, interface, name) return adapter
getAdapter() is different, though:
from zope.component import getAdapter getAdapter(x, IPortletType) Traceback (most recent call last): File "<console>", line 1, in <module> File "/Users/optilude/.buildout/eggs/zope.component-3.7.1-py2.6.egg/zope/component/_api.py", line 98, in getAdapter raise ComponentLookupError(object, interface, name) ComponentLookupError: ((), <InterfaceClass plone.portlets.interfaces.IPortletType>, u'')
which matches the contract spelled out in the docstrings for IComponent. That class raises TypeError only for invalid values passed to the various registration functions.
Something else is raising TypeError then. :)
The interface machinery catches whatever the adapter hook raises and returns a TypeError instead.
At any rate, we are talking about errors raised from zope.itnerface APIs, which nowhere mention or use CLE::
Ok.
$ svn info . | grep URL URL: svn+ssh://svn.zope.org/repos/main/zope.interface/trunk $ svn up At revision 106615. $ find . -name "*.py" | xargs grep ComponentLookupError $
Nobody calling an interface today has any *defined* behavior to expect in the case of failure (in fact, '__call__' is not even part of IInterface!)
Please point to existing code which calls 'IFoo.utility(name="bar")' and catches a CLE. Since this is a new API we are talkign about, there can't be any BBB concerns.
True, but we're also going to ask people to change their use of getUtility(IFoo) to IFoo.utility and ditto for adaption. If I have existing code that looks like this:
try: foo = IFoo(bar) except TypeError: pass
I would like to be able to move that "mechanically" to:
try: foo = IFoo.adapt(bar) except TypeError: pass
I can't see anything compelling about making that transform mechanical. As noted above, it is already *wrong* to be relying on a specific exception type here anyway.
If I have to change the exception type in any of the scenarios (utility lookup would be similar) then that would be another change to detect, and possibly a harder one to spot in less contrived code.
Any code today which wants a utility is calling 'getUtilty' (if it *knows* the utility must be registered) or 'queryUtility' (if it thinks it might not be). Less facetiously than my first challenge: please point to actual code in the wild which looks like::
try: foo = getUtilty(IFoo, name='bar') except ComponentLookupError: # do something
instead of::
foo = queryUtility(IFoo, name='bar') if foo is None: # do something
I will argue that any code doing the first, outside of maybe tests of the ZCA itself is plain broken.
Perhaps, but I'm pretty sure people have done this. They may also have done it in tests.
Why would we bend over backward to keep obviously broken code seeming to work?
I'm not religious about it, but I think that if we're only changing the API, it'd be preferable not to change the exceptions we throw unless semantics also change.
Given that the behavior isn't part of any API to date, I'm suggesting that we *document* the behavior ahead of time (for new code), without any regard for BBB. We could document the existing __call__ behavior as well, if needed. In either case, I think TypeError (or maybe LookupError) is the *right* choice: we don't want to "hide" zope.component's API functions and then turn around and require folks to import zope.component just to catch its local exception types. Tres. - -- =================================================================== Tres Seaver +1 540-429-0999 tseaver@palladion.com Palladion Software "Excellence by Design" http://palladion.com -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iEYEARECAAYFAkspU4EACgkQ+gerLs4ltQ7koQCgpoHUw8vQZfAQcJec9zWAdKhU V3kAoIliRpCfujwFSCIs4Gi6z+BIwq0N =2FUT -----END PGP SIGNATURE-----
Tres Seaver wrote:
In either case, I think TypeError (or maybe LookupError) is the *right* choice: we don't want to "hide" zope.component's API functions and then turn around and require folks to import zope.component just to catch its local exception types.
Yeah, that's a compelling reason. Martin -- Author of `Professional Plone Development`, a book for developers who want to work with Plone. See http://martinaspeli.net/plone-book
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Martin Aspeli wrote:
Tres Seaver wrote:
In either case, I think TypeError (or maybe LookupError) is the *right* choice: we don't want to "hide" zope.component's API functions and then turn around and require folks to import zope.component just to catch its local exception types.
Yeah, that's a compelling reason.
I have checked in a branch which makes failed adaptation (inside the __call__ of an interface) raise a LookupError instead of a TypeError: the branch also documents the semantics of __call__. I would like to merge this to the trunk a 3.6.0 version (bumped to indicate the quasi-API change). http://svn.zope.org/zope.interface/branches/tseaver-raise_lookup_error/?rev=... Tres. - -- =================================================================== Tres Seaver +1 540-429-0999 tseaver@palladion.com Palladion Software "Excellence by Design" http://palladion.com -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iEYEARECAAYFAkspjwMACgkQ+gerLs4ltQ4mggCg090UYuKxFt2WH5iuiQJvqtbT yMwAoNPvKEhj2xKhWiribWNT+j7/LBUa =k4US -----END PGP SIGNATURE-----
Tres Seaver wrote:
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
Martin Aspeli wrote:
Tres Seaver wrote:
In either case, I think TypeError (or maybe LookupError) is the *right* choice: we don't want to "hide" zope.component's API functions and then turn around and require folks to import zope.component just to catch its local exception types. Yeah, that's a compelling reason.
I have checked in a branch which makes failed adaptation (inside the __call__ of an interface) raise a LookupError instead of a TypeError: the branch also documents the semantics of __call__. I would like to merge this to the trunk a 3.6.0 version (bumped to indicate the quasi-API change).
http://svn.zope.org/zope.interface/branches/tseaver-raise_lookup_error/?rev=...
While I'm fine with the principle of this change, I do consider it dangerous - people might be catching TypeError now instead of using the "alternate" argument. Quite a bit of code depending on TypeError (even if undocumented) might unexpectedly break. We could support both errors by introducing an exception that subclasses both TypeError and LookupError. The question is whether such a strategy would help us deprecate TypeError altogether - I don't see how we can detect that TypeError was caught instead of LookupError when our exception is handled, so we can't output any messages.. Besides this you've documented the default argument as "default" while it is in fact "alternate" (which we want to deprecate in favor of default), so the documentation of __call__ isn't correct. I think this needs some more thinking, unfortunately. I wish I could see a gradual way forward on moving from TypeError to LookupError. Regards, Martijn
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Martijn Faassen wrote:
Tres Seaver wrote:
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
Martin Aspeli wrote:
Tres Seaver wrote:
In either case, I think TypeError (or maybe LookupError) is the *right* choice: we don't want to "hide" zope.component's API functions and then turn around and require folks to import zope.component just to catch its local exception types. Yeah, that's a compelling reason. I have checked in a branch which makes failed adaptation (inside the __call__ of an interface) raise a LookupError instead of a TypeError: the branch also documents the semantics of __call__. I would like to merge this to the trunk a 3.6.0 version (bumped to indicate the quasi-API change).
http://svn.zope.org/zope.interface/branches/tseaver-raise_lookup_error/?rev=...
While I'm fine with the principle of this change, I do consider it dangerous - people might be catching TypeError now instead of using the "alternate" argument. Quite a bit of code depending on TypeError (even if undocumented) might unexpectedly break.
If so, that code is already broken: it depends on an undocumented implementation detail (of a non-API method). The patch makes the __call__ method an API, and documents the (new) exception type. Anybody whose code breaks when this happens can hold off upgrading zope.interface until they fix that usage.
We could support both errors by introducing an exception that subclasses both TypeError and LookupError. The question is whether such a strategy would help us deprecate TypeError altogether - I don't see how we can detect that TypeError was caught instead of LookupError when our exception is handled, so we can't output any messages..
Besides this you've documented the default argument as "default" while it is in fact "alternate" (which we want to deprecate in favor of default), so the documentation of __call__ isn't correct.
__call__ is not an API, so again, anybody relying on its argument names can hold off on upgrading.
I think this needs some more thinking, unfortunately. I wish I could see a gradual way forward on moving from TypeError to LookupError.
I don't see any way, or any benefit, to holding off. Just publish the new version (with a bump), and let people find and fix the problematic places in their code as they upgrade. Tres. - -- =================================================================== Tres Seaver +1 540-429-0999 tseaver@palladion.com Palladion Software "Excellence by Design" http://palladion.com -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iEYEARECAAYFAksrvcMACgkQ+gerLs4ltQ5bewCg1jxWXSGE/cj+1OJ8X9yc0IIN KUcAnilGOd2LdOA4ZXtUKYSMVfbMLYGl =BkFw -----END PGP SIGNATURE-----
Hey, Tres Seaver wrote: [let's not suddenly change the behavior of __call__ for backwards compatibility reasons]
If so, that code is already broken: it depends on an undocumented implementation detail (of a non-API method). The patch makes the __call__ method an API, and documents the (new) exception type. Anybody whose code breaks when this happens can hold off upgrading zope.interface until they fix that usage.
Documentation status aside, are you really maintaining that a feature that people have been using for many years is not actually part of the API? So that we can change willy-nilly without any concern for backwards compatibility? If you do insist on taking that position, then please note that while API may not be documented in zope.interface it is certainly documented in other places. The __call__ behavior and the TypeError behavior for instance is documented in at least one published book (see bottom of the page): http://docs.zope.org/zope3/Book/ifaceschema/interface/show.html Documented for years. [snip part where changing the name of a parameter is also fine as it's undocumented] -1 against merging this. Regards, Martijn
Martijn Faassen wrote: [snip]
If you do insist on taking that position, then please note that while API may not be documented in zope.interface...
actually the API is documented in zope.interface's README.txt, including TypeError: If an object cannot be adapted, then a TypeError is raised::
class I(zope.interface.Interface): ... pass
I(0) Traceback (most recent call last): ... TypeError: ('Could not adapt', 0, <InterfaceClass __main__.I>)
That documentation went in there 3 years and 7 months ago. Regards, Martijn
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Martijn Faassen wrote:
Hey,
Tres Seaver wrote: [let's not suddenly change the behavior of __call__ for backwards compatibility reasons]
If so, that code is already broken: it depends on an undocumented implementation detail (of a non-API method). The patch makes the __call__ method an API, and documents the (new) exception type. Anybody whose code breaks when this happens can hold off upgrading zope.interface until they fix that usage.
Documentation status aside, are you really maintaining that a feature that people have been using for many years is not actually part of the API? So that we can change willy-nilly without any concern for backwards compatibility?
I didn't say "without concern" -- I suggested that we bump the minor version to indicate that this was something of an API change. If you would rather raise a compatibliity error (blending the two types together), I could live with that. I would still rather document LookupError, as it is the *correct* exception type: TypeError says "you violated the contract by passing me a bad argument", rather than "I couldn't find what you were looking for." Note that CLE already derives from LookupError, which reinforces the point.
If you do insist on taking that position, then please note that while API may not be documented in zope.interface it is certainly documented in other places. The __call__ behavior and the TypeError behavior for instance is documented in at least one published book (see bottom of the page):
http://docs.zope.org/zope3/Book/ifaceschema/interface/show.html
Documented for years.
That example is a poor bit of documentation, as well as a bad test (depends on the repr of an exception). There is way too much "example cruft" and way too little prose there to help anybody figure out how to use interfaces by calling them. In particular, the role of the adapter hook is completely obscured. If __call__ is an API, why is it not documented where the rest of the API for Interface is specified? Tres. - -- =================================================================== Tres Seaver +1 540-429-0999 tseaver@palladion.com Palladion Software "Excellence by Design" http://palladion.com -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iEYEARECAAYFAksr/lUACgkQ+gerLs4ltQ620ACgifVngQZiIOhHNtHsDLtF8i0l dVYAoNhlXxcfJA4eMiyFIFo7nNHKiUQ+ =FCFu -----END PGP SIGNATURE-----
Tres Seaver wrote: [snip]
If __call__ is an API, why is it not documented where the rest of the API for Interface is specified?
I think you shouldn't focus on documentation to the exclusion of the practical situation, which is that people have been calling interfaces for many years now to accomplish adaptation, apparently having found about it from a multitude of sources. It has gained C optimizations. The practicality of this is that this is a well-known and well-used API. What we should fix is its documentation in interfaces.py. A little bit of googling even found a definition in Twisted that says: # FIXME: deprecate attribute somehow? CannotAdapt = TypeError though I cannot find many examples of using TypeError or CannotAdapt to handle adaptation. I cannot tell why it is not in interfaces.py. Perhaps a plain and simple oversight? Perhaps it was initially felt to be experimental, but became accepted (gaining C optimizations, and a mention in README.txt), and it was forgotten to add it to the interfaces documentation? Maybe Jim knows. :) Regards, Martijn
On Thu, Dec 17, 2009 at 02:53, Tres Seaver <tseaver@palladion.com> wrote:
I have checked in a branch which makes failed adaptation (inside the __call__ of an interface) raise a LookupError instead of a TypeError: the branch also documents the semantics of __call__. I would like to merge this to the trunk a 3.6.0 version (bumped to indicate the quasi-API change).
I'm +-0 on the change, but don't think it's a quasi-API change, but a proper API-change. If we do it, would it be a better idea to do it together with all other API changes that are planned, and call it 4.0? The Python 3 branch is ready for merging, and I personally think that although it's completely backwards compatible the changes are big enough, and the addition of @implementor as a class decorator is yet another API-change. I'd love to see the Python 3 branch, the adapt()/utility() and possibly this change all go into a new release. zope.testing is also big enough change to warrant a 4.0, as I have removed zope.testing.doctest. Maybe all Python 3 compatible releases shuld be called 4.0? Or will this confuse people? -- Lennart Regebro: http://regebro.wordpress.com/ Python 3 Porting: http://python-incompatibility.googlecode.com/ +33 661 58 14 64
Lennart Regebro wrote:
On Thu, Dec 17, 2009 at 02:53, Tres Seaver <tseaver@palladion.com> wrote:
I have checked in a branch which makes failed adaptation (inside the __call__ of an interface) raise a LookupError instead of a TypeError: the branch also documents the semantics of __call__. I would like to merge this to the trunk a 3.6.0 version (bumped to indicate the quasi-API change).
I'm +-0 on the change, but don't think it's a quasi-API change, but a proper API-change. If we do it, would it be a better idea to do it together with all other API changes that are planned, and call it 4.0? The Python 3 branch is ready for merging, and I personally think that although it's completely backwards compatible the changes are big enough, and the addition of @implementor as a class decorator is yet another API-change.
If you can coordinate it with Thomas and such I'm +1 on merging the Py3K together with the utility/adapt changes into a 4.0. But I'm still a bit unsure about changing that error message. I believe very strongly we should not let the Python 3.0 transition moment be the same moment we do big changes to existing APIs that can break compatibility. Whether this warrants as a "big change" I don't know..
I'd love to see the Python 3 branch, the adapt()/utility() and possibly this change all go into a new release. zope.testing is also big enough change to warrant a 4.0, as I have removed zope.testing.doctest.
Is there a backwards compatibility import? If the pattern has been just a normal feature release. Or did you also fix 3k support for zope.testing?
Maybe all Python 3 compatible releases shuld be called 4.0? Or will this confuse people?
I don't know yet. :) Regards, Martijn
On Thu, Dec 17, 2009 at 15:56, Martijn Faassen <faassen@startifact.com> wrote:
Is there a backwards compatibility import?
Nope. We could put one there, but I thought we might want to deprecate it in zope.testing instead. We can do both of course. zope.testing.doctest and stdlibs doctest has grown apart a bit but I don't think it should affect users of doctest, just the zope.testing tests themselves. It's mostly output formatting issues.
Or did you also fix 3k support for zope.testing?
I did that too, yes, and that was the main reason to remove the doctest module, as porting it to Python 3 again seemed silly. :) -- Lennart Regebro: Python, Zope, Plone, Grok http://regebro.wordpress.com/ +33 661 58 14 64
Hey, Tres Seaver wrote: [snip]
Any code today which wants a utility is calling 'getUtilty' (if it *knows* the utility must be registered) or 'queryUtility' (if it thinks it might not be). Less facetiously than my first challenge: please point to actual code in the wild which looks like::
try: foo = getUtilty(IFoo, name='bar') except ComponentLookupError: # do something
instead of::
foo = queryUtility(IFoo, name='bar') if foo is None: # do something
I will argue that any code doing the first, outside of maybe tests of the ZCA itself is plain broken.
I have code like that in the wild - I have no real reason why I didn't queryUtility, but I didn't think it mattered. Why is it plain broken? Isn't getUtility supported to raise ComponentLookupError if it cannot find the required utility? Regards, Martijn
Martijn Faassen wrote:
Hey,
Tres Seaver wrote: [snip]
Any code today which wants a utility is calling 'getUtilty' (if it *knows* the utility must be registered) or 'queryUtility' (if it thinks it might not be). Less facetiously than my first challenge: please point to actual code in the wild which looks like::
try: foo = getUtilty(IFoo, name='bar') except ComponentLookupError: # do something
instead of::
foo = queryUtility(IFoo, name='bar') if foo is None: # do something
I will argue that any code doing the first, outside of maybe tests of the ZCA itself is plain broken.
I have code like that in the wild - I have no real reason why I didn't queryUtility, but I didn't think it mattered.
Why is it plain broken? Isn't getUtility supported to raise ComponentLookupError if it cannot find the required utility?
The interface declaration for the zope.component API does assert that getUtility raises a CLE if it cannot find the utility. Also, I wouldn't say that queryUtility should be used instead of using getUtility and catching the CLE, as that would deprive us of all the advantages of using exceptions, such as propagation along the call stack. -- Thomas
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Thomas Lotze wrote:
Martijn Faassen wrote:
Hey,
Tres Seaver wrote: [snip]
Any code today which wants a utility is calling 'getUtilty' (if it *knows* the utility must be registered) or 'queryUtility' (if it thinks it might not be). Less facetiously than my first challenge: please point to actual code in the wild which looks like::
try: foo = getUtilty(IFoo, name='bar') except ComponentLookupError: # do something
instead of::
foo = queryUtility(IFoo, name='bar') if foo is None: # do something
I will argue that any code doing the first, outside of maybe tests of the ZCA itself is plain broken. I have code like that in the wild - I have no real reason why I didn't queryUtility, but I didn't think it mattered.
Why is it plain broken? Isn't getUtility supported to raise ComponentLookupError if it cannot find the required utility?
The interface declaration for the zope.component API does assert that getUtility raises a CLE if it cannot find the utility. Also, I wouldn't say that queryUtility should be used instead of using getUtility and catching the CLE, as that would deprive us of all the advantages of using exceptions, such as propagation along the call stack.
Those advantages don't obtain if the call site itself catches the CLE: at that point, all you have is a more expensive and less clear way to get what 'queryUtility' gives you. Tres - -- =================================================================== Tres Seaver +1 540-429-0999 tseaver@palladion.com Palladion Software "Excellence by Design" http://palladion.com -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iEYEARECAAYFAksrvvwACgkQ+gerLs4ltQ64JgCfc9sKxivpsIZJT8A3ep+pW0cZ J7AAn0yRW2137WO7cHq92zAXCTj33cwf =anzG -----END PGP SIGNATURE-----
Martijn Faassen wrote:
* The hook invokes the `query*` functions to play nice with any other component hooks and the interface methods raise a TypeError if all of them fail to find a component.
A TypeError instead of a ComponentLookupError?
I was thinking we should keep the behavior as close to zope.component as we can, including ComponentLookupError. Don't you get a ComponentLookupError with the classic adapter hook too? So I'm -1 to making this a TypeError.
The ComponentLookupError is defined by zope.component. So the only way of getting a CLE raised by Interface.adapt or Interface.utility would be to call the non-query-style lookup functions from the zope.component API and let the error propagate. The current implementation of the new interface methods pretend, however, to allow for more hookability than just calling a zope.component function, so it is intentional to not let any error propagate but defer to the next hook instead and raise an exception of a type known to zope.interface (i.e. not CLE) if none of the hooks succeeds. The choice of TypeError was made in analogy with __call__. Currently, you get a TypeError if IFoo(bar) fails while zope.component.getAdapter(bar, IFoo) raises CLE. IMO, the clean way to solve this is to drop the analogy with the __call__ implementation and no longer try to cater for generic `adapt` and `utility` behaviour. Together with avoiding a direct dependency of zope.interface on zope.component, this leads to injecting non-hookable implementations of `adapt` and `utility` from zope.component itself.
I think a monkey patch would be fine. Opinions about making it more structured:
* have dummy implementations in zope.interface that raise NotImplementedError
That would still introduce too many zope.component concepts into zope.interface IMO: obviously that of utilities as one of the dummy methods would bear that name, and those of named components and lookup contexts if the methods were to be specified by IInterface.
* have that NotImplementedError say to look for implementations in zope.component
I would actually like a mechanism that doesn't care about what package will inject which pieces of API. Do you think such generality is asking too much (at least at this point in time)?
* have the docstring on the methods that are injected be clear that they are being injected into zope.interface
Of course.
That way someone reading the zope.interface code can at least find out that this functionality is injected from zope.component.
IMO, we should mention this as a usage example but not as a requirement.
[as an aside it'd be interesting to find out how much of of zope.component functionality could sensibly make it into zope.interface, but that's another project; Gary Poster has some ideas in relation to this]
I think before doing such a thing, we should come up with a precise definition of which concepts are the concern of interfaces, which are that of the ZCA and exactly where to draw the line. Anything else looks IMO dangerously like a slippery slope towards mixing it all up into zope.interface.
I'm still sneakily hoping that in a few years we'll be able to get calling the interface be the one true way of looking up implementations of that interface (utilities and adapters and whatnots), but we'll see.
Me too.
Did you make an implicit 'default' deprecated on __call__ yet by the way?
Not yet; the other issue looked more interesting so far ;o) BTW, that parameter isn't even called 'default' but 'alternate' in Interface.__call__. The very name of the default argument is another thing that is sneaking from zope.component into zope.component. -- Thomas
Thomas Lotze wrote:
Martijn Faassen wrote:
* have dummy implementations in zope.interface that raise NotImplementedError
That would still introduce too many zope.component concepts into zope.interface IMO: obviously that of utilities as one of the dummy methods would bear that name, and those of named components and lookup contexts if the methods were to be specified by IInterface.
I think having these markers is very important for code readability. People reading the code will otherwise have no idea whatsoever where these methods come from. I'm not sure whether much of a general mechanism is really needed, after all we already have monkey patching (um, sorry Tres, modifying a class). I can see there being an API in zope.interface that allows one to plug into .utility() and .adapt()
* have that NotImplementedError say to look for implementations in zope.component
I would actually like a mechanism that doesn't care about what package will inject which pieces of API. Do you think such generality is asking too much (at least at this point in time)?
I think it will be less clear compared to the methods being there and saying they're injection points intended to be overwritten.
Did you make an implicit 'default' deprecated on __call__ yet by the way?
Not yet; the other issue looked more interesting so far ;o) BTW, that parameter isn't even called 'default' but 'alternate' in Interface.__call__. The very name of the default argument is another thing that is sneaking from zope.component into zope.component.
Let's deprecate 'alternate' and introduce 'default' then. Might actually make the deprecation more easy.. Regards, Martijn
Martijn Faassen wrote:
Thomas Lotze wrote:
Martijn Faassen wrote:
* have dummy implementations in zope.interface that raise NotImplementedError
That would still introduce too many zope.component concepts into zope.interface IMO: obviously that of utilities as one of the dummy methods would bear that name, and those of named components and lookup contexts if the methods were to be specified by IInterface.
I think having these markers is very important for code readability. People reading the code will otherwise have no idea whatsoever where these methods come from.
I see your point, but I have two objections: - The very concept of the ZCA introduces a related problem with code readability: just by reading the code that uses components you will never be able to tell where a particular adapter or utility comes from, or even what adapters or utilities you can hope to look up in the first place. So having to have some knowledge of the larger system that uses the interface mechanics isn't an entirely new thing, and having to learn about these two methods is a very small one-time effort compared to the "readability obstacles" that using the system thus entails. - As pointed out before, I consider it a goal to treat all uses of interfaces equally (which seems to me to be related to the effort to make interfaces more of a part of the language). By implementing stubs for `adapt` and `utility` (or specifying them in IInterface) we'd make the ZCA stand out as a particular use of interfaces. IMO, we serve those who seek to understand the API just as well by documenting the two methods as prominent examples of interface usage. I guess we should make a decision about the latter point of view before discussing a particular implementation strategy.
I'm not sure whether much of a general mechanism is really needed, after all we already have monkey patching (um, sorry Tres, modifying a class). I can see there being an API in zope.interface that allows one to plug into .utility() and .adapt()
If you're talking about the component hooks, then that's the part I hope to get rid of as it causes more trouble than it helps. (This is what I tried to point out in the message that started this thread.)
parameter isn't even called 'default' but 'alternate' in Interface.__call__. The very name of the default argument is another thing that is sneaking from zope.component into zope.component.
Let's deprecate 'alternate' and introduce 'default' then. Might actually make the deprecation more easy..
I don't think so. We're going to deprecate not spelling out the name of the parameter, so it won't matter which name not to spell out. OTOH, we'll additionally have to deprecate the name `alternate` where it is used. I'm not sure whether we should make the name of that parameter consistent between zope.component and zope.interface, or leave it alone in order not to pretend the relation between the different implementations of adaptation to be stronger than it actually is. -- Thomas
Thomas Lotze wrote:
I'm not sure whether we should make the name of that parameter consistent between zope.component and zope.interface,
Sorry, nevermind. Of course we'll want to rename that parameter as our secret plan is to access the ZCA through Interface.__call__ one day. -- Thomas
Thomas Lotze wrote: [snip]
- The very concept of the ZCA introduces a related problem with code readability: just by reading the code that uses components you will never be able to tell where a particular adapter or utility comes from, or even what adapters or utilities you can hope to look up in the first place. So having to have some knowledge of the larger system that uses the interface mechanics isn't an entirely new thing, and having to learn about these two methods is a very small one-time effort compared to the "readability obstacles" that using the system thus entails.
I think the cost of introducing some dummy methods that document this is very low, and we should do it. Other readability obstacles aren't an excuse to create more. :)
- As pointed out before, I consider it a goal to treat all uses of interfaces equally (which seems to me to be related to the effort to make interfaces more of a part of the language). By implementing stubs for `adapt` and `utility` (or specifying them in IInterface) we'd make the ZCA stand out as a particular use of interfaces. IMO, we serve those who seek to understand the API just as well by documenting the two methods as prominent examples of interface usage.
I'd say the best way of documenting them is by putting a few stubs in the code. That way they're hard to miss. It'll also be easy to understand why they're *not* working if zope.component isn't installed. I really see the cost of doing this as pretty low - it's not like we're putting actual real implementations in there, just information, so I see this primarily as a form of documentation. [snip my argument that monkey patching the methods in seems a decent way to do it]
If you're talking about the component hooks, then that's the part I hope to get rid of as it causes more trouble than it helps. (This is what I tried to point out in the message that started this thread.)
If they can be replaced by a simple assignment to __call__ that might be clearer. I am just talking about not inventing some kind of sophisticated way to put these methods in. I think the most clearly readable code would be this (if this is indeed possible due to the weirdness of Interface): from zope.interface import Interface Interface.__call__ = our_call Interface.adapter = our_adapter Interface.utility = our_utility I do think it's iportant that Interface defines these methods, if only to raise NotImplementedError.
parameter isn't even called 'default' but 'alternate' in Interface.__call__. The very name of the default argument is another thing that is sneaking from zope.component into zope.component. Let's deprecate 'alternate' and introduce 'default' then. Might actually make the deprecation more easy..
I don't think so. We're going to deprecate not spelling out the name of the parameter, so it won't matter which name not to spell out.
OTOH, we'll additionally have to deprecate the name `alternate` where it is used. I'm not sure whether we should make the name of that parameter consistent between zope.component and zope.interface, or leave it alone in order not to pretend the relation between the different implementations of adaptation to be stronger than it actually is.
Definitely make it consistent. Anything else is much harder to learn and remember. While this may be a signal "something different is going on", it's in my mind an actively harmful one. We already signal something different may happen as it's after all a different method. Regards, Martijn
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Martijn Faassen wrote:
Thomas Lotze wrote:
Martijn Faassen wrote:
* have dummy implementations in zope.interface that raise NotImplementedError That would still introduce too many zope.component concepts into zope.interface IMO: obviously that of utilities as one of the dummy methods would bear that name, and those of named components and lookup contexts if the methods were to be specified by IInterface.
I think having these markers is very important for code readability. People reading the code will otherwise have no idea whatsoever where these methods come from.
I'm not sure whether much of a general mechanism is really needed, after all we already have monkey patching (um, sorry Tres, modifying a class). I can see there being an API in zope.interface that allows one to plug into .utility() and .adapt()
I don't see any win for the "generic" extensions either: we already know *exactly* what two methods we want to use, and have no use cases at all for adding any others.
* have that NotImplementedError say to look for implementations in zope.component I would actually like a mechanism that doesn't care about what package will inject which pieces of API. Do you think such generality is asking too much (at least at this point in time)?
I think it will be less clear compared to the methods being there and saying they're injection points intended to be overwritten.
- -1 to any docstring / exception method which points outside the zope.itnerface package. There is a perfectly reasonable "default" implementation anyway (in the absence of any hooks): - - 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 would much rather keep a "hook registration" API in the interface class for these methods than obscure their origin semantics via a monkey patch.
Did you make an implicit 'default' deprecated on __call__ yet by the way? Not yet; the other issue looked more interesting so far ;o) BTW, that parameter isn't even called 'default' but 'alternate' in Interface.__call__. The very name of the default argument is another thing that is sneaking from zope.component into zope.component.
Let's deprecate 'alternate' and introduce 'default' then. Might actually make the deprecation more easy..
Note that Interface.__call__ is not documented at all as an API:: $ grep __call__ src/zope/interface/interfaces.py $ I think we should just rename the argument without any deprecation. Tres. - -- =================================================================== Tres Seaver +1 540-429-0999 tseaver@palladion.com Palladion Software "Excellence by Design" http://palladion.com -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iEYEARECAAYFAkspVZYACgkQ+gerLs4ltQ52fACcC3Pd8uGvTzjkKwmTX97PrQaf oxkAoLoA1lRxpPA6HpQYQtifxQBM8sbF =jebA -----END PGP SIGNATURE-----
Hey, Tres Seaver wrote: [snip]
- -1 to any docstring / exception method which points outside the zope.itnerface package. There is a perfectly reasonable "default" implementation anyway (in the absence of any hooks):
You're against a docstring talking about zope.component overriding these? Seriously? I mean, just mentioning zope.component in the docstring is taboo? Now as to the default behavior of these methods:
- - 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
Basically the default behavior of the methods would be like nothing ever got registered. That looks reasonable to me, +1.
I would much rather keep a "hook registration" API in the interface class for these methods than obscure their origin semantics via a monkey patch.
Okay, I guess that does make sense. Regards, Martijn
On Wed, Dec 16, 2009 at 04:48:06PM -0500, Tres Seaver wrote:
There is a perfectly reasonable "default" implementation anyway (in the absence of any hooks):
- 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 would much rather keep a "hook registration" API in the interface class for these methods than obscure their origin semantics via a monkey patch.
I'll throw this into the pot for your contemplation: I probably wasted many hours during my first several years of Zope 3 development, trying to figure out why zope.schema.Choice fields wouldn't find my IVocabularyFactory utilities I was registering right here in the test's setUp, and why I had to use some strange VocabularyRegistries instead of mechanically converting ZCML directives into provideAdapter statements. Until I discovered one day that just by *importing* a module (specifically, zope.app.component.vocabulary) I get a side-effect of replacing one vocabulary mechanism with another one. So I'm -99 for magical side effects of import statements, but thankfully the community opinion is already against them. I'm -0 for having an implementation that seems to be working, but isn't (utility lookup when no utilities can possibly be defined? very useful, that), unless you invoke some magical incantation. It's only -0 rather than something stronger because the same sort of situation already exists with Interface.__call__ hook, and it doesn't cause any problems in practice (probably because everybody calls placelessSetUp() at the start of their test setUp). Marius Gedminas -- http://pov.lt/ -- Zope 3 consulting and development
Marius Gedminas wrote: [snip]
So I'm -99 for magical side effects of import statements, but thankfully the community opinion is already against them.
Yeah, I was a bit too cavalier in earlier discussions about such a solution. Tres suggested having a proper plugin API for this and a registration function, and I agree. This should be called early in the startup sequence but not during import time.
I'm -0 for having an implementation that seems to be working, but isn't (utility lookup when no utilities can possibly be defined? very useful, that), unless you invoke some magical incantation.
It's only -0 rather than something stronger because the same sort of situation already exists with Interface.__call__ hook, and it doesn't cause any problems in practice (probably because everybody calls placelessSetUp() at the start of their test setUp).
I see this as a bootstrapping issue as we're fiddling with the component architecture itself. Normally when I want to provide a plugin API for a library I'd look into using adapters or utilities. The library would look these up, and external systems have to provide them. If they don't, it won't work (ComponentLookupError) or alternatively the library falls back on default behavior. Such a design pattern I dare say is fairly accepted within the user community of the ZCA. :) But since we are bootstrapping the component architecture itself, we have to come up with an alternative implementation. We instead offer a registration API in zope.interface and we have zope.component register the appropriate plugin. We then modify the startup procedure to tell zope.component to register itself with zope.interface, and we modify the testing infrastructure provided by zope.component to do the same thing. As you say something very similar is going on with the traditional adapter hook already. Regards, Martijn
Hi there, Here's a summary of what has been going on in this thread with some attempts at conclusions that have support of the consensus so that Thomas can proceed * We want to implement .adapter(), .utility() and .__call__() in zope.component as much as possible. * we want a similar mechanism for each of them to plug in. * 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? * there was some discussion about general plugin points on Interface. Those have a complexity cost compared to simply poking the methods into the class. * the methods can be on zope.interface even if zope.component isn't installed. They will behave as if the component registry is empty. 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 What's the behavior of __call__ now if zope.component isn't around? * 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. What if there's some global object that zope.component can register into that has this API? class ILookupPlugin(Interface): def singleAdapt(interface, context): """Look up an adapter that provides interface in the adapter registry for context. If no adapter can be found, raise LookupError. ( (or return None or another sentinel? See below for an argument in favor of LookupError) """ def multiAdapt(interface, contexts): """Look up an adapter that provides interface for contexts. """ def utility(interface): """Lookup an utility that provides interface. """ And a way to register this into zope.interface (globally): def registerLookupPlugin(plugin): ... The default plugin provided by zope.interface always raises ComponentLookupError (or returns the sentinel). The plugin is not responsible for handling defaults, just registry lookups. Then the code in __call__, adapt and utility can: * check whether the context (if available) already provides interface, return that. * look up in the plugin * if the plugin raises (or returns sentinel) and there is a default, return default * if there no default, raise LookupError. I think we can turn the message from ComponentLookupError into a LookupError fairly safely, so I think having the plugin raise exceptions is better as it retains more information. In fact it'd be entirely safe to just reraise ComponentLookupError as we only specify LookupError in the API and the ComponentLookupError *is* a LookupError 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. And we can define sensible enough behavior even if zope.component is not installed. If someone comes up with *another* implementation besides zope.component, say, zope.component2, zope.interface could cleanly deal with that too. [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..] Regards, Martijn
Hi, On Thu, Dec 17, 2009 at 10:15 AM, Martijn Faassen <faassen@startifact.com> wrote:
* 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?
Maybe I've misunderstanding, but what's the advantage of making IFoo(x) raise a LookupError instead of a TypeError? I've tried to follow the thread but I've been confused about this. I do rely on catching TypeErrors quite often in my code -- I had thought it was intended as part of the API. I like that treating it as typecasting instead of lookup blurs the conceptual distinction between "adapting x to IFoo" and "asserting x implements IFoo directly" -- when I actually want to know one or the other I can use explicit adaptation or check providedBy. In other words I use IFoo(x) when I don't care whether IFoo(x) == x. I suppose it's not a big deal but catching LookupErrors instead would feel somewhat less semantic for the way I think about this. But from what I've caught of the larger thread it sounds like I'm in the minority here. -Ethan
On 12/17/09 16:36 , Ethan Jucovy wrote:
Hi,
On Thu, Dec 17, 2009 at 10:15 AM, Martijn Faassen <faassen@startifact.com> wrote:
* 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?
Maybe I've misunderstanding, but what's the advantage of making IFoo(x) raise a LookupError instead of a TypeError? I've tried to follow the thread but I've been confused about this. I do rely on catching TypeErrors quite often in my code -- I had thought it was intended as part of the API.
I like that treating it as typecasting instead of lookup blurs the conceptual distinction between "adapting x to IFoo" and "asserting x implements IFoo directly" -- when I actually want to know one or the other I can use explicit adaptation or check providedBy. In other words I use IFoo(x) when I don't care whether IFoo(x) == x.
I suppose it's not a big deal but catching LookupErrors instead would feel somewhat less semantic for the way I think about this. But from what I've caught of the larger thread it sounds like I'm in the minority here.
Perhapse LookupError should be a subclass of TypeError. Wichert.
I'll throw out the obvious... Why not subclass Interface in zope.component and make the required API additions there? If it were anybody but us thinking about doing this, they'd probably just subclass. - C Martijn Faassen wrote:
Wichert Akkerman wrote: [knip]
Perhapse LookupError should be a subclass of TypeError.
It's a Python built-in. :)
Regards,
Martijn
_______________________________________________ Zope-Dev maillist - Zope-Dev@zope.org https://mail.zope.org/mailman/listinfo/zope-dev ** No cross posts or HTML encoding! ** (Related lists - https://mail.zope.org/mailman/listinfo/zope-announce https://mail.zope.org/mailman/listinfo/zope )
Yeah, I was thinking that too, as a "I don't have time to think hard about this" little daydream. Actually I believe you would want to subclass InterfaceClass and make your new zope.component.Interface an instance of the new InterfaceClass and specify zope.interface's Interface as something it extends. Then packages in the Zope world would start to use that Interface, I'd guess. I don't know how I feel about it. Gary On Dec 17, 2009, at 11:51 AM, Chris McDonough wrote:
I'll throw out the obvious...
Why not subclass Interface in zope.component and make the required API additions there? If it were anybody but us thinking about doing this, they'd probably just subclass.
- C
Martijn Faassen wrote:
Wichert Akkerman wrote: [knip]
Perhapse LookupError should be a subclass of TypeError.
It's a Python built-in. :)
Regards,
Martijn
_______________________________________________ Zope-Dev maillist - Zope-Dev@zope.org https://mail.zope.org/mailman/listinfo/zope-dev ** No cross posts or HTML encoding! ** (Related lists - https://mail.zope.org/mailman/listinfo/zope-announce https://mail.zope.org/mailman/listinfo/zope )
_______________________________________________ Zope-Dev maillist - Zope-Dev@zope.org https://mail.zope.org/mailman/listinfo/zope-dev ** No cross posts or HTML encoding! ** (Related lists - https://mail.zope.org/mailman/listinfo/zope-announce https://mail.zope.org/mailman/listinfo/zope )
We've been through this option recently already. It has been quickly shot down by those who want interfaces to get the new behaviour immediately available in existing interfaces, instead of having to rewrite already existing interfaces to support the new API. Painful memories of a time when we had 2 interface implementations come to mind. Though in this case, with z.c.Interface extending z.i.Interface, we probably wouldn't have so much backward compatibility issues as we had before: the new API would just not be available in non migrated code, and old code could treat z.c.Interfaces just like z.i.Interfaces. On Thu, Dec 17, 2009 at 15:07, Gary Poster <gary.poster@gmail.com> wrote:
Yeah, I was thinking that too, as a "I don't have time to think hard about this" little daydream.
Actually I believe you would want to subclass InterfaceClass and make your new zope.component.Interface an instance of the new InterfaceClass and specify zope.interface's Interface as something it extends.
Then packages in the Zope world would start to use that Interface, I'd guess.
I don't know how I feel about it.
Gary
On Dec 17, 2009, at 11:51 AM, Chris McDonough wrote:
I'll throw out the obvious...
Why not subclass Interface in zope.component and make the required API additions there? If it were anybody but us thinking about doing this, they'd probably just subclass.
- C
Martijn Faassen wrote:
Wichert Akkerman wrote: [knip]
Perhapse LookupError should be a subclass of TypeError.
It's a Python built-in. :)
Regards,
Martijn
_______________________________________________ Zope-Dev maillist - Zope-Dev@zope.org https://mail.zope.org/mailman/listinfo/zope-dev ** No cross posts or HTML encoding! ** (Related lists - https://mail.zope.org/mailman/listinfo/zope-announce https://mail.zope.org/mailman/listinfo/zope )
_______________________________________________ Zope-Dev maillist - Zope-Dev@zope.org https://mail.zope.org/mailman/listinfo/zope-dev ** No cross posts or HTML encoding! ** (Related lists - https://mail.zope.org/mailman/listinfo/zope-announce https://mail.zope.org/mailman/listinfo/zope )
_______________________________________________ Zope-Dev maillist - Zope-Dev@zope.org https://mail.zope.org/mailman/listinfo/zope-dev ** No cross posts or HTML encoding! ** (Related lists - https://mail.zope.org/mailman/listinfo/zope-announce https://mail.zope.org/mailman/listinfo/zope )
Chris McDonough wrote:
I'll throw out the obvious...
Why not subclass Interface in zope.component and make the required API additions there? If it were anybody but us thinking about doing this, they'd probably just subclass.
Because then we'd need to rebase all our interfaces to be able to use the new feature, making it pretty useless as a handy shortcut. regards, Martijn
Martijn Faassen wrote:
Chris McDonough wrote:
I'll throw out the obvious...
Why not subclass Interface in zope.component and make the required API additions there? If it were anybody but us thinking about doing this, they'd probably just subclass.
Because then we'd need to rebase all our interfaces to be able to use the new feature, making it pretty useless as a handy shortcut.
I guess the counterargument is that you need to change your code anyway to make use of the new feature. So what's the difference? - C
Chris McDonough wrote:
Martijn Faassen wrote:
Chris McDonough wrote:
I'll throw out the obvious...
Why not subclass Interface in zope.component and make the required API additions there? If it were anybody but us thinking about doing this, they'd probably just subclass. Because then we'd need to rebase all our interfaces to be able to use the new feature, making it pretty useless as a handy shortcut.
I guess the counterargument is that you need to change your code anyway to make use of the new feature. So what's the difference?
I'm a bit surprised I need to spell it out, so perhaps I am missing something. With your subclass proposal, if I use package a.b.c that defines an interface, and I import that interface and want to use the new feature, I'd need to change package a.b.c to make use of the new feature. That's a code change (changing the interface import), and a dependency change (making sure the new version of zope.component is in use), and a release. I need to do that for all packages I import interfaces from. I need to *know* which packages have been updated or have things blow up on me. That's quite a hassle. The end result would be that everybody ends up inheriting from the new zope.component Interface just to get access to the new methods everywhere, or that the new methods don't get uptake from users as nobody bothers to use the new API because it is too much work changing your dependencies. If we change the Interface base class, I could use the new feature with any interface coming from anywhere. I'd only need to make sure that my own package depends on a newer version of zope.interface (or possibly a newer version of zope.component, depending on the way these methods are implemented). Regards, Martijn
Chris McDonough wrote:
I'll throw out the obvious...
Why not subclass Interface in zope.component and make the required API additions there? If it were anybody but us thinking about doing this, they'd probably just subclass.
Because then, if you use third-party code that uses zope.interface.Interface and other code (third-party or your own) that uses the subclassed interfaces, you'll have to deal with both types at the same time in your client code. You could use the new API on some interfaces but not on others, possibly on the same line of code. How readable or maintainable would such code be? -- Thomas
Thomas Lotze wrote:
Chris McDonough wrote:
I'll throw out the obvious...
Why not subclass Interface in zope.component and make the required API additions there? If it were anybody but us thinking about doing this, they'd probably just subclass.
Because then, if you use third-party code that uses zope.interface.Interface and other code (third-party or your own) that uses the subclassed interfaces, you'll have to deal with both types at the same time in your client code. You could use the new API on some interfaces but not on others, possibly on the same line of code. How readable or maintainable would such code be?
I'm not sure, but if we had it to do all over again, this would be an obvious solution. It would be the work of maybe two days to convert all "ZTK" packages to use a z.c.interface.Interface, and any existing package would need to be touched anyway to use .adapt and .utility. So it bears some weight I think, even if it is eventually rejected. - C
Chris McDonough wrote:
Thomas Lotze wrote:
Because then, if you use third-party code that uses zope.interface.Interface and other code (third-party or your own) that uses the subclassed interfaces, you'll have to deal with both types at the same time in your client code. You could use the new API on some interfaces but not on others, possibly on the same line of code. How readable or maintainable would such code be?
I'm not sure, but if we had it to do all over again, this would be an obvious solution. It would be the work of maybe two days to convert all "ZTK" packages to use a z.c.interface.Interface, and any existing package would need to be touched anyway to use .adapt and .utility. So it bears some weight I think, even if it is eventually rejected.
It does certainly bear some weight as one possibility to be considered. However, my point wasn't so much about converting a limited existing amount of code; you're obviously right about having to touch that anyway in order to use the new methods. My objection is this: If we go to the trouble of implementing basic interfaces in zope.interface plus derived ones with component lookup capabilities in zope.component and keep both around for their respective reasons of existence, then it is expected that there will be code that uses both types of interfaces for these very reasons (and maybe other types which have other added behaviour). Such code would become a lot less maintainable since you'd never know whether a given interface has a particular method just because the one next to it does. OTOH, registering all behaviour an application needs onto the same interface type doesn't create that problem. As long as you're familiar with the application at large, you will know for every interface that occurs in it which methods is has, how they need to be called and what their semantics are. Also, subclassing for adding behaviour introduces the typical problems of hierarchies and tight coupling. This isn't a practical problem as long as we only ever talk about adaptation as the only use of interfaces, but I'm trying to discuss "interfaces as a language feature" with a greater set of possible use cases. -- Thomas
Thomas Lotze wrote:
Chris McDonough wrote:
Thomas Lotze wrote:
Because then, if you use third-party code that uses zope.interface.Interface and other code (third-party or your own) that uses the subclassed interfaces, you'll have to deal with both types at the same time in your client code. You could use the new API on some interfaces but not on others, possibly on the same line of code. How readable or maintainable would such code be? I'm not sure, but if we had it to do all over again, this would be an obvious solution. It would be the work of maybe two days to convert all "ZTK" packages to use a z.c.interface.Interface, and any existing package would need to be touched anyway to use .adapt and .utility. So it bears some weight I think, even if it is eventually rejected.
It does certainly bear some weight as one possibility to be considered. However, my point wasn't so much about converting a limited existing amount of code; you're obviously right about having to touch that anyway in order to use the new methods.
My objection is this: If we go to the trouble of implementing basic interfaces in zope.interface plus derived ones with component lookup capabilities in zope.component and keep both around for their respective reasons of existence, then it is expected that there will be code that uses both types of interfaces for these very reasons (and maybe other types which have other added behaviour). Such code would become a lot less maintainable since you'd never know whether a given interface has a particular method just because the one next to it does.
OTOH, registering all behaviour an application needs onto the same interface type doesn't create that problem. As long as you're familiar with the application at large, you will know for every interface that occurs in it which methods is has, how they need to be called and what their semantics are.
Also, subclassing for adding behaviour introduces the typical problems of hierarchies and tight coupling. This isn't a practical problem as long as we only ever talk about adaptation as the only use of interfaces, but I'm trying to discuss "interfaces as a language feature" with a greater set of possible use cases.
Maybe a "hook" or "API extension" just isn't the right thing here. Maybe we just give up on the idea that z.component worldview doesn't belong in z.interface, for the sake of implementation simplicity. - Move the Components class from zope.component.registry to zope.interface.adapter and leave behind an import alias. - Move the "base" registry from zope.component.globalregistry into zope.interface.adapter and leave behind an import alias. - Move the getSiteManager function from zope.component._api into zope.interface.adapter and leave behind an import alias. - Move the "hookable" implementation from zope.component.hookable into zope.interface.hookable and leave behind an import alias. - Change the zope.interface.interface.InterfaceClass implementation directly, adding "adapts" and "utility" methods, which would use zope.interface.adapter.getSiteManager to find the registry. z.interface would then become responsible for: - Providing an implementation of a component registry which has a z.c worldview: adapters and utilities, stolen from z.c.registry.Components. - Providing a function that returns the current component registry (getSiteManager, which will continue to be hookable; although maybe we should take the opportunity to rename it to actually mean something). - Providing the instance that is the "default" component registry. All other responsibility (global API functions, a persistent registry implementation, security stuff, zcml support, etc), would remain in z.component. This would also line up conceptually with usage of "the ZCA" by BFG, which does not hook getSiteManager by default, but still uses a ZCA component registry. - C
Chris McDonough wrote:
Thomas Lotze wrote:
Chris McDonough wrote:
Thomas Lotze wrote:
Because then, if you use third-party code that uses zope.interface.Interface and other code (third-party or your own) that uses the subclassed interfaces, you'll have to deal with both types at the same time in your client code. You could use the new API on some interfaces but not on others, possibly on the same line of code. How readable or maintainable would such code be? I'm not sure, but if we had it to do all over again, this would be an obvious solution. It would be the work of maybe two days to convert all "ZTK" packages to use a z.c.interface.Interface, and any existing package would need to be touched anyway to use .adapt and .utility. So it bears some weight I think, even if it is eventually rejected. It does certainly bear some weight as one possibility to be considered. However, my point wasn't so much about converting a limited existing amount of code; you're obviously right about having to touch that anyway in order to use the new methods.
My objection is this: If we go to the trouble of implementing basic interfaces in zope.interface plus derived ones with component lookup capabilities in zope.component and keep both around for their respective reasons of existence, then it is expected that there will be code that uses both types of interfaces for these very reasons (and maybe other types which have other added behaviour). Such code would become a lot less maintainable since you'd never know whether a given interface has a particular method just because the one next to it does.
OTOH, registering all behaviour an application needs onto the same interface type doesn't create that problem. As long as you're familiar with the application at large, you will know for every interface that occurs in it which methods is has, how they need to be called and what their semantics are.
Also, subclassing for adding behaviour introduces the typical problems of hierarchies and tight coupling. This isn't a practical problem as long as we only ever talk about adaptation as the only use of interfaces, but I'm trying to discuss "interfaces as a language feature" with a greater set of possible use cases.
Maybe a "hook" or "API extension" just isn't the right thing here. Maybe we just give up on the idea that z.component worldview doesn't belong in z.interface, for the sake of implementation simplicity.
- Move the Components class from zope.component.registry to zope.interface.adapter and leave behind an import alias.
- Move the "base" registry from zope.component.globalregistry into zope.interface.adapter and leave behind an import alias.
- Move the getSiteManager function from zope.component._api into zope.interface.adapter and leave behind an import alias.
- Move the "hookable" implementation from zope.component.hookable into zope.interface.hookable and leave behind an import alias.
- Change the zope.interface.interface.InterfaceClass implementation directly, adding "adapts" and "utility" methods, which would use zope.interface.adapter.getSiteManager to find the registry.
z.interface would then become responsible for:
- Providing an implementation of a component registry which has a z.c worldview: adapters and utilities, stolen from z.c.registry.Components.
- Providing a function that returns the current component registry (getSiteManager, which will continue to be hookable; although maybe we should take the opportunity to rename it to actually mean something).
- Providing the instance that is the "default" component registry.
All other responsibility (global API functions, a persistent registry implementation, security stuff, zcml support, etc), would remain in z.component.
This would also line up conceptually with usage of "the ZCA" by BFG, which does not hook getSiteManager by default, but still uses a ZCA component registry.
- C
OK, I just tried this. It's maybe 2 days worth of effort to untangle this as it also effectively means: - Moving the majority of interfaces and exceptions from zope.component.interfaces to zope.interface.interfaces - Folding zope.event into zope.interface (this turns out to make sense when you're down the rabbit hole some of the way). - Untangling the tests. I thought maybe it might be a bit easier (I thought maybe I could get it done today) so I'm going to give up the idea of doing it "on spec" without some positive feedback about the idea. - C
Hey, Chris McDonough wrote: [snip experiment folding zope.component bits into zope.interface]
I thought maybe it might be a bit easier (I thought maybe I could get it done today) so I'm going to give up the idea of doing it "on spec" without some positive feedback about the idea.
Thanks for trying this! I do think it's an interesting direction to explore. We really need to involve Gary into this, though, to see whether it fits his thoughts. I've cc-ed him on this so he knows to look in this thread again. Regards, Martijn
Hey, Ethan Jucovy wrote:
On Thu, Dec 17, 2009 at 10:15 AM, Martijn Faassen <faassen@startifact.com> wrote:
* 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?
Maybe I've misunderstanding, but what's the advantage of making IFoo(x) raise a LookupError instead of a TypeError? I've tried to follow the thread but I've been confused about this. I do rely on catching TypeErrors quite often in my code -- I had thought it was intended as part of the API.
Consistency. The other lookup APIs in zope.component raise ComponentLookupError, which is a kind of LookupError. Good data point about catching TypeError though - I don't think we can just make a release that breaks this.
I like that treating it as typecasting instead of lookup blurs the conceptual distinction between "adapting x to IFoo" and "asserting x implements IFoo directly" -- when I actually want to know one or the other I can use explicit adaptation or check providedBy. In other words I use IFoo(x) when I don't care whether IFoo(x) == x.
Yeah, TypeError makes sense from the type casting perspective. Perhaps my earlier idea about a new Error that subclasses both TypeError and LookupError isn't so ugly after all.. After all a failure to look up an adapter can easily be interpreted as both. Regards, Martijn
Martijn Faassen wrote:
Here's a summary of what has been going on in this thread with some attempts at conclusions that have support of the consensus so that Thomas can proceed
Thank you, half an hour later and I'd have written the summary ;o)
* We want to implement .adapter(), .utility() and .__call__() in zope.component as much as possible.
The method's name is `adapt`, JFTR.
* 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.
* 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. I'd say changing the error is worth a 4.0 release, but we might want to postpone this change and see whether there's anything else we want to change about zope.interface in a backwards-incompatible way, and if so, put that in the 4.0 release as well. I guess we don't want too many backwards-incompatible releases of such a central package. 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.
* there was some discussion about general plugin points on Interface. Those have a complexity cost compared to simply poking the methods into the class.
As for poking the methods into the class, see the new tlotze-patching-interfaces branches of zope.interface and zope.component for the minimum change this would take IMO. The change to zope.interface is really just about documentation.
* 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.
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. In that sense, these stubs would be worse than zope.interface not documenting the methods at all. 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.
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.
* 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.
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, but then, that's after some application has made its choice about plugin certain packages together. It's not baked into zope.interface, and that's what we're trying so hard to achieve.
[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`, 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. Which brings me back to the question I asked earlier, and which nobody has replied to so far: 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? -- Thomas
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
On Thu, Dec 17, 2009 at 05:48:04PM +0100, Martijn Faassen wrote:
Thomas Lotze wrote:
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.
I'd like to advance the following argument that I haven't seen yet in this thread: discoverability. Specifically, letting the developer figure out what's happening. When I see code like this: formatter = IHTMLFormatter(widget).to_html() I can look up the definition of IHTMLFormatter, see that it's a "class" inheriting from Interface, look up zope.interface.Interface, see that it's an InterfaceClass instance, look up InterfaceClass.__call__ to see what it does -- checks for widget.__conform__, checks if widget provides IHTMLFormatter directly, loops through adapter_hooks. Then I can grep for adapter_hooks in my buildout omelette tree and find that zope.component hooks into it. And so on. If there's only monkey-patching going on, I'd get stuck in step 4: InterfaceClass has no __call__. Where does it come from? Monkey-patching is sufficiently frowned-upon in the Python world that it wouldn't occur for me to recursively grep "InterfaceClass.__call__ ="; I'd assume I missed something and run in circles. The main point of my email completed, I still cannot force myself to desist from further comments.
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!).
It'd be simpler if utilities didn't exist as a concept in zope.interface, but y'all have decided that's too ambitious. (Personally I kinda think redesigning zope.component/zope.interface APIs is too ambitious; the Zope community would be better served by having a website with up-to-date documentation of everything, launchpad bug trackers for every package in the ZTK and KGS, stable and regular ZTK and KGS releases, etc. But I know that I have no right to tell anyone what they should work on instead of their chosen pet project.)
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..
(Unless it trips people up leading them to assume they made a mistake in their adapter/utility registrations, when they instead forgot to register the hook. But that's unlikely, as I've already said elsewhere.) Marius Gedminas -- http://pov.lt/ -- Zope 3 consulting and development
Hey, Marius Gedminas wrote:
I'd like to advance the following argument that I haven't seen yet in this thread: discoverability.
I was actually thinking very much about people who read the code all the time, but apparently I wasn't very clear. That's also why I moved away from a position of monkey patching to a plugin API. [snip]
If there's only monkey-patching going on, I'd get stuck in step 4: InterfaceClass has no __call__. Where does it come from? Monkey-patching is sufficiently frowned-upon in the Python world that it wouldn't occur for me to recursively grep "InterfaceClass.__call__ ="; I'd assume I missed something and run in circles.
I agree. I think therefore we should put implementations in zope.interface, that can defer to a plugin registered by zope.component, just as we have for the adapter hook. (though I think we can come up with a more straightforward pattern than the whole hookable stuff, which I think I understood once but have since forgotten :) [snip]
It'd be simpler if utilities didn't exist as a concept in zope.interface, but y'all have decided that's too ambitious.
(Personally I kinda think redesigning zope.component/zope.interface APIs is too ambitious; the Zope community would be better served by having a website with up-to-date documentation of everything, launchpad bug trackers for every package in the ZTK and KGS, stable and regular ZTK and KGS releases, etc. But I know that I have no right to tell anyone what they should work on instead of their chosen pet project.)
I think the Zope community needs to be able to do all these things (which I've personally invested quite a bit of time on improving as well) *and* improve the ZCA APIs. Some people ask "why explore the solar system when world hunger isn't yet solved?". Lots of people have lots of cogent answers to that question. We need project infrastructure, yes, but we also need a community: * where ideas arise that when implemented affect a lot of people * where we can discuss such ideas * where we have people and mechanisms to reach such discussions to a *conclusion* * where we have volunteers who actually implement the conclusion If we don't have that, we don't have a functional community *either*. We'll always be in maintenance mode.
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..
(Unless it trips people up leading them to assume they made a mistake in their adapter/utility registrations, when they instead forgot to register the hook. But that's unlikely, as I've already said elsewhere.)
Yeah, I sympathise. I believe this is an unfortunate consequence of any plugin system. To mitigate that issue, I think clear warning signs are needed in zope.interface's dummy plugin so that people who step into it get a clear clue something didn't get registered right. Regards, Martijn
* Martijn Faassen <faassen@startifact.com> [2009-12-17 17:48]:
* Thomas Lotze <tl@gocept.com>:
zope.interface [should be] completely unaware of any particular uses of interfaces
Why is it a problem that the zope.interface package gains knowledge about adaptation (which it always had, anyway) and utility lookup?
This is the key issue, I think. (And I'd like to ask you not to argue "it has always been that way", since what we're trying to do here precisely is to improve the status quo.) The question is, which concepts belong into zope.interface and which don't? One might also phrase that as, what is the responsibility and purpose of zope.interface (drawing upon the software engineering terms of cohesion and coupling). My opinion is that
[__call__ introduces] the following notion into zope.interface: "please look up an object that provides this interface, somehow. (with zero or more context objects)"
might well belong into zope.interface, but that anything of these:
adapters, utilities, null-adapters or contextual utilities
absolutely do not. I think there is a clear distinction between what "interfaces" are about and what "component architecture" is about, and I think it is worthwile to maintain and clarify that distinction. For me, a notion of "looking up something that provides an interface" belongs to what "interfaces" are about. But notions of "utilities" which might or might not be singletons, of "adapters" which might or might not take additional parameters such as a name to look them up, or notions of a "context" or a "registry" (which for me comes up immediately when thinking about "global" vs. "local" utilities) that influences the lookup, for me emphatically do *not* belong to what "interfaces" are about. By putting method stubs into zope.interface which are exclusively about concepts of zope.component we would be a) On a conceptual level: blurring the distinction between the two packages and concepts, which I think is a Bad Thing(tm) b) On a more practical level: coupling zope.interface to zope.component. Given all the recent efforts of untangling the dependency structure between our packages, I think doing that would be a step in the exact opposite direction. Wolfgang
Wolfgang Schnerring wrote:
* Martijn Faassen <faassen@startifact.com> [2009-12-17 17:48]:
* Thomas Lotze <tl@gocept.com>:
zope.interface [should be] completely unaware of any particular uses of interfaces Why is it a problem that the zope.interface package gains knowledge about adaptation (which it always had, anyway) and utility lookup?
This is the key issue, I think. (And I'd like to ask you not to argue "it has always been that way", since what we're trying to do here precisely is to improve the status quo.)
I'm saying that because I don't recall people arguing against this before. Since the __call__ logic is only about adapter lookup, does this mean that your argument is expanded against this as well or does it fall under the notion of looking up anything by interface? I think from the rest of your mail that you mean the latter. At least it's a strict subset of an expanded notion of "lookup". [snip]
By putting method stubs into zope.interface which are exclusively about concepts of zope.component we would be
I would say that to be consistent in that position, you would need to argue not just against method stubs but against the presence of these methods at all, no matter where they come from. The rest is just an implementation detail. Reasoning from your position that zope.interface shouldn't have notions of adapter and utility lookup, it hardly matters what package implements these methods. To the user of Interface it will look the same, except that zope.component monkey-patching things in would be harder to understand and debug. To be consistent we have the following options: * only provide a general notion of "looking up something by interface" in zope.interface. This general notion unfortunately clearly doesn't have support from a large number of people so sadly I had to let that go. * not change anything at all. Looking up these things remains the task of zope.component and we better use zope.component APIs. Monkey-patching methods in from another package to me looks like it doesn't improve the conceptual story at all. Conceptually Interface ends up as messed up as before and it becomes more difficult to understand what's going on for the code reader or debugger.
which might or might not take additional parameters such as a name to look them up ... for me emphatically do *not* belong to what "interfaces" are about.
I think you can see a name as another context in a multi-adaptation, where this context is special in that: * it needs to be human-readable text * it cannot extend another name, unlike interfaces, which can extend each other It's just a handy shortcut. :)
By putting method stubs into zope.interface which are exclusively about concepts of zope.component we would be a) On a conceptual level: blurring the distinction between the two packages and concepts, which I think is a Bad Thing(tm) b) On a more practical level: coupling zope.interface to zope.component. Given all the recent efforts of untangling the dependency structure between our packages, I think doing that would be a step in the exact opposite direction.
I don't think the dependency refactoring argument holds. We were untangling physical dependencies. This wouldn't change physical dependencies one bit: zope.component would still depend on zope.interface. There are likely tons of places where package A depends on package B and also configures package B, changing its behavior. This would be no different: zope.component depends on zope.interface and plugs into it as well to configure some of its behavior. That's what it does today with the adapter hook. Putting notions of conceptual purity aside, I was arguing for a pragmatic solution that results in code that is as unsurprising as possible. I don't think .adapt and .utility are perfect, and I would have gone for another solution myself (a more generalized lookup method), but they seem to have consensus support. I don't think they do much in the way of active harm by being there in zope.interface. They help readability of the code that uses them. Moreover, this pragmatic solution would make code that looks up multi adapters and utilities more convenient to write. So, zope.interface needs to gain these methods, and open up a plugin point so that these methods can be fully implemented by another package. I think this can be constructed so that the only new notions to zope.interface are: * multi-adaptation, an extension of adaptation * look up by name, a conceptual extension of multi-adaptation :) * utility lookup Too many for my tastes when there's such an elegant general lookup method available, but I can't convince enough people of that. If we are going to make Interface support these methods for pragmatic considerations, we should just face it and implement them on Interface (delegating to a plugin). Anything else just helps to obfuscate what is going on. Regards, Martijn
Hi, this is a long message with a lot of replies to things that I don't agree with. Since I realize that making those points over and over again doesn't get us anywhere, I'd like to point out first that I'm going to implement Martijn's suggestions anyway on one of my branches, hoping that seeing more actual code to talk about might help getting to more consensus. Martijn Faassen wrote:
* 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.
Technically true, so my statement was admittedly too strong. I just don't feel comfortable with the idea, which may well be just because it's the "let's make both exceptions work sowmehow" solution to me instead of the clear change I thought we were considering. OTOH, it's not that big an issue either, so I guess I'd be fine with it.
* 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.
Hm, I can't help feeling pushed into this. While this plugin stuff is indeed nice *assuming* that we want all the zope.component concepts in zope.interface, it doesn't contribute to the decision about *whether* we want that in the first place.
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.
When I wrote that, I was assuming that we were talking about patching interfaces with methods that have zope.component's full lookup semantics, including `name` and `context` parameters - which abviously would change the signatures. If we're talking about something that doesn't add those parameters, there's no lie involved but I don't see how our lookup methods could then access the full feature set of zope.component's lookups.
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!).
Well, we obviously disagree completely about this. The existing __call__ method is neither a good example of a zope.component lookup API, nor do I think that it was fortunate to wire up the adaptation concept in zope.interface through __call__ in the first place. I'd rather try to loosen this reference to component concepts than use it as a reason for adding more.
You'll have to go into more detail. Why does it feel wrong to you?
Because it is just another way of encoding the zope.component details within zope.interface. I would love to add a plugin API that is generic enough to apply equally to other uses of interfaces than component lookup, and that would allow implementations of component lookup with different concepts and different lookup methods than those of zope.component.
Why is it a problem that the zope.interface package gains knowledge about adaptation (which it always had, anyway)
(and which, IMO, it shouldn't have in the first place)
and utility lookup?
Because if we're serious about making interfaces more of a language feature, they should at their core be reduced to *being* mostly information (maybe with a bit of verification code) instead of extended to *doing* things that derive mainly from *our* particular ways of using them.
Because to the user of those methods on Interface, it looks exactly like the packa does have such knowledge. We shouldn't lie.ge
I don't think that we lie as long as we state that we have a plugin system of any kind (including monkey-patching, for the sake of discussion). Speaking about lies again, I also consider it a kind of lie to say that we provide a plugin system if it turns out in the end that it isn't much good for anything but the one particular plugin provided by zope.component.
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.
Would this in your mind still include a list of hooks whose behaviour needs to be coordinated by the Interface methods (which affects at least how exceptions are caught or propagated), or are we talking about a single plugin being registered at a time? I'd prefer the latter as it's more straight-forward. Or are there really compelling use cases for registering more than one hook at the same time? -- Thomas
Hey, A few points instead of a point by point reply. * I thought we had agreed to add methods to Interface that support multi adapter and utility lookup. We seem to have, but only about the external API, not about where the implementation is. (even if in my plugin proposal the implementation still lies in zope.component) * Apparently not even single adapter lookup is supported by you, at least as implemented in zope.interface. I had not previously realized that the current implementation of __call__ could be considered wrong by you. * You appear to be looking for something that at least in the Python language is only accepted as a kind of hack: an ability to extend the API of an existing class far away from the definition of that class, without there being a subclassing relationship (or I suppose, a class decorator). The idea in Python is generally that the API of a class is defined by that class or its base classes, not elsewhere. * There is of course a way to extend what one can do with an object in Python. Subclassing is one way, adaptation another, and defining functions another. The last is what zope.component does. * I do not know of a user community that uses zope.interface and does not use a component architecture. The Twisted community for instance uses zope.interface's adapter registry and __call__ hook. This is an argument for making component lookup an integral part of zope.interface's responsibilities. I think you could even make an argument for folding zope.component's responsibilities into zope.interface, though I'm not currently proposing this (also because we're are looking to improve the implementation, see below). Generalizing what kinds of component lookups one can do using Interface seems like a good idea from this perspective. * I think it is entirely possible that people will plug in alternative component lookup strategies to zope.interface in the future, as the long discussion also involved changing the way zope.component's internals worked. * All this questioning of fundamentals makes me inclined to give up on this project for the time being. I thought we had reached a conclusion, but I was wrong. Too many cooks? Some more technical points: * you wonder about a 'context' parameter. Are you talking about the lookup context here? We hadn't considered passing through the lookup context in previous discussions. We could leave that out for the time being. * A single plugin seems fine instead of a list of plugin points for zope.interface. I think a single plugin, using a global would be the most straightforward and readable solution, but perhaps some performance measurements would be interesting. Regards, Martijn
On Thu, Dec 17, 2009 at 04:59:12PM +0100, Thomas Lotze wrote:
Martijn Faassen wrote:
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. In that sense, these stubs would be worse than zope.interface not documenting the methods at all.
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.
I like things to fail noisily and loudly unconfigured and give good information about what's wrong. So my preferred implementation of a stub "utility" function on Interface is: def utility(default=None): """Lookup a utility for this interface. A utility is a ${long explanation of utility concept}. This method behaves like ${explanation of utility method contract}. """ raise NotImplementedError("""No Utility lookup mechanism has been configured. If you wish to use utility lookups on interfaces, please configure a package that contains this mechanism. Packages known to implement this are: zope.component """) I agree that this encodes in the zope.interface package concepts from zope.component. I feel uncomfortable about that. But I feel more uncomfortable with magic where it is impossible to find out what is going on by reading the code. -- Brian Sutherland
On Fri, Dec 18, 2009 at 08:51, Brian Sutherland <brian@vanguardistas.net> wrote:
I like things to fail noisily and loudly unconfigured and give good information about what's wrong.
+1
So my preferred implementation of a stub "utility" function on Interface is:
def utility(default=None): """Lookup a utility for this interface.
A utility is a ${long explanation of utility concept}.
This method behaves like ${explanation of utility method contract}. """ raise NotImplementedError("""No Utility lookup mechanism has been configured. If you wish to use utility lookups on interfaces, please configure a package that contains this mechanism. Packages known to implement this are: zope.component """)
I agree that this encodes in the zope.interface package concepts from zope.component.
I think that is stretching the "encoding concepts" a bit too far. Yes, we make zope.interface aware that such a thing as utility-registries exist, but say we don't implement it. I don't think that's a problem. The error message also gives an example of an implementation. That's probably not a problem either.
I feel uncomfortable about that.
I don't. :-) -- Lennart Regebro: http://regebro.wordpress.com/ Python 3 Porting: http://python-incompatibility.googlecode.com/ +33 661 58 14 64
Hi, On Fri, Dec 18, 2009 at 9:47 AM, Lennart Regebro <regebro@gmail.com> wrote:
On Fri, Dec 18, 2009 at 08:51, Brian Sutherland <brian@vanguardistas.net> wrote:
I like things to fail noisily and loudly unconfigured and give good information about what's wrong.
+1 [snip] we make zope.interface aware that such a thing as utility-registries exist, but say we don't implement it. I don't think that's a problem. The error message also gives an example of an implementation. That's probably not a problem either.
I feel uncomfortable about that.
I don't. :-)
+1 from my perspective of "I don't know or understand the core ZCA codebase very well (and don't understand all the implications in this discussion) but often read or trace through the code." A well-documented NotImplementedError seems much more human-useful than a default implementation that fulfills the contract, because it assertively announces the expectation for the most common case by far: "you probably want to plug in a real implementation here." Then if there is a need for the proposed default implementation, it can be provided as a plugin by some other package, right? -Ethan
Ethan Jucovy wrote:
+1 from my perspective of "I don't know or understand the core ZCA codebase very well (and don't understand all the implications in this discussion) but often read or trace through the code." A well-documented NotImplementedError seems much more human-useful than a default implementation that fulfills the contract, because it assertively announces the expectation for the most common case by far: "you probably want to plug in a real implementation here." Then if there is a need for the proposed default implementation, it can be provided as a plugin by some other package, right?
I'm now convinced people want to see a clear NotImplementedError. I think if we went with a plugin structure, we could do something like this: class Interface: ... def utility(...): return lookup_plugin.utility(...) class NotImplementedLookupPlugin(object): def utility(...): raise NotImplementedError(""" this is the not implemented lookup plugin. You need to install another one. Blah blah zope.component blah blah""") lookup_plugin = NotImplementedLookupPlugin() def set_lookup_plugin(plugin): global lookup_plugin lookup_plugin = plugin As long as external packages do not register a proper plugin, it'll raise NotImplementedErrors. Both the actual error as well as the name of the default lookup plugin signal something hasn't been installed. We could also easily provide *another* plugin in zope.interface that does the "as if the registry is empty" behavior, if that turns out to be useful (perhaps for testing). But to use it someone would need to explicitly enable it. Tests could also define fake lookup plugins with other behavior so we can test these methods properly. Regards, Martijn
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Ethan Jucovy wrote:
Hi,
On Fri, Dec 18, 2009 at 9:47 AM, Lennart Regebro <regebro@gmail.com> wrote:
On Fri, Dec 18, 2009 at 08:51, Brian Sutherland <brian@vanguardistas.net> wrote:
I like things to fail noisily and loudly unconfigured and give good information about what's wrong. +1 [snip] we make zope.interface aware that such a thing as utility-registries exist, but say we don't implement it. I don't think that's a problem. The error message also gives an example of an implementation. That's probably not a problem either.
I feel uncomfortable about that. I don't. :-)
+1 from my perspective of "I don't know or understand the core ZCA codebase very well (and don't understand all the implications in this discussion) but often read or trace through the code." A well-documented NotImplementedError seems much more human-useful than a default implementation that fulfills the contract, because it assertively announces the expectation for the most common case by far: "you probably want to plug in a real implementation here." Then if there is a need for the proposed default implementation, it can be provided as a plugin by some other package, right?
The same argument applies in your case: you could plug in your own wrapper implementation which raised errors if not replaced. - -1 to raising NotImplementedError. Tres. - -- =================================================================== Tres Seaver +1 540-429-0999 tseaver@palladion.com Palladion Software "Excellence by Design" http://palladion.com -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iEYEARECAAYFAkssANoACgkQ+gerLs4ltQ57XgCdGN8W4q4IevSbQX+XgaRaUXA4 rNkAn1ART1odK+s576b8GbjGX6JIJh6u =VJnE -----END PGP SIGNATURE-----
participants (15)
-
Brian Sutherland -
Chris McDonough -
Christian Theune -
Ethan Jucovy -
Gary Poster -
Lennart Regebro -
Leonardo Rochael Almeida -
Marius Gedminas -
Martijn Faassen -
Martin Aspeli -
Matthias Lehmann -
Thomas Lotze -
Tres Seaver -
Wichert Akkerman -
Wolfgang Schnerring