I'm trying to implement a decorator which accepts some arguments. Usually decorators with arguments are implemented as double-nested closures, like this:
def mydecorator(param1, param2):
# do something with params
def wrapper(fn):
def actual_decorator(actual_func_arg1, actual_func_arg2):
print("I'm decorated!")
return fn(actual_func_arg1, actual_func_arg2)
return actual_decorator
return wrapper
But personally I don't like such approach because it is very unreadable and difficult to understand.
So I ended up with this:
class jsonschema_validate(object):
def __init__(self, schema):
self._schema = schema
def __call__(self, fn):
self._fn = fn
return self._decorator
def _decorator(self, req, resp, *args, **kwargs):
try:
jsonschema.validate(req.media, self._schema, format_checker=jsonschema.FormatChecker())
except jsonschema.ValidationError as e:
_log.exception('Validation failed: %r', e)
raise errors.HTTPBadRequest('Bad request')
return self._fn(req, resp, *args, **kwargs)
The idea is very simple: at instantiation time we just captures decorator args, and at call time we capture decorated function and return decorator instance's method, which is bound. It is important it to be bound because at decorator's invocation time we want to access self with all information stored in it.
Then we use it on some class:
class MyResource(object):
@jsonschema_validate(my_resource_schema)
def on_post(self, req, resp):
pass
Unfortunately, this approach doesn't work. The problem is that at decorator invocation time we looses context of decorated instance because at decoration time (when defining class) decorated method is not bound. Binding occurs later at attribute access time. But at this moment we already have decorator's bound method (jsonschema_validate._decorator) and self is passed implicitly, and it's value isn't MyResource instance, rather jsonschema_validate instance. And we don't want to loose this self value because we want to access it's attributes at decorator invocation time. In the end it results in TypeError when calling self._fn(req, resp, *args, **kwargs) with complains that "required positional argument 'resp' is missing" because passed in req arg becomes MyResource.on_post "self" and all arguments effectively "shifts".
So, is there a way implement decorator as a class rather than as a bunch of nested functions?
Note
As my first attempt of implementing decorator as simple class was failed rather quickly, I immediately reverted to nested functions. It seems like properly implemented class approach is even more unreadable and tangled, but I want to find solution anyway for the fun of the thing.
UPDATE
Finally found solution, see my own answer.