PK uDm$! python-aspectlib-0.8/.buildinfo# Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. config: tags: PK uD python-aspectlib-0.8/objects.inv# Sphinx inventory version 2 # Project: aspectlib # Version: 0.8.0 # The remainder of this file is compressed using zlib. xڭMo0Hn\UڤM6"(q7G6T$TrO8$*,wfNyCFOV#{LzRD׀Esm'MtXC{UKwZ:yܢG+reΠF1; rm8i]u9Hlv3*TI]-rĮcm[+H9{;-Y'4"]<(J\|x]=o:ѸbU"_/O+ ?~-A\g x̵wQ*l :!گynA ?`8PK uDV\ \ python-aspectlib-0.8/index.html
aspectlib is an aspect-oriented programming, monkey-patch and decorators library. It is useful when changing behavior in existing code is desired.
aspectlib provides two core tools to do AOP: Aspects and a weaver.
An aspect can be created by decorating a generator with an Aspect. The generator yields advices - simple behavior changing instructions.
An Aspect instance is a simple function decorator. Decorating a function with an aspect will change the function’s behavior according to the advices yielded by the generator.
Example:
@aspectlib.Aspect
def strip_return_value():
result = yield aspectlib.Proceed
yield aspectlib.Return(result.strip())
@strip_return_value
def read(name):
return open(name).read()
You can use these advices:
Patches classes and functions with the given aspect. When used with a class it will patch all the methods. In AOP parlance these patched functions and methods are referred to as cut-points.
Returns a Rollback object that can be used a context manager. It will undo all the changes at the end of the context.
Example:
@aspectlib.Aspect
def mock_open():
yield aspectlib.Return(StringIO("mystuff"))
with aspectlib.weave(open, mock_open):
assert open("/doesnt/exist.txt").read() == "mystuff"
You can use aspectlib.weave() on: classes, instances, builtin functions, module level functions, methods, classmethods, staticmethods, instance methods etc.
pip install aspectlib
Or, if you live in the stone age:
easy_install aspectlib
For you convenience there is a python-aspectlib meta-package that will just install aspectlib, in case you run pip install python-aspectlib by mistake.
OS: | Any |
---|---|
Runtime: | Python 2.6, 2.7, 3.3, 3.4 or PyPy |
Python 3.2, 3.1 and 3.0 are NOT supported (some objects are too crippled).
There are perfectly sane use cases for monkey-patching (aka weaving):
Then in those situations:
Because it does more things that just patching. Depending on the target object it will patch and/or create one or more subclasses and objects.
Some frameworks don’t resort to monkey patching but instead force the user to use ridiculous amounts of abstractions and wrapping in order to make weaving possible. Notable example: spring-python.
For all intents and purposes I think it’s wrong to have such high amount of boilerplate in Python.
Also, aspectlib is targeting a different stage of development: the maintenance stage - where the code is already written and needs additional behavior, in a hurry :)
Where code is written from scratch and AOP is desired there are better choices than both aspectlib and spring-python.
aspectlib was initially written because I was tired of littering other people’s code with prints and logging statements just to fix one bug or understand how something works. aspectlib.debug.log is aspectlib‘s crown jewel. Of course, aspectlib has other applications, see the Rationale.
TODO: Make a more configurable retry decorator and add it in aspectlib.contrib.
class Client(object):
def __init__(self, address):
self.address = address
self.connect()
def connect(self):
# establish connection
def action(self, data):
# do some stuff
def retry(retries=(1, 5, 15, 30, 60), retry_on=(IOError, OSError), prepare=None):
assert len(retries)
@aspectlib.Aspect
def retry_aspect(*args, **kwargs):
durations = retries
while True:
try:
yield aspectlib.Proceed
break
except retry_on as exc:
if durations:
logging.warn(exc)
time.sleep(durations[0])
durations = durations[1:]
if prepare:
prepare(*args, **kwargs)
else:
raise
return retry_aspect
Now patch the Client class to have the retry functionality on all its methods:
aspectlib.weave(Client, retry())
or with different retry options (reconnect before retry):
aspectlib.weave(Client, retry(prepare=lambda self, *_: self.connect())
or just for one method:
aspectlib.weave(Client.action, retry())
You can see here the advantage of having reusable retry functionality. Also, the retry handling is decoupled from the Client class.
... those damn sockets:
>>> import aspectlib, socket, sys
>>> with aspectlib.weave(
... socket.socket,
... aspectlib.debug.log(
... print_to=sys.stdout,
... stacktrace=None,
... ),
... lazy=True,
... ):
... s = socket.socket()
... s.connect(('google.com', 80))
... s.send(b'GET / HTTP/1.0\r\n\r\n')
... s.recv(8)
... s.close()
...
{socket...}.connect(('google.com', 80))
{socket...}.connect => None
{socket...}.send(...'GET / HTTP/1.0\r\n\r\n')
{socket...}.send => 18
18
{socket...}.recv(8)
{socket...}.recv => ...HTTP/1.0...
...'HTTP/1.0'
...
The output looks a bit funky because it is written to be run by doctest - so you don’t use broken examples :)
Mock behavior for tests:
class MyTestCase(unittest.TestCase):
def test_stuff(self):
@aspectlib.Aspect
def mock_stuff(self, value):
if value == 'special':
yield aspectlib.Return('mocked-result')
else:
yield aspectlib.Proceed
with aspectlib.weave(foo.Bar.stuff, mock_stuff):
obj = foo.Bar()
self.assertEqual(obj.stuff('special'), 'mocked-result')
aspectlib.ALL_METHODS | Compiled regular expression objects |
aspectlib.NORMAL_METHODS | Compiled regular expression objects |
aspectlib.weave | Send a message to a recipient |
aspectlib.Rollback | When called, rollbacks all the patches and changes the weave() has done. |
aspectlib.Aspect | Container for the advice yielding generator. |
aspectlib.Proceed | Instructs the Aspect Calls to call the decorated function. Can be used multiple times. |
aspectlib.Return | Instructs the Aspect to return a value. |
Container for the advice yielding generator. Can be used as a decorator on other function to change behavior according to the advices yielded from the generator.
Instructs the Aspect Calls to call the decorated function. Can be used multiple times.
If not used as an instance then the default args and kwargs are used.
When called, rollbacks all the patches and changes the weave() has done.
Alias of __exit__.
Alias of __exit__.
Compiled regular expression objects
Compiled regular expression objects
Send a message to a recipient
Parameters: |
|
---|---|
Returns: | aspectlib.Rollback instance |
Raises TypeError: | |
If target is a unacceptable object, or the specified options are not available for that type of object. |
Changed in version 0.4.0: Replaced only_methods, skip_methods, skip_magicmethods options with methods. Renamed on_init option to lazy. Added aliases option. Replaced skip_subclasses option with subclasses.
aspectlib.debug.log | Decorates func to have logging. |
aspectlib.debug.format_stack | Returns a one-line string with the current callstack. |
aspectlib.debug.frame_iterator | Yields frames till there are no more. |
aspectlib.debug.strip_non_ascii | Convert to string (using str) and replace non-ascii characters with a dot (.). |
Returns a one-line string with the current callstack.
Convert to string (using str) and replace non-ascii characters with a dot (.).
Decorates func to have logging.
Parameters: |
|
---|---|
Returns: | A decorator or a wrapper. |
Example:
>>> @log(print_to=sys.stdout)
... def a(weird=False):
... if weird:
... raise RuntimeError('BOOM!')
>>> a()
a() <<< ...
a => None
>>> try:
... a(weird=True)
... except Exception:
... pass # naughty code !
a(weird=True) <<< ...
a ~ raised RuntimeError('BOOM!',)
You can conveniently use this to logs just errors, or just results, example:
>>> import aspectlib
>>> with aspectlib.weave(float, log(call=False, result=False, print_to=sys.stdout)):
... try:
... float('invalid')
... except Exception as e:
... pass # naughty code !
float('invalid') <<< ...
float ~ raised ValueError(...float...invalid...)
This makes debugging naughty code easier.
PS: Without the weaving it looks like this:
>>> try:
... log(call=False, result=False, print_to=sys.stdout)(float)('invalid')
... except Exception:
... pass # naughty code !
float('invalid') <<< ...
float ~ raised ValueError(...float...invalid...)
Changed in version 0.5.0: Renamed arguments to call_args. Renamed arguments_repr to call_args_repr. Added call option.
aspectlib.test.record | Factory or decorator (depending if func is initially given). |
aspectlib.test.mock | Factory for a decorator that makes the function return a given return_value. |
This module is designed to be a lightweight, orthogonal and easy to learn replacement for the popular mock framework.
Example usage, suppose you want to test this class:
>>> class ProductionClass(object):
... def method(self):
... return 'stuff'
>>> real = ProductionClass()
With aspectlib.test:
>>> from aspectlib import weave, test
>>> patch = weave(real.method, [test.mock(3), test.record(call=True)])
>>> real.method(3, 4, 5, key='value')
3
>>> assert real.method.calls == [(real, (3, 4, 5), {'key': 'value'})]
As a bonus, you have an easy way to rollback all the mess:
>>> patch.rollback()
>>> real.method()
'stuff'
With mock:
>>> from mock import Mock
>>> real = ProductionClass()
>>> real.method = Mock(return_value=3)
>>> real.method(3, 4, 5, key='value')
3
>>> real.method.assert_called_with(3, 4, 5, key='value')
Factory for a decorator that makes the function return a given return_value.
Parameters: |
|
---|---|
Returns: | A decorator. |
Factory or decorator (depending if func is initially given).
Parameters: |
|
---|---|
Returns: | A wrapper that has a calls property. |
The decorator returns a wrapper that records all calls made to func. The history is available as a call property. If access to the function is too hard then you need to specify the history manually.
Example:
>>> @record
... def a():
... pass
>>> a(1, 2, 3, b='c')
>>> a.calls
[Call(self=None, args=(1, 2, 3), kwargs={'b': 'c'})]
Or, with your own history list:
>>> calls = []
>>> @record(history=calls)
... def a():
... pass
>>> a(1, 2, 3, b='c')
>>> a.calls
[Call(self=None, args=(1, 2, 3), kwargs={'b': 'c'})]
>>> calls is a.calls
True
class BaseProcessor(object):
def process_foo(self, data):
# do some work
def process_bar(self, data):
# do some work
class ValidationConcern(aspectlib.Concern):
@aspectlib.Aspect
def process_foo(self, data):
# validate data
if is_valid_foo(data):
yield aspectlib.Proceed
else:
raise ValidationError()
@aspectlib.Aspect
def process_bar(self, data):
# validate data
if is_valid_bar(data):
yield aspectlib.Proceed
else:
raise ValidationError()
aspectlib.weave(BaseProcesor, ValidationConcern)
class MyProcessor(BaseProcessor):
def process_foo(self, data):
# do some work
def process_bar(self, data):
# do some work
# MyProcessor automatically inherits BaseProcesor's ValidationConcern
Changed aspectlib.debug.log:
- Renamed arguments to call_args.
- Renamed arguments_repr to call_args_repr.
- Added call option.
- Fixed issue with logging from old-style methods (object name was a generic “instance”).
Fixed issues with weaving some types of builtin methods.
Allow to apply multiple aspects at the same time.
Validate string targets before weaving. aspectlib.weave('mod.invalid name', aspect) now gives a clear error (invalid name is not a valid identifier)
Various documentation improvements and examples.
Changed aspectlib.weave:
- Replaced only_methods, skip_methods, skip_magicmethods options with methods.
- Renamed on_init option to lazy.
- Added aliases option.
- Replaced skip_subclasses option with subclasses.
Fixed weaving methods from a string target.