Skip to content

Commit bb689e9

Browse files
authored
Merge pull request #88 from guokr/falcon
添加falcon 支持
2 parents ce5421e + 39619a3 commit bb689e9

File tree

14 files changed

+752
-4
lines changed

14 files changed

+752
-4
lines changed

README.md

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
# Flask/Tornado RESTful Application Code Generator
1+
# Flask/Tornado/Falcon RESTful Application Code Generator
22

33
[![Build Status][travis-image]][travis-url] [![PyPi Version][pypi-image]][pypi-url]
44

55
## Overview
66

7-
Generate Flask/Tornado-RESTful application code from a Swagger Specification doc.
7+
Generate Flask/Tornado/Falcon-RESTful application code from a Swagger Specification doc.
88

99
**Alpha version for now, it can not handle all validation properly.**
1010

@@ -32,7 +32,7 @@ Command Options:
3232
--spec, --specification Generate online specification json response.
3333
--ui Generate swagger ui.
3434
-j, --jobs INTEGER Parallel jobs for processing.
35-
-tlp, --templates gen flask/tornado templates,default flask.
35+
-tlp, --templates gen flask/tornado/falcon templates, default flask.
3636
--version Show current version.
3737
--help Show this message and exit.
3838

@@ -65,7 +65,7 @@ Generate example-app from [apis.yml](https://github.com/guokr/swagger-py-codegen
6565
| |__ validators.py
6666
|__ requirements.txt
6767

68-
$ swagger_py_codegen -s docs/panel.yml example-app -p demo -tlp=tornado
68+
$ swagger_py_codegen -s api.yml example-app -p demo -tlp=tornado
6969
$ tree (tornado-demo)
7070
.
7171
|__ api.yml
@@ -88,6 +88,28 @@ Generate example-app from [apis.yml](https://github.com/guokr/swagger-py-codegen
8888
| |__ validators.py
8989
|__ requirements.txt
9090

91+
$ swagger_py_codegen -s api.yml example-app -p demo -tlp=falcon
92+
$ tree (falcon-demo)
93+
.
94+
|__ api.yml
95+
|__ example-app
96+
|__ demo
97+
| |__ __init__.py
98+
| |__ v1
99+
| |__ api
100+
| | |__ __init__.py
101+
| | |__ oauth_auth_approach_approach.py
102+
| | |__ oauth_auth_approach.py
103+
| | |__ users_token.py
104+
| | |__ users_current.py
105+
| | |__ users.py
106+
| |__ __init__.py
107+
| |__ routes.py
108+
| |__ schemas.py
109+
| |__ validators.py
110+
|__ requirements.txt
111+
112+
91113
Install example-app requirements:
92114

93115
$ cd example-app

swagger_py_codegen/command.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from ._version import __version__
1515
from .flask import FlaskGenerator
1616
from .tornado import TornadoGenerator
17+
from .falcon import FalconGenerator
1718
from .parser import Swagger
1819
from .base import Template
1920

@@ -96,6 +97,8 @@ def generate(destination, swagger_doc, force=False, package=None,
9697
swagger = Swagger(data, pool)
9798
if templates == 'tornado':
9899
generator = TornadoGenerator(swagger)
100+
elif templates == 'falcon':
101+
generator = FalconGenerator(swagger)
99102
else:
100103
generator = FlaskGenerator(swagger)
101104
generator.with_spec = specification

swagger_py_codegen/falcon.py

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
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))

swagger_py_codegen/jsonschema.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ def build_data(swagger):
9696
validators=validators,
9797
filters=filters,
9898
scopes=scopes,
99+
base_path=swagger.base_path,
99100
merge_default=getsource(merge_default),
100101
normalize=getsource(normalize)
101102
)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import absolute_import, print_function
3+
4+
from six import with_metaclass
5+
import six
6+
import falcon
7+
8+
from ..validators import request_validate, response_filter
9+
10+
import inspect
11+
12+
before_decorators = [request_validate]
13+
after_decorators = [response_filter]
14+
15+
16+
if six.PY3:
17+
ismethod = inspect.isfunction
18+
else:
19+
ismethod = inspect.ismethod
20+
21+
22+
def add_before_decorators(model_class):
23+
for name, m in inspect.getmembers(model_class, ismethod):
24+
if name in ['on_get', 'on_post', 'on_put', 'on_delete']:
25+
setattr(model_class, name, falcon.before(*before_decorators)(m))
26+
27+
28+
def add_after_decorators(model_class):
29+
for name, m in inspect.getmembers(model_class, ismethod):
30+
if name in ['on_get', 'on_post', 'on_put', 'on_delete']:
31+
setattr(model_class, name, falcon.after(*after_decorators)(m))
32+
33+
34+
class APIMetaclass(type):
35+
"""
36+
Metaclass of the Model.
37+
"""
38+
def __init__(cls, name, bases, attrs):
39+
super(APIMetaclass, cls).__init__(name, bases, attrs)
40+
add_before_decorators(cls)
41+
add_after_decorators(cls)
42+
43+
class Resource(with_metaclass(APIMetaclass, object)):
44+
45+
pass

0 commit comments

Comments
 (0)