Make provided/implementedBy and adapter registries respect super().
The query functions now start by looking at the next class in the MRO (interfaces directly provided by the underlying object are not found). Adapter registries automatically pick up providedBy change to start finding the correct implementations of adapters, but to make that really useful they needed to change to unpack super() arguments and pass __self__ to the factory. Fixes #11 Unfortunately, this makes PyPy unable to build the C extensions. Additional crash-safety for adapter lookup. Make the C functions get the cache only after resolving the ``required`` into a tuple, in case of side-effects like...clearing the cache. This could lead to the ``cache`` object being deallocated before we used it. Drop the ``tuplefy`` function in favor of a direct call to ``PySequence_Tuple``. It's what the ``tuple`` constructor would do anyway and saves a few steps. Make sure that getting ``providedBy(super())`` and ``implementedBy(super())`` have no side effects.
This commit is contained in:
parent
c931999371
commit
1e720c3819
|
@ -27,10 +27,6 @@ jobs:
|
|||
- sphinx-build -b doctest -d docs/_build/doctrees docs docs/_build/doctest
|
||||
after_success:
|
||||
|
||||
- name: PyPy C Extensions
|
||||
env: PURE_PYTHON=0
|
||||
python: pypy
|
||||
|
||||
- name: CPython No C Extension
|
||||
env: PURE_PYTHON=1
|
||||
python: 3.8
|
||||
|
|
17
CHANGES.rst
17
CHANGES.rst
|
@ -134,6 +134,23 @@
|
|||
`dolmen.builtins <https://pypi.org/project/dolmen.builtins/>`_.
|
||||
See `issue 138 <https://github.com/zopefoundation/zope.interface/issues/138>`_.
|
||||
|
||||
- Make ``providedBy()`` and ``implementedBy()`` respect ``super``
|
||||
objects. For instance, if class ``Derived`` implements ``IDerived``
|
||||
and extends ``Base`` which in turn implements ``IBase``, then
|
||||
``providedBy(super(Derived, derived))`` will return ``[IBase]``.
|
||||
Previously it would have returned ``[IDerived]`` (in general, it
|
||||
would previously have returned whatever would have been returned
|
||||
without ``super``).
|
||||
|
||||
Along with this change, adapter registries will unpack ``super``
|
||||
objects into their ``__self___`` before passing it to the factory.
|
||||
Together, this means that ``component.getAdapter(super(Derived,
|
||||
self), ITarget)`` is now meaningful.
|
||||
|
||||
See `issue 11 <https://github.com/zopefoundation/zope.interface/issues/11>`_.
|
||||
|
||||
- Fix a potential interpreter crash in the low-level adapter
|
||||
registry lookup functions. See issue 11.
|
||||
|
||||
4.7.1 (2019-11-11)
|
||||
==================
|
||||
|
|
|
@ -202,11 +202,11 @@ factories:
|
|||
... pass
|
||||
|
||||
>>> @zope.interface.implementer(IR)
|
||||
... class X:
|
||||
... class X(object):
|
||||
... pass
|
||||
|
||||
>>> @zope.interface.implementer(IProvide1)
|
||||
... class Y:
|
||||
... class Y(object):
|
||||
... def __init__(self, context):
|
||||
... self.context = context
|
||||
|
||||
|
@ -238,6 +238,29 @@ We can register and lookup by name too:
|
|||
>>> y.context is x
|
||||
True
|
||||
|
||||
Passing ``super`` objects works as expected to find less specific adapters:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> class IDerived(IR):
|
||||
... pass
|
||||
>>> @zope.interface.implementer(IDerived)
|
||||
... class Derived(X):
|
||||
... pass
|
||||
>>> class DerivedAdapter(Y):
|
||||
... def query_next(self):
|
||||
... return registry.queryAdapter(
|
||||
... super(type(self.context), self.context),
|
||||
... IProvide1)
|
||||
>>> registry.register([IDerived], IProvide1, '', DerivedAdapter)
|
||||
>>> derived = Derived()
|
||||
>>> adapter = registry.queryAdapter(derived, IProvide1)
|
||||
>>> adapter.__class__.__name__
|
||||
'DerivedAdapter'
|
||||
>>> adapter = adapter.query_next()
|
||||
>>> adapter.__class__.__name__
|
||||
'Y'
|
||||
|
||||
When the adapter factory produces ``None``, then this is treated as if no
|
||||
adapter has been found. This allows us to prevent adaptation (when desired)
|
||||
and let the adapter factory determine whether adaptation is possible based on
|
||||
|
|
|
@ -17,7 +17,9 @@ import os
|
|||
import pkg_resources
|
||||
sys.path.append(os.path.abspath('../src'))
|
||||
rqmt = pkg_resources.require('zope.interface')[0]
|
||||
|
||||
# Import and document the pure-python versions of things; they tend to have better
|
||||
# docstrings and signatures.
|
||||
os.environ['PURE_PYTHON'] = '1'
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
|
|
8
setup.py
8
setup.py
|
@ -69,10 +69,12 @@ codeoptimization = [
|
|||
]
|
||||
|
||||
is_jython = 'java' in sys.platform
|
||||
is_pypy = hasattr(sys, 'pypy_version_info')
|
||||
|
||||
# Jython cannot build the C optimizations. Everywhere else,
|
||||
# including PyPy, defer the decision to runtime.
|
||||
if is_jython:
|
||||
# Jython cannot build the C optimizations. Nor, as of 7.3, can PyPy (
|
||||
# it doesn't have PySuper_Type) Everywhere else, defer the decision to
|
||||
# runtime.
|
||||
if is_jython or is_pypy:
|
||||
ext_modules = []
|
||||
else:
|
||||
ext_modules = codeoptimization
|
||||
|
|
|
@ -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,12 +986,17 @@ _lookup(lookup *self,
|
|||
"name is not a string or unicode");
|
||||
return NULL;
|
||||
}
|
||||
cache = _getcache(self, provided, name);
|
||||
if (cache == NULL)
|
||||
|
||||
/* 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;
|
||||
|
||||
required = tuplefy(required);
|
||||
if (required == NULL)
|
||||
|
||||
cache = _getcache(self, provided, name);
|
||||
if (cache == NULL)
|
||||
return NULL;
|
||||
|
||||
if (PyTuple_GET_SIZE(required) == 1)
|
||||
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
"""
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue