method_decorators are not regular decorators
See original GitHub issueHello,
I just spot one weird behavior about the method_decorators
feature and I want to know if it was intended or not.
This is how you normally make decorators for object’s methods:
from functools import wraps
def dec1(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
self.dec1 = True
return func(self, *args, **kwargs)
return wrapper
def dec2(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
self.dec2 = True
return func(self, *args, **kwargs)
return wrapper
class A:
@dec1
@dec2
def foo(self):
print(vars(self))
a = A()
a.foo()
# outputs: {'dec2': True, 'dec1': True}
As you can see there, you’re supposed to receive self
in the arguments. But this particular example doesn’t work at all with the method_decorators
simply because the method you receive as func
is bind to the instance object. Therefore, you don’t receive self
in the arguments and it is not possible to do something with self
.
Of course you can still get the Resource’s instance in the __self__
attribute of the function. But it will work only with the first decorator of the list. The next decorators will receive the wrapper method of each previous decorator (and those are not bind to the Resource’s instance).
Example (in Python 3):
from functools import wraps
def dec1(func):
@wraps(func)
def wrapper(**kwargs):
func.__self__.dec1 = True # ok
return func(**kwargs)
return wrapper
def dec2(func):
@wraps(func)
def wrapper(**kwargs):
func.__self__.dec2 = True # fails because func is actually the wrapper function of dec1 and doesn't have a __self__ attribute
return func(**kwargs)
return wrapper
class A(Resource):
method_decorators = [dec1, dec2]
def get(self, **kwargs):
print(vars(self))
If this behavior is not intended, it can be easily fixed by getting the class’s method and provide the self argument during the call. Here is how to do it:
class Resource(MethodView):
"""
Represents an abstract RESTful resource. (...)
"""
representations = None
method_decorators = []
def dispatch_request(self, *args, **kwargs):
meth = getattr(type(self), request.method.lower(), None) # get the class' method that is not bound to the instance self
if meth is None and request.method == 'HEAD':
meth = getattr(type(self), 'get', None) # get the class' method
assert meth is not None, 'Unimplemented method %r' % request.method
for decorator in self.method_decorators:
meth = decorator(meth) # no change, we just apply each function to the next decorator
resp = meth(self, *args, **kwargs) # call the method with the instance object in argument
(...)
Do you want a PR for this proposal?
Issue Analytics
- State:
- Created 7 years ago
- Reactions:4
- Comments:14 (2 by maintainers)
Top GitHub Comments
+1 on the bad naming, and willingness to implement @cecton 's proposal. I’ve been trying to refactor three stacked decorators called on every method of a parent Resource using the
method_decorators
with no success. (because I’m usingself
to access resource specific properties likeself.authorized_methods
). They are absolutely not equivalent to classic python decorators and it might be good to have a note about it in docs if you guys don’t want to change the current behavior.This is a real deal-breaker for me too… I’m going to have to abandon the
method_decorators
approach and manually decorate my methods.