[Zope3-checkins] SVN: Zope3/trunk/src/zope/interface/ Fixed bug 396 in multi-adapter lookup:

Jim Fulton jim at zope.com
Sun Nov 6 16:38:21 EST 2005


Log message for revision 39948:
  Fixed bug 396 in multi-adapter lookup:
  
  http://www.zope.org/Collectors/Zope3-dev/396
  

Changed:
  U   Zope3/trunk/src/zope/interface/adapter.py
  U   Zope3/trunk/src/zope/interface/tests/test_adapter.py

-=-
Modified: Zope3/trunk/src/zope/interface/adapter.py
===================================================================
--- Zope3/trunk/src/zope/interface/adapter.py	2005-11-06 16:41:15 UTC (rev 39947)
+++ Zope3/trunk/src/zope/interface/adapter.py	2005-11-06 21:38:20 UTC (rev 39948)
@@ -51,7 +51,7 @@
 #   '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, 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
@@ -172,6 +172,7 @@
         # override less-specific data.
         ancestors.reverse()
         for ancestor in ancestors:
+            ancestor_spec = ancestor.spec()
             
             for key, v in ancestor.selfImplied.iteritems():
 
@@ -206,8 +207,20 @@
                     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] = {}
@@ -227,8 +240,8 @@
                     for name, value in byname.iteritems():
                         if isinstance(value, dict):
                             # We have {with -> value}
-                            # convert it to sorted [(with, value]
-                            byname[name] = orderwith(value)
+                            # convert it to [(with, value]
+                            byname[name] = value.items()
 
         self.get = implied.get
 
@@ -287,33 +300,7 @@
     def __repr__(self):
         return '<%s(%s)>' % (self.__class__.__name__, self.spec())
 
-def orderwith(bywith):
 
-    # Convert {with -> adapter} to withs, [(with, value)]
-    # such that there are no i, j, i < j, such that
-    #           withs[j][0] extends withs[i][0].
-
-    withs = []
-    for with, value in bywith.iteritems():
-        for i, (w, v) in enumerate(withs):
-            if withextends(with, w):
-                withs.insert(i, (with, value))
-                break
-        else:
-            withs.append((with, value))
-            
-    return withs
-    
-
-def withextends(with1, with2):
-    for spec1, spec2 in zip(with1, with2):
-        if spec1.extends(spec2):
-            return True
-        if spec1 != spec2:
-            break
-    return False
-
-
 class AdapterLookup(object):
     # Adapter lookup support
     # We have a class here because we want to provide very
@@ -362,43 +349,46 @@
 
         # Multi adapter
 
-        with = required[1:]
+        with = required
         key = provided, order
 
         for surrogate in self.get(required[0]), self._default:
             byname = surrogate.get(key)
-            if not byname:
-                continue
+            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
 
-            bywith = byname.get(name)
-            if not bywith:
-                continue
+                            # 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]
 
-            # 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.
+            with = with[1:] # on second pass through, don't use first spec
 
-            # `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]
-
         return default
 
     def lookup1(self, required, provided, name='', default=None):
@@ -584,41 +574,45 @@
 
         # Multi adapter
 
-        with = required[1:]
+        with = required
         key = provided, order
         first = ()
 
         for surrogate in self.get(required[0]), self._default:
             byname = surrogate.get(key)
-            if not byname:
-                continue
+            if byname:
+                for name, bywith in byname.iteritems():
+                    if not bywith or name in first:
+                        continue
 
-            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
 
-                # 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]
 
-                # If any match was found, return the best one.
-                if best:
-                    yield name, best[1]
+                first = byname
 
-            first = byname
+            with = with[1:] # on second pass through, don't use first spec
 
     def subscribe(self, required, provided, value):
         if required:

Modified: Zope3/trunk/src/zope/interface/tests/test_adapter.py
===================================================================
--- Zope3/trunk/src/zope/interface/tests/test_adapter.py	2005-11-06 16:41:15 UTC (rev 39947)
+++ Zope3/trunk/src/zope/interface/tests/test_adapter.py	2005-11-06 21:38:20 UTC (rev 39948)
@@ -35,24 +35,6 @@
 class IR1(IR0):
     pass
 
-
-def test_orderwith():
-    """
-    >>> Interface = zope.interface.Interface
-    >>> bywith = {(Interface, Interface): 'A0',
-    ...           (IF0,       Interface): 'A1', 
-    ...           (Interface, IB0):       'A2', 
-    ...           (IF0,       IB0):       'A3', 
-    ...           (IF1,       IB0):       'A4', 
-    ...           (IF0,       IB1):       'A5', 
-    ...           (IF1,       IB1):       'A6', 
-    ...          }
-
-    >>> [value for spec, value in zope.interface.adapter.orderwith(bywith)]
-    ['A6', 'A4', 'A5', 'A3', 'A1', 'A2', 'A0']
-    """
-
-
 def test_multi_adapter_get_best_match():
     """
     >>> registry = AdapterRegistry()
@@ -310,7 +292,15 @@
     42
     """
 
-
+def test_correct_multi_adapter_lookup():
+    """
+    >>> registry = AdapterRegistry()
+    >>> registry.register([IF0, IB1], IR0, '', 'A01')
+    >>> registry.register([IF1, IB0], IR0, '', 'A10')
+    >>> registry.lookup((IF1, IB1), IR0, '')
+    'A10'
+    """
+    
 def test_suite():
     from zope.testing import doctest, doctestunit
     return unittest.TestSuite((



More information about the Zope3-Checkins mailing list