Viewing file: adapter.py (21.53 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (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
$Id: adapter.py 105433 2009-11-02 08:33:25Z ctheune $ """
import weakref from zope.interface import providedBy, Interface, ro
_marker = object 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=()):
# {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 LookupBasePy 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 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=u''): 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 = u'' 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(u'') if not old: return
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[u''] = new else: # Instead of setting components[u''] = 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. if u'' in components: del components[u''] 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, _): class XXXTwistedFakeOut: selfImplied = {} return XXXTwistedFakeOut
_not_in_mapping = object() class LookupBasePy(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=u'', default=None): cache = self._getcache(provided, name) 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=u'', default=None): 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=u'', default=None): return self.adapter_hook(provided, object, name, default)
def adapter_hook(self, provided, object, name=u'', default=None): 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
LookupBase = LookupBasePy
class VerifyingBasePy(LookupBasePy):
def changed(self, originally_changed): LookupBasePy.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 LookupBasePy._getcache(self, provided, name)
def lookupAll(self, required, provided): self._verify() return LookupBasePy.lookupAll(self, required, provided)
def subscriptions(self, required, provided): self._verify() return LookupBasePy.subscriptions(self, required, provided)
VerifyingBase = VerifyingBasePy
try: import _zope_interface_coptimizations except ImportError: pass else: from _zope_interface_coptimizations import LookupBase, VerifyingBase
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, # is 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=u''): 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=u'', 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): 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.iteritems())
def names(self, required, provided): return [c[0] for c in self.lookupAll(required, provided)]
def _uncached_subscriptions(self, required, provided): 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, u'', 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
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
class VerifyingAdapterRegistry(BaseAdapterRegistry):
LookupClass = VerifyingAdapterLookup
def _convert_None_to_Interface(x): if x is None: return Interface else: return x
def _normalize_name(name): if isinstance(name, basestring): return unicode(name)
raise TypeError("name must be a regular or unicode string")
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)
|