Opened 12 years ago
Closed 12 years ago
#21513 closed Uncategorized (fixed)
method_decorator doesn't work with argumented Class-Based Decorator
| Reported by: | dpwrussell | Owned by: | nobody |
|---|---|---|---|
| Component: | Utilities | Version: | 1.6 |
| Severity: | Normal | Keywords: | decorator, functools, CBV, method_decorator |
| Cc: | Triage Stage: | Unreviewed | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
https://code.djangoproject.com/ticket/13093 was a similar example of this. decorator_from_middleware has been adapted to use available_attrs. The same treatment is needed in method_decorator:
method_decorator blindly attempts to access attributes which are not present on an object, only on a class. This causes method_decorator to fail completely if it is used on any decorator used like this (with or without actual arguments):
class MyDecorator(object):
def __init__(self, extra):
self.extra = extra
def __call__(self, f):
def wrapped(a,b):
return f(a,b) + self.extra
return update_wrapper(wrapped, f)
class C(object):
def __init__(self):
@method_decorator(MyDecorator(5))
def addStuff(self, a, b):
return a+b
c = C()
print(c.addStuff(1,2)) # Should return 1+2+5=8
Something like this would be a fix, although I'm not really familiar enough with the nuances to know for 100% if this will work in all cases. Seems to be ok though:
from functools import update_wrapper
from django.utils.decorators import available_attrs, WRAPPER_ASSIGNMENTS
def method_decorator(decorator):
"""
Converts a function decorator into a method decorator
"""
# 'func' is a function at the time it is passed to _dec, but will eventually
# be a method of the class it is defined it.
def _dec(func):
def _wrapper(self, *args, **kwargs):
@decorator
def bound_func(*args2, **kwargs2):
return func(self, *args2, **kwargs2)
# bound_func has the signature that 'decorator' expects i.e. no
# 'self' argument, but it is a closure over self so it can call
# 'func' correctly.
return bound_func(*args, **kwargs)
# In case 'decorator' adds attributes to the function it decorates, we
# want to copy those. We don't have access to bound_func in this scope,
# but we can cheat by using it on a dummy function.
@decorator
def dummy(*args, **kwargs):
pass
update_wrapper(_wrapper, dummy)
# Need to preserve any existing attributes of 'func', including the name.
update_wrapper(_wrapper, func)
return _wrapper
update_wrapper(_dec, decorator, assigned=available_attrs(decorator))
# Change the name to aid debugging.
if hasattr(decorator, '__name__'):
_dec.__name__ = 'method_decorator(%s)' % decorator.__name__
else:
_dec.__name__ = 'method_decorator(%s)' % decorator.__class__.__name__
return _dec
Change History (3)
comment:1 by , 12 years ago
comment:3 by , 12 years ago
| Resolution: | → fixed |
|---|---|
| Status: | new → closed |
FYI It seems the https://code.djangoproject.com/ticket/13879 was going down a route of adding an method_decorator_with_args decorator and I wanted to differentiate from that.