summaryrefslogtreecommitdiff
path: root/src/zope
diff options
context:
space:
mode:
Diffstat (limited to 'src/zope')
-rw-r--r--src/zope/interface/_zope_interface_coptimizations.c91
-rw-r--r--src/zope/interface/adapter.py34
-rw-r--r--src/zope/interface/declarations.py92
-rw-r--r--src/zope/interface/tests/test_declarations.py320
-rw-r--r--src/zope/interface/tests/test_registry.py155
5 files changed, 647 insertions, 45 deletions
diff --git a/src/zope/interface/_zope_interface_coptimizations.c b/src/zope/interface/_zope_interface_coptimizations.c
index f4f9cce..3a2ecec 100644
--- a/src/zope/interface/_zope_interface_coptimizations.c
+++ b/src/zope/interface/_zope_interface_coptimizations.c
@@ -43,6 +43,11 @@ static PyObject *str__conform__, *str_call_conform, *adapter_hooks;
static PyObject *str_uncached_lookup, *str_uncached_lookupAll;
static PyObject *str_uncached_subscriptions;
static PyObject *str_registry, *strro, *str_generation, *strchanged;
+static PyObject *str__get__;
+static PyObject *str__self__;
+static PyObject *str__thisclass__;
+static PyObject *str__self_class__;
+static PyObject *str__mro__;
static PyTypeObject *Implements;
@@ -91,6 +96,7 @@ import_declarations(void)
return 0;
}
+
static PyTypeObject SpecType; /* Forward */
static PyObject *
@@ -111,6 +117,12 @@ implementedBy(PyObject *ignored, PyObject *cls)
PyObject *dict = NULL, *spec;
+ if (PyObject_TypeCheck(cls, &PySuper_Type))
+ {
+ // Let merging be handled by Python.
+ return implementedByFallback(cls);
+ }
+
if (PyType_Check(cls))
{
dict = TYPE(cls)->tp_dict;
@@ -168,6 +180,7 @@ getObjectSpecification(PyObject *ignored, PyObject *ob)
if (result != NULL && PyObject_TypeCheck(result, &SpecType))
return result;
+
PyErr_Clear();
/* We do a getattr here so as not to be defeated by proxies */
@@ -176,11 +189,11 @@ getObjectSpecification(PyObject *ignored, PyObject *ob)
{
PyErr_Clear();
if (imported_declarations == 0 && import_declarations() < 0)
- return NULL;
+ return NULL;
+
Py_INCREF(empty);
return empty;
}
-
result = implementedBy(NULL, cls);
Py_DECREF(cls);
@@ -192,7 +205,15 @@ providedBy(PyObject *ignored, PyObject *ob)
{
PyObject *result, *cls, *cp;
+ result = NULL;
+
+ if (PyObject_TypeCheck(ob, &PySuper_Type))
+ {
+ return implementedBy(NULL, ob);
+ }
+
result = PyObject_GetAttr(ob, str__providedBy__);
+
if (result == NULL)
{
PyErr_Clear();
@@ -947,27 +968,14 @@ _getcache(lookup *self, PyObject *provided, PyObject *name)
return result
*/
-static PyObject *
-tuplefy(PyObject *v)
-{
- if (! PyTuple_Check(v))
- {
- v = PyObject_CallFunctionObjArgs(OBJECT(&PyTuple_Type), v, NULL);
- if (v == NULL)
- return NULL;
- }
- else
- Py_INCREF(v);
- return v;
-}
static PyObject *
_lookup(lookup *self,
PyObject *required, PyObject *provided, PyObject *name,
PyObject *default_)
{
PyObject *result, *key, *cache;
-
+ result = key = cache = NULL;
#ifdef PY3K
if ( name && !PyUnicode_Check(name) )
#else
@@ -978,14 +986,19 @@ _lookup(lookup *self,
"name is not a string or unicode");
return NULL;
}
- cache = _getcache(self, provided, name);
- if (cache == NULL)
- return NULL;
- required = tuplefy(required);
+ /* If `required` is a lazy sequence, it could have arbitrary side-effects,
+ such as clearing our caches. So we must not retreive the cache until
+ after resolving it. */
+ required = PySequence_Tuple(required);
if (required == NULL)
return NULL;
+
+ cache = _getcache(self, provided, name);
+ if (cache == NULL)
+ return NULL;
+
if (PyTuple_GET_SIZE(required) == 1)
key = PyTuple_GET_ITEM(required, 0);
else
@@ -1154,12 +1167,23 @@ _adapter_hook(lookup *self,
return NULL;
if (factory != Py_None)
- {
+ {
+ if (PyObject_TypeCheck(object, &PySuper_Type)) {
+ PyObject* self = PyObject_GetAttr(object, str__self__);
+ if (self == NULL)
+ {
+ Py_DECREF(factory);
+ return NULL;
+ }
+ // Borrow the reference to self
+ Py_DECREF(self);
+ object = self;
+ }
result = PyObject_CallFunctionObjArgs(factory, object, NULL);
Py_DECREF(factory);
if (result == NULL || result != Py_None)
return result;
- }
+ }
else
result = factory; /* None */
@@ -1217,15 +1241,16 @@ _lookupAll(lookup *self, PyObject *required, PyObject *provided)
{
PyObject *cache, *result;
+ /* resolve before getting cache. See note in _lookup. */
+ required = PySequence_Tuple(required);
+ if (required == NULL)
+ return NULL;
+
ASSURE_DICT(self->_mcache);
cache = _subcache(self->_mcache, provided);
if (cache == NULL)
return NULL;
- required = tuplefy(required);
- if (required == NULL)
- return NULL;
-
result = PyDict_GetItem(cache, required);
if (result == NULL)
{
@@ -1287,15 +1312,16 @@ _subscriptions(lookup *self, PyObject *required, PyObject *provided)
{
PyObject *cache, *result;
+ /* resolve before getting cache. See note in _lookup. */
+ required = PySequence_Tuple(required);
+ if (required == NULL)
+ return NULL;
+
ASSURE_DICT(self->_scache);
cache = _subcache(self->_scache, provided);
if (cache == NULL)
return NULL;
- required = tuplefy(required);
- if (required == NULL)
- return NULL;
-
result = PyDict_GetItem(cache, required);
if (result == NULL)
{
@@ -1736,6 +1762,11 @@ init(void)
DEFINE_STRING(_generation);
DEFINE_STRING(ro);
DEFINE_STRING(changed);
+ DEFINE_STRING(__self__);
+ DEFINE_STRING(__get__);
+ DEFINE_STRING(__thisclass__);
+ DEFINE_STRING(__self_class__);
+ DEFINE_STRING(__mro__);
#undef DEFINE_STRING
adapter_hooks = PyList_New(0);
if (adapter_hooks == NULL)
diff --git a/src/zope/interface/adapter.py b/src/zope/interface/adapter.py
index 40b2921..e9ca06f 100644
--- a/src/zope/interface/adapter.py
+++ b/src/zope/interface/adapter.py
@@ -30,6 +30,22 @@ __all__ = [
'VerifyingAdapterRegistry',
]
+# ``tuple`` and ``list`` cooperate so that ``tuple([some list])``
+# directly allocates and iterates at the C level without using a
+# Python iterator. That's not the case for
+# ``tuple(generator_expression)`` or ``tuple(map(func, it))``.
+##
+# 3.8
+# ``tuple([t for t in range(10)])`` -> 610ns
+# ``tuple(t for t in range(10))`` -> 696ns
+# ``tuple(map(lambda t: t, range(10)))`` -> 881ns
+##
+# 2.7
+# ``tuple([t fon t in range(10)])`` -> 625ns
+# ``tuple(t for t in range(10))`` -> 665ns
+# ``tuple(map(lambda t: t, range(10)))`` -> 958ns
+#
+# All three have substantial variance.
class BaseAdapterRegistry(object):
@@ -114,7 +130,7 @@ class BaseAdapterRegistry(object):
self.unregister(required, provided, name, value)
return
- required = tuple(map(_convert_None_to_Interface, required))
+ required = tuple([_convert_None_to_Interface(r) for r in required])
name = _normalize_name(name)
order = len(required)
byorder = self._adapters
@@ -143,7 +159,7 @@ class BaseAdapterRegistry(object):
self.changed(self)
def registered(self, required, provided, name=u''):
- required = tuple(map(_convert_None_to_Interface, required))
+ required = tuple([_convert_None_to_Interface(r) for r in required])
name = _normalize_name(name)
order = len(required)
byorder = self._adapters
@@ -162,7 +178,7 @@ class BaseAdapterRegistry(object):
return components.get(name)
def unregister(self, required, provided, name, value=None):
- required = tuple(map(_convert_None_to_Interface, required))
+ required = tuple([_convert_None_to_Interface(r) for r in required])
order = len(required)
byorder = self._adapters
if order >= len(byorder):
@@ -210,7 +226,7 @@ class BaseAdapterRegistry(object):
self.changed(self)
def subscribe(self, required, provided, value):
- required = tuple(map(_convert_None_to_Interface, required))
+ required = tuple([_convert_None_to_Interface(r) for r in required])
name = u''
order = len(required)
byorder = self._subscribers
@@ -237,7 +253,7 @@ class BaseAdapterRegistry(object):
self.changed(self)
def unsubscribe(self, required, provided, value=None):
- required = tuple(map(_convert_None_to_Interface, required))
+ required = tuple([_convert_None_to_Interface(r) for r in required])
order = len(required)
byorder = self._subscribers
if order >= len(byorder):
@@ -378,6 +394,8 @@ class LookupBase(object):
factory = self.lookup((required, ), provided, name)
if factory is not None:
+ if isinstance(object, super):
+ object = object.__self__
result = factory(object)
if result is not None:
return result
@@ -539,11 +557,11 @@ class AdapterLookupBase(object):
return result
def queryMultiAdapter(self, objects, provided, name=u'', default=None):
- factory = self.lookup(map(providedBy, objects), provided, name)
+ factory = self.lookup([providedBy(o) for o in objects], provided, name)
if factory is None:
return default
- result = factory(*objects)
+ result = factory(*[o.__self__ if isinstance(o, super) else o for o in objects])
if result is None:
return default
@@ -594,7 +612,7 @@ class AdapterLookupBase(object):
return result
def subscribers(self, objects, provided):
- subscriptions = self.subscriptions(map(providedBy, objects), provided)
+ subscriptions = self.subscriptions([providedBy(o) for o in objects], provided)
if provided is None:
result = ()
for subscription in subscriptions:
diff --git a/src/zope/interface/declarations.py b/src/zope/interface/declarations.py
index 3118593..9c15b9b 100644
--- a/src/zope/interface/declarations.py
+++ b/src/zope/interface/declarations.py
@@ -54,6 +54,16 @@ _ADVICE_ERROR = ('Class advice impossible in Python3. '
_ADVICE_WARNING = ('The %s API is deprecated, and will not work in Python3 '
'Use the @%s class decorator instead.')
+def _next_super_class(ob):
+ # When ``ob`` is an instance of ``super``, return
+ # the next class in the MRO that we should actually be
+ # looking at. Watch out for diamond inheritance!
+ self_class = ob.__self_class__
+ class_that_invoked_super = ob.__thisclass__
+ complete_mro = self_class.__mro__
+ next_class = complete_mro[complete_mro.index(class_that_invoked_super) + 1]
+ return next_class
+
class named(object):
def __init__(self, name):
@@ -69,8 +79,8 @@ class Declaration(Specification):
__slots__ = ()
- def __init__(self, *interfaces):
- Specification.__init__(self, _normalizeargs(interfaces))
+ def __init__(self, *bases):
+ Specification.__init__(self, _normalizeargs(bases))
def __contains__(self, interface):
"""Test whether an interface is in the specification
@@ -195,10 +205,14 @@ class Implements(Declaration):
# interfaces actually declared for a class
declared = ()
+ # Weak cache of {class: <implements>} for super objects.
+ # Created on demand.
+ _super_cache = None
+
__name__ = '?'
@classmethod
- def named(cls, name, *interfaces):
+ def named(cls, name, *bases):
# Implementation method: Produce an Implements interface with
# a fully fleshed out __name__ before calling the constructor, which
# sets bases to the given interfaces and which may pass this object to
@@ -206,9 +220,13 @@ class Implements(Declaration):
# by name, this needs to be set.
inst = cls.__new__(cls)
inst.__name__ = name
- inst.__init__(*interfaces)
+ inst.__init__(*bases)
return inst
+ def changed(self, originally_changed):
+ self._super_cache = None
+ return super(Implements, self).changed(originally_changed)
+
def __repr__(self):
return '<implementedBy %s>' % (self.__name__)
@@ -276,6 +294,53 @@ def _implements_name(ob):
'.' + (getattr(ob, '__name__', '?') or '?')
+def _implementedBy_super(sup):
+ # TODO: This is now simple enough we could probably implement
+ # in C if needed.
+
+ # If the class MRO is strictly linear, we could just
+ # follow the normal algorithm for the next class in the
+ # search order (e.g., just return
+ # ``implemented_by_next``). But when diamond inheritance
+ # or mixins + interface declarations are present, we have
+ # to consider the whole MRO and compute a new Implements
+ # that excludes the classes being skipped over but
+ # includes everything else.
+ implemented_by_self = implementedBy(sup.__self_class__)
+ cache = implemented_by_self._super_cache
+ if cache is None:
+ cache = implemented_by_self._super_cache = weakref.WeakKeyDictionary()
+
+ key = sup.__thisclass__
+ try:
+ return cache[key]
+ except KeyError:
+ pass
+
+ next_cls = _next_super_class(sup)
+ # For ``implementedBy(cls)``:
+ # .__bases__ is .declared + [implementedBy(b) for b in cls.__bases__]
+ # .inherit is cls
+
+ implemented_by_next = implementedBy(next_cls)
+ mro = sup.__self_class__.__mro__
+ ix_next_cls = mro.index(next_cls)
+ classes_to_keep = mro[ix_next_cls:]
+ new_bases = [implementedBy(c) for c in classes_to_keep]
+
+ new = Implements.named(
+ implemented_by_self.__name__ + ':' + implemented_by_next.__name__,
+ *new_bases
+ )
+ new.inherit = implemented_by_next.inherit
+ new.declared = implemented_by_next.declared
+ # I don't *think* that new needs to subscribe to ``implemented_by_self``;
+ # it auto-subscribed to its bases, and that should be good enough.
+ cache[key] = new
+
+ return new
+
+
@_use_c_impl
def implementedBy(cls):
"""Return the interfaces implemented for a class' instances
@@ -283,6 +348,11 @@ def implementedBy(cls):
The value returned is an `~zope.interface.interfaces.IDeclaration`.
"""
try:
+ if isinstance(cls, super):
+ # Yes, this needs to be inside the try: block. Some objects
+ # like security proxies even break isinstance.
+ return _implementedBy_super(cls)
+
spec = cls.__dict__.get('__implemented__')
except AttributeError:
@@ -449,6 +519,8 @@ class implementer(object):
def __call__(self, ob):
if isinstance(ob, DescriptorAwareMetaClasses):
+ # This is the common branch for new-style (object) and
+ # on Python 2 old-style classes.
classImplements(ob, *self.interfaces)
return ob
@@ -890,12 +962,22 @@ def getObjectSpecification(ob):
@_use_c_impl
def providedBy(ob):
+ """
+ Return the interfaces provided by *ob*.
+ If *ob* is a :class:`super` object, then only interfaces implemented
+ by the remainder of the classes in the method resolution order are
+ considered. Interfaces directly provided by the object underlying *ob*
+ are not.
+ """
# Here we have either a special object, an old-style declaration
# or a descriptor
# Try to get __providedBy__
try:
+ if isinstance(ob, super): # Some objects raise errors on isinstance()
+ return implementedBy(ob)
+
r = ob.__providedBy__
except:
# Not set yet. Fall back to lower-level thing that computes it
@@ -943,7 +1025,7 @@ def providedBy(ob):
class ObjectSpecificationDescriptor(object):
"""Implement the `__providedBy__` attribute
- The `__providedBy__` attribute computes the interfaces peovided by
+ The `__providedBy__` attribute computes the interfaces provided by
an object.
"""
diff --git a/src/zope/interface/tests/test_declarations.py b/src/zope/interface/tests/test_declarations.py
index 5256b07..ccf4dde 100644
--- a/src/zope/interface/tests/test_declarations.py
+++ b/src/zope/interface/tests/test_declarations.py
@@ -529,6 +529,177 @@ class Test_implementedByFallback(unittest.TestCase):
__implemented__ = impl
self.assertTrue(self._callFUT(Foo) is impl)
+ def test_super_when_base_implements_interface(self):
+ from zope.interface import Interface
+ from zope.interface.declarations import implementer
+
+ class IBase(Interface):
+ pass
+
+ class IDerived(IBase):
+ pass
+
+ @implementer(IBase)
+ class Base(object):
+ pass
+
+ @implementer(IDerived)
+ class Derived(Base):
+ pass
+
+ self.assertEqual(list(self._callFUT(Derived)), [IDerived, IBase])
+ sup = super(Derived, Derived)
+ self.assertEqual(list(self._callFUT(sup)), [IBase])
+
+ def test_super_when_base_implements_interface_diamond(self):
+ from zope.interface import Interface
+ from zope.interface.declarations import implementer
+
+ class IBase(Interface):
+ pass
+
+ class IDerived(IBase):
+ pass
+
+ @implementer(IBase)
+ class Base(object):
+ pass
+
+ class Child1(Base):
+ pass
+
+ class Child2(Base):
+ pass
+
+ @implementer(IDerived)
+ class Derived(Child1, Child2):
+ pass
+
+ self.assertEqual(list(self._callFUT(Derived)), [IDerived, IBase])
+ sup = super(Derived, Derived)
+ self.assertEqual(list(self._callFUT(sup)), [IBase])
+
+ def test_super_when_parent_implements_interface_diamond(self):
+ from zope.interface import Interface
+ from zope.interface.declarations import implementer
+
+ class IBase(Interface):
+ pass
+
+ class IDerived(IBase):
+ pass
+
+
+ class Base(object):
+ pass
+
+ class Child1(Base):
+ pass
+
+ @implementer(IBase)
+ class Child2(Base):
+ pass
+
+ @implementer(IDerived)
+ class Derived(Child1, Child2):
+ pass
+
+ self.assertEqual(Derived.__mro__, (Derived, Child1, Child2, Base, object))
+ self.assertEqual(list(self._callFUT(Derived)), [IDerived, IBase])
+ sup = super(Derived, Derived)
+ fut = self._callFUT(sup)
+ self.assertEqual(list(fut), [IBase])
+ self.assertIsNone(fut._dependents)
+
+ def test_super_when_base_doesnt_implement_interface(self):
+ from zope.interface import Interface
+ from zope.interface.declarations import implementer
+
+ class IBase(Interface):
+ pass
+
+ class IDerived(IBase):
+ pass
+
+ class Base(object):
+ pass
+
+ @implementer(IDerived)
+ class Derived(Base):
+ pass
+
+ self.assertEqual(list(self._callFUT(Derived)), [IDerived])
+
+ sup = super(Derived, Derived)
+ self.assertEqual(list(self._callFUT(sup)), [])
+
+ def test_super_when_base_is_object(self):
+ from zope.interface import Interface
+ from zope.interface.declarations import implementer
+
+ class IBase(Interface):
+ pass
+
+ class IDerived(IBase):
+ pass
+
+ @implementer(IDerived)
+ class Derived(object):
+ pass
+
+ self.assertEqual(list(self._callFUT(Derived)), [IDerived])
+
+ sup = super(Derived, Derived)
+ self.assertEqual(list(self._callFUT(sup)), [])
+ def test_super_multi_level_multi_inheritance(self):
+ from zope.interface.declarations import implementer
+ from zope.interface import Interface
+
+ class IBase(Interface):
+ pass
+
+ class IM1(Interface):
+ pass
+
+ class IM2(Interface):
+ pass
+
+ class IDerived(IBase):
+ pass
+
+ class IUnrelated(Interface):
+ pass
+
+ @implementer(IBase)
+ class Base(object):
+ pass
+
+ @implementer(IM1)
+ class M1(Base):
+ pass
+
+ @implementer(IM2)
+ class M2(Base):
+ pass
+
+ @implementer(IDerived, IUnrelated)
+ class Derived(M1, M2):
+ pass
+
+ d = Derived
+ sd = super(Derived, Derived)
+ sm1 = super(M1, Derived)
+ sm2 = super(M2, Derived)
+
+ self.assertEqual(list(self._callFUT(d)),
+ [IDerived, IUnrelated, IM1, IBase, IM2])
+ self.assertEqual(list(self._callFUT(sd)),
+ [IM1, IBase, IM2])
+ self.assertEqual(list(self._callFUT(sm1)),
+ [IM2, IBase])
+ self.assertEqual(list(self._callFUT(sm2)),
+ [IBase])
+
class Test_implementedBy(Test_implementedByFallback,
OptimizationTestMixin):
@@ -1575,6 +1746,155 @@ class Test_providedByFallback(unittest.TestCase):
spec = self._callFUT(foo)
self.assertEqual(list(spec), [IFoo])
+ def test_super_when_base_implements_interface(self):
+ from zope.interface import Interface
+ from zope.interface.declarations import implementer
+
+ class IBase(Interface):
+ pass
+
+ class IDerived(IBase):
+ pass
+
+ @implementer(IBase)
+ class Base(object):
+ pass
+
+ @implementer(IDerived)
+ class Derived(Base):
+ pass
+
+ derived = Derived()
+ self.assertEqual(list(self._callFUT(derived)), [IDerived, IBase])
+
+ sup = super(Derived, derived)
+ fut = self._callFUT(sup)
+ self.assertIsNone(fut._dependents)
+ self.assertEqual(list(fut), [IBase])
+
+ def test_super_when_base_doesnt_implement_interface(self):
+ from zope.interface import Interface
+ from zope.interface.declarations import implementer
+
+ class IBase(Interface):
+ pass
+
+ class IDerived(IBase):
+ pass
+
+ class Base(object):
+ pass
+
+ @implementer(IDerived)
+ class Derived(Base):
+ pass
+
+ derived = Derived()
+ self.assertEqual(list(self._callFUT(derived)), [IDerived])
+
+ sup = super(Derived, derived)
+ self.assertEqual(list(self._callFUT(sup)), [])
+
+ def test_super_when_base_is_object(self):
+ from zope.interface import Interface
+ from zope.interface.declarations import implementer
+
+ class IBase(Interface):
+ pass
+
+ class IDerived(IBase):
+ pass
+
+ @implementer(IDerived)
+ class Derived(object):
+ pass
+
+ derived = Derived()
+ self.assertEqual(list(self._callFUT(derived)), [IDerived])
+
+ sup = super(Derived, derived)
+ fut = self._callFUT(sup)
+ self.assertIsNone(fut._dependents)
+ self.assertEqual(list(fut), [])
+
+ def test_super_when_object_directly_provides(self):
+ from zope.interface import Interface
+ from zope.interface.declarations import implementer
+ from zope.interface.declarations import directlyProvides
+
+ class IBase(Interface):
+ pass
+
+ class IDerived(IBase):
+ pass
+
+ @implementer(IBase)
+ class Base(object):
+ pass
+
+ class Derived(Base):
+ pass
+
+ derived = Derived()
+ self.assertEqual(list(self._callFUT(derived)), [IBase])
+
+ directlyProvides(derived, IDerived)
+ self.assertEqual(list(self._callFUT(derived)), [IDerived, IBase])
+
+ sup = super(Derived, derived)
+ fut = self._callFUT(sup)
+ self.assertIsNone(fut._dependents)
+ self.assertEqual(list(fut), [IBase])
+
+ def test_super_multi_level_multi_inheritance(self):
+ from zope.interface.declarations import implementer
+ from zope.interface import Interface
+
+ class IBase(Interface):
+ pass
+
+ class IM1(Interface):
+ pass
+
+ class IM2(Interface):
+ pass
+
+ class IDerived(IBase):
+ pass
+
+ class IUnrelated(Interface):
+ pass
+
+ @implementer(IBase)
+ class Base(object):
+ pass
+
+ @implementer(IM1)
+ class M1(Base):
+ pass
+
+ @implementer(IM2)
+ class M2(Base):
+ pass
+
+ @implementer(IDerived, IUnrelated)
+ class Derived(M1, M2):
+ pass
+
+ d = Derived()
+ sd = super(Derived, d)
+ sm1 = super(M1, d)
+ sm2 = super(M2, d)
+
+ self.assertEqual(list(self._callFUT(d)),
+ [IDerived, IUnrelated, IM1, IBase, IM2])
+ self.assertEqual(list(self._callFUT(sd)),
+ [IM1, IBase, IM2])
+ self.assertEqual(list(self._callFUT(sm1)),
+ [IM2, IBase])
+ self.assertEqual(list(self._callFUT(sm2)),
+ [IBase])
+
class Test_providedBy(Test_providedByFallback,
OptimizationTestMixin):
diff --git a/src/zope/interface/tests/test_registry.py b/src/zope/interface/tests/test_registry.py
index e5a8eb0..db91c52 100644
--- a/src/zope/interface/tests/test_registry.py
+++ b/src/zope/interface/tests/test_registry.py
@@ -1332,8 +1332,102 @@ class ComponentsTests(unittest.TestCase):
comp = self._makeOne()
comp.registerAdapter(_Factory, (ibar,), ifoo)
adapter = comp.getAdapter(_context, ifoo)
- self.assertTrue(isinstance(adapter, _Factory))
- self.assertTrue(adapter.context is _context)
+ self.assertIsInstance(adapter, _Factory)
+ self.assertIs(adapter.context, _context)
+
+ def test_getAdapter_hit_super(self):
+ from zope.interface import Interface
+ from zope.interface.declarations import implementer
+
+ class IBase(Interface):
+ pass
+
+ class IDerived(IBase):
+ pass
+
+ class IFoo(Interface):
+ pass
+
+ @implementer(IBase)
+ class Base(object):
+ pass
+
+ @implementer(IDerived)
+ class Derived(Base):
+ pass
+
+ class AdapterBase(object):
+ def __init__(self, context):
+ self.context = context
+
+ class AdapterDerived(object):
+ def __init__(self, context):
+ self.context = context
+
+ comp = self._makeOne()
+ comp.registerAdapter(AdapterDerived, (IDerived,), IFoo)
+ comp.registerAdapter(AdapterBase, (IBase,), IFoo)
+ self._should_not_change(comp)
+
+ derived = Derived()
+ adapter = comp.getAdapter(derived, IFoo)
+ self.assertIsInstance(adapter, AdapterDerived)
+ self.assertIs(adapter.context, derived)
+
+ supe = super(Derived, derived)
+ adapter = comp.getAdapter(supe, IFoo)
+ self.assertIsInstance(adapter, AdapterBase)
+ self.assertIs(adapter.context, derived)
+
+ def test_getAdapter_hit_super_when_parent_implements_interface_diamond(self):
+ from zope.interface import Interface
+ from zope.interface.declarations import implementer
+
+ class IBase(Interface):
+ pass
+
+ class IDerived(IBase):
+ pass
+
+ class IFoo(Interface):
+ pass
+
+ class Base(object):
+ pass
+
+ class Child1(Base):
+ pass
+
+ @implementer(IBase)
+ class Child2(Base):
+ pass
+
+ @implementer(IDerived)
+ class Derived(Child1, Child2):
+ pass
+
+ class AdapterBase(object):
+ def __init__(self, context):
+ self.context = context
+
+ class AdapterDerived(object):
+ def __init__(self, context):
+ self.context = context
+
+ comp = self._makeOne()
+ comp.registerAdapter(AdapterDerived, (IDerived,), IFoo)
+ comp.registerAdapter(AdapterBase, (IBase,), IFoo)
+ self._should_not_change(comp)
+
+ derived = Derived()
+ adapter = comp.getAdapter(derived, IFoo)
+ self.assertIsInstance(adapter, AdapterDerived)
+ self.assertIs(adapter.context, derived)
+
+ supe = super(Derived, derived)
+ adapter = comp.getAdapter(supe, IFoo)
+ self.assertIsInstance(adapter, AdapterBase)
+ self.assertIs(adapter.context, derived)
def test_queryMultiAdapter_miss(self):
from zope.interface.declarations import InterfaceClass
@@ -1448,6 +1542,63 @@ class ComponentsTests(unittest.TestCase):
self.assertTrue(isinstance(adapter, _Factory))
self.assertEqual(adapter.context, (_context1, _context2))
+ def _should_not_change(self, comp):
+ # Be sure that none of the underlying structures
+ # get told that they have changed during this process
+ # because that invalidates caches.
+ def no_changes(*args):
+ self.fail("Nothing should get changed")
+ comp.changed = no_changes
+ comp.adapters.changed = no_changes
+ comp.adapters._v_lookup.changed = no_changes
+
+ def test_getMultiAdapter_hit_super(self):
+ from zope.interface import Interface
+ from zope.interface.declarations import implementer
+
+ class IBase(Interface):
+ pass
+
+ class IDerived(IBase):
+ pass
+
+ class IFoo(Interface):
+ pass
+
+ @implementer(IBase)
+ class Base(object):
+ pass
+
+ @implementer(IDerived)
+ class Derived(Base):
+ pass
+
+ class AdapterBase(object):
+ def __init__(self, context1, context2):
+ self.context1 = context1
+ self.context2 = context2
+
+ class AdapterDerived(AdapterBase):
+ pass
+
+ comp = self._makeOne()
+ comp.registerAdapter(AdapterDerived, (IDerived, IDerived), IFoo)
+ comp.registerAdapter(AdapterBase, (IBase, IDerived), IFoo)
+ self._should_not_change(comp)
+
+ derived = Derived()
+ adapter = comp.getMultiAdapter((derived, derived), IFoo)
+ self.assertIsInstance(adapter, AdapterDerived)
+ self.assertIs(adapter.context1, derived)
+ self.assertIs(adapter.context2, derived)
+
+ supe = super(Derived, derived)
+ adapter = comp.getMultiAdapter((supe, derived), IFoo)
+ self.assertIsInstance(adapter, AdapterBase)
+ self.assertNotIsInstance(adapter, AdapterDerived)
+ self.assertIs(adapter.context1, derived)
+ self.assertIs(adapter.context2, derived)
+
def test_getAdapters_empty(self):
from zope.interface.declarations import InterfaceClass
from zope.interface.declarations import implementer