/aop

Aspect oriented programming for Python. Patch everything!

Primary LanguagePython

AOP

Badges? We ain’t got no badges! We don’t need no badges! I don’t have to show you any stinking badges!

Aspect-oriented programming

Features:

  1. Patch any module: your project, stdlib, built-ins.
  2. Patch any object: functions, class instances.
  3. Pure Python implementation: run it on CPython or PyPy.

TODO:

  1. Patch already imported objects
  2. Patch __init__ and __new__.
  3. Test PyPy

Example

import aop

def multiply(context):
    print(context.aspect, context.args, context.kwargs)
    yield
    context.result *= 100

aop.register(
    handler=multiply,
    modules=aop.match(equals='math'),
    targets=aop.match(regexp='(sin|cos)')
)

Ok, let's check:

import math
math.cos(0)
# prints: cos (0,) {}
# returns: 100.0

Usage

Register

Register new advice:

aop.register(
    handler=some_handler,
    modules=aop.match(equals='math'),
    targets=aop.match(regexp='(sin|cos)')
)

Parameters for aop.register:

  • handler -- advice for joinpoint processing.
  • paths -- expression for path to module.
  • modules -- expression for module name.
  • targets -- expression for object name.
  • methods -- expression for called object's method. It's __call__ for functions.

Match

Available kwargs for aop.match:

  • regexp -- object name fully match to regular expression.
  • startswith -- object name starts with specified string.
  • endswith -- object name ends with specified string.
  • contains -- object contains specified string.
  • equals -- object name equal to specified string.

Handler and context

Handler looks like:

def multiply(context):
    ...  # before aspect call
    yield
    ...  # after aspect call

Context's properties:

  • aspect -- name of target.
  • method -- name of called method or __call__ for functions.
  • module -- name of module where aspect defined.
  • path -- path to module where aspect defined.
  • args -- tuple of passed positional args
  • kwargs -- dict of passed keyword args
  • result -- target's method response

Enable and disable

Register all advices or just enable patching before all other imports in project:

import aop
aop.enable()
...  # all other imports

Also it's recommend finally enable patching after register last advice:

aop.register(...)
aop.register(...)
aop.enable(final=True)

If you want to disable patching:

aop.disable()

Inspect

Inspect object:

aop.inspect(math.isclose, print=True)

Patch import system automatically

Now this package can't patch modules that imported before aop.enable() or aop.register(...):

$ python3 special_cases/2_after.py
...
AssertionError: not patched

Although you can run your script via aop runner:

$ python3 -m aop special_cases/2_after.py
cos (0,) {}