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

michael dunstan michael at elyt.com
Mon Feb 9 02:41:23 EST 2004


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

Modified Files:
	surrogate.py 
Log Message:
Add subscription adapters (partial adapters that return all possible factories
that match your query). 


=== Zope3/src/zope/interface/surrogate.py 1.2 => 1.3 ===
--- Zope3/src/zope/interface/surrogate.py:1.2	Fri Nov 21 12:11:46 2003
+++ Zope3/src/zope/interface/surrogate.py	Mon Feb  9 02:41:21 2004
@@ -21,7 +21,7 @@
 # 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
+# declaration).  We use weak references in order to remove surrogates
 # if the corresponding specification goes away.
 
 # Each surrogate keeps track of:
@@ -36,39 +36,44 @@
 
 # The registrations are of the form:
 
-#   {(with, name, specification) -> factories}
+#   {(subscription, with, name, specification) -> factories}
 
 # where:
 
-#   with is a tuple of specs that is non-empty only in the case
+#   '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' is a unicode adapter name.  Unnamed adapters have an empty
 #   name.
 
-#   specification is the interface being adapted to.
+#   '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.)
+#   '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 3 forms:
 
-#   specification -> factories
+#   (subscription, specification) -> factories
 
 #      for simple unnamed adapters
 
-#   (specification, name) -> factories
+#   (subscription, specification, name) -> factories
 
 #      for named adapters
 
-#   (specification, name, order) -> {with -> factories}
+#   (subscription, specification, name, order) -> {with -> factories}
 
 #      for multi-adapters.  
 
 
 from __future__ import generators
 import weakref
+from sets import Set
 from zope.interface.ro import ro
 from zope.interface.declarations import providedBy
 from zope.interface.interface import InterfaceClass
@@ -122,14 +127,28 @@
         # override less-specific data.
         ancestors.reverse()
         for ancestor in ancestors:
-            implied.update(ancestor.selfImplied)
+            for key, adapters in ancestor.selfImplied.iteritems():
+                subscription = key[0]
+                if subscription:
+                    adapters = tuple(map(tuple, adapters))
+                    implied[key] = tuple(Set(implied.get(key, ()) + adapters))
+                else:
+                    implied[key] = adapters
             for k, ancestor_adapters in ancestor.multImplied.iteritems():
                 implied_adapters = implied.get(k)
                 if implied_adapters:
-                    implied_adapters.update(ancestor_adapters)
+                    subscription = k[0]
+                    if subscription:
+                        for key, adapters in ancestor_adapters.iteritems():
+                            # XXX: remove dupes?
+                            implied_adapters[key] = implied_adapters.get(
+                                key, []) + adapters
+                    else:
+                        implied_adapters.update(ancestor_adapters)
                 else:
                     implied[k] = ancestor_adapters.copy()
 
+
         self.get = implied.get
 
     def get(self, key):
@@ -163,11 +182,24 @@
     def _adaptTo(self, specification, factories, name='', with=()):
         if factories is None:
             try:
-                del self.adapters[with, name, specification]
+                del self.adapters[False, tuple(with), name, specification]
             except KeyError:
                 pass
         else:
-            self.adapters[tuple(with), name, specification] = factories
+            self.adapters[False, tuple(with), name, specification] = factories
+
+        self.dirty()
+
+    def _subscriptionAdaptTo(self, specification, factories, name='', with=()):
+        if factories is None:
+            raise TypeError, ("Unregistering subscription adapters" 
+                              " isn't implemented")
+
+        # Add factories to our list of factory lists.
+        key = (True, tuple(with), name, specification)
+        factoriesList = self.adapters.get(key, ())
+        factoriesList = factoriesList + (factories,)
+        self.adapters[key] = factoriesList
 
         self.dirty()
 
@@ -213,8 +245,7 @@
             self._surrogates[ref] = surrogate
 
         return surrogate
-
-
+        
     def provideAdapter(self, required, provided, factories, name=u'', with=()):
         """Register an adapter
 
@@ -233,6 +264,25 @@
                             "must be a string or unicode")
         required._adaptTo(provided, factories, unicode(name), with)
 
+    def provideSubscriptionAdapter(self, required, provided, factories,
+                                   name=u'', with=()):
+        """Register a subscription adapter
+
+        Note that the given name must be convertable to unicode.
+        Use an empty string for unnamed subscription adapters. It is
+        impossible to have a named subscription 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._subscriptionAdaptTo(provided, factories, unicode(name), with)
+
     def queryAdapter(self, ob, interface, default=None, raw=False):
         """Query a simple adapter
 
@@ -289,9 +339,9 @@
         declaration = providedBy(ob)
         s = self.get(declaration)
 
-        factories = s.get(interface)
+        factories = s.get((False, interface))
         if factories is None:
-            factories = self._default.get(interface)
+            factories = self._default.get((False, interface))
 
         if factories is not None:
             if raw:
@@ -303,6 +353,109 @@
 
         return default
 
+
+    def querySubscriptionAdapter(self, ob, interface, name=u'', default=(),
+                            raw=False):
+        """Query for subscription adapters
+
+        >>> import zope.interface
+        >>> class IAnimal(zope.interface.Interface):
+        ...     pass
+        >>> class IPoultry(IAnimal):
+        ...     pass
+        >>> class IChicken(IPoultry):
+        ...     pass
+        >>> class ISeafood(IAnimal):
+        ...     pass
+
+        >>> class Poultry:
+        ...     zope.interface.implements(IPoultry)
+        >>> poultry = Poultry()
+
+        >>> registry = SurrogateRegistry()
+
+        Adapting to some other interface for which there is no
+        subscription adapter returns the default:
+
+        >>> class IRecipe(zope.interface.Interface):
+        ...     pass
+        >>> class ISausages(IRecipe):
+        ...     pass
+        >>> class INoodles(IRecipe):
+        ...     pass
+        >>> class IKFC(IRecipe):
+        ...     pass
+
+        >>> list(registry.querySubscriptionAdapter(poultry, IRecipe))
+        []
+        >>> registry.querySubscriptionAdapter(poultry, IRecipe, default=42)
+        42
+
+        Unless we define a subscription adapter:
+
+        >>> def sausages(ob):
+        ...     return 'sausages'
+
+        >>> registry.provideSubscriptionAdapter(IAnimal, ISausages, [sausages])
+        >>> list(registry.querySubscriptionAdapter(poultry, ISausages))
+        ['sausages']
+
+        And define another subscription adapter:
+
+        >>> def noodles(ob):
+        ...     return 'noodles'
+
+        >>> registry.provideSubscriptionAdapter(IPoultry, INoodles, [noodles])
+        >>> meals = list(registry.querySubscriptionAdapter(poultry, IRecipe))
+        >>> meals.sort()
+        >>> meals
+        ['noodles', 'sausages']
+
+        >>> class Chicken:
+        ...     zope.interface.implements(IChicken)
+        >>> chicken = Chicken()
+
+        >>> def kfc(ob):
+        ...     return 'kfc'
+
+        >>> registry.provideSubscriptionAdapter(IChicken, IKFC, [kfc])
+        >>> meals = list(registry.querySubscriptionAdapter(chicken, IRecipe))
+        >>> meals.sort()
+        >>> meals
+        ['kfc', 'noodles', 'sausages']
+
+        And the answer for poultry hasn't changed:
+
+        >>> registry.provideSubscriptionAdapter(IPoultry, INoodles, [noodles])
+        >>> meals = list(registry.querySubscriptionAdapter(poultry, IRecipe))
+        >>> meals.sort()
+        >>> meals
+        ['noodles', 'sausages']
+        """
+
+        declaration = providedBy(ob)
+        s = self.get(declaration)
+
+        if name:
+            key = (True, interface, name)
+        else:
+            key = (True, interface)
+
+        factoriesLists = s.get(key)
+        if factoriesLists is None:
+            factoriesLists = self._default.get(key)
+
+        if factoriesLists is not None:
+            if raw:
+                return factoriesLists
+            
+            return [factory(ob)
+                    for factories in factoriesLists
+                    for factory in factories]
+
+        return default
+
+
     def queryNamedAdapter(self, ob, interface, name, default=None, raw=False):
         """Query a named simple adapter
 
@@ -356,7 +509,10 @@
 
         declaration = providedBy(ob)
         s = self.get(declaration)
-        key = name and (interface, name) or interface
+        if name:
+            key = (False, interface, name)
+        else:
+            key = (False, interface)
         factories = s.get(key)
         if factories is None:
             factories = self._default.get(key)
@@ -370,7 +526,7 @@
         return default
 
     def queryMultiAdapter(self, objects, interface, name=u'',
-                          default=None, raw=False):
+                          default=None, raw=False, _subscription=False):
         """
 
         >>> import zope.interface
@@ -432,54 +588,218 @@
         True
         >>> a.y is r
         True
-        
+
         """
         ob = objects[0]
         order = len(objects)
         obs = objects[1:]
 
         declaration = providedBy(ob)
-        surrogate = self.get(declaration)
+        if _subscription:
+            result = []
+
+        surrogates = (self.get(declaration), self._default)
 
-        while 1:
-            adapters = surrogate.get((interface, name, order))
+        for surrogate in surrogates:
+            adapters = surrogate.get((_subscription, interface, name, order))
             if adapters:
-                matched = None
-                matched_factories = None
+                if _subscription:
+                    matched_factories = []
+                else:
+                    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
+                        if _subscription:
+                            matched_factories.extend(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
+                            # 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
+                    if not _subscription:
+                        assert len(matched_factories) == 1, \
+                               "matched_factories has more than 1 element: " \
+                               + repr(matched_factories)
+                        return matched_factories[0](*objects)
+                    else:
+                        for factories in matched_factories:
+                            assert len(factories) == 1
+                            result.append(factories[0](*objects))
+
+        if _subscription and result:
+            return result
+        else:
+            return default
+
+    def querySubscriptionMultiAdapter(self, objects, interface, name=u'',
+                                 default=(), raw=False):
+        """
+        Subscription multiadaption works too:
+
+        >>> import zope.interface
+        >>> class IAnimal(zope.interface.Interface):
+        ...     pass
+        >>> class IPoultry(IAnimal):
+        ...     pass
+        >>> class IChicken(IPoultry):
+        ...     pass
+        >>> class ISeafood(IAnimal):
+        ...     pass
+
+        >>> class Animal:
+        ...     zope.interface.implements(IAnimal)
+        >>> animal = Animal()
+
+        >>> class Poultry:
+        ...     zope.interface.implements(IPoultry)
+        >>> poultry = Poultry()
+
+        >>> class Poultry:
+        ...     zope.interface.implements(IPoultry)
+        >>> poultry = Poultry()
+
+        >>> class Chicken:
+        ...     zope.interface.implements(IChicken)
+        >>> chicken = Chicken()
+        
+
+        >>> class IRecipe(zope.interface.Interface):
+        ...     pass
+        >>> class ISausages(IRecipe):
+        ...     pass
+        >>> class INoodles(IRecipe):
+        ...     pass
+        >>> class IKFC(IRecipe):
+        ...     pass
+
+        >>> class IDrink(zope.interface.Interface):
+        ...     pass
+
+        >>> class Drink:
+        ...     zope.interface.implements(IDrink)
+        >>> drink = Drink()
+
+        >>> class Meal:
+        ...     def __init__(self, animal, drink):
+        ...         self.animal, self.drink = animal, drink
+        >>> class Lunch(Meal):
+        ...     pass
+        >>> class Dinner(Meal):
+        ...     pass
+
+        >>> registry = SurrogateRegistry()
+        >>> query = registry.querySubscriptionMultiAdapter
+        >>> provide = registry.provideSubscriptionAdapter
+
+        Can't find adapters for insufficiently specific interfaces:
+        
+        >>> provide(IPoultry, IRecipe, [Dinner], with=[IDrink])
+        >>> list(query((animal, drink), IRecipe))
+        []
+
+        But we can for equally specific:
+
+        >>> adapters = list(query((poultry, drink), IRecipe))
+        >>> len(adapters)
+        1
+
+        And for more specific:
+
+        >>> adapters = list(query((chicken, drink), IRecipe))
+        >>> len(adapters)
+        1
+
+        >>> provide(IAnimal, IRecipe, [Meal], with=[IDrink])
+        >>> provide(IAnimal, IRecipe, [Lunch], with=[IDrink])
+        >>> adapters = list(query((animal, drink), IRecipe)) 
+        >>> names = [a.__class__.__name__ for a in adapters]
+        >>> names.sort()
+        >>> names
+        ['Lunch', 'Meal']
+        >>> adapters[0].animal is animal
+        True
+        >>> adapters[0].drink is drink
+        True
+        >>> adapters[1].animal is animal
+        True
+        >>> adapters[1].drink is drink
+        True
+
+        Mixed specificities:
+
+        >>> registry = SurrogateRegistry()
+        >>> query = registry.querySubscriptionMultiAdapter
+        >>> provide = registry.provideSubscriptionAdapter
+
+        >>> provide(IPoultry, IRecipe, [Meal], with=[IDrink])
+        >>> provide(IChicken, IRecipe, [Lunch], with=[IDrink])
+
+        We can only use IPoultry recipes on poultry -- we can't apply chicken
+        recipes because poultry isn't specific enough.  So there's only one
+        choice for poultry:
+        
+        >>> adapters = list(query((poultry, drink), IRecipe))
+        >>> len(adapters)
+        1
+
+        But using chicken, we can use poultry *and* chicken recipes:
+        
+        >>> adapters = list(query((chicken, drink), IRecipe))
+        >>> len(adapters)
+        2
 
-                    return matched_factories[0](*objects)
+        We should get the same results if we swap the order of the input types:
 
-            # Fall back to default if we haven't already
-            if surrogate is self._default:
-                return default
-            surrogate = self._default
+        >>> registry = SurrogateRegistry()
+        >>> query = registry.querySubscriptionMultiAdapter
+        >>> provide = registry.provideSubscriptionAdapter
+
+        >>> provide(IDrink, IRecipe, [Meal], with=[IPoultry])
+        >>> provide(IDrink, IRecipe, [Lunch], with=[IChicken])
+
+        >>> adapters = list(query((drink,poultry), IRecipe))
+        >>> len(adapters)
+        1
+        >>> adapters = list(query((drink,chicken), IRecipe))
+        >>> len(adapters)
+        2
+
+        And check that names work, too:
+
+        >>> adapters = list(query((drink,poultry), IRecipe, name='Joes Diner'))
+        >>> len(adapters)
+        0
+        
+        >>> provide(IDrink, IRecipe, [Meal], with=[IPoultry],name='Joes Diner')
+        >>> adapters = list(query((drink,poultry), IRecipe, name='Joes Diner'))
+        >>> len(adapters)
+        1
+
+        """
+        return self.queryMultiAdapter(objects, interface, name, default, raw,
+                                      _subscription=True)
 
     def getRegisteredMatching(self,
                               required=None,
@@ -645,7 +965,10 @@
                     ancestor = ancestor.spec()
                     if ancestor is Default:
                         ancestor = None
-                    for (rwith, aname, target), factories in items:
+                    for key, factories in items:
+                        subscription, rwith, aname, target = key
+                        if subscription:
+                            raise NotImplementedError
                         if with is not None and not mextends(with, rwith):
                             continue
                         if name is not None and aname != name:
@@ -675,33 +998,36 @@
     implied = {}
     multi = {}
     registered = {}
-
     # Add adapters and interfaces directly implied by same:
-    for (with, name, target), factories in adapters.iteritems():
+    for (subscription, with, name, target), factories in adapters.iteritems():
         if with:
             _add_multi_adapter(with, name, target, target, multi,
-                               registered, factories)
+                               registered, factories, subscription)
         elif name:
             _add_named_adapter(target, target, name, implied,
-                               registered, factories)
+                               registered, factories, subscription)
         else:
-            _add_adapter(target, target, implied, registered, factories)
+            _add_adapter(target, target, implied, registered, factories,
+                         subscription)
 
     return implied, multi
 
-def _add_adapter(target, provided, implied, registered, factories):
-    if (target not in implied
+def _add_adapter(target, provided, implied, registered, factories,
+                 subscription):
+    key = subscription, target
+    if (key not in implied
         or
-        (target in registered and registered[target].extends(provided))
+        (key in registered and registered[key].extends(provided))
         ):
-        registered[target] = provided
-        implied[target] = factories
+        registered[key] = provided
+        implied[key] = factories
         for b in target.__bases__:
-            _add_adapter(b, provided, implied, registered, factories)
+            _add_adapter(b, provided, implied, registered, factories,
+                         subscription)
 
 def _add_named_adapter(target, provided, name,
-                        implied, registered, factories):
-    key = target, name
+                        implied, registered, factories, subscription):
+    key = subscription, target, name
     if (key not in implied
         or
         (key in registered and registered[key].extends(provided))
@@ -710,18 +1036,18 @@
         implied[key] = factories
         for b in target.__bases__:
             _add_named_adapter(b, provided, name,
-                               implied, registered, factories)
+                               implied, registered, factories, subscription)
 
 def _add_multi_adapter(interfaces, name, target, provided, implied,
-                       registered, factories):
+                       registered, factories, subscription):
     order = len(interfaces)+1
-    key = target, name, order
+    key = subscription, target, name, order
     adapters = implied.get(key)
     if adapters is None:
         adapters = {}
         implied[key] = adapters
 
-    key = key, interfaces # The full key has all 4
+    key = key, interfaces # The full key has all 5
     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
@@ -731,4 +1057,4 @@
 
     for b in target.__bases__:
         _add_multi_adapter(interfaces, name, b, provided, implied,
-                           registered, factories)
+                           registered, factories, subscription)




More information about the Zodb-checkins mailing list