Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
Luke Plant authored October 21, 2010
17  django/utils/decorators.py
@@ -11,15 +11,28 @@ def method_decorator(decorator):
11 11
     """
12 12
     Converts a function decorator into a method decorator
13 13
     """
  14
+    # 'func' is a function at the time it is passed to _dec, but will eventually
  15
+    # be a method of the class it is defined it.
14 16
     def _dec(func):
15 17
         def _wrapper(self, *args, **kwargs):
  18
+            @decorator
16 19
             def bound_func(*args2, **kwargs2):
17 20
                 return func(self, *args2, **kwargs2)
18 21
             # bound_func has the signature that 'decorator' expects i.e.  no
19 22
             # 'self' argument, but it is a closure over self so it can call
20 23
             # 'func' correctly.
21  
-            return decorator(bound_func)(*args, **kwargs)
22  
-        return wraps(func)(_wrapper)
  24
+            return bound_func(*args, **kwargs)
  25
+        # In case 'decorator' adds attributes to the function it decorates, we
  26
+        # want to copy those. We don't have access to bound_func in this scope,
  27
+        # but we can cheat by using it on a dummy function.
  28
+        @decorator
  29
+        def dummy(*args, **kwargs):
  30
+            pass
  31
+        update_wrapper(_wrapper, dummy)
  32
+        # Need to preserve any existing attributes of 'func', including the name.
  33
+        update_wrapper(_wrapper, func)
  34
+
  35
+        return _wrapper
23 36
     update_wrapper(_dec, decorator)
24 37
     # Change the name to aid debugging.
25 38
     _dec.__name__ = 'method_decorator(%s)' % decorator.__name__
48  tests/regressiontests/decorators/tests.py
@@ -126,14 +126,60 @@ def wrapper(arg):
126 126
 simple_dec_m = method_decorator(simple_dec)
127 127
 
128 128
 
  129
+# For testing method_decorator, two decorators that add an attribute to the function
  130
+def myattr_dec(func):
  131
+    def wrapper(*args, **kwargs):
  132
+        return func(*args, **kwargs)
  133
+    wrapper.myattr = True
  134
+    return wraps(func)(wrapper)
  135
+
  136
+myattr_dec_m = method_decorator(myattr_dec)
  137
+
  138
+
  139
+def myattr2_dec(func):
  140
+    def wrapper(*args, **kwargs):
  141
+        return func(*args, **kwargs)
  142
+    wrapper.myattr2 = True
  143
+    return wraps(func)(wrapper)
  144
+
  145
+myattr2_dec_m = method_decorator(myattr2_dec)
  146
+
  147
+
129 148
 class MethodDecoratorTests(TestCase):
130 149
     """
131 150
     Tests for method_decorator
132 151
     """
133  
-    def test_method_decorator(self):
  152
+    def test_preserve_signature(self):
134 153
         class Test(object):
135 154
             @simple_dec_m
136 155
             def say(self, arg):
137 156
                 return arg
138 157
 
139 158
         self.assertEqual("test:hello", Test().say("hello"))
  159
+
  160
+    def test_preserve_attributes(self):
  161
+        # Sanity check myattr_dec and myattr2_dec
  162
+        @myattr_dec
  163
+        @myattr2_dec
  164
+        def func():
  165
+            pass
  166
+
  167
+        self.assertEqual(getattr(func, 'myattr', False), True)
  168
+        self.assertEqual(getattr(func, 'myattr2', False), True)
  169
+
  170
+        # Now check method_decorator
  171
+        class Test(object):
  172
+            @myattr_dec_m
  173
+            @myattr2_dec_m
  174
+            def method(self):
  175
+                "A method"
  176
+                pass
  177
+
  178
+        self.assertEqual(getattr(Test().method, 'myattr', False), True)
  179
+        self.assertEqual(getattr(Test().method, 'myattr2', False), True)
  180
+
  181
+        self.assertEqual(getattr(Test.method, 'myattr', False), True)
  182
+        self.assertEqual(getattr(Test.method, 'myattr2', False), True)
  183
+
  184
+        self.assertEqual(Test.method.__doc__, 'A method')
  185
+        self.assertEqual(Test.method.im_func.__name__, 'method')

0 notes on commit 5864834

Please sign in to comment.
Something went wrong with that request. Please try again.