diff options
Diffstat (limited to 'src/zope/interface/registry.py')
| -rw-r--r-- | src/zope/interface/registry.py | 79 |
1 files changed, 65 insertions, 14 deletions
diff --git a/src/zope/interface/registry.py b/src/zope/interface/registry.py index 8539d27..7c853ce 100644 --- a/src/zope/interface/registry.py +++ b/src/zope/interface/registry.py @@ -14,6 +14,8 @@ """Basic components support """ from collections import defaultdict +import functools +import weakref try: from zope.event import notify @@ -70,6 +72,53 @@ class _UnhashableComponentCounter(object): class _UtilityRegistrations(object): + # Strong reference {id(components): _UtilityRegistrations} + _regs_for_components = {} + # Weak reference {id(components): components}, used for cleanup. + _weakrefs_for_components = {} + + @classmethod + def for_components(cls, components): + # We manage these utility/subscription registrations as associated + # objects with a weakref to avoid making any changes to + # the pickle format. They are keyed off the id of the component because + # Components subclasses are not guaranteed to be hashable. + key = id(components) + try: + regs = cls._regs_for_components[key] + except KeyError: + regs = None + else: + # In case the components have been re-initted, clear the cache + # (zope.component.testing does this between tests, which calls Components.__init__, + # so we should typically not get here) + if (regs._utilities is not components.utilities + or regs._utility_registrations is not components._utility_registrations): + regs = None # pragma: no cover + + if regs is None: + regs = cls(components.utilities, components._utility_registrations) + cls._regs_for_components[key] = regs + + if key not in cls._weakrefs_for_components: + cleanup = functools.partial(cls._cleanup_for_components, key) + cls._weakrefs_for_components[key] = weakref.ref(components, cleanup) + + return regs + + @classmethod + def _cleanup_for_components(cls, key, *args): + cls._weakrefs_for_components.pop(key, None) + cls._regs_for_components.pop(key, None) + + @classmethod + def reset_for_components(cls, components): + cls._cleanup_for_components(id(components)) + + @classmethod + def clear_cache(cls): + cls._regs_for_components.clear() + def __init__(self, utilities, utility_registrations): # {provided -> {component: count}} self._cache = defaultdict(lambda: defaultdict(int)) @@ -134,6 +183,12 @@ class _UtilityRegistrations(object): if not subscribed: self._utilities.unsubscribe((), provided, component) +try: + from zope.testing import cleanup +except ImportError: # pragma: no cover + pass +else: + cleanup.addCleanUp(_UtilityRegistrations.clear_cache) @implementer(IComponents) class Components(object): @@ -147,14 +202,14 @@ class Components(object): # __init__ is used for test cleanup as well as initialization. # XXX add a separate API for test cleanup. - # See _utility_registrations below. - if hasattr(self, '_v_utility_registrations_cache'): - del self._v_utility_registrations_cache + _UtilityRegistrations.reset_for_components(self) def __repr__(self): return "<%s %s>" % (self.__class__.__name__, self.__name__) def _init_registries(self): + # Subclasses have never been required to call this, merely to implement + # it to initialize these two properties. self.adapters = AdapterRegistry() self.utilities = AdapterRegistry() @@ -166,16 +221,12 @@ class Components(object): @property def _utility_registrations_cache(self): - # We use a _v_ attribute internally so that data aren't saved in ZODB. - # If data are pickled in other contexts, the data will be carried along. - # There's no harm in pickling the extra data othr than that it would - # be somewhat wasteful. It's doubtful that that's an issue anyway. - try: - return self._v_utility_registrations_cache - except AttributeError: - self._v_utility_registrations_cache = _UtilityRegistrations( - self.utilities, self._utility_registrations) - return self._v_utility_registrations_cache + # We go through the class mapping to be sure that this never + # gets pickled. There are "persistent" subclasses of us that aren't + # actually Persistent objects themselves (only their registries are) + # so using a _v name won't work. Similarly, there are subclasses + # that inherit from dict too so overriding __getstate__ won't work. + return _UtilityRegistrations.for_components(self) def _getBases(self): # Subclasses might override @@ -192,7 +243,7 @@ class Components(object): __bases__ = property( lambda self: self._getBases(), lambda self, bases: self._setBases(bases), - ) + ) def registerUtility(self, component=None, provided=None, name=u'', info=u'', event=True, factory=None): |
