Code

Ticket #6700: smart_decorator.diff

File smart_decorator.diff, 3.9 KB (added by SmileyChris, 6 years ago)
Line 
1Index: django/utils/decorators.py
2===================================================================
3--- django/utils/decorators.py  (revision 7189)
4+++ django/utils/decorators.py  (working copy)
5@@ -1,11 +1,49 @@
6-"Functions that help with dynamically creating decorators for views."
7+"Functions that help with creating decorators."
8 
9 import types
10+from inspect import getargspec
11 try:
12     from functools import wraps
13 except ImportError:
14     from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
15 
16+def smart_decorator(decorator):
17+    """
18+    Smartens up "decorators" which accept additional arguments.
19+
20+    It allows the decorator to be called correctly in any of the following
21+    formats::
22+
23+        @my_smartened_decorator(arg1, arg2)
24+        my_func = my_smartened_decorator(arg1, arg2)(my_func)
25+        my_func = my_smartened_decorator(my_func, arg1, arg2)
26+
27+    If all additional decorator arguments have defaults, it also allows for the
28+    following formats::
29+
30+        @my_smartened_decorator
31+        my_func = my_smartened_decorator(my_func)
32+    """
33+    def _simple_dec(function):
34+        return decorator(function)
35+    def _dec_with_args(*args, **kwargs):
36+        def _inner_dec(function):
37+            return decorator(function, *args, **kwargs)
38+        return wraps(decorator)(_inner_dec)
39+    def _smart_dec(function_or_value, *args, **kwargs):
40+        if not isinstance(function_or_value, types.FunctionType):
41+            args = (function_or_value,) + args
42+            return _dec_with_args(*args, **kwargs)
43+        return decorator(function_or_value, *args, **kwargs)
44+    args, varargs, varkw, defaults = getargspec(decorator)
45+    assert args, 'Decorators must take at least one argument'
46+    if not varargs and not varkw:
47+        if len(args) == 1:
48+            return wraps(decorator)(_simple_dec)
49+        if len(defaults) == len(args) - 1:
50+            return wraps(decorator)(_smart_dec)
51+    return wraps(decorator)(_dec_with_args)
52+
53 def decorator_from_middleware(middleware_class):
54     """
55     Given a middleware class (not an instance), returns a view decorator. This
56Index: tests/regressiontests/utils/decorators.py
57===================================================================
58--- tests/regressiontests/utils/decorators.py   (revision 0)
59+++ tests/regressiontests/utils/decorators.py   (revision 0)
60@@ -0,0 +1,58 @@
61+r"""
62+>>> from django.utils.decorators import smart_decorator, wraps
63+
64+>>> def ping(extra_text=''):
65+...     "Simple ping method"
66+...     print extra_text or 'ping'
67+
68+>>> def my_dec(func, arg='arg1', arg2=None):
69+...     print arg
70+...     if arg2:
71+...         print 'arg2:', arg2
72+...     def _dec(*args, **kwargs):
73+...         return func(*args, **kwargs)
74+...     return wraps(func)(_dec)
75+>>> my_dec = smart_decorator(my_dec)
76+
77+# It accepts just the function
78+>>> p1 = my_dec(ping)
79+arg1
80+
81+# ... or the function and arguments
82+>>> p2 = my_dec(ping, 'two')
83+two
84+>>> p2 = my_dec(ping, arg2='two')
85+arg1
86+arg2: two
87+
88+# ... or the 2.4 syntax of (arguments)(function)
89+>>> p3 = my_dec('three')(ping)
90+three
91+
92+# In any of those cases, the actual decorated function still works fine:
93+>>> p1()
94+ping
95+>>> p1('pong')
96+pong
97+>>> p1(extra_text='pang')
98+pang
99+>>> p2()
100+ping
101+>>> p2('pong')
102+pong
103+>>> p2(extra_text='pang')
104+pang
105+>>> p3()
106+ping
107+>>> p3('pong')
108+pong
109+>>> p3(extra_text='pang')
110+pang
111+
112+>>> p1.__doc__
113+'Simple ping method'
114+>>> p2.__doc__
115+'Simple ping method'
116+>>> p3.__doc__
117+'Simple ping method'
118+"""
119Index: tests/regressiontests/utils/tests.py
120===================================================================
121--- tests/regressiontests/utils/tests.py        (revision 7189)
122+++ tests/regressiontests/utils/tests.py        (working copy)
123@@ -8,11 +8,13 @@
124 
125 import timesince
126 import datastructures
127+import decorators
128 
129 # Extra tests
130 __test__ = {
131     'timesince': timesince,
132     'datastructures': datastructures,
133+    'decorators': decorators,
134 }
135 
136 class TestUtilsHtml(TestCase):