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
""" _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', '
\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/') - 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)