Skip to content

Commit b1b26ff

Browse files
Added custom arguments to the doc decorator.
Existing properties of routes can be overridden by defining them using named arguments to @autodoc.doc; the only exceptions being rule and endpoint. As of now, custom arguments shown using the default template are in raw format. Some formatting touch-ups Added unit tests for custom parameters. Shortened some of the unittest names a little bit Fixed up some indentations in the default template Updated templates to include the location stuff Reset default template to bare defaults Resolve conflict for: Fix autoescaping in custom template example Added custom properties to the custom example Removed the unittest for HTML w/ custom params There are far too many complications with importing an external template from within our unittest module, and using anything besides the default template is showing itself as being absurdly complicated. In order to comply to the request of removing any custom param stuff from the default template, I am just going to remove this unittest in order to get it passing again without needing to do some pretty crazy roundabout stuff.
1 parent 1dd61ec commit b1b26ff

File tree

4 files changed

+121
-31
lines changed

4 files changed

+121
-31
lines changed

examples/custom/blog.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,10 @@ def get_post(id):
5757

5858

5959
@app.route('/post', methods=["POST"])
60-
@auto.doc(groups=['posts', 'private'])
60+
@auto.doc(groups=['posts', 'private'],
61+
form_data=['title', 'content', 'authorid'])
6162
def post_post():
62-
"""Create a new post.
63-
Form Data: title, content, authorid.
64-
"""
63+
"""Create a new post."""
6564
authorid = request.form.get('authorid', None)
6665
Post(request.form['title'],
6766
request.form['content'],
@@ -84,11 +83,10 @@ def get_user(id):
8483

8584

8685
@app.route('/users', methods=['POST'])
87-
@auto.doc(groups=['users', 'private'])
86+
@auto.doc(groups=['users', 'private'],
87+
form_data=['username'])
8888
def post_user(id):
89-
"""Creates a new user.
90-
Form Data: username.
91-
"""
89+
"""Creates a new user."""
9290
User(request.form['username'])
9391
redirect('/users')
9492

examples/custom/templates/autodoc_custom.html

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
margin: 20px 20px;
2323
}
2424

25-
.location { font-style: italic;}
25+
.location { font-style: italic; }
2626

2727
ul.methods:before { content: "Methods: "; }
2828
ul.methods li {
@@ -43,6 +43,11 @@
4343
ul.arguments li:after { content: ","; }
4444
ul.arguments li:last-child:after { content: ""; }
4545

46+
ul.property li {
47+
display: inline;
48+
list-style: none;
49+
}
50+
4651
.docstring:before { content: "Description: "; }
4752
</style>
4853
</head>
@@ -60,9 +65,9 @@ <h1>
6065
<a id="rule-{{doc.rule|urlencode}}" class="rule"><h2>{{doc.rule|escape}}</h2></a>
6166
<p class="location">{{doc.location.filename}}:{{doc.location.line}}</p>
6267
<ul class="methods">
63-
{% for method in doc.methods -%}
64-
<li class="method">{{method}}</li>
65-
{% endfor %}
68+
{% for method in doc.methods -%}
69+
<li class="method">{{method}}</li>
70+
{% endfor %}
6671
</ul>
6772
<ul class="arguments">
6873
{% for arg in doc.args %}
@@ -72,6 +77,11 @@ <h1>
7277
</li>
7378
{% endfor %}
7479
</ul>
80+
<ul class="property">
81+
{% for prop in doc if prop not in defaults %}
82+
<li class="property">{{prop}}:{{doc[prop]}}</br></li>
83+
{% endfor %}
84+
</ul>
7585
<p class="docstring">{% autoescape false %}{{doc.docstring|urlize|nl2br}}{% endautoescape %}</p>
7686
</div>
7787
{% endfor %}

flask_autodoc/autodoc.py

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ class Autodoc(object):
2626
def __init__(self, app=None):
2727
self.app = app
2828
self.func_groups = defaultdict(set)
29+
self.func_props = defaultdict()
30+
self.immutable_props = ['rule', 'endpoint']
31+
self.default_props = ['methods', 'docstring',
32+
'args', 'defaults', 'location'] + self.immutable_props
2933
self.func_locations = defaultdict(dict)
3034
if app is not None:
3135
self.init_app(app)
@@ -57,7 +61,7 @@ def nl2br(eval_ctx, value):
5761
for p in _paragraph_re.split(value))
5862
return result
5963

60-
def doc(self, groups=None):
64+
def doc(self, groups=None, **properties):
6165
"""Add flask route to autodoc for automatic documentation
6266
6367
Any route decorated with this method will be added to the list of
@@ -66,6 +70,14 @@ def doc(self, groups=None):
6670
By default, the route is added to the 'all' group.
6771
By specifying group or groups argument, the route can be added to one
6872
or multiple other groups as well, besides the 'all' group.
73+
74+
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
76+
the generare() function, they will be added to the route's properties,
77+
which can be accessed from the template.
78+
79+
If a parameter is passed in with a name that is already in the dict, but
80+
not of a reserved name, the passed parameter overrides that dict value.
6981
"""
7082
def decorator(f):
7183
# Set group[s]
@@ -77,6 +89,7 @@ def decorator(f):
7789
groupset.add(groups)
7890
groupset.add('all')
7991
self.func_groups[f] = groupset
92+
self.func_props[f] = properties
8093

8194
# Set location
8295
caller_frame = inspect.stack()[1]
@@ -120,20 +133,24 @@ def generate(self, groups='all', sort=None):
120133
func = current_app.view_functions[rule.endpoint]
121134
arguments = rule.arguments if rule.arguments else ['None']
122135
func_groups = self.func_groups[func]
136+
func_props = self.func_props[func] if func in self.func_props \
137+
else {}
123138
location = self.func_locations.get(func, None)
124139

125140
if func_groups.intersection(groups_to_generate):
126-
links.append(
127-
dict(
128-
methods=rule.methods,
129-
rule="%s" % rule,
130-
endpoint=rule.endpoint,
131-
docstring=func.__doc__,
132-
args=arguments,
133-
defaults=rule.defaults,
134-
location=location,
135-
)
141+
props = dict(
142+
methods=rule.methods,
143+
rule="%s" % rule,
144+
endpoint=rule.endpoint,
145+
docstring=func.__doc__,
146+
args=arguments,
147+
defaults=rule.defaults,
148+
location=location,
136149
)
150+
for p in func_props:
151+
if p not in self.immutable_props:
152+
props[p] = func_props[p]
153+
links.append(props)
137154
if sort:
138155
return sort(links)
139156
else:
@@ -150,10 +167,12 @@ def html(self, groups='all', template=None, **context):
150167
By specifying the group or groups arguments, only routes belonging to
151168
those groups will be returned.
152169
"""
170+
context['autodoc'] = context['autodoc'] if 'autodoc' in context \
171+
else self.generate(groups=groups)
172+
context['defaults'] = context['defaults'] if 'defaults' in context \
173+
else self.default_props
153174
if template:
154-
return render_template(template,
155-
autodoc=self.generate(groups=groups),
156-
**context)
175+
return render_template(template, **context)
157176
else:
158177
filename = os.path.join(
159178
os.path.dirname(__file__),
@@ -163,7 +182,4 @@ def html(self, groups='all', template=None, **context):
163182
with open(filename) as file:
164183
content = file.read()
165184
with current_app.app_context():
166-
return render_template_string(
167-
content,
168-
autodoc=self.generate(groups=groups),
169-
**context)
185+
return render_template_string(content, **context)

tests/test_autodoc.py

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import unittest
22
import sys
3+
import os
34

45
from flask import Flask
56
from flask.ext.autodoc import Autodoc
@@ -104,7 +105,6 @@ def pub():
104105
self.assertIn('/pub', doc[0]['rule'])
105106

106107
def testGroups(self):
107-
108108
@self.app.route('/a')
109109
@self.autodoc.doc()
110110
def a():
@@ -143,6 +143,71 @@ def c():
143143
self.assertIn('/b', rules)
144144
self.assertIn('/c', rules)
145145

146+
def testCustomParams(self):
147+
@self.app.route('/needsargs', methods=['GET'])
148+
@self.autodoc.doc('needs_getargs', getargs={
149+
'a': 'A Value',
150+
'b': 'B Value'
151+
})
152+
def getit():
153+
return 'I need specific GET parameters.'
154+
155+
@self.app.route('/noargs')
156+
@self.autodoc.doc(groups=['needs_json', 'noargs'],
157+
expected_type='application/json')
158+
def needjson():
159+
return 'I do not need any parameters, but am picky about types.'
160+
161+
with self.app.app_context():
162+
doc = self.autodoc.generate('needs_getargs')
163+
self.assertTrue(len(doc) == 1)
164+
self.assertIn('getargs', doc[0])
165+
self.assertEqual('B Value', doc[0]['getargs']['b'])
166+
167+
doc = self.autodoc.generate('noargs')
168+
self.assertTrue(len(doc) == 1)
169+
self.assertNotIn('getargs', doc[0])
170+
171+
doc = self.autodoc.generate('needs_json')
172+
self.assertTrue(len(doc) == 1)
173+
self.assertIn('expected_type', doc[0])
174+
self.assertEqual('application/json', doc[0]['expected_type'])
175+
176+
def testOverrideParams(self):
177+
@self.app.route('/added')
178+
@self.autodoc.doc('add', args=['option'])
179+
def original():
180+
return 'I make my own options.'
181+
182+
@self.app.route('/modified', defaults={'option1': 1})
183+
@self.app.route('/modified/<int:option1>')
184+
@self.autodoc.doc('modify', args=['option2'], defaults=[2])
185+
def override_allowed(option1):
186+
return 'I modify my own options.'
187+
188+
@self.app.route('/prohibited')
189+
@self.autodoc.doc('fail', rule='/not/supposed/to/be/here')
190+
def override_prohibited():
191+
return 'I make my own rules.'
192+
193+
with self.app.app_context():
194+
doc = self.autodoc.generate('add')
195+
self.assertTrue(len(doc) == 1)
196+
self.assertIn('option', doc[0]['args'])
197+
198+
doc = self.autodoc.generate('modify')
199+
args = [doc[i]['args'] for i in range(len(doc))]
200+
defaults = [doc[i]['defaults'] for i in range(len(doc))]
201+
self.assertNotIn(['option1'], args)
202+
self.assertNotIn([1], defaults)
203+
self.assertIn(['option2'], args)
204+
self.assertIn([2], defaults)
205+
206+
doc = self.autodoc.generate('fail')
207+
self.assertTrue(len(doc) == 1)
208+
self.assertNotEqual('/not/supposed/to/be/here', doc[0]['rule'])
209+
self.assertEqual('/prohibited', doc[0]['rule'])
210+
146211
def testHTML(self):
147212
@self.app.route('/')
148213
@self.autodoc.doc()
@@ -182,3 +247,4 @@ def ab(param1, param2):
182247
'\/p1\/.*string:param1.*\/p2\/.*int:param2.*'
183248
)
184249
self.assertIn('Returns arguments', doc)
250+

0 commit comments

Comments
 (0)