Opened 11 years ago
Closed 11 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 , 11 years ago
comment:3 by , 11 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.