API: Added auto generated API documentation at /api/documentation

to implement this generator in every Path you want to document add the
following decorator:
@auto.doc()
This commit is contained in:
Sagi Dayan 2015-05-12 11:32:22 +03:00
parent 252ecbb40e
commit 278a39710a
11 changed files with 338 additions and 0 deletions

View file

@ -10,6 +10,8 @@ from flask import Flask, request, render_template, redirect, abort
# from User import User # from User import User
from flask.ext.github import GitHub from flask.ext.github import GitHub
from flask.ext.cors import CORS, cross_origin from flask.ext.cors import CORS, cross_origin
from flask.ext.autodoc import Autodoc
@ -22,6 +24,7 @@ app.config['GITHUB_CLIENT_SECRET'] = githubKeys.getSecret()
github = GitHub(app) github = GitHub(app)
cross = CORS(app) cross = CORS(app)
auto = Autodoc(app)
@app.errorhandler(404) @app.errorhandler(404)
def page_not_found(e): def page_not_found(e):
@ -31,6 +34,10 @@ def page_not_found(e):
def wellcomePage(): def wellcomePage():
return app.send_static_file('index.html') return app.send_static_file('index.html')
@app.route('/api/documentation')
def documentation():
return auto.html()
@app.route('/home') @app.route('/home')
def returnHome(): def returnHome():
try: try:
@ -41,7 +48,13 @@ def returnHome():
@app.route('/api/getUserByToken/<string:token>', methods=["GET"]) @app.route('/api/getUserByToken/<string:token>', methods=["GET"])
@auto.doc()
def getUserByToken(token): def getUserByToken(token):
'''
param: String - token: users se-Token
return: JSON object of the user
if no valid seToken, return message: No User Found
'''
query = User.all() query = User.all()
query.filter("seToken = ", token) query.filter("seToken = ", token)

View file

@ -0,0 +1,92 @@
Metadata-Version: 1.1
Name: Flask-Autodoc
Version: 0.1.1
Summary: Documentation generator for flask
Home-page: http://github.com/acoomans/flask-autodoc
Author: Arnaud Coomans
Author-email: arnaud.coomans@gmail.com
License: MIT
Description: Flask-Autodoc
=============
Flask Autodoc is a Flask extension that automatically creates documentation for your endpoints based on the routes,
function arguments and docstring.
## Install
To install Flask-Autodoc:
python setup.py install
## Usage
Start using Flask-Autodoc by importing it and initializing it:
from flask import Flask
from flask.ext.autodoc import Autodoc
app = Flask(__name__)
auto = Autodoc(app)
by default, Flask-Autodoc will only document the routes you explicitly tell him to with the _doc_ decorator,
like this:
@app.route('/user/<int:id>')
@auto.doc
def show_user(id):
"""This returns a user with a given id."""
return user_from_database(id)
to generate the documentation from an endpoint, use the _html()_ method:
@app.route('/documentation')
def documentation():
return auto.html()
if you to access the documentation without it being rendered in html:
@app.route('/documentation')
def documentation():
return auto.generate()
the documentation will then be returned as a list of rules, where each rule is a dictionary containing:
- methods: the set of allowed methods (ie ['GET', 'POST'])
- rule: relative url (ie '/user/<int:id>')
- endpoint: function name (ie 'show_user')
- doc: docstring of the function
- args: function arguments
- defaults: defaults values for the arguments
## Groups
You may want to group endpoints together, to have different documentation sets. With this you can for example, only
show some endpoints to third party developer and have full documentation for your own.
to assign an endpoint to a group, pass the name of the group as argument of the _doc_ decorator:
@app.route('/user/<int:id>')
@auto.doc("public")
def show_user(id):
to assign an endpoint to multiple groups, pass a list of group names as the _groups_ argument to _doc_:
@app.route('/user/<int:id>')
@auto.doc(groups=["public","private"])
def show_user(id):
to generate the documentation for a specific group, pass the name of the group to the _generate_ or _html_ methods:
auto.generate("public")
or
auto.html("public")
Platform: any
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Software Development :: Libraries :: Python Modules

View file

@ -0,0 +1,13 @@
MANIFEST.in
README
setup.cfg
setup.py
Flask_Autodoc.egg-info/PKG-INFO
Flask_Autodoc.egg-info/SOURCES.txt
Flask_Autodoc.egg-info/dependency_links.txt
Flask_Autodoc.egg-info/not-zip-safe
Flask_Autodoc.egg-info/requires.txt
Flask_Autodoc.egg-info/top_level.txt
flask_autodoc/__init__.py
flask_autodoc/autodoc.py
flask_autodoc/templates/autodoc_default.html

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,12 @@
../flask_autodoc/autodoc.py
../flask_autodoc/__init__.py
../flask_autodoc/templates/autodoc_default.html
../flask_autodoc/autodoc.pyc
../flask_autodoc/__init__.pyc
./
top_level.txt
SOURCES.txt
requires.txt
PKG-INFO
not-zip-safe
dependency_links.txt

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@
Flask

View file

@ -0,0 +1 @@
flask_autodoc

View file

@ -0,0 +1,2 @@
__author__ = 'arnaud'
from autodoc import Autodoc

View file

@ -0,0 +1,126 @@
import os
import re
from collections import defaultdict
from flask import current_app, render_template, render_template_string
from jinja2 import evalcontextfilter
try:
from flask import _app_ctx_stack as stack
except ImportError:
from flask import _request_ctx_stack as stack
class Autodoc(object):
def __init__(self, app=None):
self.app = app
self.groups = defaultdict(set)
if app is not None:
self.init_app(app)
def init_app(self, app):
if hasattr(app, 'teardown_appcontext'):
app.teardown_appcontext(self.teardown)
else:
app.teardown_request(self.teardown)
self.add_custom_template_filters(app)
def teardown(self, exception):
ctx = stack.top
def add_custom_template_filters(self, app):
"""Add custom filters to jinja2 templating engine"""
self.add_custom_nl2br_filters(app)
def add_custom_nl2br_filters(self, app):
"""Add a custom filter nl2br to jinja2
Replaces all newline to <BR>
"""
_paragraph_re = re.compile(r'(?:\r\n|\r|\n){3,}')
@app.template_filter()
@evalcontextfilter
def nl2br(eval_ctx, value):
result = u'\n\n'.join(u'%s' % p.replace('\n', '<br>\n') for p in _paragraph_re.split(value))
return result
def doc(self, group=None, aa=None, groups=None):
"""Decorator to add flask route to autodoc for automatic documentation\
Any route decorated with this method will be added to the list of routes to be documented by the generate() or
html() methods.
By default, the route is added to the 'all' group.
By specifying group or groups argument, the route can be added to one or multiple other groups as well, besides
the 'all' group.
"""
def decorator(f):
if groups:
groupset = set(groups)
else:
groupset = set()
if group:
groupset.add(group)
groupset.add("all")
for g in groupset:
self.groups[g].add(f)
return f
return decorator
def generate(self, group="all", groups=[], sort=None):
"""Returns a list of dict describing the routes specified by the doc() method
Each dict contains:
- methods: the set of allowed methods (ie ['GET', 'POST'])
- rule: relative url (ie '/user/<int:id>')
- endpoint: function name (ie 'show_user')
- doc: docstring of the function
- args: function arguments
- defaults: defaults values for the arguments
By specifying the group or groups arguments, only routes belonging to those groups will be returned.
Routes are sorted alphabetically based on the rule.
"""
links = []
for rule in current_app.url_map.iter_rules():
if rule.endpoint == 'static':
continue
func = current_app.view_functions[rule.endpoint]
if (groups and [True for g in groups if func in self.groups[g]]) or \
(not groups and func in self.groups[group]):
links.append(
dict(
methods = rule.methods,
rule = "%s" % rule,
endpoint = rule.endpoint,
docstring = func.__doc__,
args = list(func.func_code.co_varnames),
defaults = rule.defaults
)
)
if sort:
return sort(links)
else:
return sorted(links, cmp=lambda x,y: cmp(x['rule'], y['rule']))
def html(self, template=None, group="all", groups=None, **context):
"""Returns an html string of the routes specified by the doc() method
A template can be specified. A list of routes is available under the 'autodoc' value (refer to the documentation
for the generate() for a description of available values). If no template is specified, a default template is
used.
By specifying the group or groups arguments, only routes belonging to those groups will be returned.
"""
if template:
return render_template(template, autodoc=self.generate(group), **context)
else:
filename = os.path.dirname(__file__)+"/templates/autodoc_default.html"
with open(filename) as file:
content = file.read()
with current_app.app_context():
return render_template_string(content, autodoc=self.generate(group=group, groups=groups), **context)

View file

@ -0,0 +1,76 @@
<html>
<head>
<title>
{% if title is defined -%}
{{title}}
{% else -%}
Documentation
{% endif -%}
</title>
<style>
* {
margin: 0;
padding: 0;
font-family: Verdana, "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif;
}
body {
margin: 10px;
}
div.mapping {
margin: 20px 20px;
}
ul.methods:before { content: "Methods: "; }
ul.methods li {
display: inline;
list-style: none;
}
ul.methods li:after { content: ","; }
ul.methods li:last-child:after { content: ""; }
ul.arguments:before { content: "Arguments: "; }
ul.arguments li {
display: inline;
list-style: none;
}
ul.arguments .argument { font-style:italic }
ul.arguments .default:not(:empty):before { content: "("; }
ul.arguments .default:not(:empty):after { content: ")"; }
ul.arguments li:after { content: ","; }
ul.arguments li:last-child:after { content: ""; }
.docstring:before { content: "Description: "; }
</style>
</head>
<body>
<h1>
{% if title is defined -%}
{{title}}
{% else -%}
Documentation
{% endif -%}
</h1>
{% for doc in autodoc %}
<div class="mapping">
<a id="rule-{{doc.rule|urlencode}}" class="rule"><h2>{{doc.rule|escape}}</h2></a>
<ul class="methods">
{% for method in doc.methods -%}
<li class="method">{{method}}</li>
{% endfor %}
</ul>
<ul class="arguments">
{% for arg in doc.args %}
<li>
<span class="argument">{{arg}}</span>
<span class="default">{{doc.defaults[arg]}}</span>
</li>
{% endfor %}
</ul>
<p class="docstring">{{doc.docstring|urlize|nl2br}}</p>
</div>
{% endfor %}
</body>
</html>