Mercurial > hg
view mercurial/thirdparty/zope/interface/adapter.py @ 51017:edc44ab7437a
debug-revlog: keep the revlog open for the analysis duration
This is more explicit. No performance differences were observed.
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Mon, 25 Sep 2023 16:57:53 +0200 |
parents | 68ee61822182 |
children |
line wrap: on
line source
############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Adapter management """ from __future__ import absolute_import import weakref from . import implementer from . import providedBy from . import Interface from . import ro from .interfaces import IAdapterRegistry from ._compat import _normalize_name from ._compat import STRING_TYPES _BLANK = u'' class BaseAdapterRegistry(object): # List of methods copied from lookup sub-objects: _delegated = ('lookup', 'queryMultiAdapter', 'lookup1', 'queryAdapter', 'adapter_hook', 'lookupAll', 'names', 'subscriptions', 'subscribers') # All registries maintain a generation that can be used by verifying # registries _generation = 0 def __init__(self, bases=()): # The comments here could be improved. Possibly this bit needs # explaining in a separate document, as the comments here can # be quite confusing. /regebro # {order -> {required -> {provided -> {name -> value}}}} # Here "order" is actually an index in a list, "required" and # "provided" are interfaces, and "required" is really a nested # key. So, for example: # for order == 0 (that is, self._adapters[0]), we have: # {provided -> {name -> value}} # but for order == 2 (that is, self._adapters[2]), we have: # {r1 -> {r2 -> {provided -> {name -> value}}}} # self._adapters = [] # {order -> {required -> {provided -> {name -> [value]}}}} # where the remarks about adapters above apply self._subscribers = [] # Set, with a reference count, keeping track of the interfaces # for which we have provided components: self._provided = {} # Create ``_v_lookup`` object to perform lookup. We make this a # separate object to to make it easier to implement just the # lookup functionality in C. This object keeps track of cache # invalidation data in two kinds of registries. # Invalidating registries have caches that are invalidated # when they or their base registies change. An invalidating # registry can only have invalidating registries as bases. # See LookupBaseFallback below for the pertinent logic. # Verifying registies can't rely on getting invalidation messages, # so have to check the generations of base registries to determine # if their cache data are current. See VerifyingBasePy below # for the pertinent object. self._createLookup() # Setting the bases causes the registries described above # to be initialized (self._setBases -> self.changed -> # self._v_lookup.changed). self.__bases__ = bases def _setBases(self, bases): self.__dict__['__bases__'] = bases self.ro = ro.ro(self) self.changed(self) __bases__ = property(lambda self: self.__dict__['__bases__'], lambda self, bases: self._setBases(bases), ) def _createLookup(self): self._v_lookup = self.LookupClass(self) for name in self._delegated: self.__dict__[name] = getattr(self._v_lookup, name) def changed(self, originally_changed): self._generation += 1 self._v_lookup.changed(originally_changed) def register(self, required, provided, name, value): if not isinstance(name, STRING_TYPES): raise ValueError('name is not a string') if value is None: self.unregister(required, provided, name, value) return required = tuple(map(_convert_None_to_Interface, required)) name = _normalize_name(name) order = len(required) byorder = self._adapters while len(byorder) <= order: byorder.append({}) components = byorder[order] key = required + (provided,) for k in key: d = components.get(k) if d is None: d = {} components[k] = d components = d if components.get(name) is value: return components[name] = value n = self._provided.get(provided, 0) + 1 self._provided[provided] = n if n == 1: self._v_lookup.add_extendor(provided) self.changed(self) def registered(self, required, provided, name=_BLANK): required = tuple(map(_convert_None_to_Interface, required)) name = _normalize_name(name) order = len(required) byorder = self._adapters if len(byorder) <= order: return None components = byorder[order] key = required + (provided,) for k in key: d = components.get(k) if d is None: return None components = d return components.get(name) def unregister(self, required, provided, name, value=None): required = tuple(map(_convert_None_to_Interface, required)) order = len(required) byorder = self._adapters if order >= len(byorder): return False components = byorder[order] key = required + (provided,) # Keep track of how we got to `components`: lookups = [] for k in key: d = components.get(k) if d is None: return lookups.append((components, k)) components = d old = components.get(name) if old is None: return if (value is not None) and (old is not value): return del components[name] if not components: # Clean out empty containers, since we don't want our keys # to reference global objects (interfaces) unnecessarily. # This is often a problem when an interface is slated for # removal; a hold-over entry in the registry can make it # difficult to remove such interfaces. for comp, k in reversed(lookups): d = comp[k] if d: break else: del comp[k] while byorder and not byorder[-1]: del byorder[-1] n = self._provided[provided] - 1 if n == 0: del self._provided[provided] self._v_lookup.remove_extendor(provided) else: self._provided[provided] = n self.changed(self) def subscribe(self, required, provided, value): required = tuple(map(_convert_None_to_Interface, required)) name = _BLANK order = len(required) byorder = self._subscribers while len(byorder) <= order: byorder.append({}) components = byorder[order] key = required + (provided,) for k in key: d = components.get(k) if d is None: d = {} components[k] = d components = d components[name] = components.get(name, ()) + (value, ) if provided is not None: n = self._provided.get(provided, 0) + 1 self._provided[provided] = n if n == 1: self._v_lookup.add_extendor(provided) self.changed(self) def unsubscribe(self, required, provided, value=None): required = tuple(map(_convert_None_to_Interface, required)) order = len(required) byorder = self._subscribers if order >= len(byorder): return components = byorder[order] key = required + (provided,) # Keep track of how we got to `components`: lookups = [] for k in key: d = components.get(k) if d is None: return lookups.append((components, k)) components = d old = components.get(_BLANK) if not old: # this is belt-and-suspenders against the failure of cleanup below return # pragma: no cover if value is None: new = () else: new = tuple([v for v in old if v is not value]) if new == old: return if new: components[_BLANK] = new else: # Instead of setting components[_BLANK] = new, we clean out # empty containers, since we don't want our keys to # reference global objects (interfaces) unnecessarily. This # is often a problem when an interface is slated for # removal; a hold-over entry in the registry can make it # difficult to remove such interfaces. del components[_BLANK] for comp, k in reversed(lookups): d = comp[k] if d: break else: del comp[k] while byorder and not byorder[-1]: del byorder[-1] if provided is not None: n = self._provided[provided] + len(new) - len(old) if n == 0: del self._provided[provided] self._v_lookup.remove_extendor(provided) self.changed(self) # XXX hack to fake out twisted's use of a private api. We need to get them # to use the new registed method. def get(self, _): # pragma: no cover class XXXTwistedFakeOut: selfImplied = {} return XXXTwistedFakeOut _not_in_mapping = object() class LookupBaseFallback(object): def __init__(self): self._cache = {} self._mcache = {} self._scache = {} def changed(self, ignored=None): self._cache.clear() self._mcache.clear() self._scache.clear() def _getcache(self, provided, name): cache = self._cache.get(provided) if cache is None: cache = {} self._cache[provided] = cache if name: c = cache.get(name) if c is None: c = {} cache[name] = c cache = c return cache def lookup(self, required, provided, name=_BLANK, default=None): if not isinstance(name, STRING_TYPES): raise ValueError('name is not a string') cache = self._getcache(provided, name) required = tuple(required) if len(required) == 1: result = cache.get(required[0], _not_in_mapping) else: result = cache.get(tuple(required), _not_in_mapping) if result is _not_in_mapping: result = self._uncached_lookup(required, provided, name) if len(required) == 1: cache[required[0]] = result else: cache[tuple(required)] = result if result is None: return default return result def lookup1(self, required, provided, name=_BLANK, default=None): if not isinstance(name, STRING_TYPES): raise ValueError('name is not a string') cache = self._getcache(provided, name) result = cache.get(required, _not_in_mapping) if result is _not_in_mapping: return self.lookup((required, ), provided, name, default) if result is None: return default return result def queryAdapter(self, object, provided, name=_BLANK, default=None): return self.adapter_hook(provided, object, name, default) def adapter_hook(self, provided, object, name=_BLANK, default=None): if not isinstance(name, STRING_TYPES): raise ValueError('name is not a string') required = providedBy(object) cache = self._getcache(provided, name) factory = cache.get(required, _not_in_mapping) if factory is _not_in_mapping: factory = self.lookup((required, ), provided, name) if factory is not None: result = factory(object) if result is not None: return result return default def lookupAll(self, required, provided): cache = self._mcache.get(provided) if cache is None: cache = {} self._mcache[provided] = cache required = tuple(required) result = cache.get(required, _not_in_mapping) if result is _not_in_mapping: result = self._uncached_lookupAll(required, provided) cache[required] = result return result def subscriptions(self, required, provided): cache = self._scache.get(provided) if cache is None: cache = {} self._scache[provided] = cache required = tuple(required) result = cache.get(required, _not_in_mapping) if result is _not_in_mapping: result = self._uncached_subscriptions(required, provided) cache[required] = result return result LookupBasePy = LookupBaseFallback # BBB try: from ._zope_interface_coptimizations import LookupBase except ImportError: LookupBase = LookupBaseFallback class VerifyingBaseFallback(LookupBaseFallback): # Mixin for lookups against registries which "chain" upwards, and # whose lookups invalidate their own caches whenever a parent registry # bumps its own '_generation' counter. E.g., used by # zope.component.persistentregistry def changed(self, originally_changed): LookupBaseFallback.changed(self, originally_changed) self._verify_ro = self._registry.ro[1:] self._verify_generations = [r._generation for r in self._verify_ro] def _verify(self): if ([r._generation for r in self._verify_ro] != self._verify_generations): self.changed(None) def _getcache(self, provided, name): self._verify() return LookupBaseFallback._getcache(self, provided, name) def lookupAll(self, required, provided): self._verify() return LookupBaseFallback.lookupAll(self, required, provided) def subscriptions(self, required, provided): self._verify() return LookupBaseFallback.subscriptions(self, required, provided) VerifyingBasePy = VerifyingBaseFallback #BBB try: from ._zope_interface_coptimizations import VerifyingBase except ImportError: VerifyingBase = VerifyingBaseFallback class AdapterLookupBase(object): def __init__(self, registry): self._registry = registry self._required = {} self.init_extendors() super(AdapterLookupBase, self).__init__() def changed(self, ignored=None): super(AdapterLookupBase, self).changed(None) for r in self._required.keys(): r = r() if r is not None: r.unsubscribe(self) self._required.clear() # Extendors # --------- # When given an target interface for an adapter lookup, we need to consider # adapters for interfaces that extend the target interface. This is # what the extendors dictionary is about. It tells us all of the # interfaces that extend an interface for which there are adapters # registered. # We could separate this by order and name, thus reducing the # number of provided interfaces to search at run time. The tradeoff, # however, is that we have to store more information. For example, # if the same interface is provided for multiple names and if the # interface extends many interfaces, we'll have to keep track of # a fair bit of information for each name. It's better to # be space efficient here and be time efficient in the cache # implementation. # TODO: add invalidation when a provided interface changes, in case # the interface's __iro__ has changed. This is unlikely enough that # we'll take our chances for now. def init_extendors(self): self._extendors = {} for p in self._registry._provided: self.add_extendor(p) def add_extendor(self, provided): _extendors = self._extendors for i in provided.__iro__: extendors = _extendors.get(i, ()) _extendors[i] = ( [e for e in extendors if provided.isOrExtends(e)] + [provided] + [e for e in extendors if not provided.isOrExtends(e)] ) def remove_extendor(self, provided): _extendors = self._extendors for i in provided.__iro__: _extendors[i] = [e for e in _extendors.get(i, ()) if e != provided] def _subscribe(self, *required): _refs = self._required for r in required: ref = r.weakref() if ref not in _refs: r.subscribe(self) _refs[ref] = 1 def _uncached_lookup(self, required, provided, name=_BLANK): required = tuple(required) result = None order = len(required) for registry in self._registry.ro: byorder = registry._adapters if order >= len(byorder): continue extendors = registry._v_lookup._extendors.get(provided) if not extendors: continue components = byorder[order] result = _lookup(components, required, extendors, name, 0, order) if result is not None: break self._subscribe(*required) return result def queryMultiAdapter(self, objects, provided, name=_BLANK, default=None): factory = self.lookup(map(providedBy, objects), provided, name) if factory is None: return default result = factory(*objects) if result is None: return default return result def _uncached_lookupAll(self, required, provided): required = tuple(required) order = len(required) result = {} for registry in reversed(self._registry.ro): byorder = registry._adapters if order >= len(byorder): continue extendors = registry._v_lookup._extendors.get(provided) if not extendors: continue components = byorder[order] _lookupAll(components, required, extendors, result, 0, order) self._subscribe(*required) return tuple(result.items()) def names(self, required, provided): return [c[0] for c in self.lookupAll(required, provided)] def _uncached_subscriptions(self, required, provided): required = tuple(required) order = len(required) result = [] for registry in reversed(self._registry.ro): byorder = registry._subscribers if order >= len(byorder): continue if provided is None: extendors = (provided, ) else: extendors = registry._v_lookup._extendors.get(provided) if extendors is None: continue _subscriptions(byorder[order], required, extendors, _BLANK, result, 0, order) self._subscribe(*required) return result def subscribers(self, objects, provided): subscriptions = self.subscriptions(map(providedBy, objects), provided) if provided is None: result = () for subscription in subscriptions: subscription(*objects) else: result = [] for subscription in subscriptions: subscriber = subscription(*objects) if subscriber is not None: result.append(subscriber) return result class AdapterLookup(AdapterLookupBase, LookupBase): pass @implementer(IAdapterRegistry) class AdapterRegistry(BaseAdapterRegistry): LookupClass = AdapterLookup def __init__(self, bases=()): # AdapterRegisties are invalidating registries, so # we need to keep track of out invalidating subregistries. self._v_subregistries = weakref.WeakKeyDictionary() super(AdapterRegistry, self).__init__(bases) def _addSubregistry(self, r): self._v_subregistries[r] = 1 def _removeSubregistry(self, r): if r in self._v_subregistries: del self._v_subregistries[r] def _setBases(self, bases): old = self.__dict__.get('__bases__', ()) for r in old: if r not in bases: r._removeSubregistry(self) for r in bases: if r not in old: r._addSubregistry(self) super(AdapterRegistry, self)._setBases(bases) def changed(self, originally_changed): super(AdapterRegistry, self).changed(originally_changed) for sub in self._v_subregistries.keys(): sub.changed(originally_changed) class VerifyingAdapterLookup(AdapterLookupBase, VerifyingBase): pass @implementer(IAdapterRegistry) class VerifyingAdapterRegistry(BaseAdapterRegistry): LookupClass = VerifyingAdapterLookup def _convert_None_to_Interface(x): if x is None: return Interface else: return x def _lookup(components, specs, provided, name, i, l): if i < l: for spec in specs[i].__sro__: comps = components.get(spec) if comps: r = _lookup(comps, specs, provided, name, i+1, l) if r is not None: return r else: for iface in provided: comps = components.get(iface) if comps: r = comps.get(name) if r is not None: return r return None def _lookupAll(components, specs, provided, result, i, l): if i < l: for spec in reversed(specs[i].__sro__): comps = components.get(spec) if comps: _lookupAll(comps, specs, provided, result, i+1, l) else: for iface in reversed(provided): comps = components.get(iface) if comps: result.update(comps) def _subscriptions(components, specs, provided, name, result, i, l): if i < l: for spec in reversed(specs[i].__sro__): comps = components.get(spec) if comps: _subscriptions(comps, specs, provided, name, result, i+1, l) else: for iface in reversed(provided): comps = components.get(iface) if comps: comps = comps.get(name) if comps: result.extend(comps)