Tornado-swirl is a wrapper for tornado which enables swagger-ui support, adapted from Serena Feng's tornado-swagger project and then heavily modified to work with Tornado 5 and Python 3 and uses Google-style docstrings to get OpenAPI 3.0 API path and component descriptors.
The main idea for this project is to automatically extract API specs from the usual docstrings of our Tornado RequestHandlers.
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.
- 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.
- V. 0.1.11 -- Fixed bug where descriptions are not being included in schema and schema properties.
- V. 0.1.10 -- Lowered Python version requirement to 3.6, Added add_global_tag to tornado_swirl import.
- v. 0.1.8 -- Support
tags
OpenAPI property. - v. 0.1.7 -- Fixed bug where multiple host value (comma delimited) appears on
servers
value. Also modifiedswirl.describe
behavior. - v. 0.1.5 -- Fixed bug where
application/json
response has noschema
attribute. - v. 0.1.2 -- Fixed openapi package inclusion
Tutorial and More Details Here
import tornado.web
import tornado_swirl as swirl
@swirl.restapi('/item/(?P<itemid>\d+)')
class ItemHandler(tornado.web.RequestHandler):
def get(self, itemid):
"""Get Item data.
Gets Item data from database.
Path Parameter:
itemid (int) -- The item id
Tags:
items
"""
pass
@swirl.schema
class User(object):
"""This is the user class
Your usual long description.
Properties:
name (string) -- required. Name of user
age (int) -- Age of user
"""
pass
def make_app():
return swirl.Application(swirl.api_routes())
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
TODO: Models/Components
Run your app:
% python app.py
And visit:
% curl http://localhost:8888/swagger/spec
Or you can view via Swagger UI in your browser:
http://localhost:8888/swagger/spec.html
Swagger spec will look something like:
{
"openapi": "3.0.0",
"info": {
"title": "Sample API",
"description": "Foo bar",
"version": "v1.0"
},
"servers": [
{
"url": "http://localhost:8888/",
"description": "This server"
}
],
"schemes": [
"http",
"https"
],
"paths": {
"/test": {
"get": {
"operationId": "MainHandler.get",
"summary": "Test summary",
"description": "Test description",
"parameters": [
{
"in": "query",
"name": "param1",
"required": true,
"description": "test",
"schema": {
"type": "integer",
"format": "int32",
"maximum": 200,
"minimum": 1
}
}
],
"responses": {
"200": {
"description": "Foomanchu",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
}
},
"/test/{emp_uid}/{date}": {
"get": {
"operationId": "TestHandler.get",
"summary": "Test get",
"description": "Hiho",
"parameters": [
{
"in": "path",
"name": "emp_uid",
"required": true,
"description": "test",
"schema": {
"type": "integer",
"format": "int32"
}
},
{
"in": "path",
"name": "date",
"required": true,
"description": "test",
"schema": {
"type": "string",
"format": "date"
}
},
{
"in": "cookie",
"name": "x",
"required": false,
"description": "some foo",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Test data",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/User"
}
}
}
}
},
"201": {
"description": "Test user",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
}
},
"400": {
"description": "Fudge",
"content": {
"application/json": {
"schema": {
"type": null
}
}
}
}
}
}
},
"/item/{itemid}": {
"get": {
"operationId": "ItemHandler.get",
"summary": "Get Item data.",
"description": "Gets Item data from database.",
"parameters": [
{
"in": "path",
"name": "itemid",
"required": true,
"description": "The item id",
"schema": {
"type": "integer",
"format": "int32"
}
}
],
"responses": {}
}
}
},
"components": {
"schemas": {
"User": {
"type": "object",
"description": "User def",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "User name"
},
"age": {
"type": "integer",
"format": "int32",
"maximum": 100,
"minimum": 1
}
}
}
}
}
}
We used SerenaFeng's tornado-swagger as starting point for this project.
We decided on (almost like) Google style module docs for the doc format since epydoc used in tornado-swagger has been a discontinued project and did not work on Python 3. We wanted to maintain a uniform python docstring format across all our projects so we decided on extracting OpenAPI 3.0 data from our existing/common docstring format so we made our own parser based on Brian Ray's Medium article on FSMs.
- Support of
object
type. Current version does not support OpenAPIobject
type (directly). One will need to declare model classes and mark them with the @schema decorator and reference them. - We are trying to lower the python version requirements. Python2.7 support will require a significant recode because of how we generate the path spec data (we add a path_spec attribute to the instance method/handler which is allowed in Python3+ but not in Python 2.7). There is a weird behavior in Python3.5 where in the parsing of the URL patterns returns an arbitrary pattern: i.e. in some test runs, a URL pattern is detected as /test/{a}/{b} and sometimes it comes out as /test/{b}/{a}. For now, only tests for Python 3.6 and Python 3.7 are passing (via tox).
Please send your suggestions, comments to: r.duldulao@salarium.com
(c) 2018 Salarium LTD. All rights reserved.