Skip to content

Commit dda7006

Browse files
authored
Merge pull request #92 from guokr/sanic
Sanic
2 parents 2b1c1a1 + 768082f commit dda7006

File tree

14 files changed

+773
-5
lines changed

14 files changed

+773
-5
lines changed

README.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Swagger Py Codegen is a Python web framework generator, which can help you gener
1111
* [Flask](http://flask.pocoo.org/) (Python)
1212
* [Tornado](http://www.tornadoweb.org/en/stable/) (Python)
1313
* [Falcon](https://falconframework.org/) (Python)
14+
* [Sanic](https://github.com/channelcat/sanic) (Python)
1415

1516

1617
**Alpha version for now, it may not handle all validation properly. If you found a bug, feel free to contact us.**
@@ -122,6 +123,31 @@ Generate example-app from [api.yml](https://github.com/guokr/swagger-py-codegen/
122123
| |__ validators.py
123124
|__ requirements.txt
124125

126+
127+
#### Sanic Example
128+
129+
$ swagger_py_codegen -s api.yml example-app -p demo -tlp=sanic
130+
$ tree (sanic-demo)
131+
.
132+
|__ api.yml
133+
|__ example-app
134+
|__ demo
135+
| |__ __init__.py
136+
| |__ v1
137+
| |__ api
138+
| | |__ __init__.py
139+
| | |__ oauth_auth_approach_approach.py
140+
| | |__ oauth_auth_approach.py
141+
| | |__ users_token.py
142+
| | |__ users_current.py
143+
| | |__ users.py
144+
| |__ __init__.py
145+
| |__ routes.py
146+
| |__ schemas.py
147+
| |__ validators.py
148+
|__ requirements.txt
149+
150+
125151
#### Run Web Server
126152

127153
Install example-app requirements:
@@ -146,7 +172,7 @@ Then you can visit [http://127.0.0.1:5000/static/swagger-ui/index.html](http://1
146172
|component|compatibility|
147173
|-----|-----|
148174
|OpenAPI Spec|2.0|
149-
|Python|2.\*, 3.\*|
175+
|Python|2.\*, 3.\*(Sanic only 3.\*)|
150176

151177

152178
## Authors

api.yml

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ definitions:
7070
type: string
7171
User:
7272
properties:
73+
id:
74+
type: string
7375
nickname:
7476
type: string
7577
avatar:
@@ -93,6 +95,16 @@ parameters:
9395
in: header
9496
required: true
9597
type: string
98+
uid_in_path:
99+
name: uid
100+
in: path
101+
required: true
102+
type: string
103+
name_in_query:
104+
name: name
105+
in: query
106+
required: false
107+
type: string
96108
securityDefinitions:
97109
OAuth2:
98110
type: oauth2
@@ -223,5 +235,18 @@ paths:
223235
$ref: '#/definitions/Error'
224236
parameters:
225237
- $ref: '#/parameters/AccessToken'
226-
security:
227-
- OAuth2: [open]
238+
/users/{uid}:
239+
get:
240+
summary: get current user info
241+
responses:
242+
200:
243+
schema:
244+
$ref: '#/definitions/User'
245+
description: user info
246+
default:
247+
description: Unexpected error
248+
schema:
249+
$ref: '#/definitions/Error'
250+
parameters:
251+
- $ref: '#/parameters/uid_in_path'
252+
- $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))

swagger_py_codegen/templates/falcon/api.tpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,4 @@ class APIMetaclass(type):
4242

4343
class Resource(with_metaclass(APIMetaclass, object)):
4444

45-
pass
45+
pass

0 commit comments

Comments
 (0)