diff options
Diffstat (limited to 'src/zope')
| -rw-r--r-- | src/zope/interface/_zope_interface_coptimizations.c | 91 | ||||
| -rw-r--r-- | src/zope/interface/adapter.py | 34 | ||||
| -rw-r--r-- | src/zope/interface/declarations.py | 92 | ||||
| -rw-r--r-- | src/zope/interface/tests/test_declarations.py | 320 | ||||
| -rw-r--r-- | src/zope/interface/tests/test_registry.py | 155 |
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 |
