[Zope3-checkins] CVS: Zope3/src/zope/interface - surrogate.py:1.2

Jim Fulton cvs-admin at zope.org
Fri Nov 21 12:11:46 EST 2003


Update of /cvs-repository/Zope3/src/zope/interface
In directory cvs.zope.org:/tmp/cvs-serv31839/src/zope/interface

Added Files:
	surrogate.py 
Log Message:
Created a new model for managing adapters.  The idea is that we manage
objects that are (sort of) stand-ins for specifications (interfaces or
declarations) that keep adapter info.


=== Zope3/src/zope/interface/surrogate.py 1.1 => 1.2 ===
--- /dev/null	Fri Nov 21 12:11:46 2003
+++ Zope3/src/zope/interface/surrogate.py	Fri Nov 21 12:11:46 2003
@@ -0,0 +1,734 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation 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.
+#
+##############################################################################
+"""Surrogate-specification registry implementation
+
+$Id$
+"""
+
+# Implementation notes
+
+# We keep a collection of surrogates.
+
+# A surrogate is a surrogate for a specification (interface or
+# declaration).  We use week 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:
+
+#   {(with, name, specification) -> factories}
+
+# where:
+
+#   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.
+
+#   factories is normally a tuple of factories, but can be anything.
+#   (See the "raw" option to the query-adapter calls.)
+
+# The implied adapters are held in a single dictionary. The items in the
+# dictionary are of 3 forms:
+
+#   specification -> factories
+
+#      for simple unnamed adapters
+
+#   (specification, name) -> factories
+
+#      for named adapters
+
+#   (specification, name, order) -> {with -> factories}
+
+#      for multi-adapters.  
+
+
+from __future__ import generators
+import weakref
+from zope.interface.ro import ro
+from zope.interface.declarations import providedBy
+from zope.interface.interface import InterfaceClass
+
+Default = InterfaceClass("Default", (), {})
+
+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()
+        spec.subscribe(self)
+        self.adapters = {}
+        self.dependents = weakref.WeakKeyDictionary()
+
+        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
+        for dependent in self.dependents.keys():
+            dependent.dirty()
+
+    def clean(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:
+            implied.update(ancestor.selfImplied)
+            for k, ancestor_adapters in ancestor.multImplied.iteritems():
+                implied_adapters = implied.get(k)
+                if implied_adapters:
+                    implied_adapters.update(ancestor_adapters)
+                else:
+                    implied[k] = ancestor_adapters.copy()
+
+        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] = 1
+
+    def unsubscribe(self, dependent):
+        del self.dependents[dependent]
+
+    def _adaptTo(self, specification, factories, name='', with=()):
+        if factories is None:
+            try:
+                del self.adapters[with, name, specification]
+            except KeyError:
+                pass
+        else:
+            self.adapters[tuple(with), name, specification] = factories
+
+        self.dirty()
+
+    def changed(self, which=None):
+        self.dirty()
+
+    def __repr__(self):
+        return '<%s(%s)>' % (self.__class__.__name__, self.spec())
+
+class SurrogateRegistry(object):
+    """Surrogate registry
+    """
+
+    # Implementation node:
+    # 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
+        surrogates = {Default.weakref(): default}
+        self._surrogates = surrogates
+
+        def _remove(k):
+            try:
+                del surrogates[k]
+            except KeyError:
+                pass
+
+        self._remove = _remove
+
+    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)
+            self._surrogates[ref] = surrogate
+
+        return surrogate
+
+
+    def provideAdapter(self, required, provided, factories, name=u'', with=()):
+        """Register an adapter
+
+        Note that the given name must be convertable to unicode.
+        Use an empty string for unnamed adapters. It is impossible to
+        have a named adapter with an empty name.
+        """
+        required = self.get(required)
+        if with:
+            with = tuple(with)
+        else:
+            with = ()
+
+        if not isinstance(name, basestring):
+            raise TypeError("The name provided to provideAdapter "
+                            "must be a string or unicode")
+        required._adaptTo(provided, factories, unicode(name), with)
+
+    def queryAdapter(self, ob, interface, default=None, raw=False):
+        """Query a simple adapter
+
+           >>> import zope.interface
+           >>> class F0(zope.interface.Interface):
+           ...     pass
+           >>> class F1(F0):
+           ...     pass
+
+           >>> class C:
+           ...     zope.interface.implements(F1)
+           >>> c = C()
+
+           >>> registry = SurrogateRegistry()
+
+           Adapting to some other interface for which there is no
+           adapter returns the default:
+
+           >>> class B0(zope.interface.Interface):
+           ...     pass
+           >>> class B1(B0):
+           ...     pass
+
+           >>> registry.queryAdapter(c, B0)
+           >>> registry.queryAdapter(c, B0, 42)
+           42
+
+           Unless we define an adapter:
+
+           >>> def f1(ob):
+           ...     return 1
+
+           >>> registry.provideAdapter(F0, B1, [f1])
+           >>> registry.queryAdapter(c, B0)
+           1
+
+           If we define a more specific adapter (for F1), we'll get that:
+
+           >>> def f2(ob):
+           ...     return 2
+
+           >>> registry.provideAdapter(F1, B1, [f2])
+           >>> registry.queryAdapter(c, B0)
+           2
+
+           >>> def f3(ob):
+           ...     return 3
+
+           >>> registry.provideAdapter(F1, B0, [f3])
+           >>> registry.queryAdapter(c, B0)
+           3
+           """
+
+        declaration = providedBy(ob)
+        s = self.get(declaration)
+
+        factories = s.get(interface)
+        if factories is None:
+            factories = self._default.get(interface)
+
+        if factories is not None:
+            if raw:
+                return factories
+            
+            for factory in factories:
+                ob = factory(ob)
+            return ob
+
+        return default
+
+    def queryNamedAdapter(self, ob, interface, name, default=None, raw=False):
+        """Query a named simple adapter
+
+        >>> import zope.interface
+        >>> class F0(zope.interface.Interface):
+        ...     pass
+        >>> class F1(F0):
+        ...     pass
+
+        >>> class C:
+        ...     zope.interface.implements(F1)
+        >>> c = C()
+
+        >>> registry = SurrogateRegistry()
+
+        If we ask for a named adapter, we won't get a result unless there
+        is a named adapter, even if the object implements the interface:
+
+        >>> registry.queryNamedAdapter(c, F0, 'bob')
+            
+        >>> class B0(zope.interface.Interface):
+        ...     pass
+        >>> class B1(B0):
+        ...     pass
+
+
+        >>> def f1(ob):
+        ...     return 1
+
+        >>> registry.provideAdapter(F0, B1, [f1], name='bob')
+        >>> registry.queryNamedAdapter(c, B0, 'bob')
+        1
+        >>> registry.queryNamedAdapter(c, B0, 'bruce')
+        
+
+        >>> def f2(ob):
+        ...     return 2
+
+        >>> registry.provideAdapter(F1, B1, [f2], name='bob')
+        >>> registry.queryNamedAdapter(c, B0, 'bob')
+        2
+        
+        >>> def f3(ob):
+        ...     return 3
+
+        >>> registry.provideAdapter(F1, B0, [f3], name='bob')
+        >>> registry.queryNamedAdapter(c, B0, 'bob')
+        3
+
+        """
+
+        declaration = providedBy(ob)
+        s = self.get(declaration)
+        key = name and (interface, name) or interface
+        factories = s.get(key)
+        if factories is None:
+            factories = self._default.get(key)
+        if factories is not None:
+            if raw:
+                return factories
+            for factory in factories:
+                ob = factory(ob)
+            return ob
+
+        return default
+
+    def queryMultiAdapter(self, objects, interface, name=u'',
+                          default=None, raw=False):
+        """
+
+        >>> import zope.interface
+        >>> class IF0(zope.interface.Interface):
+        ...     pass
+        >>> class IF1(IF0):
+        ...     pass
+
+        >>> class IR0(zope.interface.Interface):
+        ...     pass
+        >>> class IR1(IR0):
+        ...     pass
+
+        >>> class F1:
+        ...     zope.interface.implements(IF1)
+        >>> c = F1()
+
+        >>> class R1:
+        ...     zope.interface.implements(IR1)
+        >>> r = R1()
+
+        >>> registry = SurrogateRegistry()
+
+        If we ask for a multi adapter, we won't get a result unless there
+        is a named adapter, even if the object implements the interface:
+
+        >>> registry.queryMultiAdapter((c, r), IF0, 'bob')
+
+        >>> class IB0(zope.interface.Interface):
+        ...     pass
+        >>> class IB1(IB0):
+        ...     pass
+
+
+        >>> class f1:
+        ...     def __init__(self, x, y):
+        ...         self.x, self.y = x, y
+
+
+        >>> registry.provideAdapter(IF0, IB1, [f1], name='bob', with=[IR0])
+        >>> a = registry.queryMultiAdapter((c, r), IB0, 'bob')
+        >>> a.__class__ is f1
+        True
+        >>> a.x is c
+        True
+        >>> a.y is r
+        True
+
+        >>> registry.queryMultiAdapter((c, r), IB0, 'bruce')
+
+        >>> class f2(f1):
+        ...     pass
+
+        >>> registry.provideAdapter(IF1, IB1, [f2], name='bob', with=[IR1])
+        >>> a = registry.queryMultiAdapter((c, r), IB0, 'bob')
+        >>> a.__class__ is f2
+        True
+        >>> a.x is c
+        True
+        >>> a.y is r
+        True
+        
+        """
+        ob = objects[0]
+        order = len(objects)
+        obs = objects[1:]
+
+        declaration = providedBy(ob)
+        surrogate = self.get(declaration)
+
+        while 1:
+            adapters = surrogate.get((interface, name, order))
+            if adapters:
+                matched = None
+                matched_factories = None
+                for interfaces, factories in adapters.iteritems():
+                    for iface, ob in zip(interfaces, obs):
+                        if not iface.isImplementedBy(ob):
+                            break # This one is no good
+                    else:
+                        # we didn't break, so we have a match
+                        if matched is None:
+                            matched = interfaces
+                            matched_factories = factories
+                        else:
+                            # see if the new match is better than the old one:
+                            for iface, m in zip(interfaces, matched):
+                                if iface.extends(m):
+                                    # new is better than old
+                                    matched = interfaces
+                                    matched_factories = factories
+                                    break
+                                elif m.extends(iface):
+                                    # old is better than new
+                                    break
+                                
+
+                if matched_factories is not None:
+                    if raw:
+                        return matched_factories
+
+                    assert len(matched_factories) == 1
+
+                    return matched_factories[0](*objects)
+
+            # Fall back to default if we haven't already
+            if surrogate is self._default:
+                return default
+            surrogate = self._default
+
+    def getRegisteredMatching(self,
+                              required=None,
+                              provided=None,
+                              name=None,
+                              with=None,
+                              ):
+        """Search for registered adapters
+
+           Return a 5-tuple with:
+
+           - (first) required interface
+
+           - provided interface
+
+           - a tuple of additional required interfaces (for multi-adapters)
+
+           - name, and
+
+           - a sequence of factories. (Note that this could be arbitrary data).
+           
+
+           Note, this is usually slow!
+
+           >>> from zope.interface import Interface
+
+           >>> class R1(Interface):
+           ...     pass
+           >>> class R12(Interface):
+           ...     pass
+           >>> class R2(R1):
+           ...     pass
+           >>> class R3(R2):
+           ...     pass
+           >>> class R4(R3):
+           ...     pass
+
+           >>> class P1(Interface):
+           ...     pass
+           >>> class P2(P1):
+           ...     pass
+           >>> class P3(P2):
+           ...     pass
+           >>> class P4(P3):
+           ...     pass
+
+
+           >>> registry = SurrogateRegistry()
+           >>> registry.provideAdapter(None, P3, 'default P3')
+           >>> registry.provideAdapter(Interface, P3, 'any P3')
+           >>> registry.provideAdapter(R2, P3, 'R2 P3')
+           >>> registry.provideAdapter(R2, P3, "bobs R2 P3", name='bob')
+
+           >>> from pprint import PrettyPrinter
+           >>> pprint = PrettyPrinter(width=60).pprint
+           >>> def sorted(x):
+           ...    x = [(getattr(r, '__name__', None), p.__name__, w, n, f)
+           ...         for (r, p, w, n, f) in x]
+           ...    x.sort()
+           ...    pprint(x)
+
+           >>> sorted(registry.getRegisteredMatching())
+           [(None, 'P3', (), u'', 'default P3'),
+            ('Interface', 'P3', (), u'', 'any P3'),
+            ('R2', 'P3', (), u'', 'R2 P3'),
+            ('R2', 'P3', (), u'bob', 'bobs R2 P3')]
+
+           >>> sorted(registry.getRegisteredMatching(name=''))
+           [(None, 'P3', (), u'', 'default P3'),
+            ('Interface', 'P3', (), u'', 'any P3'),
+            ('R2', 'P3', (), u'', 'R2 P3')]
+
+           >>> sorted(registry.getRegisteredMatching(required=[R1]))
+           [(None, 'P3', (), u'', 'default P3'),
+            ('Interface', 'P3', (), u'', 'any P3')]
+
+           >>> sorted(registry.getRegisteredMatching(required=R1))
+           [(None, 'P3', (), u'', 'default P3'),
+            ('Interface', 'P3', (), u'', 'any P3')]
+
+           >>> sorted(registry.getRegisteredMatching(provided=[P1]))
+           [(None, 'P3', (), u'', 'default P3'),
+            ('Interface', 'P3', (), u'', 'any P3'),
+            ('R2', 'P3', (), u'', 'R2 P3'),
+            ('R2', 'P3', (), u'bob', 'bobs R2 P3')]
+
+           >>> sorted(registry.getRegisteredMatching(provided=P1))
+           [(None, 'P3', (), u'', 'default P3'),
+            ('Interface', 'P3', (), u'', 'any P3'),
+            ('R2', 'P3', (), u'', 'R2 P3'),
+            ('R2', 'P3', (), u'bob', 'bobs R2 P3')]
+
+           >>> sorted(registry.getRegisteredMatching(provided=P3))
+           [(None, 'P3', (), u'', 'default P3'),
+            ('Interface', 'P3', (), u'', 'any P3'),
+            ('R2', 'P3', (), u'', 'R2 P3'),
+            ('R2', 'P3', (), u'bob', 'bobs R2 P3')]
+
+           >>> sorted(registry.getRegisteredMatching(
+           ...     required = (R4, R12),
+           ...     provided = (P1, )))
+           [(None, 'P3', (), u'', 'default P3'),
+            ('Interface', 'P3', (), u'', 'any P3'),
+            ('R2', 'P3', (), u'', 'R2 P3'),
+            ('R2', 'P3', (), u'bob', 'bobs R2 P3')]
+
+           >>> sorted(registry.getRegisteredMatching(
+           ...     required = (R4, R12),
+           ...     provided = (P3, )))
+           [(None, 'P3', (), u'', 'default P3'),
+            ('Interface', 'P3', (), u'', 'any P3'),
+            ('R2', 'P3', (), u'', 'R2 P3'),
+            ('R2', 'P3', (), u'bob', 'bobs R2 P3')]
+
+           >>> sorted(registry.getRegisteredMatching(
+           ...     required = (R2, ),
+           ...     provided = (P3, )))
+           [(None, 'P3', (), u'', 'default P3'),
+            ('Interface', 'P3', (), u'', 'any P3'),
+            ('R2', 'P3', (), u'', 'R2 P3'),
+            ('R2', 'P3', (), u'bob', 'bobs R2 P3')]
+
+           >>> sorted(registry.getRegisteredMatching(
+           ...     required = (R2, ),
+           ...     provided = (P3, ),
+           ...     name='bob'))
+           [('R2', 'P3', (), u'bob', 'bobs R2 P3')]
+
+           >>> sorted(registry.getRegisteredMatching(
+           ...     required = (R3, ),
+           ...     provided = (P1, ),
+           ...     name='bob'))
+           [('R2', 'P3', (), u'bob', 'bobs R2 P3')]
+
+           """
+
+        if name is not None:
+            name = unicode(name)
+        
+        if isinstance(required, InterfaceClass):
+            required = (required, )
+        elif required is None:
+            required = [ref() for ref in self._surrogates.keys()
+                                   if ref() is not None]
+
+        required = tuple(required)+(None,)
+
+        if isinstance(provided, InterfaceClass):
+            provided = (provided, )
+
+
+        seen = {}
+
+        for required in required:
+            s = self.get(required)
+            for ancestor in ro(s):
+                if ancestor in seen:
+                    continue
+                seen[ancestor] = 1
+                adapters = ancestor.adapters
+                if adapters:
+                    items = adapters.iteritems()
+                    ancestor = ancestor.spec()
+                    if ancestor is Default:
+                        ancestor = None
+                    for (rwith, aname, target), factories in items:
+                        if with is not None and not mextends(with, rwith):
+                            continue
+                        if name is not None and aname != name:
+                            continue
+
+                        if provided:
+                            for p in provided:
+                                if target.extends(p, False):
+                                    break
+                            else:
+                                # None matching
+                                continue
+
+                        yield (ancestor, target, rwith, aname, factories)
+
+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 = {}
+    registered = {}
+
+    # Add adapters and interfaces directly implied by same:
+    for (with, name, target), factories in adapters.iteritems():
+        if with:
+            _add_multi_adapter(with, name, target, target, multi,
+                               registered, factories)
+        elif name:
+            _add_named_adapter(target, target, name, implied,
+                               registered, factories)
+        else:
+            _add_adapter(target, target, implied, registered, factories)
+
+    return implied, multi
+
+def _add_adapter(target, provided, implied, registered, factories):
+    if (target not in implied
+        or
+        (target in registered and registered[target].extends(provided))
+        ):
+        registered[target] = provided
+        implied[target] = factories
+        for b in target.__bases__:
+            _add_adapter(b, provided, implied, registered, factories)
+
+def _add_named_adapter(target, provided, name,
+                        implied, registered, factories):
+    key = target, name
+    if (key not in implied
+        or
+        (key in registered and registered[key].extends(provided))
+        ):
+        registered[key] = provided
+        implied[key] = factories
+        for b in target.__bases__:
+            _add_named_adapter(b, provided, name,
+                               implied, registered, factories)
+
+def _add_multi_adapter(interfaces, name, target, provided, implied,
+                       registered, factories):
+    order = len(interfaces)+1
+    key = target, name, order
+    adapters = implied.get(key)
+    if adapters is None:
+        adapters = {}
+        implied[key] = adapters
+
+    key = key, interfaces # The full key has all 4
+    if key not in registered or registered[key].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[key] = provided
+        adapters[interfaces] = factories
+
+    for b in target.__bases__:
+        _add_multi_adapter(interfaces, name, b, provided, implied,
+                           registered, factories)




More information about the Zope3-Checkins mailing list