mirror of
https://github.com/correl/tornado-openapi3-example.git
synced 2024-11-23 11:09:56 +00:00
Create project
This commit is contained in:
commit
e23e7bc8eb
10 changed files with 309 additions and 0 deletions
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
.DS_Store
|
||||
.idea
|
||||
*.log
|
||||
tmp/
|
||||
|
||||
*.py[cod]
|
||||
*.egg
|
||||
build
|
||||
htmlcov
|
||||
poetry.lock
|
0
README.rst
Normal file
0
README.rst
Normal file
20
pyproject.toml
Normal file
20
pyproject.toml
Normal file
|
@ -0,0 +1,20 @@
|
|||
[tool.poetry]
|
||||
name = "tornado-openapi3-example"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = ["Correl <correl@gmail.com>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.7"
|
||||
tornado = "^6"
|
||||
tornado-openapi3 = "^0.2.4"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
black = { version = "*", allow-prereleases = true }
|
||||
pytest = "^5.2"
|
||||
pytest-black = "*"
|
||||
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
5
tests/test_tornado_openapi3_example.py
Normal file
5
tests/test_tornado_openapi3_example.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from tornado_openapi3_example import __version__
|
||||
|
||||
|
||||
def test_version():
|
||||
assert __version__ == '0.1.0'
|
1
tornado_openapi3_example/__init__.py
Normal file
1
tornado_openapi3_example/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
__version__ = "0.1.0"
|
105
tornado_openapi3_example/__main__.py
Normal file
105
tornado_openapi3_example/__main__.py
Normal file
|
@ -0,0 +1,105 @@
|
|||
import json
|
||||
import os
|
||||
import logging
|
||||
import pathlib
|
||||
import pkg_resources
|
||||
|
||||
import msgpack
|
||||
from openapi_core import create_spec # type: ignore
|
||||
from openapi_core.exceptions import OpenAPIError # type: ignore
|
||||
from openapi_core.deserializing.exceptions import DeserializeError # type: ignore
|
||||
from openapi_core.schema.media_types.exceptions import ( # type: ignore
|
||||
InvalidContentType,
|
||||
)
|
||||
from openapi_core.templating.paths.exceptions import OperationNotFound # type: ignore
|
||||
from openapi_core.unmarshalling.schemas.exceptions import ValidateError # type: ignore
|
||||
from openapi_core.validation.exceptions import InvalidSecurity # type: ignore
|
||||
import tornado.ioloop
|
||||
import tornado.web
|
||||
from tornado_openapi3 import RequestValidator
|
||||
import yaml
|
||||
|
||||
|
||||
class OpenAPISpecHandler(tornado.web.RequestHandler):
|
||||
async def get(self) -> None:
|
||||
self.set_header("Content-Type", "application/x-yaml")
|
||||
return self.render("openapi.yaml")
|
||||
|
||||
|
||||
class OpenAPIRequestHandler(tornado.web.RequestHandler):
|
||||
async def prepare(self) -> None:
|
||||
maybe_coro = super().prepare()
|
||||
if maybe_coro and asyncio.iscoroutine(maybe_coro): # pragma: no cover
|
||||
await maybe_coro
|
||||
|
||||
spec = create_spec(yaml.safe_load(self.render_string("openapi.yaml")))
|
||||
validator = RequestValidator(spec)
|
||||
result = validator.validate(self.request)
|
||||
try:
|
||||
result.raise_for_errors()
|
||||
except OperationNotFound:
|
||||
self.set_status(405)
|
||||
self.finish()
|
||||
except InvalidContentType:
|
||||
self.set_status(415)
|
||||
self.finish()
|
||||
except (DeserializeError, ValidateError):
|
||||
self.set_status(400)
|
||||
self.finish()
|
||||
except InvalidSecurity:
|
||||
self.set_status(401)
|
||||
self.finish()
|
||||
except OpenAPIError:
|
||||
raise
|
||||
self.validated = result
|
||||
|
||||
|
||||
class LoginHandler(OpenAPIRequestHandler):
|
||||
async def post(self) -> None:
|
||||
self.set_header("Content-Type", "application/json")
|
||||
self.finish(
|
||||
json.dumps(
|
||||
{
|
||||
"username": self.validated.body["username"],
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class NoteHandler(OpenAPIRequestHandler):
|
||||
async def get(self, identifier: str) -> None:
|
||||
self.set_header("Content-Type", "application/json")
|
||||
self.finish(
|
||||
json.dumps(
|
||||
{
|
||||
"subject": "Shopping list",
|
||||
"body": "\n".join(["- Dish soap", "- Potatoes", "- Milk"]),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def make_app():
|
||||
pkg_root = pathlib.Path(pkg_resources.resource_filename(__package__, ""))
|
||||
return tornado.web.Application(
|
||||
[
|
||||
(r"/", tornado.web.RedirectHandler, {"url": "/static/index.html"}),
|
||||
(
|
||||
r"/static/(.*)",
|
||||
tornado.web.StaticFileHandler,
|
||||
{"path": pkg_root / "static", "default_filename": "index.html"},
|
||||
),
|
||||
(f"/openapi.yaml", OpenAPISpecHandler),
|
||||
(r"/login", LoginHandler),
|
||||
(r"/notes/(?P<identifier>.+)", NoteHandler),
|
||||
],
|
||||
debug=os.environ.get("DEBUG"),
|
||||
template_path=str(pkg_root / "templates"),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
app = make_app()
|
||||
app.listen(8888)
|
||||
tornado.ioloop.IOLoop.current().start()
|
0
tornado_openapi3_example/app.py
Normal file
0
tornado_openapi3_example/app.py
Normal file
19
tornado_openapi3_example/static/index.html
Normal file
19
tornado_openapi3_example/static/index.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Tornado OpenAPI3 Example</title>
|
||||
<meta charset="utf8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="referrer" content="same-origin">
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<redoc spec-url="/openapi.yaml"></redoc>
|
||||
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
|
||||
</body>
|
||||
</html>
|
149
tornado_openapi3_example/templates/openapi.yaml
Normal file
149
tornado_openapi3_example/templates/openapi.yaml
Normal file
|
@ -0,0 +1,149 @@
|
|||
---
|
||||
openapi: "3.0.0"
|
||||
info:
|
||||
title: Tornado OpenAPI3 Example
|
||||
version: "1.0.0"
|
||||
description: |
|
||||
An example application using tornado-openapi3 to validate requests and
|
||||
responses.
|
||||
paths:
|
||||
/:
|
||||
get:
|
||||
summary: Service Root
|
||||
tags:
|
||||
- Documentation
|
||||
responses:
|
||||
'301':
|
||||
description: Redirect to HTML documentation
|
||||
headers:
|
||||
Location:
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- /static/index.html
|
||||
/static/index.html:
|
||||
get:
|
||||
summary: HTML Rendered OpenAPI Specification
|
||||
tags:
|
||||
- Documentation
|
||||
responses:
|
||||
'200':
|
||||
description: HTML documentation
|
||||
content:
|
||||
text/html:
|
||||
schema:
|
||||
type: string
|
||||
/openapi.yaml:
|
||||
get:
|
||||
summary: YAML OpenAPI specification
|
||||
tags:
|
||||
- Documentation
|
||||
responses:
|
||||
'200':
|
||||
description: OpenAPI specification
|
||||
content:
|
||||
application/x-yaml:
|
||||
schema:
|
||||
type: string
|
||||
/login:
|
||||
post:
|
||||
summary: Log In
|
||||
tags:
|
||||
- Examples
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/login'
|
||||
responses:
|
||||
'200':
|
||||
description: Login successful
|
||||
'403':
|
||||
description: Forbidden
|
||||
/notes:
|
||||
post:
|
||||
summary: Create a note
|
||||
tags:
|
||||
- Examples
|
||||
security:
|
||||
- token: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/note'
|
||||
responses:
|
||||
'204':
|
||||
description: Note created
|
||||
/notes/{id}:
|
||||
get:
|
||||
summary: Retrieve a note
|
||||
tags:
|
||||
- Examples
|
||||
security:
|
||||
- token: []
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
schema:
|
||||
type: integer
|
||||
required: true
|
||||
responses:
|
||||
'200':
|
||||
description: Note
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/note'
|
||||
text/html:
|
||||
schema:
|
||||
type: string
|
||||
delete:
|
||||
summary: Remove a note
|
||||
tags:
|
||||
- Examples
|
||||
security:
|
||||
- token: []
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
schema:
|
||||
type: integer
|
||||
required: true
|
||||
responses:
|
||||
'204':
|
||||
description: Note deleted
|
||||
components:
|
||||
schemas:
|
||||
login:
|
||||
type: object
|
||||
properties:
|
||||
username:
|
||||
type: string
|
||||
example: admin
|
||||
password:
|
||||
type: string
|
||||
example: correct-horse-battery-staple
|
||||
additionalProperties: false
|
||||
required:
|
||||
- username
|
||||
- password
|
||||
note:
|
||||
type: object
|
||||
properties:
|
||||
subject:
|
||||
type: string
|
||||
maxLength: 72
|
||||
body:
|
||||
type: string
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
securitySchemes:
|
||||
token:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: Access token
|
Loading…
Reference in a new issue