diff options
author | Jason Madden <jamadden@gmail.com> | 2020-01-28 16:04:31 -0600 |
---|---|---|
committer | Jason Madden <jamadden@gmail.com> | 2020-01-28 16:04:31 -0600 |
commit | 97997956d1a17a0cfb064dfe0487291e8f8ee181 (patch) | |
tree | a491a49d49388774f3b04ced86985578e6be4bac | |
parent | 489328a5afc1430a064db2dba7a7a408591789f9 (diff) | |
download | zope-interface-97997956d1a17a0cfb064dfe0487291e8f8ee181.tar.gz |
The _empty singleton has no-op subscribe/unsubscribe methods.issue162
Turns out they can be called in some very strange error cases. See #162 and #163 for details.
This should fix #162 (at least the provided test case, five.intid, passes now).
It also does enough work on #163 that (a) the test can be written and run in pure-python mode, which was needed to debug it and (b) five.intid runs in pure-python mode (well, with a bunch of other small hacks to Acquisition, ExtensionClass, DocumentTemplate and AccessControl), but I won't claim that it fully fixes #163. For one thing, there are no specific tests. For another, I see more such differences.
-rw-r--r-- | CHANGES.rst | 18 | ||||
-rw-r--r-- | src/zope/interface/declarations.py | 29 | ||||
-rw-r--r-- | src/zope/interface/interface.py | 5 | ||||
-rw-r--r-- | src/zope/interface/tests/test_adapter.py | 18 |
4 files changed, 55 insertions, 15 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index ad7140b..ae900d8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -54,12 +54,18 @@ fields in your subclass before attempting to hash or sort it. See `issue 157 <https://github.com/zopefoundation/zope.interface/issues/157>`_. -- Remove unneeded overwrite and call to anyway inherited `__hash__` method - from `.declarations.Implements` class. Watching a reindex index process in - ZCatalog with on a Py-Spy after 10k samples the time for `.adapter._lookup` - was reduced from 27.5s to 18.8s (~1.5x faster). Overall reindex index time - shrunk from 369s to 293s (1.26x faster). See - `PR 161 <https://github.com/zopefoundation/zope.interface/pull/161>`_. +- Remove unneeded override of the ``__hash__`` method from + ``zope.interface.declarations.Implements``. Watching a reindex index + process in ZCatalog with on a Py-Spy after 10k samples the time for + ``.adapter._lookup`` was reduced from 27.5s to 18.8s (~1.5x faster). + Overall reindex index time shrunk from 369s to 293s (1.26x faster). + See `PR 161 + <https://github.com/zopefoundation/zope.interface/pull/161>`_. + +- Make the Python implementation closer to the C implementation by + ignoring all exceptions, not just ``AttributeError``, during (parts + of) interface adaptation. See `issue 163 + <https://github.com/zopefoundation/zope.interface/issues/163>`_. 4.7.1 (2019-11-11) ================== diff --git a/src/zope/interface/declarations.py b/src/zope/interface/declarations.py index 97ccd55..04e2cc8 100644 --- a/src/zope/interface/declarations.py +++ b/src/zope/interface/declarations.py @@ -145,13 +145,15 @@ class _ImmutableDeclaration(Declaration): if new_bases != (): raise TypeError("Cannot set non-empty bases on shared empty Declaration.") + # As the immutable empty declaration, we cannot be changed. + # This means there's no logical reason for us to have dependents + # or subscriptions: we'll never notify them. So there's no need for + # us to keep track of any of that. @property def dependents(self): return {} - def changed(self, originally_changed): - # Does nothing, we have no dependents or dependencies - return + changed = subscribe = unsubscribe = lambda self, _ignored: None def interfaces(self): # An empty iterator @@ -163,6 +165,16 @@ class _ImmutableDeclaration(Declaration): def get(self, name, default=None): return default + def weakref(self, callback=None): + # We're a singleton, we never go away. So there's no need to return + # distinct weakref objects here; their callbacks will never + # be called. Instead, we only need to return a callable that + # returns ourself. The easiest one is to return _ImmutableDeclaration + # itself; testing on Python 3.8 shows that's faster than a function that + # returns _empty. (Remember, one goal is to avoid allocating any + # object, and that includes a method.) + return _ImmutableDeclaration + ############################################################################## # @@ -855,18 +867,19 @@ def ObjectSpecification(direct, cls): @_use_c_impl def getObjectSpecification(ob): - - provides = getattr(ob, '__provides__', None) + try: + provides = getattr(ob, '__provides__', None) + except: + provides = None if provides is not None: if isinstance(provides, SpecificationBase): return provides try: cls = ob.__class__ - except AttributeError: + except: # We can't get the class, so just consider provides return _empty - return implementedBy(cls) @@ -879,7 +892,7 @@ def providedBy(ob): # Try to get __providedBy__ try: r = ob.__providedBy__ - except AttributeError: + except: # Not set yet. Fall back to lower-level thing that computes it return getObjectSpecification(ob) diff --git a/src/zope/interface/interface.py b/src/zope/interface/interface.py index 7fa56aa..57998e0 100644 --- a/src/zope/interface/interface.py +++ b/src/zope/interface/interface.py @@ -159,7 +159,10 @@ class InterfaceBase(object): def __call__(self, obj, alternate=_marker): """Adapt an object to the interface """ - conform = getattr(obj, '__conform__', None) + try: + conform = getattr(obj, '__conform__', None) + except: + conform = None if conform is not None: adapter = self._call_conform(conform) if adapter is not None: diff --git a/src/zope/interface/tests/test_adapter.py b/src/zope/interface/tests/test_adapter.py index 25cb4bb..6703a48 100644 --- a/src/zope/interface/tests/test_adapter.py +++ b/src/zope/interface/tests/test_adapter.py @@ -997,6 +997,24 @@ class AdapterLookupBaseTests(unittest.TestCase): result = alb.queryMultiAdapter((foo,), IBar, default=_default) self.assertTrue(result is _default) + def test_queryMultiAdapter_errors_on_attribute_access(self): + # Which leads to using the _empty singleton as "requires" + # argument. See https://github.com/zopefoundation/zope.interface/issues/162 + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + registry = self._makeRegistry() + alb = self._makeOne(registry) + alb.lookup = alb._uncached_lookup + class UnexpectedErrorsLikeAcquisition(object): + + def __getattribute__(self, name): + raise RuntimeError("Acquisition does this. Ha-ha!") + + result = alb.queryMultiAdapter( + (UnexpectedErrorsLikeAcquisition(),), + IFoo, + ) + def test_queryMultiAdaptor_factory_miss(self): from zope.interface.declarations import implementer from zope.interface.interface import InterfaceClass |