Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fixed a bug with method_decorator not preserving the attributes of th…

…e wrapped method, which is important for decorators like csrf_exempt

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14311 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 5864834fa5cafb4705e42356b07054f450af8cc4 1 parent df0bb3c
@spookylukey spookylukey authored
View
17 django/utils/decorators.py
@@ -11,15 +11,28 @@ 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 decorator(bound_func)(*args, **kwargs)
- return wraps(func)(_wrapper)
+ 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)
# Change the name to aid debugging.
_dec.__name__ = 'method_decorator(%s)' % decorator.__name__
View
48 tests/regressiontests/decorators/tests.py
@@ -126,14 +126,60 @@ def wrapper(arg):
simple_dec_m = method_decorator(simple_dec)
+# For testing method_decorator, two decorators that add an attribute to the function
+def myattr_dec(func):
+ def wrapper(*args, **kwargs):
+ return func(*args, **kwargs)
+ wrapper.myattr = True
+ return wraps(func)(wrapper)
+
+myattr_dec_m = method_decorator(myattr_dec)
+
+
+def myattr2_dec(func):
+ def wrapper(*args, **kwargs):
+ return func(*args, **kwargs)
+ wrapper.myattr2 = True
+ return wraps(func)(wrapper)
+
+myattr2_dec_m = method_decorator(myattr2_dec)
+
+
class MethodDecoratorTests(TestCase):
"""
Tests for method_decorator
"""
- def test_method_decorator(self):
+ def test_preserve_signature(self):
class Test(object):
@simple_dec_m
def say(self, arg):
return arg
self.assertEqual("test:hello", Test().say("hello"))
+
+ def test_preserve_attributes(self):
+ # Sanity check myattr_dec and myattr2_dec
+ @myattr_dec
+ @myattr2_dec
+ def func():
+ pass
+
+ self.assertEqual(getattr(func, 'myattr', False), True)
+ self.assertEqual(getattr(func, 'myattr2', False), True)
+
+ # Now check method_decorator
+ class Test(object):
+ @myattr_dec_m
+ @myattr2_dec_m
+ def method(self):
+ "A method"
+ pass
+
+ self.assertEqual(getattr(Test().method, 'myattr', False), True)
+ self.assertEqual(getattr(Test().method, 'myattr2', False), True)
+
+ self.assertEqual(getattr(Test.method, 'myattr', False), True)
+ self.assertEqual(getattr(Test.method, 'myattr2', False), True)
+
+ self.assertEqual(Test.method.__doc__, 'A method')
+ self.assertEqual(Test.method.im_func.__name__, 'method')
Please sign in to comment.
Something went wrong with that request. Please try again.