diff options
| author | Nathan Yergler <nathan@yergler.net> | 2006-02-27 17:35:32 +0000 |
|---|---|---|
| committer | Nathan Yergler <nathan@yergler.net> | 2006-02-27 17:35:32 +0000 |
| commit | 25d0e7963ffb5052a5a24e7173b8cba1ee3df64e (patch) | |
| tree | 9696cd604c80b9c8b889fa2bf6db6c07ce2e8eed /src/zope/interface/adapter.py | |
| parent | 16f036c6217eefff009c2135f36ea291910b1416 (diff) | |
| download | zope-interface-25d0e7963ffb5052a5a24e7173b8cba1ee3df64e.tar.gz | |
Correcting borked move.
Diffstat (limited to 'src/zope/interface/adapter.py')
| -rw-r--r-- | src/zope/interface/adapter.py | 744 |
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) |
