|
| 1 | +from __future__ import absolute_import |
| 2 | +import re |
| 3 | +from collections import OrderedDict |
| 4 | + |
| 5 | +from .base import Code, CodeGenerator |
| 6 | +from .jsonschema import Schema, SchemaGenerator, build_default |
| 7 | + |
| 8 | +SUPPORT_METHODS = ['get', 'post', 'put', 'delete', 'patch', 'options', 'head'] |
| 9 | + |
| 10 | + |
| 11 | +class Router(Code): |
| 12 | + |
| 13 | + template = 'sanic/routers.tpl' |
| 14 | + dest_template = '%(package)s/%(module)s/routes.py' |
| 15 | + override = True |
| 16 | + |
| 17 | + |
| 18 | +class View(Code): |
| 19 | + |
| 20 | + template = 'sanic/view.tpl' |
| 21 | + dest_template = '%(package)s/%(module)s/api/%(view)s.py' |
| 22 | + override = False |
| 23 | + |
| 24 | + |
| 25 | +class Specification(Code): |
| 26 | + |
| 27 | + template = 'sanic/specification.tpl' |
| 28 | + dest_template = '%(package)s/static/%(module)s/swagger.json' |
| 29 | + override = True |
| 30 | + |
| 31 | + |
| 32 | +class Validator(Code): |
| 33 | + |
| 34 | + template = 'sanic/validators.tpl' |
| 35 | + dest_template = '%(package)s/%(module)s/validators.py' |
| 36 | + override = True |
| 37 | + |
| 38 | + |
| 39 | +class Api(Code): |
| 40 | + |
| 41 | + template = 'sanic/api.tpl' |
| 42 | + dest_template = '%(package)s/%(module)s/api/__init__.py' |
| 43 | + |
| 44 | + |
| 45 | +class Blueprint(Code): |
| 46 | + |
| 47 | + template = 'sanic/blueprint.tpl' |
| 48 | + dest_template = '%(package)s/%(module)s/__init__.py' |
| 49 | + |
| 50 | + |
| 51 | +class App(Code): |
| 52 | + |
| 53 | + template = 'sanic/app.tpl' |
| 54 | + dest_template = '%(package)s/__init__.py' |
| 55 | + |
| 56 | + |
| 57 | +class Requirements(Code): |
| 58 | + |
| 59 | + template = 'sanic/requirements.tpl' |
| 60 | + dest_template = 'requirements.txt' |
| 61 | + |
| 62 | + |
| 63 | +class UIIndex(Code): |
| 64 | + |
| 65 | + template = 'ui/index.html' |
| 66 | + dest_template = '%(package)s/static/swagger-ui/index.html' |
| 67 | + |
| 68 | + |
| 69 | +def _swagger_to_sanic_url(url, swagger_path_node): |
| 70 | + types = { |
| 71 | + 'integer': 'int', |
| 72 | + 'long': 'int', |
| 73 | + 'float': 'float', |
| 74 | + 'double': 'float' |
| 75 | + } |
| 76 | + node = swagger_path_node |
| 77 | + params = re.findall(r'\{([^\}]+?)\}', url) |
| 78 | + url = re.sub(r'{(.*?)}', '<\\1>', url) |
| 79 | + |
| 80 | + def _type(parameters): |
| 81 | + for p in parameters: |
| 82 | + if p.get('in') != 'path': |
| 83 | + continue |
| 84 | + t = p.get('type', 'string') |
| 85 | + if t in types: |
| 86 | + yield '<%s>' % p['name'], '<%s:%s>' % (types[t], p['name']) |
| 87 | + |
| 88 | + for old, new in _type(node.get('parameters', [])): |
| 89 | + url = url.replace(old, new) |
| 90 | + |
| 91 | + for k in SUPPORT_METHODS: |
| 92 | + if k in node: |
| 93 | + for old, new in _type(node[k].get('parameters', [])): |
| 94 | + url = url.replace(old, new) |
| 95 | + |
| 96 | + return url, params |
| 97 | + |
| 98 | + |
| 99 | +def _remove_characters(text, deletechars): |
| 100 | + return text.translate({ord(x): None for x in deletechars}) |
| 101 | + |
| 102 | + |
| 103 | +def _path_to_endpoint(swagger_path): |
| 104 | + return _remove_characters( |
| 105 | + swagger_path.strip('/').replace('/', '_').replace('-', '_'), |
| 106 | + '{}') |
| 107 | + |
| 108 | + |
| 109 | +def _path_to_resource_name(swagger_path): |
| 110 | + return _remove_characters(swagger_path.title(), '{}/_-') |
| 111 | + |
| 112 | + |
| 113 | +def _location(swagger_location): |
| 114 | + location_map = { |
| 115 | + 'body': 'json', |
| 116 | + 'header': 'headers', |
| 117 | + 'formData': 'form', |
| 118 | + 'query': 'args' |
| 119 | + } |
| 120 | + return location_map.get(swagger_location) |
| 121 | + |
| 122 | + |
| 123 | +class SanicGenerator(CodeGenerator): |
| 124 | + |
| 125 | + dependencies = [SchemaGenerator] |
| 126 | + |
| 127 | + def __init__(self, swagger): |
| 128 | + super(SanicGenerator, self).__init__(swagger) |
| 129 | + self.with_spec = False |
| 130 | + self.with_ui = False |
| 131 | + |
| 132 | + def _dependence_callback(self, code): |
| 133 | + if not isinstance(code, Schema): |
| 134 | + return code |
| 135 | + schemas = code |
| 136 | + # schemas default key likes `('/some/path/{param}', 'method')` |
| 137 | + # use sanic endpoint to replace default validator's key, |
| 138 | + # example: `('some_path_param', 'method')` |
| 139 | + validators = OrderedDict() |
| 140 | + for k, v in schemas.data['validators'].items(): |
| 141 | + locations = {_location(loc): val for loc, val in v.items()} |
| 142 | + validators[(_path_to_endpoint(k[0]), k[1])] = locations |
| 143 | + |
| 144 | + # filters |
| 145 | + filters = OrderedDict() |
| 146 | + for k, v in schemas.data['filters'].items(): |
| 147 | + filters[(_path_to_endpoint(k[0]), k[1])] = v |
| 148 | + |
| 149 | + # scopes |
| 150 | + scopes = OrderedDict() |
| 151 | + for k, v in schemas.data['scopes'].items(): |
| 152 | + scopes[(_path_to_endpoint(k[0]), k[1])] = v |
| 153 | + |
| 154 | + schemas.data['validators'] = validators |
| 155 | + schemas.data['filters'] = filters |
| 156 | + schemas.data['scopes'] = scopes |
| 157 | + self.schemas = schemas |
| 158 | + self.validators = validators |
| 159 | + self.filters = filters |
| 160 | + return schemas |
| 161 | + |
| 162 | + def _process_data(self): |
| 163 | + |
| 164 | + views = [] # [{'endpoint':, 'name':, url: '', params: [], methods: {'get': {'requests': [], 'response'}}}, ..] |
| 165 | + |
| 166 | + for paths, data in self.swagger.search(['paths', '*']): |
| 167 | + swagger_path = paths[-1] |
| 168 | + url, params = _swagger_to_sanic_url(swagger_path, data) |
| 169 | + endpoint = _path_to_endpoint(swagger_path) |
| 170 | + name = _path_to_resource_name(swagger_path) |
| 171 | + |
| 172 | + methods = OrderedDict() |
| 173 | + for method in SUPPORT_METHODS: |
| 174 | + if method not in data: |
| 175 | + continue |
| 176 | + methods[method] = {} |
| 177 | + validator = self.validators.get((endpoint, method.upper())) |
| 178 | + if validator: |
| 179 | + methods[method]['requests'] = list(validator.keys()) |
| 180 | + |
| 181 | + for status, res_data in data[method].get('responses', {}).items(): |
| 182 | + if isinstance(status, int) or status.isdigit(): |
| 183 | + example = res_data.get('examples', {}).get('application/json') |
| 184 | + |
| 185 | + if not example: |
| 186 | + example = build_default(res_data.get('schema')) |
| 187 | + response = example, int(status), build_default(res_data.get('headers')) |
| 188 | + methods[method]['response'] = response |
| 189 | + break |
| 190 | + |
| 191 | + views.append(dict( |
| 192 | + url=url, |
| 193 | + params=params, |
| 194 | + endpoint=endpoint, |
| 195 | + methods=methods, |
| 196 | + name=name |
| 197 | + )) |
| 198 | + |
| 199 | + return views |
| 200 | + |
| 201 | + def _get_oauth_scopes(self): |
| 202 | + for path, scopes in self.swagger.search(('securityDefinitions', '*', 'scopes')): |
| 203 | + return scopes |
| 204 | + return None |
| 205 | + |
| 206 | + def _process(self): |
| 207 | + views = self._process_data() |
| 208 | + yield Router(dict(views=views)) |
| 209 | + for view in views: |
| 210 | + yield View(view, dist_env=dict(view=view['endpoint'])) |
| 211 | + if self.with_spec: |
| 212 | + try: |
| 213 | + import simplejson as json |
| 214 | + except ImportError: |
| 215 | + import json |
| 216 | + swagger = {} |
| 217 | + swagger.update(self.swagger.origin_data) |
| 218 | + swagger.pop('host', None) |
| 219 | + swagger.pop('schemes', None) |
| 220 | + yield Specification(dict(swagger=json.dumps(swagger, indent=2))) |
| 221 | + |
| 222 | + yield Validator() |
| 223 | + |
| 224 | + yield Api() |
| 225 | + |
| 226 | + yield Blueprint(dict(scopes_supported=self.swagger.scopes_supported, |
| 227 | + blueprint=self.swagger.module_name)) |
| 228 | + yield App(dict(blueprint=self.swagger.module_name, |
| 229 | + base_path=self.swagger.base_path)) |
| 230 | + |
| 231 | + yield Requirements() |
| 232 | + |
| 233 | + if self.with_ui: |
| 234 | + yield UIIndex(dict(spec_path='/static/%s/swagger.json' % self.swagger.module_name)) |
0 commit comments