Some explanations on the aspects module
The core module defines the base class for all aspects.
The weaver module defines the class which is responsible for weaving code. The Weaver class is a Singleton, and you can import the existing instance created in the weaver module, using this import command :
from logilab.aspects.weaver import weaver
The main methods which can be called on the weaver's instance are weave_pointcut and unweave_pointcut which weave and unweave some aspect-related code on a specified list of breakpoints. There are also some convenience methods to weave directly on an entire module, or class, or instance without having to create a PointCut's instance.
The lib module contains some useful aspects like :
- LoggerAspect which will trace all method calls.
- ContractAspect which will enable design by contract in Python.
- Other aspects exist like ProfilerAspect, DispatcherAspect, ConfirmationAspect or ObserverAspect, but they are not always fully implemented. Anyway, you can use them or want to have a look at them to give you ideas of useful aspects or improvements.
Some unit tests, and some examples can be found in the tests or examples directory :
When you want to weave an aspect on an entire class, or module, you will directly use the weave_methods or weave_module methods. But, when you want to weave an aspect only in a part of the class (i.e. not on all methods), then what you'll have to do is to create a PointCut's instance, and then call the weave_pointcut method.
A PointCut (in this package), is basically a dictionnary with instances or classes as keys, and the list of method's names which you want weave an aspect on. As PointCut inherits the 'dict' Python base type, you can directly defines its keys and values, though it is recommended to use the provided methods :
add_method (resp. remove_method) which will add (resp. remove) a method_name, for a given instance or class, to the pointcut
the list of static methods that instantiate directly a Pointcut from a class, an instance, or a module :
When the pointcut is created, then you just have to weave the aspect on it by doing :
(Taken from logger_example.py) :
# Import the weaver and the aspect to use from logilab.aspects.weaver import weaver from logilab.aspects.lib.logger import LoggerApsect import sys stack = StackImpl() # Push an element on the stack, the method call is not traced stack.push("an element") # Weave aspect code (log will be done on sys.stderr) weaver.weave_methods(stack, LoggerAspect, sys.stderr) # Push an other element, method call will now be traced stack.push("another element") # Unweave logger aspect weaver.unweave(stack, LoggerAspect) # Now, call methods aren't traced anymore stack.push("a third element")
In this example, we have weaved an aspect on a given instance. As a consequence, other instances of the same class will not be aspected. The best way of weaving all class instances, is to weave the aspect directly on the class, not on the instances. The syntax is exactly the same :
weaver.weave_methods(StackImpl, LoggerAspect, sys.stderr)
For now, it's only possible to wrap methods, not attribute accesses.
To create a new aspect, you must define a class which inherits from AbstractAspect (in aspects.core), and define before(), after() and around() methods. Note that you can choose to override only one of this three methods since the default behaviour is to "simply pass". It is important, when overriding the around method to explicitly call self._proceed(...) which is the effective call to the wrapped method.
Let's write a simple aspect which will write BEFORE before the method call and AFTER after.
from logilab.aspects.core import AbstractAspect class SimpleAspect(AbstractAspect): def before(self, wobj, context, *args, **kwargs): """Before method """ print "BEFORE ",context['method_name'] def after(self, wobj, context, *args, **kwargs): """After method. print the return value """ print "AFTER ",self.method_name,", return value is ", context['ret_v']
This example is quite simple, and is not really useful, but it should show how to define your own aspects.
Here is some information on the above code:
- The before() parameters are :
- self : The aspect instance
- wobj : The weaved object instance (on which is called the wrapped method)
- context : a dictionnary which holds a set of values in relation with method calls. (see below).
- args and kwargs are the arguments passed to the wrapped method. If inside before(), you want to have the name and the value of each argument, you can use the function reassign_function_arguments in the aspects.prototypes module. It will return a dictionary containing arg names as keys, and arg values as values.
- The after() and around() parameters are the same.
- context can have the following keys :
- method_name : the called method's name.
- ret_v which : the value returned by the wrapped method.
- excption which : the exception raised by the wrapped method, or None if no exception was raised.
IMPORTANT : The weaver is a Singleton that holds all informations related to the weaving, for instance which method is weaved, which aspect it's weaved with, etc.. When an aspect is weaved on a method, the weaver will look is that aspect has already been instantiated, and in that case, it will use that aspect's instance to weave code around the method. This will soon be changed because it can be unconvenient. (aspects behave a bit like Singleton).
The aspects module contains some useful aspects like :
- LoggerAspect : which will trace all method calls.
- ContractAspect : contracts can be seen as a sub-part of aspects, and this particular aspect will allow you to use contracts in Python. We have choosen to consider contracts definitions as a part of documentation. The conditions are thus specified in method and modules docstrings. You can find more information about contracts and aspects here.
- There are other aspects like ProfilerAspect (a simple method profiler), DispatcherAspect (to enable multimethods), ConfirmationAspect (to ask confirmation before an operation), or ObserverAspect (an aspect implementation of the Observer Design Pattern), but they are not always fully implemented. Anyway, you can use them or want to have a look at them to give you ideas of useful aspects or improvements.
Here is a set of useful links for Aspect Oriented Programming:
- The original idea.
- Aspect home page.
- Another page listing aspects links.
- AspectJ is a Java exhaustive implementation of AOP. Here's an AspectJ tutorial.
- Some citesite results.
- Some other Python solutions for aspect programming : Pythius, and PEAK (which is actually not only an aspect implementation).
The aspect aspects module can be downloaded from Logilab's ftp site. You will find the source distribution and some debian packages. To install it from the source distribution, just type python setup.py install after having unzipped the archive.