Skip to content

Commit

Permalink
Added swagger_ui_headers_handlers and swagger_spec_headers describe k…
Browse files Browse the repository at this point in the history
…wargs, custom headers on swagger UI handlers and spec, minor code cleanup
  • Loading branch information
Rodolfo N. Duldulao, Jr committed Feb 20, 2019
1 parent 8d5495b commit 883aa9e
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 35 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
19 changes: 15 additions & 4 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand All @@ -140,6 +149,7 @@ def get(self, itemid):
"""
pass


@schema
class User(object):
"""User
Expand All @@ -155,6 +165,7 @@ class User(object):
"""
pass


@restapi('/path/to/api')
class MyHandler(tornado.web.RequestHandler):
async def get(self):
Expand Down
24 changes: 12 additions & 12 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
)
7 changes: 5 additions & 2 deletions tornado_swirl/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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):
Expand Down
4 changes: 1 addition & 3 deletions tornado_swirl/swagger.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
45 changes: 31 additions & 14 deletions tornado_swirl/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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

Expand All @@ -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')
Expand All @@ -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'
Expand Down Expand Up @@ -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():
Expand Down Expand Up @@ -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)
Expand All @@ -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": {
Expand Down Expand Up @@ -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])
Expand Down

0 comments on commit 883aa9e

Please sign in to comment.