Pondering over the Shotgun hook framework at work today, I wrote a simple hook decorator handling pre-function execution and post-function execution events.
A simple version of a hook decorator that would invoke one or more callbacks after the main function has executed could look something like this:
class Hook(object):
def __init__(self, func):
self.callbacks = []
self.basefunc = func
def callback(self, func):
if callable(func):
try:
self.callbacks.remove(func)
except ValueError:
pass
self.callbacks.append(func)
return func
def __call__(self, *args, **kwargs):
result = self.basefunc(*args, **kwargs)
for func in self.callbacks:
newresult = func(result)
result = result if newresult is None else newresult
return result
Usage of this simple model would be pretty straight forward, like so:
# Declaring the main function as a hook
@Hook
def main_func(num):
return int(num)
@main_func.callback
def argument(num):
print('argument: {}'.format(num))
@main_func.callback
def power(num):
return num ** 2
print(main_func(4))
# argument: 4
# 16
Now to the second version of the hook decorator, supporting pre and post execution callbacks.
class Hook(object):
def __init__(self, func):
self.pre_callbacks = []
self.post_callbacks = []
self.basefunc = func
def pre(self, func):
if callable(func):
try:
self.pre_callbacks.remove(func)
except ValueError:
pass
self.pre_callbacks.append(func)
return func
def post(self, func):
if callable(func):
try:
self.post_callbacks.remove(func)
except ValueError:
pass
self.post_callbacks.append(func)
return func
def __call__(self, *args, **kwargs):
for func in self.pre_callbacks:
args, kwargs = func(*args, **kwargs)
result = self.basefunc(*args, **kwargs)
for func in self.post_callbacks:
newresult = func(result)
result = result if newresult is None else newresult
return result
The hook decorator takes an arbitrary amount of pre-execution callback(s)
as well as post-execution callback(s), which are themselves declared using
the decorator syntax. The hook will pass all the *args
and **kwargs
that it will pass to the main function to the pre
callback(s).
The result of the main function will be passed to the post
callback(s).
The fact that we can still use this hook as a Python decorator makes usage extremely simple:
# Declaring the main function as a hook
@Hook
def main_func(num):
return int(num)
# Declaring a `pre` function
@main_func.pre
def notify(*args, **kwargs):
print("args: {0}".format(args))
print("kwargs: {0}".format(kwargs))
return args, kwargs
# Declaring a `post` function
@main_func.post
def square(result):
return result ** 2
# Running the main function with the hooks
print(main_func(4))
# args: (4,)
# kwargs: {}
# 16