summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Madden <jamadden@gmail.com>2020-01-28 16:04:31 -0600
committerJason Madden <jamadden@gmail.com>2020-01-28 16:04:31 -0600
commit97997956d1a17a0cfb064dfe0487291e8f8ee181 (patch)
treea491a49d49388774f3b04ced86985578e6be4bac
parent489328a5afc1430a064db2dba7a7a408591789f9 (diff)
downloadzope-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.rst18
-rw-r--r--src/zope/interface/declarations.py29
-rw-r--r--src/zope/interface/interface.py5
-rw-r--r--src/zope/interface/tests/test_adapter.py18
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