flask_base.py (7444B)
1 """ 2 sphinxcontrib.autohttp.flask 3 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 5 The sphinx.ext.autodoc-style HTTP API reference builder (from Flask) 6 for sphinxcontrib.httpdomain. 7 8 :copyright: Copyright 2011 by Hong Minhee 9 :license: BSD, see LICENSE for details. 10 11 """ 12 13 import re 14 import itertools 15 import six 16 17 from docutils import nodes 18 from docutils.parsers.rst import directives 19 from docutils.statemachine import ViewList 20 21 from sphinx.util import force_decode 22 from sphinx.util.compat import Directive 23 from sphinx.util.nodes import nested_parse_with_titles 24 from sphinx.util.docstrings import prepare_docstring 25 from sphinx.pycode import ModuleAnalyzer 26 27 from sphinxcontrib import httpdomain 28 from sphinxcontrib.autohttp.common import http_directive, import_object 29 30 31 def translate_werkzeug_rule(rule): 32 from werkzeug.routing import parse_rule 33 buf = six.StringIO() 34 for conv, arg, var in parse_rule(rule): 35 if conv: 36 buf.write('(') 37 if conv != 'default': 38 buf.write(conv) 39 buf.write(':') 40 buf.write(var) 41 buf.write(')') 42 else: 43 buf.write(var) 44 return buf.getvalue() 45 46 47 def get_routes(app, endpoint=None, order=None): 48 endpoints = [] 49 for rule in app.url_map.iter_rules(endpoint): 50 url_with_endpoint = ( 51 six.text_type(next(app.url_map.iter_rules(rule.endpoint))), 52 rule.endpoint 53 ) 54 if url_with_endpoint not in endpoints: 55 endpoints.append(url_with_endpoint) 56 if order == 'path': 57 endpoints.sort() 58 endpoints = [e for _, e in endpoints] 59 for endpoint in endpoints: 60 methodrules = {} 61 for rule in app.url_map.iter_rules(endpoint): 62 methods = rule.methods.difference(['OPTIONS', 'HEAD']) 63 path = translate_werkzeug_rule(rule.rule) 64 for method in methods: 65 if method in methodrules: 66 methodrules[method].append(path) 67 else: 68 methodrules[method] = [path] 69 for method, paths in methodrules.items(): 70 yield method, paths, endpoint 71 72 73 def quickref_directive(method, path, content): 74 rcomp = re.compile("^\s*.. :quickref:\s*(?P<quick>.*)$") 75 method = method.lower().strip() 76 if isinstance(content, six.string_types): 77 content = content.splitlines() 78 description="" 79 name="" 80 ref = path.replace("<","(").replace(">",")").replace("/","-").replace(":","-") 81 for line in content: 82 qref = rcomp.match(line) 83 if qref: 84 quickref = qref.group("quick") 85 parts = quickref.split(";",1) 86 if len(parts)>1: 87 name = parts[0] 88 description= parts[1] 89 else: 90 description= quickref 91 break 92 93 row ={} 94 row['name'] = name 95 row['operation'] = ' - `%s %s <#%s-%s>`_' % (method.upper(), path, method.lower(), ref) 96 row['description'] = description 97 98 return row 99 100 class AutoflaskBase(Directive): 101 102 has_content = True 103 required_arguments = 1 104 option_spec = {'endpoints': directives.unchanged, 105 'blueprints': directives.unchanged, 106 'modules': directives.unchanged, 107 'order': directives.unchanged, 108 'undoc-endpoints': directives.unchanged, 109 'undoc-blueprints': directives.unchanged, 110 'undoc-modules': directives.unchanged, 111 'undoc-static': directives.unchanged, 112 'include-empty-docstring': directives.unchanged} 113 114 @property 115 def endpoints(self): 116 endpoints = self.options.get('endpoints', None) 117 if not endpoints: 118 return None 119 return re.split(r'\s*,\s*', endpoints) 120 121 @property 122 def undoc_endpoints(self): 123 undoc_endpoints = self.options.get('undoc-endpoints', None) 124 if not undoc_endpoints: 125 return frozenset() 126 return frozenset(re.split(r'\s*,\s*', undoc_endpoints)) 127 128 @property 129 def blueprints(self): 130 blueprints = self.options.get('blueprints', None) 131 if not blueprints: 132 return None 133 return frozenset(re.split(r'\s*,\s*', blueprints)) 134 135 @property 136 def undoc_blueprints(self): 137 undoc_blueprints = self.options.get('undoc-blueprints', None) 138 if not undoc_blueprints: 139 return frozenset() 140 return frozenset(re.split(r'\s*,\s*', undoc_blueprints)) 141 142 @property 143 def modules(self): 144 modules = self.options.get('modules', None) 145 if not modules: 146 return frozenset() 147 return frozenset(re.split(r'\s*,\s*', modules)) 148 149 @property 150 def undoc_modules(self): 151 undoc_modules = self.options.get('undoc-modules', None) 152 if not undoc_modules: 153 return frozenset() 154 return frozenset(re.split(r'\s*,\s*', undoc_modules)) 155 156 @property 157 def order(self): 158 order = self.options.get('order', None) 159 if order not in (None, 'path'): 160 raise ValueError('Invalid value for :order:') 161 return order 162 163 def make_rst(self, qref=False): 164 app = import_object(self.arguments[0]) 165 if self.endpoints: 166 routes = itertools.chain(*[get_routes(app, endpoint, self.order) 167 for endpoint in self.endpoints]) 168 else: 169 routes = get_routes(app, order=self.order) 170 for method, paths, endpoint in routes: 171 try: 172 blueprint, _, endpoint_internal = endpoint.rpartition('.') 173 if self.blueprints and blueprint not in self.blueprints: 174 continue 175 if blueprint in self.undoc_blueprints: 176 continue 177 except ValueError: 178 pass # endpoint is not within a blueprint 179 180 if endpoint in self.undoc_endpoints: 181 continue 182 try: 183 static_url_path = app.static_url_path # Flask 0.7 or higher 184 except AttributeError: 185 static_url_path = app.static_path # Flask 0.6 or under 186 if ('undoc-static' in self.options and endpoint == 'static' and 187 static_url_path + '/(path:filename)' in paths): 188 continue 189 view = app.view_functions[endpoint] 190 191 if self.modules and view.__module__ not in self.modules: 192 continue 193 194 if self.undoc_modules and view.__module__ in self.modules: 195 continue 196 197 docstring = view.__doc__ or '' 198 if hasattr(view, 'view_class'): 199 meth_func = getattr(view.view_class, method.lower(), None) 200 if meth_func and meth_func.__doc__: 201 docstring = meth_func.__doc__ 202 if not isinstance(docstring, six.text_type): 203 analyzer = ModuleAnalyzer.for_module(view.__module__) 204 docstring = force_decode(docstring, analyzer.encoding) 205 206 if not docstring and 'include-empty-docstring' not in self.options: 207 continue 208 docstring = prepare_docstring(docstring) 209 if qref == True: 210 for path in paths: 211 row = quickref_directive(method, path, docstring) 212 yield row 213 else: 214 for line in http_directive(method, paths, docstring): 215 yield line