Skip to content

Commit 694a345

Browse files
committed
Merge pull request #19 from lukeyeager/redecorate
Allow a function to be decorated twice
2 parents 36f4b03 + ada0468 commit 694a345

File tree

2 files changed

+74
-14
lines changed

2 files changed

+74
-14
lines changed

flask_autodoc/autodoc.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def nl2br(eval_ctx, value):
6161
for p in _paragraph_re.split(value))
6262
return result
6363

64-
def doc(self, groups=None, **properties):
64+
def doc(self, groups=None, set_location=True, **properties):
6565
"""Add flask route to autodoc for automatic documentation
6666
6767
Any route decorated with this method will be added to the list of
@@ -71,32 +71,41 @@ def doc(self, groups=None, **properties):
7171
By specifying group or groups argument, the route can be added to one
7272
or multiple other groups as well, besides the 'all' group.
7373
74+
If set_location is True, the location of the function will be stored.
75+
NOTE: this assumes that the decorator is placed just before the
76+
function (in the normal way).
77+
7478
Custom parameters may also be passed in beyond groups, if they are
75-
named something not already in the dict descibed in the docstring for
79+
named something not already in the dict descibed in the docstring for
7680
the generare() function, they will be added to the route's properties,
7781
which can be accessed from the template.
7882
7983
If a parameter is passed in with a name that is already in the dict, but
8084
not of a reserved name, the passed parameter overrides that dict value.
8185
"""
8286
def decorator(f):
83-
# Set group[s]
84-
if type(groups) is list:
85-
groupset = set(groups)
87+
# Get previous group list (if any)
88+
if f in self.func_groups:
89+
groupset = self.func_groups[f]
8690
else:
8791
groupset = set()
88-
if type(groups) is str:
89-
groupset.add(groups)
92+
93+
# Set group[s]
94+
if type(groups) is list:
95+
groupset.update(groups)
96+
elif type(groups) is str:
97+
groupset.add(groups)
9098
groupset.add('all')
9199
self.func_groups[f] = groupset
92100
self.func_props[f] = properties
93101

94102
# Set location
95-
caller_frame = inspect.stack()[1]
96-
self.func_locations[f] = {
97-
'filename': caller_frame[1],
98-
'line': caller_frame[2],
99-
}
103+
if set_location:
104+
caller_frame = inspect.stack()[1]
105+
self.func_locations[f] = {
106+
'filename': caller_frame[1],
107+
'line': caller_frame[2],
108+
}
100109

101110
return f
102111
return decorator

tests/test_autodoc.py

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import inspect
2+
import os.path
13
import unittest
24
import sys
35
import os
@@ -13,6 +15,14 @@ def setUp(self):
1315
self.app.debug = True
1416
self.autodoc = Autodoc(self.app)
1517

18+
@staticmethod
19+
def thisFile():
20+
"""Returns the basename of __file__ without a trailing 'c'"""
21+
filename = os.path.basename(__file__)
22+
if filename.endswith('.pyc'):
23+
filename = filename[:-1]
24+
return filename
25+
1626
def testGet(self):
1727
@self.app.route('/')
1828
@self.autodoc.doc()
@@ -30,7 +40,7 @@ def index():
3040
self.assertEqual(d['endpoint'], 'index')
3141
self.assertEqual(d['docstring'], 'Returns a hello world message')
3242
self.assertIsInstance(d['location']['line'], int)
33-
self.assertIn(__file__, d['location']['filename'])
43+
self.assertIn(self.thisFile(), d['location']['filename'])
3444
self.assertFalse(d['defaults'])
3545

3646
def testPost(self):
@@ -50,7 +60,7 @@ def index():
5060
self.assertEqual(d['endpoint'], 'index')
5161
self.assertEqual(d['docstring'], 'Returns a hello world message')
5262
self.assertIsInstance(d['location']['line'], int)
53-
self.assertIn(__file__, d['location']['filename'])
63+
self.assertIn(self.thisFile(), d['location']['filename'])
5464
self.assertFalse(d['defaults'])
5565

5666
def testParams(self):
@@ -248,3 +258,44 @@ def ab(param1, param2):
248258
)
249259
self.assertIn('Returns arguments', doc)
250260

261+
def testLocation(self):
262+
line_no = inspect.stack()[0][2] + 2 # the doc() line
263+
@self.app.route('/location')
264+
@self.autodoc.doc()
265+
def location():
266+
return 'location'
267+
268+
with self.app.app_context():
269+
doc = self.autodoc.generate()
270+
d = doc[0]
271+
self.assertIsInstance(d['location']['line'], int)
272+
self.assertEqual(d['location']['line'], line_no)
273+
self.assertIn(self.thisFile(), d['location']['filename'])
274+
275+
def testNoLocation(self):
276+
@self.app.route('/location')
277+
@self.autodoc.doc(set_location=False)
278+
def location():
279+
return 'location'
280+
281+
with self.app.app_context():
282+
doc = self.autodoc.generate()
283+
d = doc[0]
284+
self.assertIsNone(d['location'])
285+
286+
def testRedecorate(self):
287+
@self.app.route('/redecorate')
288+
# add to "all" and "group1"
289+
@self.autodoc.doc('group1')
290+
def redecorate():
291+
return 'redecorate'
292+
293+
# add to "group2"
294+
self.app.view_functions['redecorate'] = self.autodoc.doc('group2')(redecorate)
295+
296+
with self.app.app_context():
297+
self.assertTrue(1 == len(self.autodoc.generate('all')))
298+
self.assertTrue(1 == len(self.autodoc.generate('group1')))
299+
self.assertTrue(1 == len(self.autodoc.generate('group2')))
300+
self.assertFalse(1 == len(self.autodoc.generate('group3')))
301+

0 commit comments

Comments
 (0)