I was incredibly annoyed by this issue and eventually wrote a library to solve it: decopatch.
It supports two development styles: nested (like in python decorator factories) and flat (one less level of nesting). This is how your example would be implemented in flat mode:
from decopatch import function_decorator, DECORATED
from makefun import wraps
@function_decorator
def foo_register(method_name=None, method=DECORATED):
if method_name is None:
method.gw_method = method.__name__
else:
method.gw_method = method_name
# create a signature-preserving wrapper
@wraps(method)
def wrapper(*args, **kwargs):
method(*args, **kwargs)
return wrapper
Note that I use makefun.wraps instead of functools.wraps
here so that the signature is fully preserved (the wrapper is not called at all if the arguments are invalid).
decopatch
supports an additional development style, that I call double-flat, that is dedicated to creating signature-preserving function wrappers like this one. Your example would be implemented like this:
from decopatch import function_decorator, WRAPPED, F_ARGS, F_KWARGS
@function_decorator
def foo_register(method_name=None,
method=WRAPPED, f_args=F_ARGS, f_kwargs=F_KWARGS):
# this is directly the wrapper
if method_name is None:
method.gw_method = method.__name__
else:
method.gw_method = method_name
method(*f_args, **f_kwargs)
Note that in this style, all of your code is executed in calls to method
. This might not be desirable - you might wish to perform things once at decoration time only - for this the previous style would be better.
You can check that both styles work:
@foo_register
def my_function():
print('hi...')
@foo_register('say_hi')
def my_function():
print('hi...')
Please check the documentation for details.