Skip to content

Commit 8aef901

Browse files
committed
添加sanic 支持
1 parent 2b1c1a1 commit 8aef901

File tree

11 files changed

+566
-3
lines changed

11 files changed

+566
-3
lines changed

api.yml

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,16 @@ parameters:
9393
in: header
9494
required: true
9595
type: string
96+
uid_in_path:
97+
name: uid
98+
in: path
99+
required: true
100+
type: string
101+
name_in_query:
102+
name: name
103+
in: query
104+
required: false
105+
type: string
96106
securityDefinitions:
97107
OAuth2:
98108
type: oauth2
@@ -223,5 +233,18 @@ paths:
223233
$ref: '#/definitions/Error'
224234
parameters:
225235
- $ref: '#/parameters/AccessToken'
226-
security:
227-
- OAuth2: [open]
236+
/users/{uid}:
237+
get:
238+
summary: get current user info
239+
responses:
240+
200:
241+
schema:
242+
$ref: '#/definitions/User'
243+
description: user info
244+
default:
245+
description: Unexpected error
246+
schema:
247+
$ref: '#/definitions/Error'
248+
parameters:
249+
- $ref: '#/parameters/uid_in_path'
250+
- $ref: '#/parameters/name_in_query'

swagger_py_codegen/command.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from .flask import FlaskGenerator
1616
from .tornado import TornadoGenerator
1717
from .falcon import FalconGenerator
18+
from .sanic import SanicGenerator
1819
from .parser import Swagger
1920
from .base import Template
2021

@@ -84,7 +85,7 @@ def print_version(ctx, param, value):
8485
@click.option('-j', '--jobs',
8586
default=4, help='Parallel jobs for processing.')
8687
@click.option('-tlp', '--templates',
87-
default='flask', help='gen flask/tornado templates,default flask.')
88+
default='flask', help='gen flask/tornado/falcon/sanic templates,default flask.')
8889
@click.option('--version', is_flag=True, callback=print_version,
8990
expose_value=False, is_eager=True,
9091
help='Show current version.')
@@ -99,6 +100,8 @@ def generate(destination, swagger_doc, force=False, package=None,
99100
generator = TornadoGenerator(swagger)
100101
elif templates == 'falcon':
101102
generator = FalconGenerator(swagger)
103+
elif templates == 'sanic':
104+
generator = SanicGenerator(swagger)
102105
else:
103106
generator = FlaskGenerator(swagger)
104107
generator.with_spec = specification

swagger_py_codegen/sanic.py

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
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))
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import absolute_import, print_function
3+
4+
import inspect
5+
6+
from sanic.views import HTTPMethodView
7+
8+
from ..validators import request_validate, response_filter
9+
10+
before_decorators = [request_validate]
11+
after_decorators = [response_filter]
12+
13+
methods = ['get', 'put', 'post', 'delete']
14+
15+
16+
def add_before_decorators(model_class):
17+
for name, m in inspect.getmembers(model_class, inspect.isfunction):
18+
if name in methods:
19+
for dec in before_decorators:
20+
m = dec(m)
21+
setattr(model_class, name, m)
22+
23+
24+
def add_after_decorators(model_class):
25+
for name, m in inspect.getmembers(model_class, inspect.isfunction):
26+
if name in methods:
27+
for dec in after_decorators:
28+
setattr(model_class, name, dec)
29+
30+
31+
class APIMetaclass(type):
32+
"""
33+
Metaclass of the Model.
34+
"""
35+
def __init__(cls, name, bases, attrs):
36+
super(APIMetaclass, cls).__init__(name, bases, attrs)
37+
add_before_decorators(cls)
38+
# add_after_decorators(cls)
39+
40+
41+
class Resource(HTTPMethodView, metaclass=APIMetaclass):
42+
43+
44+
pass
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import absolute_import
3+
4+
from sanic import Sanic
5+
6+
import {{ blueprint }}
7+
8+
9+
def create_app():
10+
app = Sanic(__name__)
11+
app.register_blueprint(
12+
{{ blueprint }}.bp,
13+
url_prefix='{{ base_path }}')
14+
return app
15+
16+
17+
app = create_app()
18+
19+
20+
if __name__ == '__main__':
21+
app.run(host="0.0.0.0", port=8000)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import absolute_import
3+
4+
from sanic import Blueprint
5+
6+
from .routes import routes
7+
from .validators import security
8+
9+
10+
@security.scopes_loader
11+
def current_scopes():
12+
return {{ scopes_supported }}
13+
14+
bp = Blueprint('{{ blueprint }}', __name__)
15+
16+
for route in routes:
17+
route.pop('endpoint', None)
18+
bp.add_route(route.pop('resource'), *route.pop('urls'), **route)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
sanic
2+
jsonschema
3+
ujson
4+
uwsgi

0 commit comments

Comments
 (0)