Skip to content

Commit 686be6e

Browse files
committed
Allows decorator list to apply to memembers prior to response generation. Adds decorator list testing
1 parent edaa910 commit 686be6e

File tree

3 files changed

+196
-4
lines changed

3 files changed

+196
-4
lines changed

flask_classful.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,21 @@ def make_proxy_method(cls, name):
180180
i = cls()
181181
view = getattr(i, name)
182182

183+
# Since the view is a bound instance method, first make it an actual function
184+
# So function attributes work correctly
185+
def make_func(fn):
186+
@functools.wraps(fn)
187+
def inner(*args, **kwargs):
188+
return fn(*args, **kwargs)
189+
return inner
190+
view = make_func(view)
191+
192+
# Now apply the class decorator list in reverse order
193+
# to match memeber decorator order
194+
if cls.decorators:
195+
for decorator in reversed(cls.decorators):
196+
view = decorator(view)
197+
183198
@functools.wraps(view)
184199
def proxy(**forgettable_view_args):
185200
# Always use the global request object's view_args, because they
@@ -234,10 +249,6 @@ def proxy(**forgettable_view_args):
234249

235250
return response
236251

237-
# Decorate the proxy which is a function rather than the view which is a bound instance method
238-
if cls.decorators:
239-
for decorator in cls.decorators:
240-
proxy = decorator(proxy)
241252

242253
return proxy
243254

test_classful/test_decorators.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
from flask import Flask, url_for
22
from .view_classes import DecoratedView
3+
from .view_classes import DecoratedBoldListView
4+
from .view_classes import DecoratedBoldItalicsListView
5+
from .view_classes import DecoratedListMemberView
6+
from .view_classes import DecoratedListFunctionAttributesView
7+
from .view_classes import DecoratedListMemberFunctionAttributesView
38
from nose.tools import *
49

510
app = Flask("decorated")
611
DecoratedView.register(app)
12+
DecoratedBoldListView.register(app)
13+
DecoratedBoldItalicsListView.register(app)
14+
DecoratedListMemberView.register(app)
15+
DecoratedListFunctionAttributesView.register(app)
16+
DecoratedListMemberFunctionAttributesView.register(app)
717
client = app.test_client()
818

919

@@ -51,9 +61,80 @@ def test_params_decorator():
5161
resp = client.get('/decorated/params_decorator_method/')
5262
eq_(b"Params Decorator", resp.data)
5363

64+
5465
def test_params_decorator_delete():
5566
resp = client.delete('/decorated/1234')
5667
eq_(b"Params Decorator Delete 1234", resp.data)
5768

5869

70+
def test_decorator_bold_list_get():
71+
resp = client.get('/decorated_bold_list_view/1234')
72+
ok_(b'<b>' in resp.data)
73+
ok_(b'</b>' in resp.data)
74+
75+
76+
def test_decorator_bold_list_index():
77+
resp = client.get('/decorated_bold_list_view/')
78+
ok_(b'<b>' in resp.data)
79+
ok_(b'</b>' in resp.data)
80+
81+
82+
def test_decorator_bold_italics_list_get():
83+
resp = client.get('/decorated_bold_italics_list_view/1234')
84+
ok_(b'<i>' in resp.data)
85+
ok_(b'</i>' in resp.data)
86+
ok_(b'<b>' in resp.data)
87+
ok_(b'</b>' in resp.data)
88+
89+
90+
def test_decorator_bold_italics_list_index():
91+
resp = client.get('/decorated_bold_italics_list_view/')
92+
ok_(b'<i>' in resp.data)
93+
ok_(b'</i>' in resp.data)
94+
ok_(b'<b>' in resp.data)
95+
ok_(b'</b>' in resp.data)
96+
97+
98+
def test_decorator_list_member_index():
99+
resp = client.get('/decorated_list_member_view/')
100+
ok_(b'<i>' in resp.data)
101+
ok_(b'</i>' in resp.data)
102+
ok_(b'<b>' in resp.data)
103+
ok_(b'</b>' in resp.data)
104+
ok_(b'<p>' not in resp.data)
105+
ok_(b'</p>' not in resp.data)
106+
107+
108+
def test_decorator_list_member_get():
109+
resp = client.get('/decorated_list_member_view/1234')
110+
111+
# The order should match how functions are decorated
112+
eq_(b'<b>', resp.data[:3])
113+
eq_(b'<i>', resp.data[3:6])
114+
eq_(b'<p>', resp.data[6:9])
115+
eq_(b'</p>', resp.data[-12:-8])
116+
eq_(b'</i>', resp.data[-8:-4])
117+
eq_(b'</b>', resp.data[-4:])
118+
119+
120+
# Verify list of decorators with attributes modify all functions in FlaskView
121+
def test_decorator_list_function_attributes_get():
122+
ok_(hasattr(app.view_functions['DecoratedListFunctionAttributesView:get'], '_eggs'))
123+
eq_('scrambled', app.view_functions['DecoratedListFunctionAttributesView:get']._eggs)
124+
125+
126+
# Verify list of decorators with attributes modify all functions in FlaskView
127+
def test_decorator_list_function_attributes_index():
128+
ok_(hasattr(app.view_functions['DecoratedListFunctionAttributesView:index'], '_eggs'))
129+
eq_('scrambled', app.view_functions['DecoratedListFunctionAttributesView:index']._eggs)
130+
131+
132+
# Verify decorator with attributes does not modify other members
133+
def test_decorator_list_member_function_attributes_get():
134+
eq_(hasattr(app.view_functions['DecoratedListMemberFunctionAttributesView:get'], '_eggs'), False)
135+
59136

137+
# Verify decorator with attributes modify decorated memeber functions
138+
def test_decorator_list_member_function_attributes_index():
139+
eq_(hasattr(app.view_functions['DecoratedListMemberFunctionAttributesView:index'], '_eggs'), True)
140+
eq_('scrambled', app.view_functions['DecoratedListMemberFunctionAttributesView:index']._eggs)

test_classful/view_classes.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from flask import Response
12
from flask_classful import FlaskView, route
23
from functools import wraps
34

@@ -334,4 +335,103 @@ def index(self):
334335
return "Index"
335336

336337

338+
def make_bold_decorator(fn):
339+
@wraps(fn)
340+
def inner(*args, **kwargs):
341+
return '<b>' + fn(*args, **kwargs) + '</b>'
342+
return inner
337343

344+
345+
def make_italics_decorator(fn):
346+
@wraps(fn)
347+
def inner(*args, **kwargs):
348+
return '<i>' + fn(*args, **kwargs) + '</i>'
349+
return inner
350+
351+
352+
def make_paragraph_decorator(fn):
353+
@wraps(fn)
354+
def inner(*args, **kwargs):
355+
return '<p>' + fn(*args, **kwargs) + '</p>'
356+
return inner
357+
358+
359+
class DecoratedBoldListView(FlaskView):
360+
route_base = '/decorated_bold_list_view/'
361+
decorators = [make_bold_decorator]
362+
363+
def get(self, id):
364+
return 'Get %s'%id
365+
366+
def index(self):
367+
return 'Index'
368+
369+
370+
class DecoratedBoldItalicsListView(FlaskView):
371+
route_base = '/decorated_bold_italics_list_view/'
372+
decorators = [make_bold_decorator, make_italics_decorator]
373+
374+
def get(self, id):
375+
return 'Get %s'%id
376+
377+
def index(self):
378+
return 'Index'
379+
380+
381+
class DecoratedListMemberView(FlaskView):
382+
route_base = '/decorated_list_member_view/'
383+
decorators = [
384+
# Third Decorator
385+
make_bold_decorator,
386+
387+
# Second Decorator
388+
make_italics_decorator
389+
]
390+
391+
# First decorator
392+
@make_paragraph_decorator
393+
def get(self, id):
394+
return 'Get %s'%id
395+
396+
def index(self):
397+
return 'Index'
398+
399+
400+
def eggs_attribute_decorator(eggs_style):
401+
def decorator(f):
402+
# Apply the style to the function
403+
f._eggs = eggs_style
404+
405+
@wraps(f)
406+
def decorated_function(*args, **kwargs):
407+
return f(*args, **kwargs)
408+
return decorated_function
409+
return decorator
410+
411+
412+
class DecoratedListFunctionAttributesView(FlaskView):
413+
route_base = '/decorated_list_function_attributes_view/'
414+
decorators = [
415+
make_italics_decorator,
416+
eggs_attribute_decorator('scrambled')
417+
]
418+
419+
@make_bold_decorator
420+
def get(self, id):
421+
return 'Get %s'%id
422+
423+
def index(self):
424+
return 'Index'
425+
426+
427+
class DecoratedListMemberFunctionAttributesView(FlaskView):
428+
route_base = '/decorated_list_member_function_attributes_view/'
429+
decorators = [make_italics_decorator]
430+
431+
@make_bold_decorator
432+
def get(self, id):
433+
return 'Get %s'%id
434+
435+
@eggs_attribute_decorator('scrambled')
436+
def index(self):
437+
return 'Index'

0 commit comments

Comments
 (0)