Reputation: 625
Problem statement: I want to automate the generation of machine and human readable specifications for JSON APIs so anyone can visualize and interact with our API.
One of the feasible solution is to use OpenAPISpecification (fka swagger). I was not able to find a comprehensible guide to use swagger particularly with tornado, so my questions are:
My API is written in python 2.7.11 with tornado 4.3. Please do suggest if you have any other suggestion than using swagger too.
Update: Apispec is an interesting start but it cannot be used with JSON schemas as of now, so doesn't answer my question entirely.
Upvotes: 7
Views: 4838
Reputation: 31
I wrote a plugin that is compatible with python2.7. You can install it directly using pip install -U tornado-rest-swagger
. It uses the syntax of openapi3.0. Here is an example:
import tornado.ioloop
import tornado.options
import tornado.web
from tornado_swagger.components import components
from tornado_swagger.setup import setup_swagger
class BaseHandler(tornado.web.RequestHandler):
def data_received(self, chunk):
pass
class PostsHandler(BaseHandler):
def get(self):
"""
---
tags:
- Posts
summary: List posts
description: List all posts in feed
operationId: getPost
responses:
'200':
description: A list of users
content:
application/json:
schema:
$ref: '#/components/schemas/ArrayOfPostModel'
application/xml:
schema:
$ref: '#/components/schemas/ArrayOfPostModel'
text/plain:
schema:
type: string
"""
def post(self):
"""
---
tags:
- Posts
summary: Add a new Post to the blog
operationId: addPost
requestBody:
description: Post object that needs to be added to the blog
content:
application/json:
schema:
$ref: '#/components/schemas/PostModel'
application/xml:
schema:
$ref: '#/components/schemas/PostModel'
required: true
responses:
'405':
description: Invalid input
content: {}
security:
- petstore_auth:
- 'write:pets'
- 'read:pets'
"""
class PostsDetailsHandler(BaseHandler):
def get(self, posts_id):
"""
---
tags:
- Posts
summary: Find Post by ID
description: Returns a single post
operationId: getPostById
parameters:
- name: post_id
in: path
description: ID of post to return
required: true
schema:
type: integer
format: int64
responses:
'200':
description: successful operation
content:
application/xml:
schema:
$ref: '#/components/schemas/PostModel'
application/json:
schema:
$ref: '#/components/schemas/PostModel'
'400':
description: Invalid ID supplied
content: {}
'404':
description: Pet not found
content: {}
security:
- api_key: []
"""
def patch(self, posts_id):
"""
---
tags:
- Posts
summary: Find Post by ID
description: Returns a single post
operationId: getPostById
parameters:
- name: post_id
in: path
description: ID of post to return
required: true
schema:
type: integer
format: int64
requestBody:
description: Post object that needs to be added to the blog
content:
application/json:
schema:
$ref: '#/components/schemas/PostModel'
application/xml:
schema:
$ref: '#/components/schemas/PostModel'
required: true
responses:
'400':
description: Invalid ID supplied
content: {}
'404':
description: Pet not found
content: {}
security:
- api_key: []
"""
def delete(self, posts_id):
"""
---
tags:
- Posts
summary: Delete Post by ID
description: Returns a single post
operationId: getPostById
parameters:
- name: post_id
in: path
description: ID of post to return
required: true
schema:
type: integer
format: int64
responses:
'200':
description: successful operation
content:
application/json:
schema:
type: object
description: Post model representation
properties:
id:
type: integer
format: int64
title:
type: string
text:
type: string
is_visible:
type: boolean
default: true
'400':
description: Invalid ID supplied
content: {}
'404':
description: Pet not found
content: {}
"""
@components.schemas.register
class PostModel(object):
"""
---
type: object
description: Post model representation
properties:
id:
type: integer
format: int64
title:
type: string
text:
type: string
is_visible:
type: boolean
default: true
"""
@components.schemas.register
class ArrayOfPostModel(object):
"""
---
type: array
description: Array of Post model representation
items:
$ref: '#/components/schemas/PostModel'
"""
@components.security_schemes.register
class JWTToken(object):
"""
---
type: http
scheme: bearer
bearerFormat: JWT
"""
class Application(tornado.web.Application):
_routes = [tornado.web.url(r"/api/posts", PostsHandler), tornado.web.url(r"/api/posts/(\w+)", PostsDetailsHandler)]
def __init__(self):
settings = {"debug": True}
setup_swagger(
self._routes,
swagger_url="/doc",
description="",
api_version="1.0.0",
title="Journal API",
contact=dict(name="test", email="[email protected]", url="https://www.cluas.me"),
)
super(Application, self).__init__(self._routes, **settings)
if __name__ == "__main__":
tornado.options.define("port", default="8080", help="Port to listen on")
tornado.options.parse_command_line()
app = Application()
app.listen(port=8080)
tornado.ioloop.IOLoop.current().start()
f your function is a decorator and uses python2.7, you can try wrapping your decorator with a decorator (pip install decorator
)so you can get the correct behavior.
Upvotes: 1
Reputation: 29
we recently had this requirement at work. We made our own generator which generates OpenAPI 3.0 api spec from Google style docstrings. You just need to decorate handlers and model classes. For more info: https://pypi.org/project/tornado-swirl/ -- still a work in progress though but we are actively working on it.
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
"""
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()
Upvotes: 1