Skip to content

Instantly share code, notes, and snippets.

@probablytom
Last active May 19, 2020 08:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save probablytom/022c138e1487175f893a5899e9026f61 to your computer and use it in GitHub Desktop.
Save probablytom/022c138e1487175f893a5899e9026f61 to your computer and use it in GitHub Desktop.
A Python3 compatible Asp implementation
class AspectHooks:
pre_rules = list()
around_rules = list()
post_rules = list()
def __enter__(self, *args, **kwargs):
self.old_import = __import__
import builtins
builtins.__import__ = self.__import__
def __exit__(self, *args, **kwargs):
builtins.__import__ = self.old_import
def __import__(self, *args, **kwargs):
mod = self.old_import(*args, **kwargs)
def build_wrapper(target):
@wraps(target)
def wrapper(*args, **kwargs):
pre, around, post = self.get_rules(target.__name__)
for advice in pre:
advice(target, *args, **kwargs)
ret = target(*args, **kwargs) ## TODO: implement around
for advice in post:
post_return = advice(target, ret, *args, **kwargs)
ret = ret if post_return is None else post_return
return ret
return wrapper
def apply_hooks(target_object):
for item_name in filter(lambda p: isinstance(p, str) and len(p) > 1 and p[:2] != "__", dir(target_object)):
item = getattr(target_object, item_name)
if isfunction(item) or ismethod(item):
setattr(target_object, item_name, build_wrapper(item))
elif isclass(item):
apply_hooks(item)
apply_hooks(mod)
return mod
@classmethod
def add_prelude(cls, rule, advice, urgency=0):
AspectHooks.pre_rules.append((re.compile(rule), advice, urgency))
@classmethod
def add_around(cls, rule, advice, urgency=0):
AspectHooks.around_rules.append((re.compile(rule), advice, urgency))
@classmethod
def add_encore(cls, rule, advice, urgency=0):
AspectHooks.post_rules.append((re.compile(rule), advice, urgency))
def get_rules(self, methodname):
pre, around, post = list(), list(), list()
for pattern, advice, urgency in AspectHooks.pre_rules:
if pattern.match(methodname) is not None:
pre.append((advice, urgency))
for pattern, advice, urgency in AspectHooks.around_rules:
if pattern.match(methodname) is not None:
around.append((advice, urgency))
for pattern, advice, urgency in AspectHooks.post_rules:
if pattern.match(methodname) is not None:
post.append((advice, urgency))
urgencykey = lambda ad_urg_tuple: -ad_urg_tuple[1] # Negative so we get the highest urgency first
pre, around, post = sorted(pre, key=urgencykey), sorted(around, key=urgencykey), sorted(post, key=urgencykey)
advice_functions = list(map(lambda x: x[0], pre)), list(map(lambda x: x[0], around)), list(map(lambda x: x[0], post))
return advice_functions
if __name__ == "__main__":
# Activate aspects on testmod
with AspectHooks():
import testmod
# Make an encore and apply it to functions which match any name
invocations = 0
def encore(*args, **kwargs):
global invocations
invocations += 1
AspectHooks.add_encore(".*", encore)
# Quick assertions to make sure everything's kosher
assert testmod.dosomething() is None
assert testmod.double(5) is 10
assert testmod.Calc().triple(4) is 12
assert invocations == 3
def double(n):
return n*2
class Calc:
def triple(self, n):
return n * 3
def dosomething():
pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment