[pypy-svn] r40087 - in pypy/dist/pypy/lib: . app_test

afayolle at codespeak.net afayolle at codespeak.net
Thu Mar 8 18:21:08 CET 2007


Author: afayolle
Date: Thu Mar  8 18:21:07 2007
New Revision: 40087

Modified:
   pypy/dist/pypy/lib/aop.py
   pypy/dist/pypy/lib/app_test/sample_aop_code.py
   pypy/dist/pypy/lib/app_test/test_aop.py
Log:
implement advice before and after call


Modified: pypy/dist/pypy/lib/aop.py
==============================================================================
--- pypy/dist/pypy/lib/aop.py	(original)
+++ pypy/dist/pypy/lib/aop.py	Thu Mar  8 18:21:07 2007
@@ -2,7 +2,7 @@
 
 heavily influenced by Aspect++"""
 
-
+__all__ = ('around', 'before', 'after', 'introduce', 'PointCut', 'Aspect')
 ###########################
 # API
 ###########################
@@ -16,6 +16,15 @@
         if self.requires_dynamic_pointcut != pointcut.isdynamic:
             raise TypeError('Expecting a static pointcut')
         self.pointcut = pointcut
+        dispatch = {ExecutionPointCut: self.weave_at_execution_pointcut,
+                    CallPointCut: self.weave_at_call_pointcut,
+                    InitializationPointCut: self.weave_at_initialization_pointcut,
+                    DestructionPointCut: self.weave_at_destruction_pointcut,
+                    PointCut: self.weave_at_static_pointcut,
+                    }
+        self.weave_at_pointcut = dispatch[pointcut.__class__]
+                    
+               
         
     def __call__(self, function):
         print 'wrapping advice %s on %s' % (self.pointcut, function.__name__)
@@ -23,54 +32,43 @@
         return self
 
     def weave(self, ast, enc):
-        return ast.accept(self)
+        return ast.mutate(self)
 
-    def visitFunction(self, node):
+    def default(self, node):
         if self.pointcut.match(node):
             node = self.weave_at_pointcut(node,
                                           self.pointcut.joinpoint(node))
         return node
 
-    def vistClass(self, node):
-        if self.pointcut.match(node):
-            print "found match", node.name
-        return node
-        
+##     def visitClass(self, node):
+##         if self.pointcut.match(node):
+##             print "found match", node.name
+##         return node
 
-def make_aop_call(id, targetname=None, discard=True):
-    """return an AST for a call to a woven function
-    id is the integer returned when the advice was stored in the registry"""
-    p = parser
-    arguments = [p.ASTConst(id),]
-    if targetname is not None:
-        arguments.append(p.ASTName(targetname))
-    else:
-        arguments.append(p.ASTConst(None))
-    arguments.append(p.ASTCallFunc(p.ASTName('locals'),
-                                   [], None, None)
-                     )
-                         
-    if discard:
-        returnclass = p.ASTDiscard
-    else:
-        returnclass = p.ASTReturn
-    return returnclass(p.ASTCallFunc(p.ASTName('__aop__'),
-                                      arguments,
-                                      None, # *args
-                                      None # *kwargs
-                                      )
-                        )
-
-def is_aop_call(node):
-    p = parser
-    return node.__class__ == p.ASTDiscard and \
-           node.expr.__class__ == p.ASTCallFunc and \
-           node.expr.node.varname == '__aop__'
+    def weave_at_execution_pointcut(self, node, tjp):
+        raise NotImplementedError("abstract method")
+        
+    def weave_at_call_pointcut(self, node, tjp):
+        raise NotImplementedError("abstract method")
+    
+    def weave_at_initialization_pointcut(self, node, tjp):
+        raise NotImplementedError("abstract method")
+    
+    def weave_at_destruction_pointcut(self, node, tjp):
+        raise NotImplementedError("abstract method")
+    
+    def weave_at_static_pointcut(self, node, tjp):
+        raise NotImplementedError("abstract method")
 
 class around(Advice):
     """specify code to be run instead of the pointcut"""
-    def weave_at_pointcut(self, node, tjp):
-        print "WEAVE around!!!"
+    def weave_at_execution_pointcut(self, node, tjp):
+        """weaving around a function execution moves the body of the
+        function to an inner function called
+        __aoptarget_<funcname>_<id>, and generate the following code:
+        return __aop__(id, __aoptarget_<funcname>_<id>)
+        """
+        print"WEAVE around!!!"
         p = parser
         id = __aop__.register_joinpoint(self.woven_code, tjp)
         statement = node.code
@@ -91,9 +89,20 @@
         node.code = newcode
         return node
     
+    def weave_at_call_pointcut(self, node, tjp):
+        raise NotImplementedError("abstract method")
+    
+    def weave_at_initialization_pointcut(self, node, tjp):
+        raise NotImplementedError("abstract method")
+    
+    def weave_at_destruction_pointcut(self, node, tjp):
+        raise NotImplementedError("abstract method")
+    
 class before(Advice):
     """specify code to be run before the pointcut"""
-    def weave_at_pointcut(self, node, tjp):
+    def weave_at_execution_pointcut(self, node, tjp):
+        """weaving before execution inserts a call to __aop__(id) at
+        the beginning of the wrapped function definition"""
         print "WEAVE before!!!"
         id = __aop__.register_joinpoint(self.woven_code, tjp)
         statement_list = node.code.nodes
@@ -101,11 +110,46 @@
         node.code.nodes = statement_list
         return node
         
-            
-
+    def weave_at_call_pointcut(self, node, tjp):
+        """weaving before call replaces a call to foo(bar) with the
+        following code:
+        (lambda *args,**kwargs: (__aop__(id), foo(*args,**kwargs)))(bar)[1]
+        """
+        id = __aop__.register_joinpoint(self.woven_code, tjp)
+        p = parser
+        lambda_ret = p.ASTTuple((make_aop_call(id).expr, # we don't want the ASTDiscard
+                                p.ASTCallFunc(node.node,
+                                              [],
+                                              p.ASTName('args'),
+                                              p.ASTName('kwargs')))
+                               )
+        lambda_func = p.ASTLambda([p.ASTAssName('args', 0), p.ASTAssName('kwargs', 0)],
+                                 [], # defaults
+                                 p.CO_VARARGS | p.CO_VARKEYWORDS, 
+                                 lambda_ret
+                                 )
+        call = p.ASTCallFunc(lambda_func,
+                             node.args,
+                             node.star_args,
+                             node.dstar_args)
+        newnode = p.ASTSubscript(call,
+                                 p.OP_APPLY,
+                                 p.ASTConst(1))
+        print `newnode`
+        return newnode
+    
+    def weave_at_initialization_pointcut(self, node, tjp):
+        raise NotImplementedError("abstract method")
+    
+    def weave_at_destruction_pointcut(self, node, tjp):
+        raise NotImplementedError("abstract method")
+    
 class after(Advice):
     """specify code to be run after the pointcut"""
-    def weave_at_pointcut(self, node, tjp):
+    def weave_at_execution_pointcut(self, node, tjp):
+        """weaving after execution wraps the code of the function in a
+        try...finally block, and calls __aop__(id) in the finally
+        block"""
         print "WEAVE after!!!"
         id = __aop__.register_joinpoint(self.woven_code, tjp)
         statement = node.code
@@ -113,6 +157,42 @@
         node.code = tryfinally
         return node
 
+    def weave_at_call_pointcut(self, node, tjp):
+        """weaving before call replaces a call to foo(bar) with the
+        following code:
+        (lambda *args,**kwargs: (foo(*args,**kwargs), __aop__(id)))(bar)[0]
+        """
+        id = __aop__.register_joinpoint(self.woven_code, tjp)
+        p = parser
+        lambda_ret = p.ASTTuple((p.ASTCallFunc(node.node,
+                                              [],
+                                              p.ASTName('args'),
+                                              p.ASTName('kwargs')),
+                                 make_aop_call(id).expr, # we don't want the ASTDiscard
+                                )
+                               )
+        lambda_func = p.ASTLambda([p.ASTAssName('args', 0), p.ASTAssName('kwargs', 0)],
+                                 [], # defaults
+                                 p.CO_VARARGS | p.CO_VARKEYWORDS, 
+                                 lambda_ret
+                                 )
+        call = p.ASTCallFunc(lambda_func,
+                             node.args,
+                             node.star_args,
+                             node.dstar_args)
+        newnode = p.ASTSubscript(call,
+                                 p.OP_APPLY,
+                                 p.ASTConst(0))
+        print `newnode`
+        return newnode
+    
+    
+    def weave_at_initialization_pointcut(self, node, tjp):
+        raise NotImplementedError("abstract method")
+    
+    def weave_at_destruction_pointcut(self, node, tjp):
+        raise NotImplementedError("abstract method")
+    
 class introduce(Advice):
     """insert new code in the pointcut
     this is the only advice available on static point cuts"""
@@ -124,8 +204,6 @@
 
     
         
-# TODO: add new base classes to a pointcut. Maybe with introduce ?
-
 # JoinPoint
 # --------
 
@@ -171,6 +249,7 @@
         if arguments is None:
             arguments = (), {}
         self._arguments = arguments
+        self._argnames = None
         self.func = func
     
 
@@ -210,6 +289,7 @@
     def call(self):
         """return a dynamic pointcut representing places where the pointcut is called"""
         return CallPointCut(self)
+
     def execution(self):
         """return a dynamic pointcut representing places where the pointcut is executed"""
         return ExecutionPointCut(self)
@@ -239,10 +319,7 @@
         
 class ExecutionPointCut(AbstractDynamicPointCut):
     def match(self, astnode):
-        try:
-            return astnode.name == self.pointcutdef
-        except AttributeError:
-            return False
+        return isinstance(astnode, parser.ASTFunction) and astnode.name == self.pointcutdef
 
     def joinpoint(self, node):
         """returns a join point instance for the node"""
@@ -255,7 +332,8 @@
         return jp
 
 class CallPointCut(AbstractDynamicPointCut):
-    pass
+    def match(self, node):
+        return isinstance(node, parser.ASTCallFunc) and isinstance(node.node, parser.ASTName) and node.node.varname == self.pointcutdef
 
 class DestructionPointCut(AbstractDynamicPointCut):
     pass
@@ -327,8 +405,9 @@
     def __call__(self, id, target=None, target_locals = None):
         woven_code, (aspect, joinpoint, arguments) = self.joinpoints[id]
         joinpoint.func = target
+        print 'target_locals', target_locals
         if target_locals is not None:
-            joinpoint._arguments = (), dict([(n, target_locals[n]) for n in joinpoint._argnames])
+            joinpoint._arguments = (), dict([(n, target_locals[n]) for n in joinpoint._argnames or ()])
         args = (aspect, joinpoint,) + arguments
         return woven_code(*args)
 
@@ -354,6 +433,30 @@
 
         return instance
 
+# helper functions 
+def make_aop_call(id, targetname=None, discard=True):
+    """return an AST for a call to a woven function
+    id is the integer returned when the advice was stored in the registry"""
+    p = parser
+    arguments = [p.ASTConst(id),]
+    if targetname is not None:
+        arguments.append(p.ASTName(targetname))
+    else:
+        arguments.append(p.ASTName('None'))
+    arguments.append(p.ASTCallFunc(p.ASTName('locals'),
+                                   [], None, None)
+                     )
+                         
+    if discard:
+        returnclass = p.ASTDiscard
+    else:
+        returnclass = p.ASTReturn
+    return returnclass(p.ASTCallFunc(p.ASTName('__aop__'),
+                                      arguments,
+                                      None, # *args
+                                      None # *kwargs
+                                      )
+                        )
 
 # debugging visitor
 class Debug(parser.ASTVisitor):

Modified: pypy/dist/pypy/lib/app_test/sample_aop_code.py
==============================================================================
--- pypy/dist/pypy/lib/app_test/sample_aop_code.py	(original)
+++ pypy/dist/pypy/lib/app_test/sample_aop_code.py	Thu Mar  8 18:21:07 2007
@@ -2,12 +2,13 @@
 def foo(b,c):
     print 'foo'
     a = 2
-    d = bar()
+    d = bar(a)
+    print d
     return b+a+c+d
 
 
-def bar():
-    print 'bar'
+def bar(val):
+    print 'bar', val
     return 42
 
 def baz(b,c):

Modified: pypy/dist/pypy/lib/app_test/test_aop.py
==============================================================================
--- pypy/dist/pypy/lib/app_test/test_aop.py	(original)
+++ pypy/dist/pypy/lib/app_test/test_aop.py	Thu Mar  8 18:21:07 2007
@@ -28,18 +28,6 @@
             adv = advice(dyn_pc)
             assert adv is not None
 
-    def test_is_aop(self):
-        from aop import is_aop_call
-        import parser
-        func = """
-def f():
-    __aop__(1)
-    g(12)
-    __aop__(12)
-"""
-        funcast = parser.source2ast(func).node.nodes[0]
-        result = [is_aop_call(n) for n in funcast.code.nodes]
-        assert result == [True, False, True]
 
     def test_simple_aspect_before_execution(self):
         from  aop import PointCut, Aspect, before
@@ -127,3 +115,62 @@
         assert answ == 47
         sample_aop_code.clean_module('aop_around_execution')
         
+
+    def test_simple_aspect_before_call(self):
+        from  aop import PointCut, Aspect, before
+        from app_test import sample_aop_code
+        __aop__._clear_all()
+        sample_aop_code.write_module('aop_before_call')
+        
+        class AspectTest:
+            __metaclass__ = Aspect 
+            def __init__(self):
+                self.executed = False
+            @before(PointCut('bar').call())
+            def advice_before_call(self, tjp):
+                print "IN advice before call"
+                self.executed = True
+                self.arguments = tjp._arguments
+                print "OUT advice before call"
+
+        assert __aop__.advices == []
+        aspect = AspectTest()
+        assert __aop__.advices == [(aspect, AspectTest.advice_before_call)] 
+        assert not aspect.executed
+
+        from app_test import aop_before_call
+        assert  aspect.executed == 0
+        answ = aop_before_call.foo(1,2)
+        assert aspect.executed == 1
+        assert answ == 47
+        sample_aop_code.clean_module('aop_before_call')
+
+    def test_simple_aspect_after_call(self):
+        from  aop import PointCut, Aspect, after
+        from app_test import sample_aop_code
+        __aop__._clear_all()
+        sample_aop_code.write_module('aop_after_call')
+        
+        class AspectTest:
+            __metaclass__ = Aspect 
+            def __init__(self):
+                self.executed = False
+            @after(PointCut('bar').call())
+            def advice_after_call(self, tjp):
+                print "IN advice after call"
+                self.executed = True
+                self.arguments = tjp._arguments
+                print "OUT advice after call"
+
+        assert __aop__.advices == []
+        aspect = AspectTest()
+        assert __aop__.advices == [(aspect, AspectTest.advice_after_call)] 
+        assert not aspect.executed
+
+        from app_test import aop_after_call
+        assert  aspect.executed == 0
+        answ = aop_after_call.foo(1,2)
+        assert aspect.executed == 1
+        assert answ == 47
+        sample_aop_code.clean_module('aop_after_call')
+


More information about the pypy-svn mailing list