diff --git a/README.md b/README.md index 3e3ad83..6c480b5 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ The main idea for this project is to automatically extract API specs from the us Swirl uses the ```@restapi``` decorator to get both the routing info AND the swagger spec which is derived from the method module docs. While ```@schema``` decorator is used to mark classes to include them into the ```components/schemas``` section of the resulting OpenAPI spec. ## Current Release: +* V. 0.1.15 -- Added swagger_ui_handlers_headers and swagger_spec_headers as ```describe()``` keyword arguments, adding of + custom response headers for the swagger UI elements and the JSON spec through the said keyword arguments. * V. 0.1.14 -- Modified handling of enabled_methods, added 'head' to list by default. * V. 0.1.13 -- Added "Deprecated" to mark deprecated APIs. * V. 0.1.12 -- Modified parameter naming to allow hypen/underscore digits. diff --git a/app.py b/app.py index 079e383..76dc385 100644 --- a/app.py +++ b/app.py @@ -4,7 +4,11 @@ from tornado_swirl import api_routes from tornado_swirl.swagger import Application, describe, restapi, schema, add_global_tag -describe(title='Test API', description='Just things to test') +describe(title='Test API', description='Just things to test', + swagger_ui_handlers_headers=[ + ('Cache-Control', 'public'), + ('Cache-Control', 'max-age=300') + ]) add_global_tag("internal", "Internal Use Only", "http://foo.com/tags") # @restapi(url="/test") @@ -124,13 +128,18 @@ @restapi('/withrequestbody5') class FooHandler5(tornado.web.RequestHandler): - def get(self, itemid): + def get(self): + """Get Item data. + + Gets Item data from database. + """ + pass + + def post(self): """Get Item data. Gets Item data from database. - Deprecated - HTTP Headers: Tt-I2ap-Id -- Uri. Tt-I2ap-Sec -- Some Hex token @@ -140,6 +149,7 @@ def get(self, itemid): """ pass + @schema class User(object): """User @@ -155,6 +165,7 @@ class User(object): """ pass + @restapi('/path/to/api') class MyHandler(tornado.web.RequestHandler): async def get(self): diff --git a/setup.py b/setup.py index 0bfdd6c..f7bc7fd 100644 --- a/setup.py +++ b/setup.py @@ -8,30 +8,30 @@ setup(name='tornado-swirl', python_requires='>=3.6.0', - version='0.1.14', + version='0.1.15', url='https://github.com/rduldulao/tornado-swirl', zip_safe=False, packages=['tornado_swirl'], package_data={ - 'tornado_swirl': [ - 'openapi/*.*', - 'static/*.*', - ] + 'tornado_swirl': [ + 'openapi/*.*', + 'static/*.*', + ] }, description='Extract swagger specs from your tornado project', author='Rodolfo Duldulao', license='MIT', long_description=long_description, install_requires=[ - 'tornado>=5.1.1' + 'tornado>=5.1.1' ], classifiers=[ - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.6' + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.6' ], keywords=['SWAGGER', 'OPENAPI', 'TORNADO'], - download_url='https://github.com/rduldulao/tornado-swirl/archive/v_0.1.14.tar.gz', + download_url='https://github.com/rduldulao/tornado-swirl/archive/v_0.1.15.tar.gz', ) diff --git a/tornado_swirl/settings.py b/tornado_swirl/settings.py index 4375713..c357e8c 100644 --- a/tornado_swirl/settings.py +++ b/tornado_swirl/settings.py @@ -23,6 +23,9 @@ 'enabled_methods': ['get', 'post', 'put', 'patch', 'delete', 'head'], 'exclude_namespaces': [], 'tags': [], + + 'swagger_ui_handlers_headers': [], #These should be list of tuples + 'swagger_spec_headers': [], } class SwirlVars(object): @@ -41,9 +44,9 @@ def add_global_tag(name, description=None, url=None): tag['name'] = name if description: tag['description'] = description - + if url: - tag['externalDocs'] = { 'url': url } + tag['externalDocs'] = {'url': url} SwirlVars.GLOBAL_TAGS.append(tag) def add_api_handler(cls): diff --git a/tornado_swirl/swagger.py b/tornado_swirl/swagger.py index 44fd5f7..40bf4f7 100644 --- a/tornado_swirl/swagger.py +++ b/tornado_swirl/swagger.py @@ -3,11 +3,9 @@ import inspect import tornado.web -import tornado_swirl.settings as settings -from tornado_swirl import docparser +from tornado_swirl import docparser, settings from tornado_swirl.handlers import swagger_handlers - def is_rest_api_method(obj): """Determines if function or method object is an HTTP method handler object""" return (inspect.isfunction(obj) or inspect.ismethod(obj)) and \ diff --git a/tornado_swirl/views.py b/tornado_swirl/views.py index ac3fce6..7ed9eb7 100644 --- a/tornado_swirl/views.py +++ b/tornado_swirl/views.py @@ -14,7 +14,7 @@ import tornado.web from tornado.util import re_unescape -import tornado_swirl.settings as settings +from tornado_swirl import settings __author__ = 'rduldulao' @@ -25,13 +25,21 @@ def json_dumps(obj, pretty=False): sort_keys=True, indent=4, separators=(',', ': ')) \ - if pretty else json.dumps(obj) + if pretty else json.dumps(obj) + class SwaggerUIHandler(tornado.web.RequestHandler): """Serves the Swagger UI""" + def initialize(self, static_path, **kwds): self.static_path = static_path + def set_default_headers(self): + headers = settings.default_settings.get( + 'swagger_ui_handlers_headers', []) # type: list + for (key, value) in headers: + self.add_header(key, value) + def get_template_path(self): return self.static_path @@ -40,9 +48,16 @@ def get(self): self.request.full_url(), self.reverse_url(settings.URL_SWAGGER_API_SPEC)) self.render('index.html', discovery_url=discovery_url) + class SwaggerApiHandler(tornado.web.RequestHandler): """Openapi 3.0 spec generator class handler""" + def set_default_headers(self): + headers = settings.default_settings.get( + 'swagger_spec_headers', []) # type: list + for (key, value) in headers: + self.add_header(key, value) + def get(self): """Get handler""" self.set_header('content-type', 'application/json') @@ -62,11 +77,13 @@ def get(self): forwarded = self.request.headers.get('Forwarded', None) proto = None if forwarded: - protopart = [part.strip() for part in forwarded.split(';') if part.strip().startswith('proto')] + protopart = [part.strip() for part in forwarded.split( + ';') if part.strip().startswith('proto')] if protopart: proto = protopart[0].split('=')[-1] - proto = proto or self.request.headers.get("X-Forwarded-Proto", None) or self.request.protocol + proto = proto or self.request.headers.get( + "X-Forwarded-Proto", None) or self.request.protocol servers = [{ 'url': proto + "://" + server_host + "/", 'description': 'Default server' @@ -152,7 +169,7 @@ def __get_api_spec(self, spec, operations): return paths - def __detect_content_from_type(self, val): # -> (str, bool, str): + def __detect_content_from_type(self, val): # -> (str, bool, str): if val.type.name == "file": return "file", False, val.type.contents if val.type.name in settings.get_schemas().keys(): @@ -191,9 +208,9 @@ def __get_tags(self, path_spec): def __get_request_body(self, path_spec): contents = {} if path_spec.body_params: - files_detected = 0 #content = file:xxxx default text/plain - form_data_detected = 0 #application/x-www-form-urlencoded - models_detected = 0 #application/json or application/xml + files_detected = 0 # content = file:xxxx default text/plain + form_data_detected = 0 # application/x-www-form-urlencoded + models_detected = 0 # application/json or application/xml for (_, val) in path_spec.body_params.items(): _, ismodel, ftype = self.__detect_content_from_type(val) @@ -220,12 +237,12 @@ def __get_request_body(self, path_spec): contents[entry.type.contents] = { "schema": { "type": "string", - "format": "binary" #TODO: When to use byte/base64? + "format": "binary" # TODO: When to use byte/base64? } } - elif (files_detected > 0 and \ + elif (files_detected > 0 and (form_data_detected > 0 or models_detected > 0)) or \ - models_detected > 1: + models_detected > 1: contents["multipart/form-data"] = { "schema": { "properties": { @@ -273,12 +290,12 @@ def __get_type(self, param): @staticmethod def find_api(): """Gets the API specs - + Returns: - path, route_spec, opertiations: Tuple + path, route_spec, opertiations: Tuple path -- the API endpoint URL route_spec -- the Tornado Request Handler class - operations -- list of tuples containing (method name, PathSpec object) + operations -- list of tuples containing (method name, PathSpec object) """ for route_spec in settings.api_routes(): url, _ = _find_groups(route_spec[0])