This is an internal DSL providing fluent interface to inject decoration, i.e. decorate methods at runtime.
Method names can be specified directly or using a method grabber, which introspect the class.
Given the class C, public methods can be decorated with the “log” decorator by:
decorate(PUBLIC_METHODS).of(C).using(log)
which is equivalent to:
C.foo = log(C.foo)
...
C.baz = log(C.baz)
or more usually:
class C(object):
...
@log
def foo(self, ...):
...
...
@log
def baz(self, ...):
...
Methods begining with get_ in classes A and B are decorated with “foo” and “bar”:
decorate(method_named("get_.*")).of(A, B).using(foo, bar)
equivalent to:
A.get_spam = bar(foo(A.get_spam))
...
A.get_egg = bar(foo(A.get_egg))
B.get_spamegg = bar(foo(B.get_spamegg))
...
Finally one can set exceptions in grabbers, such as in:
decorate(method_named("get_.*")).but_not('get_something').of(A).using(foo)
Warning
When using a selector based on the method name, the decorators must use functools.wraps() or a second decoration will fail, the effective name of the method being replaced by the first decorator’s one.
This module can be used as a simple “aspect oriented” programming framework. Indeed, it is easy to decorate methods, i.e. add aspect to these methods, directly at runtime.
Suppose you want to trace the execution of public methods of DAO classes in some module. You first declare a decorator to do this:
def tracer(meth):
@functools.wraps(meth)
def wrapped(*args, **kargs):
log("enter %r with args %r %r" % (
meth.__name__,
args, kargs))
result = meth(*args, **kargs)
log("exit %r with result %r" % (
meth.__name__,
result))
return result
return wrapped
and then decorate the methods with it:
decorate(PUBLIC_METHODS).of(classes_named("DAO.*").within(somemodule)).using(tracer)
The DSL is made of some predefined grabbers, factory functions and objects that create and configure the needed framework objects, then execute them (i.e. a facade pattern).
The API of this DSL is a fluent interface
Start a fluent interface “sentence”. Take a method specification as argument (i.e. a MethodsGrabber, one of the predefined or as returned by method_named(), or a user defined one).
Force multiple decoration by the same decorator.
Note
When a class and one of it superclass are decorated, inherited methods in the class are inspected twice.
To avoid decorating several times the same method with the same decorator, the decoration process register the decorator in a decorated attribute of the method.
However, multiple decoration can be forced by setting the MethodDecorator.force_decoration attribute to True, or using the decorate.allowing_multiple_decoration DSL property.
Grabbers (or selectors) are elements that select some objects (method, class) based on their name or others criteria (method type, super class, etc.)
Several grabber are provided to ease the writing of sentences. Moreover, functions are here to create custom grabbers easily and “fluently”. However, it is easy to create your own grabbers.
Grabbers of the same kind can be combined using binary boolean operators & and |. For example:
PRIVATE_METHODS & CLASS_METHODS
selects private class methods and:
method_named("spam.*") | PROTECTED_METHODS
selects protected methods or methods which name begin with “spam”.
Contruct a MethodGrabber based on names or regexp.
The constructed grabber selects a method if the name matches one of the supplied regexp, and can be used as a decorate argument.
Predefined grabbers are also defined to be used as decorate argument.
Class grabbers, as method grabbers, can be used to select the classes to decorate methods from, as arguments to of()
Two factory functions returns a ClassGrabberDSL, an iterator that is used to construct the grabber. This class should not be used directly, but through factories or predefined constants, except to create user-defined class grabbers.
ClassGrabberDSL provides several methods to grab classes (fluent interface).
Define the source to grabb classes from. Can be a module or a scope (dict) such as returned by globals().
Note
To respect the fluency of the interface, the classes from the DSL are usually imported using from ... import, and are therefore in the local scope. However, in most cases, they are not supposed to be decorated themselves.
Classes pertaining to this module are therefore automatically pruned from the scope given to the class grabber (e.g. by globals()) when it is built by the DSL.
If you want to include them anyway, combine the grabber with another one specifying explicitly the module. For example:
classes_named('Test*').within(globals()) | ALL_CLASSES.within(decoratorinjection)
Object in the framework can be used directly to decorate object, instead of using the DSL.
Moreover, the MethodGrabber and ClassGrabber object can be used to define new grabbers by instanciating them. Some utility function are defined in the module to ease the creation of custom grabbers.
Object to decorate the class. Usually built by the DSL. It uses the given grabber on every given classe to decorate the obtained method with every given decorator.
Grabber to collect classes to be decorated from a module
These selectors can be used to define custom grabbers. Selectors take two arguments: the name and the object itself, and return a boolean. Some of the function defined here are actually factory functions returning a selector.
Object to collect objects matching some criteria
object, taking the name and the object (list is OR);
exceptions are matchers that exclude some objects from the list.
To evaluate the grabber on a single object (i.e. test if it matches or not), use the match() method or call the grabber (instances are callable)
When combining grabbers, the matchers and exceptions of the two grabbers are combined using the following functions. They can be used to create complex selectors (boolean function) to pass to the grabber when creating it.
To install this module, just copy the downloaded file in your PYTHONPATH.
All comments are welcome.
This project is licensed under the Apache License, Version 2.0