diff --git a/CHANGES.rst b/CHANGES.rst index bf8ce262..70f85e95 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -27,6 +27,23 @@ See `issue 200 `_. +- Require that the second argument (*bases*) to ``InterfaceClass`` is + a tuple. This only matters when directly using ``InterfaceClass`` to + create new interfaces dynamically. Previously, an individual + interface was allowed, but did not work correctly. Now it is + consistent with ``type`` and requires a tuple. + +- Let interfaces define custom ``__adapt__`` methods. This implements + the other side of the :pep:`246` adaptation protocol: objects being + adapted could already implement ``__conform__`` if they know about + the interface, and now interfaces can implement ``__adapt__`` if + they know about particular objects. There is no performance penalty + for interfaces that do not supply custom ``__adapt__`` methods. + + This includes the ability to add new methods, or override existing + interface methods using the new ``@interfacemethod`` decorator. + + See `issue 3 `_. 5.0.2 (2020-03-30) ================== diff --git a/docs/README.rst b/docs/README.rst index 6fbd5f83..0364a816 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -863,7 +863,8 @@ Adaptation Interfaces can be called to perform adaptation. -The semantics are based on those of the PEP 246 ``adapt`` function. +The semantics are based on those of the :pep:`246` ``adapt`` +function. If an object cannot be adapted, then a ``TypeError`` is raised: @@ -897,7 +898,13 @@ If an object already implements the interface, then it will be returned: >>> I(obj) is obj True -If an object implements ``__conform__``, then it will be used: +:pep:`246` outlines a requirement: + + When the object knows about the [interface], and either considers + itself compliant, or knows how to wrap itself suitably. + +This is handled with ``__conform__``. If an object implements +``__conform__``, then it will be used: .. doctest:: @@ -936,21 +943,27 @@ Adapter hooks (see ``__adapt__``) will also be used, if present: >>> class I(zope.interface.Interface): ... pass -Interfaces implement the PEP 246 ``__adapt__`` method. +Interfaces implement the :pep:`246` ``__adapt__`` method to satisfy +the requirement: + + When the [interface] knows about the object, and either the object + already complies or the [interface] knows how to suitably wrap the + object. -This method is normally not called directly. It is called by the PEP -246 adapt framework and by the interface ``__call__`` operator. +This method is normally not called directly. It is called by the +:pep:`246` adapt framework and by the interface ``__call__`` operator. The ``adapt`` method is responsible for adapting an object to the reciever. -The default version returns ``None``: +The default version returns ``None`` (because by default no interface +"knows how to suitably wrap the object"): .. doctest:: >>> I.__adapt__(0) -unless the object given provides the interface: +unless the object given provides the interface ("the object already complies"): .. doctest:: @@ -989,6 +1002,28 @@ Hooks can be uninstalled by removing them from the list: >>> I.__adapt__(0) +It is possible to replace or customize the ``__adapt___`` +functionality for particular interfaces. + +.. doctest:: + + >>> class ICustomAdapt(zope.interface.Interface): + ... @zope.interface.interfacemethod + ... def __adapt__(self, obj): + ... if isinstance(obj, str): + ... return obj + ... return super(type(ICustomAdapt), self).__adapt__(obj) + + >>> @zope.interface.implementer(ICustomAdapt) + ... class CustomAdapt(object): + ... pass + >>> ICustomAdapt('a string') + 'a string' + >>> ICustomAdapt(CustomAdapt()) + + +.. seealso:: :func:`zope.interface.interfacemethod` + .. [#create] The main reason we subclass ``Interface`` is to cause the Python class statement to create an interface, rather than a class. diff --git a/docs/api/declarations.rst b/docs/api/declarations.rst index 955d2462..fa265e7a 100644 --- a/docs/api/declarations.rst +++ b/docs/api/declarations.rst @@ -36,6 +36,8 @@ To declare an interface itself, extend the ``Interface`` base class. .. documented in README.rst +.. autofunction:: interfacemethod + Declaring The Interfaces of Objects =================================== diff --git a/src/zope/interface/__init__.py b/src/zope/interface/__init__.py index e282fbda..3372103e 100644 --- a/src/zope/interface/__init__.py +++ b/src/zope/interface/__init__.py @@ -49,7 +49,7 @@ def meth(arg1, arg2): See the module doc strings for more information. """ __docformat__ = 'restructuredtext' - +# pylint:disable=wrong-import-position,unused-import from zope.interface.interface import Interface from zope.interface.interface import _wire @@ -75,8 +75,11 @@ def meth(arg1, arg2): from zope.interface.declarations import noLongerProvides from zope.interface.declarations import providedBy from zope.interface.declarations import provider + from zope.interface.exceptions import Invalid + from zope.interface.interface import Attribute +from zope.interface.interface import interfacemethod from zope.interface.interface import invariant from zope.interface.interface import taggedValue diff --git a/src/zope/interface/_zope_interface_coptimizations.c b/src/zope/interface/_zope_interface_coptimizations.c index 69f5bbcf..346e7f2d 100644 --- a/src/zope/interface/_zope_interface_coptimizations.c +++ b/src/zope/interface/_zope_interface_coptimizations.c @@ -49,6 +49,8 @@ static PyObject *str_registry, *strro, *str_generation, *strchanged; static PyObject *str__self__; static PyObject *str__module__; static PyObject *str__name__; +static PyObject *str__adapt__; +static PyObject *str_CALL_CUSTOM_ADAPT; static PyTypeObject *Implements; @@ -796,7 +798,7 @@ static struct PyMethodDef ib_methods[] = { */ static PyObject * -ib_call(PyObject *self, PyObject *args, PyObject *kwargs) +IB_call(PyObject *self, PyObject *args, PyObject *kwargs) { PyObject *conform, *obj, *alternate, *adapter; static char *kwlist[] = {"obj", "alternate", NULL}; @@ -835,7 +837,23 @@ ib_call(PyObject *self, PyObject *args, PyObject *kwargs) Py_DECREF(conform); } - adapter = __adapt__(self, obj); // XXX: should be self.__adapt__. + /* We differ from the Python code here. For speed, instead of always calling + self.__adapt__(), we check to see if the type has defined it. Checking in + the dict for __adapt__ isn't sufficient because there's no cheap way to + tell if it's the __adapt__ that InterfaceBase itself defines (our type + will *never* be InterfaceBase, we're always subclassed by + InterfaceClass). Instead, we cooperate with InterfaceClass in Python to + set a flag in a new subclass when this is necessary. */ + if (PyDict_GetItem(self->ob_type->tp_dict, str_CALL_CUSTOM_ADAPT)) + { + /* Doesn't matter what the value is. Simply being present is enough. */ + adapter = PyObject_CallMethodObjArgs(self, str__adapt__, obj, NULL); + } + else + { + adapter = __adapt__(self, obj); + } + if (adapter == NULL || adapter != Py_None) { return adapter; @@ -1045,7 +1063,7 @@ static PyTypeObject InterfaceBaseType = { /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ (hashfunc)IB_hash, - /* tp_call */ (ternaryfunc)ib_call, + /* tp_call */ (ternaryfunc)IB_call, /* tp_str */ (reprfunc)0, /* tp_getattro */ (getattrofunc)0, /* tp_setattro */ (setattrofunc)0, @@ -2023,6 +2041,8 @@ init(void) DEFINE_STRING(__self__); DEFINE_STRING(__name__); DEFINE_STRING(__module__); + DEFINE_STRING(__adapt__); + DEFINE_STRING(_CALL_CUSTOM_ADAPT); #undef DEFINE_STRING adapter_hooks = PyList_New(0); if (adapter_hooks == NULL) diff --git a/src/zope/interface/common/__init__.py b/src/zope/interface/common/__init__.py index a8bedf06..b40c3178 100644 --- a/src/zope/interface/common/__init__.py +++ b/src/zope/interface/common/__init__.py @@ -259,5 +259,5 @@ def getRegisteredConformers(self): return set(itertools.chain(registered, self.__extra_classes)) -ABCInterface = ABCInterfaceClass.__new__(ABCInterfaceClass, None, None, None) +ABCInterface = ABCInterfaceClass.__new__(ABCInterfaceClass, 'ABCInterfaceClass', (), {}) InterfaceClass.__init__(ABCInterface, 'ABCInterface', (Interface,), {}) diff --git a/src/zope/interface/interface.py b/src/zope/interface/interface.py index d035ade0..ff26d333 100644 --- a/src/zope/interface/interface.py +++ b/src/zope/interface/interface.py @@ -20,6 +20,7 @@ import weakref from zope.interface._compat import _use_c_impl +from zope.interface._compat import PYTHON2 as PY2 from zope.interface.exceptions import Invalid from zope.interface.ro import ro as calculate_ro from zope.interface import ro @@ -36,7 +37,10 @@ CO_VARARGS = 4 CO_VARKEYWORDS = 8 +# Put in the attrs dict of an interface by ``taggedValue`` and ``invariants`` TAGGED_DATA = '__interface_tagged_values__' +# Put in the attrs dict of an interface by ``interfacemethod`` +INTERFACE_METHODS = '__interface_methods__' _decorator_non_return = object() _marker = object() @@ -651,6 +655,21 @@ def __repr__(cls): ) +def interfacemethod(func): + """ + Convert a method specification to an actual method of the interface. + + This is a decorator that functions like `staticmethod` et al. + + The primary use of this decorator is to allow interface definitions to + define the ``__adapt__`` method. + """ + f_locals = sys._getframe(1).f_locals + methods = f_locals.setdefault(INTERFACE_METHODS, {}) + methods[func.__name__] = func + return _decorator_non_return + + class InterfaceClass(_InterfaceClassBase): """ Prototype (scarecrow) Interfaces Implementation. @@ -664,6 +683,57 @@ class InterfaceClass(_InterfaceClassBase): # #implements(IInterface) + def __new__(cls, name=None, bases=(), attrs=None, __doc__=None, # pylint:disable=redefined-builtin + __module__=None): + assert isinstance(bases, tuple) + attrs = attrs or {} + needs_custom_class = attrs.pop(INTERFACE_METHODS, None) + if needs_custom_class: + needs_custom_class.update( + {'__classcell__': attrs.pop('__classcell__')} + if '__classcell__' in attrs + else {} + ) + if '__adapt__' in needs_custom_class: + # We need to tell the C code to call this. + needs_custom_class['_CALL_CUSTOM_ADAPT'] = 1 + + if issubclass(cls, _InterfaceClassWithCustomMethods): + cls_bases = (cls,) + elif cls is InterfaceClass: + cls_bases = (_InterfaceClassWithCustomMethods,) + else: + cls_bases = (cls, _InterfaceClassWithCustomMethods) + + cls = type(cls)( # pylint:disable=self-cls-assignment + name + "", + cls_bases, + needs_custom_class + ) + elif PY2 and bases and len(bases) > 1: + bases_with_custom_methods = tuple( + type(b) + for b in bases + if issubclass(type(b), _InterfaceClassWithCustomMethods) + ) + + # If we have a subclass of InterfaceClass in *bases*, + # Python 3 is smart enough to pass that as *cls*, but Python + # 2 just passes whatever the first base in *bases* is. This means that if + # we have multiple inheritance, and one of our bases has already defined + # a custom method like ``__adapt__``, we do the right thing automatically + # and extend it on Python 3, but not necessarily on Python 2. To fix this, we need + # to run the MRO algorithm and get the most derived base manually. + # Note that this only works for consistent resolution orders + if bases_with_custom_methods: + cls = type( # pylint:disable=self-cls-assignment + name + "", + bases_with_custom_methods, + {} + ).__mro__[1] # Not the class we created, the most derived. + + return _InterfaceClassBase.__new__(cls) + def __init__(self, name, bases=(), attrs=None, __doc__=None, # pylint:disable=redefined-builtin __module__=None): # We don't call our metaclass parent directly @@ -738,7 +808,7 @@ def update_value(aname, aval): # __qualname__: PEP 3155 (Python 3.3+) '__qualname__', # __annotations__: PEP 3107 (Python 3.0+) - '__annotations__' + '__annotations__', ) and aval is not _decorator_non_return } @@ -889,6 +959,11 @@ def __reduce__(self): Specification._ROOT = Interface ro._ROOT = Interface +class _InterfaceClassWithCustomMethods(InterfaceClass): + """ + Marker class for interfaces with custom methods that override InterfaceClass methods. + """ + class Attribute(Element): """Attribute descriptions diff --git a/src/zope/interface/interfaces.py b/src/zope/interface/interfaces.py index c0ece6ae..6321d0c4 100644 --- a/src/zope/interface/interfaces.py +++ b/src/zope/interface/interfaces.py @@ -518,6 +518,35 @@ class IRange(Interface): .. seealso:: `zope.interface.invariant` """ + def interfacemethod(method): + """ + A decorator that transforms a method specification into an + implementation method. + + This is used to override methods of ``Interface`` or provide new methods. + Definitions using this decorator will not appear in :meth:`IInterface.names()`. + It is possible to have an implementation method and a method specification + of the same name. + + For example:: + + class IRange(Interface): + @interfacemethod + def __adapt__(self, obj): + if isinstance(obj, range): + # Return the builtin ``range`` as-is + return obj + return super(type(IRange), self).__adapt__(obj) + + You can use ``super`` to call the parent class functionality. Note that + the zero-argument version (``super().__adapt__``) works on Python 3.6 and above, but + prior to that the two-argument version must be used, and the class must be explicitly + passed as the first argument. + + .. versionadded:: 5.1.0 + .. seealso:: `zope.interface.interfacemethod` + """ + ### # Querying interfaces ### diff --git a/src/zope/interface/tests/test_adapter.py b/src/zope/interface/tests/test_adapter.py index 869df663..8ff96f06 100644 --- a/src/zope/interface/tests/test_adapter.py +++ b/src/zope/interface/tests/test_adapter.py @@ -796,12 +796,12 @@ def test_ctor_w_registry_provided(self): from zope.interface import Interface from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) registry = self._makeRegistry(IFoo, IBar) alb = self._makeOne(registry) self.assertEqual(sorted(alb._extendors.keys()), sorted([IBar, IFoo, Interface])) - self.assertEqual(alb._extendors[IFoo], [IFoo]) + self.assertEqual(alb._extendors[IFoo], [IFoo, IBar]) self.assertEqual(alb._extendors[IBar], [IBar]) self.assertEqual(sorted(alb._extendors[Interface]), sorted([IFoo, IBar])) @@ -847,14 +847,14 @@ def test_init_extendors_after_registry_update(self): from zope.interface import Interface from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) registry = self._makeRegistry() alb = self._makeOne(registry) registry._provided = [IFoo, IBar] alb.init_extendors() self.assertEqual(sorted(alb._extendors.keys()), sorted([IBar, IFoo, Interface])) - self.assertEqual(alb._extendors[IFoo], [IFoo]) + self.assertEqual(alb._extendors[IFoo], [IFoo, IBar]) self.assertEqual(alb._extendors[IBar], [IBar]) self.assertEqual(sorted(alb._extendors[Interface]), sorted([IFoo, IBar])) @@ -863,14 +863,14 @@ def test_add_extendor(self): from zope.interface import Interface from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) registry = self._makeRegistry() alb = self._makeOne(registry) alb.add_extendor(IFoo) alb.add_extendor(IBar) self.assertEqual(sorted(alb._extendors.keys()), sorted([IBar, IFoo, Interface])) - self.assertEqual(alb._extendors[IFoo], [IFoo]) + self.assertEqual(alb._extendors[IFoo], [IFoo, IBar]) self.assertEqual(alb._extendors[IBar], [IBar]) self.assertEqual(sorted(alb._extendors[Interface]), sorted([IFoo, IBar])) @@ -879,13 +879,13 @@ def test_remove_extendor(self): from zope.interface import Interface from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) registry = self._makeRegistry(IFoo, IBar) alb = self._makeOne(registry) alb.remove_extendor(IFoo) self.assertEqual(sorted(alb._extendors.keys()), sorted([IFoo, IBar, Interface])) - self.assertEqual(alb._extendors[IFoo], []) + self.assertEqual(alb._extendors[IFoo], [IBar]) self.assertEqual(alb._extendors[IBar], [IBar]) self.assertEqual(sorted(alb._extendors[Interface]), sorted([IBar])) @@ -895,7 +895,7 @@ def test_remove_extendor(self): def test__uncached_lookup_empty_ro(self): from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) registry = self._makeRegistry() alb = self._makeOne(registry) result = alb._uncached_lookup((IFoo,), IBar) @@ -906,7 +906,7 @@ def test__uncached_lookup_empty_ro(self): def test__uncached_lookup_order_miss(self): from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) registry = self._makeRegistry(IFoo, IBar) subr = self._makeSubregistry() registry.ro.append(subr) @@ -917,7 +917,7 @@ def test__uncached_lookup_order_miss(self): def test__uncached_lookup_extendors_miss(self): from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) registry = self._makeRegistry() subr = self._makeSubregistry() subr._adapters = [{}, {}] #utilities, single adapters @@ -930,7 +930,7 @@ def test__uncached_lookup_extendors_miss(self): def test__uncached_lookup_components_miss_wrong_iface(self): from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) IQux = InterfaceClass('IQux') registry = self._makeRegistry(IFoo, IBar) subr = self._makeSubregistry() @@ -949,7 +949,7 @@ def test__uncached_lookup_components_miss_wrong_iface(self): def test__uncached_lookup_components_miss_wrong_name(self): from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) registry = self._makeRegistry(IFoo, IBar) subr = self._makeSubregistry() @@ -968,7 +968,7 @@ def test__uncached_lookup_components_miss_wrong_name(self): def test__uncached_lookup_simple_hit(self): from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) registry = self._makeRegistry(IFoo, IBar) subr = self._makeSubregistry() _expected = object() @@ -985,7 +985,7 @@ def test__uncached_lookup_simple_hit(self): def test__uncached_lookup_repeated_hit(self): from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) registry = self._makeRegistry(IFoo, IBar) subr = self._makeSubregistry() _expected = object() @@ -1005,7 +1005,7 @@ def test_queryMultiAdaptor_lookup_miss(self): from zope.interface.declarations import implementer from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) @implementer(IFoo) class Foo(object): pass @@ -1051,7 +1051,7 @@ def test_queryMultiAdaptor_factory_miss(self): from zope.interface.declarations import implementer from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) @implementer(IFoo) class Foo(object): pass @@ -1080,7 +1080,7 @@ def test_queryMultiAdaptor_factory_hit(self): from zope.interface.declarations import implementer from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) @implementer(IFoo) class Foo(object): pass @@ -1131,7 +1131,7 @@ def factory(*args): def test__uncached_lookupAll_empty_ro(self): from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) registry = self._makeRegistry() alb = self._makeOne(registry) result = alb._uncached_lookupAll((IFoo,), IBar) @@ -1142,7 +1142,7 @@ def test__uncached_lookupAll_empty_ro(self): def test__uncached_lookupAll_order_miss(self): from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) registry = self._makeRegistry(IFoo, IBar) subr = self._makeSubregistry() registry.ro.append(subr) @@ -1154,7 +1154,7 @@ def test__uncached_lookupAll_order_miss(self): def test__uncached_lookupAll_extendors_miss(self): from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) registry = self._makeRegistry() subr = self._makeSubregistry() subr._adapters = [{}, {}] #utilities, single adapters @@ -1167,7 +1167,7 @@ def test__uncached_lookupAll_extendors_miss(self): def test__uncached_lookupAll_components_miss(self): from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) IQux = InterfaceClass('IQux') registry = self._makeRegistry(IFoo, IBar) subr = self._makeSubregistry() @@ -1185,7 +1185,7 @@ def test__uncached_lookupAll_components_miss(self): def test__uncached_lookupAll_simple_hit(self): from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) registry = self._makeRegistry(IFoo, IBar) subr = self._makeSubregistry() _expected = object() @@ -1203,7 +1203,7 @@ def test__uncached_lookupAll_simple_hit(self): def test_names(self): from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) registry = self._makeRegistry(IFoo, IBar) subr = self._makeSubregistry() _expected = object() @@ -1222,7 +1222,7 @@ def test_names(self): def test__uncached_subscriptions_empty_ro(self): from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) registry = self._makeRegistry() alb = self._makeOne(registry) result = alb._uncached_subscriptions((IFoo,), IBar) @@ -1233,7 +1233,7 @@ def test__uncached_subscriptions_empty_ro(self): def test__uncached_subscriptions_order_miss(self): from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) registry = self._makeRegistry(IFoo, IBar) subr = self._makeSubregistry() registry.ro.append(subr) @@ -1245,7 +1245,7 @@ def test__uncached_subscriptions_order_miss(self): def test__uncached_subscriptions_extendors_miss(self): from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) registry = self._makeRegistry() subr = self._makeSubregistry() subr._subscribers = [{}, {}] #utilities, single adapters @@ -1258,7 +1258,7 @@ def test__uncached_subscriptions_extendors_miss(self): def test__uncached_subscriptions_components_miss_wrong_iface(self): from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) IQux = InterfaceClass('IQux') registry = self._makeRegistry(IFoo, IBar) subr = self._makeSubregistry() @@ -1276,7 +1276,7 @@ def test__uncached_subscriptions_components_miss_wrong_iface(self): def test__uncached_subscriptions_components_miss_wrong_name(self): from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) registry = self._makeRegistry(IFoo, IBar) subr = self._makeSubregistry() wrongname = object() @@ -1293,7 +1293,7 @@ def test__uncached_subscriptions_components_miss_wrong_name(self): def test__uncached_subscriptions_simple_hit(self): from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) registry = self._makeRegistry(IFoo, IBar) subr = self._makeSubregistry() class Foo(object): @@ -1314,7 +1314,7 @@ def test_subscribers_wo_provided(self): from zope.interface.declarations import implementer from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) @implementer(IFoo) class Foo(object): pass @@ -1343,7 +1343,7 @@ def test_subscribers_w_provided(self): from zope.interface.declarations import implementer from zope.interface.interface import InterfaceClass IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) + IBar = InterfaceClass('IBar', (IFoo,)) @implementer(IFoo) class Foo(object): pass diff --git a/src/zope/interface/tests/test_declarations.py b/src/zope/interface/tests/test_declarations.py index 5d7272a9..83815d7e 100644 --- a/src/zope/interface/tests/test_declarations.py +++ b/src/zope/interface/tests/test_declarations.py @@ -864,25 +864,30 @@ class Foo(object): def test_w_existing_Implements_w_bases(self): from zope.interface.declarations import Implements from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar') - IBaz = InterfaceClass('IBaz', IFoo) - b_impl = Implements(IBaz) - impl = Implements(IFoo) - impl.declared = (IFoo,) - class Base1(object): - __implemented__ = b_impl - class Base2(object): - __implemented__ = b_impl - class Foo(Base1, Base2): - __implemented__ = impl - impl.inherit = Foo - self._callFUT(Foo, IBar) + IRoot = InterfaceClass('IRoot') + ISecondRoot = InterfaceClass('ISecondRoot') + IExtendsRoot = InterfaceClass('IExtendsRoot', (IRoot,)) + + impl_root = Implements.named('Root', IRoot) + impl_root.declared = (IRoot,) + + class Root1(object): + __implemented__ = impl_root + class Root2(object): + __implemented__ = impl_root + + impl_extends_root = Implements.named('ExtendsRoot1', IExtendsRoot) + impl_extends_root.declared = (IExtendsRoot,) + class ExtendsRoot(Root1, Root2): + __implemented__ = impl_extends_root + impl_extends_root.inherit = ExtendsRoot + + self._callFUT(ExtendsRoot, ISecondRoot) # Same spec, now different values - self.assertTrue(Foo.__implemented__ is impl) - self.assertEqual(impl.inherit, Foo) - self.assertEqual(impl.declared, (IFoo, IBar,)) - self.assertEqual(impl.__bases__, (IFoo, IBar, b_impl)) + self.assertIs(ExtendsRoot.__implemented__, impl_extends_root) + self.assertEqual(impl_extends_root.inherit, ExtendsRoot) + self.assertEqual(impl_extends_root.declared, (IExtendsRoot, ISecondRoot,)) + self.assertEqual(impl_extends_root.__bases__, (IExtendsRoot, ISecondRoot, impl_root)) class Test__implements_advice(unittest.TestCase): diff --git a/src/zope/interface/tests/test_interface.py b/src/zope/interface/tests/test_interface.py index 21003407..4bbed1a7 100644 --- a/src/zope/interface/tests/test_interface.py +++ b/src/zope/interface/tests/test_interface.py @@ -1318,9 +1318,9 @@ def testInterfaceExtendsInterface(self): new = Interface.__class__ FunInterface = new('FunInterface') - BarInterface = new('BarInterface', [FunInterface]) + BarInterface = new('BarInterface', (FunInterface,)) BobInterface = new('BobInterface') - BazInterface = new('BazInterface', [BobInterface, BarInterface]) + BazInterface = new('BazInterface', (BobInterface, BarInterface,)) self.assertTrue(BazInterface.extends(BobInterface)) self.assertTrue(BazInterface.extends(BarInterface)) @@ -2161,6 +2161,128 @@ class C(object): finally: adapter_hooks[:] = old_adapter_hooks + def test___call___w_overridden_adapt(self): + from zope.interface import Interface + from zope.interface import interfacemethod + from zope.interface import implementer + + class I(Interface): + + @interfacemethod + def __adapt__(self, obj): + return 42 + + @implementer(I) + class O(object): + pass + + self.assertEqual(42, I(object())) + # __adapt__ supercedes providedBy() if defined. + self.assertEqual(42, I(O())) + + def test___call___w_overridden_adapt_call_super(self): + import sys + from zope.interface import Interface + from zope.interface import interfacemethod + from zope.interface import implementer + + class I(Interface): + + @interfacemethod + def __adapt__(self, obj): + if not self.providedBy(obj): + return 42 + if sys.version_info[:2] > (3, 5): + # Python 3.5 raises 'RuntimeError: super() __class__ is not a type' + return super().__adapt__(obj) + + return super(type(I), self).__adapt__(obj) + + @implementer(I) + class O(object): + pass + + self.assertEqual(42, I(object())) + o = O() + self.assertIs(o, I(o)) + + def test___adapt___as_method_and_implementation(self): + from zope.interface import Interface + from zope.interface import interfacemethod + + class I(Interface): + @interfacemethod + def __adapt__(self, obj): + return 42 + + def __adapt__(to_adapt): + "This is a protocol" + + self.assertEqual(42, I(object())) + self.assertEqual(I['__adapt__'].getSignatureString(), '(to_adapt)') + + def test___adapt__inheritance_and_type(self): + from zope.interface import Interface + from zope.interface import interfacemethod + + class IRoot(Interface): + """Root""" + + class IWithAdapt(IRoot): + @interfacemethod + def __adapt__(self, obj): + return 42 + + class IOther(IRoot): + """Second branch""" + + class IUnrelated(Interface): + """Unrelated""" + + class IDerivedAdapt(IUnrelated, IWithAdapt, IOther): + """Inherits an adapt""" + # Order of "inheritance" matters here. + + class IDerived2Adapt(IDerivedAdapt): + """Overrides an inherited custom adapt.""" + @interfacemethod + def __adapt__(self, obj): + return 24 + + self.assertEqual(42, IDerivedAdapt(object())) + for iface in IRoot, IWithAdapt, IOther, IUnrelated, IDerivedAdapt: + self.assertEqual(__name__, iface.__module__) + + for iface in IRoot, IOther, IUnrelated: + self.assertEqual(type(IRoot), type(Interface)) + + # But things that implemented __adapt__ got a new type + self.assertNotEqual(type(Interface), type(IWithAdapt)) + self.assertEqual(type(IWithAdapt), type(IDerivedAdapt)) + self.assertIsInstance(IWithAdapt, type(Interface)) + + self.assertEqual(24, IDerived2Adapt(object())) + self.assertNotEqual(type(IDerived2Adapt), type(IDerivedAdapt)) + self.assertIsInstance(IDerived2Adapt, type(IDerivedAdapt)) + + def test_interfacemethod_is_general(self): + from zope.interface import Interface + from zope.interface import interfacemethod + + class I(Interface): + + @interfacemethod + def __call__(self, obj): + """Replace an existing method""" + return 42 + + @interfacemethod + def this_is_new(self): + return 42 + + self.assertEqual(I(self), 42) + self.assertEqual(I.this_is_new(), 42) + class AttributeTests(ElementTests):