summaryrefslogtreecommitdiff
path: root/src/zope/interface/adapter.py
diff options
context:
space:
mode:
authorNathan Yergler <nathan@yergler.net>2006-02-27 17:35:32 +0000
committerNathan Yergler <nathan@yergler.net>2006-02-27 17:35:32 +0000
commit25d0e7963ffb5052a5a24e7173b8cba1ee3df64e (patch)
tree9696cd604c80b9c8b889fa2bf6db6c07ce2e8eed /src/zope/interface/adapter.py
parent16f036c6217eefff009c2135f36ea291910b1416 (diff)
downloadzope-interface-25d0e7963ffb5052a5a24e7173b8cba1ee3df64e.tar.gz
Correcting borked move.
Diffstat (limited to 'src/zope/interface/adapter.py')
-rw-r--r--src/zope/interface/adapter.py744
1 files changed, 744 insertions, 0 deletions
diff --git a/src/zope/interface/adapter.py b/src/zope/interface/adapter.py
new file mode 100644
index 0000000..45d760c
--- /dev/null
+++ b/src/zope/interface/adapter.py
@@ -0,0 +1,744 @@
+############################################################################
+#
+# Copyright (c) 2002 Zope Corporation 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-style interface registry
+
+This implementation is based on a notion of "surrogate" interfaces.
+
+$Id$
+"""
+
+# Implementation notes
+
+# We keep a collection of surrogates.
+
+# A surrogate is a surrogate for a specification (interface or
+# declaration). We use weak references in order to remove surrogates
+# if the corresponding specification goes away.
+
+# Each surrogate keeps track of:
+
+# - The adapters registered directly for that surrogate, and
+
+# - The "implied" adapters, which is the adapters that can be computed
+# from instances of that surrogate.
+
+# The later data structure takes into account adapters registered for
+# specifications that the registered surrogate extends.
+
+# The registrations are of the form:
+
+# {(subscription, with, name, specification) -> factories}
+
+# where:
+
+# 'subscription' is a flag indicating if this registration is for
+# subscription adapters.
+
+# 'with' is a tuple of specs that is non-empty only in the case
+# of multi-adapters.
+
+# 'name' is a unicode adapter name. Unnamed adapters have an empty
+# name.
+
+# 'specification' is the interface being adapted to, the provided interface.
+
+# 'factories' is normally a tuple of factories, but can be anything.
+# (See the "raw" option to the query-adapter calls.) For subscription
+# adapters, it is a tuple of tuples of factories.
+
+# The implied adapters are held in a single dictionary. The items in the
+# dictionary are of several forms:
+
+# For single adapters:
+#
+# {specification -> {name -> object}
+#
+# where object is usually a sequence of factories
+
+# For multiple adapters:
+#
+# {(specification, order) -> {name -> {with -> object}}}
+
+# For single subscription adapters:
+#
+# {('s', specification) -> tuple([object])}
+
+# For multiple-subscription adapters:
+#
+# {('s', specification, order) -> {with -> tuple([object])}}
+
+
+from __future__ import generators
+
+import weakref
+from zope.interface.ro import ro
+from zope.interface.declarations import providedBy
+from zope.interface.interface import InterfaceClass, Interface
+
+Default = InterfaceClass("Default", (), {})
+Null = InterfaceClass("Null", (), {})
+
+# 2.2 backwards compatability
+try:
+ enumerate
+except NameError:
+ def enumerate(l):
+ i = 0
+ for o in l:
+ yield i, o
+ i += 1
+try:
+ basestring
+except NameError:
+ basestring = (str, unicode)
+
+
+class ReadProperty(object):
+
+ def __init__(self, func):
+ self.func = func
+
+ def __get__(self, inst, class_):
+ if inst is None:
+ return self
+ return self.func(inst)
+
+class Surrogate(object):
+ """Specification surrogate
+
+ A specification surrogate is used to hold adapter registrations on
+ behalf of a specification.
+ """
+
+ def __init__(self, spec, registry):
+ self.spec = spec.weakref()
+ self.registry = registry
+ spec.subscribe(self)
+ self.adapters = {}
+ self.dependents = weakref.WeakKeyDictionary()
+
+ self.registry = registry
+ self.__bases__ = [registry.get(base) for base in spec.__bases__]
+ for base in self.__bases__:
+ base.subscribe(self)
+
+ def dirty(self):
+ if 'get' in self.__dict__:
+ # Not already dirty
+ del self.selfImplied
+ del self.multImplied
+ del self.get
+
+ bases = [self.registry.get(base) for base in self.spec().__bases__]
+ if bases != self.__bases__:
+ # Our bases changed. unsubscribe from the old ones
+ # and subscribe to the new ones
+ for base in self.__bases__:
+ base.unsubscribe(self)
+
+ self.__bases__ = bases
+ for base in bases:
+ base.subscribe(self)
+
+ for dependent in self.dependents.keys():
+ dependent.dirty()
+
+ def clean(self):
+ for base in self.__bases__:
+ base.unsubscribe(self)
+ self.__bases__ = [self.registry.get(base)
+ for base in self.spec().__bases__]
+ for base in self.__bases__:
+ base.subscribe(self)
+
+ self.selfImplied, self.multImplied = adapterImplied(self.adapters)
+
+ implied = {}
+
+ ancestors = ro(self)
+
+ # Collect implied data in reverse order to have more specific data
+ # override less-specific data.
+ ancestors.reverse()
+ for ancestor in ancestors:
+ ancestor_spec = ancestor.spec()
+
+ for key, v in ancestor.selfImplied.iteritems():
+
+ # key is specification or ('s', specification)
+ subscription = isinstance(key, tuple) and key[0] == 's'
+ if subscription:
+ # v is tuple of subs
+ implied[key] = implied.get(key, ()) + v
+ else:
+ oldbyname = implied.get(key)
+ if not oldbyname:
+ implied[key] = oldbyname = {}
+
+ # v is name -> object
+ oldbyname.update(v)
+
+ for key, v in ancestor.multImplied.iteritems():
+ # key is (specification, order)
+ # or ('s', specification, order)
+ subscription = key[0] == 's'
+ if subscription:
+ oldwithobs = implied.get(key)
+ if not oldwithobs:
+ oldwithobs = implied[key] = {}
+
+ # v is {with -> tuple([object])}
+ for with, objects in v.iteritems():
+ oldwithobs[with] = oldwithobs.get(with, ()) + objects
+
+ else:
+ oldbyname = implied.get(key)
+ if not oldbyname:
+ implied[key] = oldbyname = {}
+
+
+ # v is {name -> {with -> ?}}
+ for name, withobs in v.iteritems():
+
+ # withobs is {with -> value}
+
+ # If ancestor is not the default,
+ # add in ancestor so we can get ordering right
+ if ancestor_spec is not Default:
+ withobs = dict([
+ (((ancestor_spec,) + with), value)
+ for (with, value) in withobs.iteritems()
+ ])
+
+ oldwithobs = oldbyname.get(name)
+ if not oldwithobs:
+ oldwithobs = oldbyname[name] = {}
+
+ # withobs is {with -> object}
+ oldwithobs.update(withobs)
+
+ # Now flatten with mappings to tuples
+ for key, v in implied.iteritems():
+ if isinstance(key, tuple):
+ if key[0] == 's':
+ # subscriptions
+ if isinstance(v, dict):
+ implied[key] = v.items()
+ else:
+ byname = v
+ for name, value in byname.iteritems():
+ if isinstance(value, dict):
+ # We have {with -> value}
+ # convert it to [(with, value]
+ byname[name] = value.items()
+
+ self.get = implied.get
+
+ def get(self, key):
+ """Get an implied value
+
+ This is only called when the surrogate is dirty
+ """
+ self.clean()
+ return self.__dict__['get'](key)
+
+ def selfImplied(self):
+ """Return selfImplied when dirty
+ """
+ self.clean()
+ return self.__dict__['selfImplied']
+ selfImplied = ReadProperty(selfImplied)
+
+ def multiImplied(self):
+ """Return _multiImplied when dirty
+ """
+ self.clean()
+ return self.__dict__['multiImplied']
+ multiImplied = ReadProperty(multiImplied)
+
+ def subscribe(self, dependent):
+ self.dependents[dependent] = self.dependents.get(dependent, 0) + 1
+
+ def unsubscribe(self, dependent):
+ n = self.dependents.get(dependent, 0) - 1
+ if not n:
+ del self.dependents[dependent]
+ elif n > 0:
+ self.dependents[dependent] = n
+ else:
+ raise KeyError(dependent)
+
+ def _adaptTo(self, specification, object, name='', with=()):
+ if object is None:
+ try:
+ del self.adapters[False, tuple(with), name, specification]
+ except KeyError:
+ pass
+ else:
+ self.adapters[False, tuple(with), name, specification
+ ] = object
+
+ self.dirty()
+
+ def _subscriptionAdaptTo(self, specification, object, with=()):
+ if object is None:
+ raise TypeError("Unregistering subscription adapters isn't "
+ "implemented")
+
+ key = (True, tuple(with), '', specification)
+ self.adapters[key] = self.adapters.get(key, ()) + (object, )
+ self.dirty()
+
+ def changed(self, which=None):
+ self.dirty()
+
+ def __repr__(self):
+ return '<%s(%s)>' % (self.__class__.__name__, self.spec())
+
+
+class AdapterLookup(object):
+ # Adapter lookup support
+ # We have a class here because we want to provide very
+ # fast lookup support in C and making this part of the adapter
+ # registry itself would provide problems if someone wanted
+ # persistent adapter registries, because we want C slots for fast
+ # lookup that would clash with persistence-supplied slots.
+ # so this class acts a little bit like a lookup adapter for the adapter
+ # registry.
+
+ def __init__(self, registry, surrogates, _remove):
+ self._registry = registry
+ self._surrogateClass = registry._surrogateClass
+ self._default = registry._default
+ self._null = registry._null
+ self._surrogates = surrogates
+ self._remove = _remove
+
+ def lookup(self, required, provided, name='', default=None):
+ order = len(required)
+ if order == 1:
+ # Simple adapter:
+ s = self.get(required[0])
+ byname = s.get(provided)
+ if byname:
+ value = byname.get(name)
+ else:
+ value = None
+
+ if value is None:
+ byname = self._default.get(provided)
+ if byname:
+ value = byname.get(name, default)
+ else:
+ return default
+
+ return value
+
+ elif order == 0:
+ # null adapter
+ byname = self._null.get(provided)
+ if byname:
+ return byname.get(name, default)
+ else:
+ return default
+
+ # Multi adapter
+
+ with = required
+ key = provided, order
+
+ for surrogate in self.get(required[0]), self._default:
+ byname = surrogate.get(key)
+ if byname:
+ bywith = byname.get(name)
+ if bywith:
+ # Selecting multi-adapters is not just a matter of
+ # matching the required interfaces of the adapter
+ # to the ones passed. Several adapters might
+ # match, but we only want the best one. We use a
+ # ranking algorithm to determine the best match.
+ # `best` carries the rank and value of the best
+ # found adapter.
+ best = None
+ for rwith, value in bywith:
+ # the `rank` describes how well the found
+ # adapter matches.
+ rank = []
+ for rspec, spec in zip(rwith, with):
+ if not spec.isOrExtends(rspec):
+ break # This one is no good
+
+ # Determine the rank of this particular
+ # specification.
+ rank.append(list(spec.__sro__).index(rspec))
+ else:
+ # If the new rank is better than the best
+ # previously recorded one, make the new
+ # adapter the best one found.
+ rank = tuple(rank)
+ if best is None or rank < best[0]:
+ best = rank, value
+ # If any match was found, return the best one.
+ if best:
+ return best[1]
+
+ with = with[1:] # on second pass through, don't use first spec
+
+ return default
+
+ def lookup1(self, required, provided, name='', default=None):
+ return self.lookup((required,), provided, name, default)
+
+ def adapter_hook(self, interface, object, name='', default=None):
+ """Hook function used when calling interfaces.
+
+ When called from Interface.__adapt__, only the interface and
+ object parameters will be passed.
+
+ If the factory produces `None`, then the default is returned. This
+ allows us to prevent adaptation (if desired) and make the factory
+ decide whether an adapter will be available.
+ """
+ factory = self.lookup1(providedBy(object), interface, name)
+ if factory is not None:
+ adapter = factory(object)
+ if adapter is not None:
+ return adapter
+ return default
+
+ def queryAdapter(self, object, interface, name='', default=None):
+ # Note that we rarely call queryAdapter directly
+ # We usually end up calling adapter_hook
+ return self.adapter_hook(interface, object, name, default)
+
+ def subscriptions(self, required, provided):
+ if provided is None:
+ provided = Null
+
+ order = len(required)
+ if order == 1:
+ # Simple subscriptions:
+ s = self.get(required[0])
+ result = s.get(('s', provided))
+ if result:
+ result = list(result)
+ else:
+ result = []
+
+ default = self._default.get(('s', provided))
+ if default:
+ result.extend(default)
+
+ return result
+
+ elif order == 0:
+ result = self._null.get(('s', provided))
+ if result:
+ return list(result)
+ else:
+ return []
+
+ # Multi
+ key = 's', provided, order
+ with = required[1:]
+ result = []
+
+ for surrogate in self.get(required[0]), self._default:
+ bywith = surrogate.get(key)
+ if not bywith:
+ continue
+
+ for rwith, values in bywith:
+ for rspec, spec in zip(rwith, with):
+ if not spec.isOrExtends(rspec):
+ break # This one is no good
+ else:
+ # we didn't break, so we have a match
+ result.extend(values)
+
+ return result
+
+ def queryMultiAdapter(self, objects, interface, name='', default=None):
+ factory = self.lookup(map(providedBy, objects), interface, name)
+ if factory is not None:
+ return factory(*objects)
+
+ return default
+
+ def subscribers(self, objects, interface):
+ subscriptions = self.subscriptions(map(providedBy, objects), interface)
+ subscribers = [subscription(*objects)
+ for subscription in subscriptions]
+ # Filter None values
+ return [x for x in subscribers if x is not None]
+
+ def get(self, declaration):
+ if declaration is None:
+ return self._default
+
+ ref = declaration.weakref(self._remove)
+ surrogate = self._surrogates.get(ref)
+ if surrogate is None:
+ surrogate = self._surrogateClass(declaration, self._registry)
+ self._surrogates[ref] = surrogate
+
+ return surrogate
+
+
+class AdapterRegistry(object):
+ """Adapter registry
+ """
+
+ # Implementation note:
+ # We are like a weakref dict ourselves. We can't use a weakref
+ # dict because we have to use spec.weakref() rather than
+ # weakref.ref(spec) to get weak refs to specs.
+
+ _surrogateClass = Surrogate
+
+ def __init__(self):
+ default = self._surrogateClass(Default, self)
+ self._default = default
+ null = self._surrogateClass(Null, self)
+ self._null = null
+
+ # Create separate lookup object and copy it's methods
+ surrogates = {Default.weakref(): default, Null.weakref(): null}
+ def _remove(k):
+ try:
+ del surrogates[k]
+ except KeyError:
+ pass
+ lookup = AdapterLookup(self, surrogates, _remove)
+
+ for name in ('lookup', 'lookup1', 'queryAdapter', 'get',
+ 'adapter_hook', 'subscriptions',
+ 'queryMultiAdapter', 'subscribers',
+ ):
+ setattr(self, name, getattr(lookup, name))
+
+ def register(self, required, provided, name, value):
+ if required:
+ with = []
+ for iface in required[1:]:
+ if iface is None:
+ iface = Interface
+ with.append(iface)
+ with = tuple(with)
+ required = self.get(required[0])
+ else:
+ with = ()
+ required = self._null
+
+ if not isinstance(name, basestring):
+ raise TypeError("The name provided to provideAdapter "
+ "must be a string or unicode")
+
+ required._adaptTo(provided, value, unicode(name), with)
+
+ def lookupAll(self, required, provided):
+ order = len(required)
+ if order == 1:
+ # Simple adapter:
+ s = self.get(required[0])
+ byname = s.get(provided)
+ if byname:
+ for item in byname.iteritems():
+ yield item
+
+ defbyname = self._default.get(provided)
+ if defbyname:
+ for name, value in defbyname.iteritems():
+ if name in byname:
+ continue
+ yield name, value
+
+ return
+
+ elif order == 0:
+ # null adapter
+ byname = self._null.get(provided)
+ if byname:
+ for item in byname.iteritems():
+ yield item
+
+ return
+
+
+ # Multi adapter
+
+ with = required
+ key = provided, order
+ first = ()
+
+ for surrogate in self.get(required[0]), self._default:
+ byname = surrogate.get(key)
+ if byname:
+ for name, bywith in byname.iteritems():
+ if not bywith or name in first:
+ continue
+
+ # See comments on lookup() above
+ best = None
+ for rwith, value in bywith:
+ # the `rank` describes how well the found
+ # adapter matches.
+ rank = []
+ for rspec, spec in zip(rwith, with):
+ if not spec.isOrExtends(rspec):
+ break # This one is no good
+
+ # Determine the rank of this particular
+ # specification.
+ rank.append(list(spec.__sro__).index(rspec))
+ else:
+ # If the new rank is better than the best
+ # previously recorded one, make the new
+ # adapter the best one found.
+ rank = tuple(rank)
+ if best is None or rank < best[0]:
+ best = rank, value
+
+ # If any match was found, return the best one.
+ if best:
+ yield name, best[1]
+
+ first = byname
+
+ with = with[1:] # on second pass through, don't use first spec
+
+ def subscribe(self, required, provided, value):
+ if required:
+ required, with = self.get(required[0]), tuple(required[1:])
+ else:
+ required = self._null
+ with = ()
+
+ if provided is None:
+ provided = Null
+
+ required._subscriptionAdaptTo(provided, value, with)
+
+def mextends(with, rwith):
+ if len(with) == len(rwith):
+ for w, r in zip(with, rwith):
+ if not w.isOrExtends(r):
+ break
+ else:
+ return True
+ return False
+
+def adapterImplied(adapters):
+ implied = {}
+ multi = {}
+
+ # This dictionary is used to catch situations specific adapters
+ # override less specific adapters.
+ # Because subscriptions are cumulative, registered doesn't apply.
+ registered = {}
+
+ # Add adapters and interfaces directly implied by same:
+
+ for key, value in adapters.iteritems():
+
+ # TODO: Backward compatibility
+ # BBB ? Don't need to handle 3-tuples some day
+ try:
+ (subscription, with, name, target) = key
+ except ValueError:
+ (with, name, target) = key
+ subscription = False
+
+ if subscription:
+ if with:
+ _add_multi_sub_adapter(with, target, multi, value)
+ else:
+ _add_named_sub_adapter(target, implied, value)
+ else:
+ if with:
+ _add_multi_adapter(with, name, target, target, multi,
+ registered, value)
+ else:
+ _add_named_adapter(target, target, name, implied,
+ registered, value)
+
+ return implied, multi
+
+def _add_named_adapter(target, provided, name, implied,
+ registered, value):
+
+ ikey = target
+ rkey = target, name
+
+ byname = implied.get(ikey)
+ if not byname:
+ byname = implied[ikey] = {}
+
+ if (name not in byname
+ or
+ (rkey in registered and registered[rkey].extends(provided))
+ ):
+
+ registered[rkey] = provided
+ byname[name] = value
+
+ for b in target.__bases__:
+ _add_named_adapter(b, provided, name, implied,
+ registered, value)
+
+def _add_multi_adapter(with, name, target, provided, implied,
+ registered, object):
+
+ ikey = target, (len(with) + 1)
+ byname = implied.get(ikey)
+ if not byname:
+ byname = implied[ikey] = {}
+
+ bywith = byname.get(name)
+ if not bywith:
+ bywith = byname[name] = {}
+
+
+ rkey = ikey, name, with # The full key has all 4
+ if (with not in bywith
+ or
+ (rkey not in registered or registered[rkey].extends(provided))
+ ):
+ # This is either a new entry or it is an entry for a more
+ # general interface that is closer provided than what we had
+ # before
+ registered[rkey] = provided
+ bywith[with] = object
+
+ for b in target.__bases__:
+ _add_multi_adapter(with, name, b, provided, implied,
+ registered, object)
+
+def _add_named_sub_adapter(target, implied, objects):
+ key = ('s', target)
+ implied[key] = implied.get(key, ()) + objects
+
+ for b in target.__bases__:
+ _add_named_sub_adapter(b, implied, objects)
+
+def _add_multi_sub_adapter(with, target, implied, objects):
+ key = 's', target, (len(with) + 1)
+ bywith = implied.get(key)
+ if not bywith:
+ bywith = implied[key] = {}
+
+ bywith[with] = bywith.get(with, ()) + objects
+
+ for b in target.__bases__:
+ _add_multi_sub_adapter(with, b, implied, objects)