[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