ef5fcae868e0 — aurelien@trantor.local 13 years ago
test mro, refactor next_method/call_next_method, method decorator accepts tuple as type specifier argument for multiple registration
3 files changed, 74 insertions(+), 21 deletions(-)

M __init__.py
M dispatch.py
M test/test_gf.py
M __init__.py +13 -5
@@ 2,14 2,22 @@ 
 
 from gf import dispatch as mm
 
-def method(*types):
+def method(*signatures):
+    return _method(_METHS, signatures)
+
+def _method(registry, signatures):
     """ decorator to globally register a method in a gf
-    named like the method itself"""
+    named like the method itself """
     def _wrap(meth):
         name = meth.__name__
-        gf = _METHS.get(name)
+        gf = registry.get(name)
         if gf is None:
-            gf = _METHS[name] = mm.GF()
-        gf.register(types, meth)
+            gf = registry[name] = mm.GF()
+        if not isinstance(signatures[0], tuple):
+            sigs = (signatures,)
+        else:
+            sigs = signatures
+        for sig in sigs:
+            gf.register(sig, meth)
         return gf
     return _wrap

          
M dispatch.py +24 -8
@@ 13,7 13,7 @@ def check_ambiguity(candidates, mros):
                    for d, s, order in zip(dom, sub, orders))
     return [cand for cand, _func in candidates
             if not any(dominates(dom, cand)
-                       for dom, _xxx in candidates)]
+                       for dom, _func in candidates)]
 
 def clos_mro(types_func_map, callsig):
     siglen = len(callsig)

          
@@ 27,32 27,48 @@ def clos_mro(types_func_map, callsig):
     return (x[1] for x in sorted((map(proximity, sig, mros), func)
                   for sig, func in table))
 
+
+class NoNextMethod(Exception): pass
+
 class GF(object):
     def __init__(self, mro=clos_mro):
         self.mro = mro
         self._reg = {}
         self._cache = {}
+        self._funcs = list()
+        self._nm_index = 0
+
+    def __str__(self):
+        return '<funcs: %s, mro: %s, reg: %s, cache: %s, pos: %s>' % (
+            self._funcs, self.mro, self._reg, self._cache, self._nm_index)
 
     def register(self, sig, func):
         self._reg[sig] = func
         self._cache = {}
 
     def __call__(self, *args, **kwargs):
-        self._pos = 0
+        self._nm_index = 0
         sig = tuple(x.__class__ for x in args)
         func = self._cache.get(sig)
         if func is None:
             self._funcs = list(self.mro(self._reg, sig))
             try:
-                self._cache[sig] = func = self._funcs[self._pos]
+                self._cache[sig] = func = self._funcs[self._nm_index]
             except IndexError:
                 raise TypeError('no defined call signature for args (%s)'
                                 % ','.join(str(arg) for arg in args))
         return func(*args, **kwargs)
 
-    def next_method(self, *args, **kwargs):
-        self._pos += 1
-        func = self._funcs[self._pos]
-        return func(*args, **kwargs)
+    def next_method(self):
+        self._nm_index += 1
+        try:
+            func = self._funcs[self._nm_index]
+        except IndexError:
+            return None
+        return func
 
-
+    def call_next_method(self, *args, **kwargs):
+        next = self.next_method()
+        if next:
+            return next(*args, **kwargs)
+        raise NoNextMethod(str(self))

          
M test/test_gf.py +37 -8
@@ 1,6 1,6 @@ 
 import unittest
 from gf import method
-from gf.dispatch import GF
+from gf.dispatch import GF, NoNextMethod
 
 class TestGF(unittest.TestCase):
 

          
@@ 27,15 27,15 @@ class TestGF(unittest.TestCase):
             l.append('top')
         @method(Left, list)
         def bip(x, l):
-            bip.next_method(x, l)
+            bip.call_next_method(x, l)
             l.append('left')
         @method(Right, list)
         def bip(x, l):
-            bip.next_method(x, l)
+            bip.call_next_method(x, l)
             l.append('right')
         @method(Bottom, list)
         def bip(x, l):
-            bip.next_method(x, l)
+            bip.call_next_method(x, l)
             l.append('bottom')
 
         b = Bottom()

          
@@ 56,13 56,13 @@ class TestGF(unittest.TestCase):
             return ('AX',)
         @method(A,Y)
         def foo(x,y):
-            return foo.next_method(x, y) + ('AY',)
+            return foo.call_next_method(x, y) + ('AY',)
         @method(B,X)
         def foo(x,y):
-            return foo.next_method(x, y) + ('BX',)
+            return foo.call_next_method(x, y) + ('BX',)
         @method(B,Y)
         def foo(x,y):
-            return foo.next_method(x, y) + ('BY',)
+            return foo.call_next_method(x, y) + ('BY',)
 
         b, y = B(), Y()
         self.assertEquals(foo(b, y), ('AX', 'AY', 'BX', 'BY'))

          
@@ 78,8 78,37 @@ class TestGF(unittest.TestCase):
         @method(B,X)
         def foo(b,x):
             return b,x
-        b, y = B(), Y()
+        a, b, x, y = A(), B(), X(), Y()
         self.assertRaises(TypeError, foo, b, y)
+        self.assertEquals(foo(a, y), (a, y))
+        self.assertEquals(foo(b, x), (b, x))
+        self.assertRaises(TypeError, foo, a, x)
+
+    def test_call_next_method(self):
+        class A(object): pass
+        class B(object): pass
+        @method((A,), (B,))
+        def check(obj):
+            check.call_next_method(obj)
+        self.assertRaises(NoNextMethod, check, B())
+
+    def test_c3_mro(self):
+        class pane(object): pass
+        class scrolling_mixin(object): pass
+        class editing_mixin(object): pass
+        class scrollable_pane(pane, scrolling_mixin): pass
+        class editable_pane(pane, editing_mixin): pass
+        class editable_scrollable_pane(scrollable_pane,
+                                       editable_pane): pass
+        order = (editable_scrollable_pane,
+                 scrollable_pane,
+                 editable_pane,
+                 pane,
+                 scrolling_mixin,
+                 editing_mixin,
+                 object)
+        self.assertEquals(tuple(editable_scrollable_pane.mro()),
+                          order)
 
 if __name__ == '__main__':
     unittest.main()