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