summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Madden <jamadden@gmail.com>2020-04-03 09:55:43 -0500
committerJason Madden <jamadden@gmail.com>2020-04-06 09:14:45 -0500
commit10eadd6305ee57910dbcc508b293f4bf0364fd84 (patch)
tree63a450400cf79c33cb21a1a81d4c00371bf84dcc
parent1af83ef9f90aa7a558314892b72eec6d62263981 (diff)
downloadzope-interface-10eadd6305ee57910dbcc508b293f4bf0364fd84.tar.gz
Let interface 'subclasses' override __adapt__.issue3
Cooperate with InterfaceClass to ensure there is no performance penalty for this. Fixes #3 +-------------------------------------------------------------+----------------+------------------------------+------------------------------+ | Benchmark | bench_master38 | bench_issue3 | bench_issue3_opt | +=============================================================+================+==============================+==============================+ | call interface (provides; deep) | 369 ns | 454 ns: 1.23x slower (+23%) | not significant | +-------------------------------------------------------------+----------------+------------------------------+------------------------------+ | call interface (provides; wide) | 373 ns | 457 ns: 1.22x slower (+22%) | 365 ns: 1.02x faster (-2%) | +-------------------------------------------------------------+----------------+------------------------------+------------------------------+ | call interface (no alternate, no conform, not provided) | 671 ns | 760 ns: 1.13x slower (+13%) | 636 ns: 1.06x faster (-5%) | +-------------------------------------------------------------+----------------+------------------------------+------------------------------+ | call interface (alternate, no conform, not provided) | 395 ns | 494 ns: 1.25x slower (+25%) | not significant | +-------------------------------------------------------------+----------------+------------------------------+------------------------------+ | call interface (no alternate, valid conform, not provided) | 250 ns | not significant | 227 ns: 1.10x faster (-9%) | +-------------------------------------------------------------+----------------+------------------------------+------------------------------+ | call interface (alternate, invalid conform, not provided) | 348 ns | 424 ns: 1.22x slower (+22%) | not significant | +-------------------------------------------------------------+----------------+------------------------------+------------------------------+
-rw-r--r--CHANGES.rst17
-rw-r--r--docs/README.rst49
-rw-r--r--docs/api/declarations.rst2
-rw-r--r--src/zope/interface/__init__.py5
-rw-r--r--src/zope/interface/_zope_interface_coptimizations.c26
-rw-r--r--src/zope/interface/common/__init__.py2
-rw-r--r--src/zope/interface/interface.py77
-rw-r--r--src/zope/interface/interfaces.py29
-rw-r--r--src/zope/interface/tests/test_adapter.py64
-rw-r--r--src/zope/interface/tests/test_declarations.py41
-rw-r--r--src/zope/interface/tests/test_interface.py126
11 files changed, 373 insertions, 65 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index bf8ce26..70f85e9 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -27,6 +27,23 @@
See `issue 200 <https://github.com/zopefoundation/zope.interface/issues/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 <https://github.com/zopefoundation/zope.interface/issues/3>`_.
5.0.2 (2020-03-30)
==================
diff --git a/docs/README.rst b/docs/README.rst
index 6fbd5f8..0364a81 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())
+ <CustomAdapt object at ...>
+
+.. 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 955d246..fa265e7 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 e282fbd..3372103 100644
--- a/src/zope/interface/__init__.py
+++ b/src/zope/interface/__init__.py
@@ -49,7 +49,7 @@ The package has several public modules:
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 @@ from zope.interface.declarations import named
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 69f5bbc..346e7f2 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 a8bedf0..b40c317 100644
--- a/src/zope/interface/common/__init__.py
+++ b/src/zope/interface/common/__init__.py
@@ -259,5 +259,5 @@ class ABCInterfaceClass(InterfaceClass):
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 d035ade..ff26d33 100644
--- a/src/zope/interface/interface.py
+++ b/src/zope/interface/interface.py
@@ -20,6 +20,7 @@ from types import FunctionType
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 @@ __all__ = [
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 @@ _InterfaceClassBase = _InterfaceMetaClass(
)
+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 + "<WithCustomMethods>",
+ 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 + "<WithCustomMethods>",
+ 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 @@ class InterfaceClass(_InterfaceClassBase):
# __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 @@ assert Interface.__sro__ == (Interface,)
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 c0ece6a..6321d0c 100644
--- a/src/zope/interface/interfaces.py
+++ b/src/zope/interface/interfaces.py
@@ -518,6 +518,35 @@ class IInterfaceDeclaration(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 869df66..8ff96f0 100644
--- a/src/zope/interface/tests/test_adapter.py
+++ b/src/zope/interface/tests/test_adapter.py
@@ -796,12 +796,12 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 @@ class AdapterLookupBaseTests(unittest.TestCase):
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 5d7272a..83815d7 100644
--- a/src/zope/interface/tests/test_declarations.py
+++ b/src/zope/interface/tests/test_declarations.py
@@ -864,25 +864,30 @@ class Test_classImplements(unittest.TestCase):
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 2100340..4bbed1a 100644
--- a/src/zope/interface/tests/test_interface.py
+++ b/src/zope/interface/tests/test_interface.py
@@ -1318,9 +1318,9 @@ class InterfaceTests(unittest.TestCase):
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 InterfaceTests(unittest.TestCase):
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):