mirror of
https://github.com/correl/tornado-openapi3.git
synced 2024-11-21 19:18:40 +00:00
Add documentation
This commit is contained in:
parent
10e5784543
commit
12813757d1
14 changed files with 313 additions and 4 deletions
1
docs/.gitignore
vendored
Normal file
1
docs/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
_build/
|
20
docs/Makefile
Normal file
20
docs/Makefile
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Minimal makefile for Sphinx documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
# You can set these variables from the command line, and also
|
||||||
|
# from the environment for the first two.
|
||||||
|
SPHINXOPTS ?=
|
||||||
|
SPHINXBUILD ?= sphinx-build
|
||||||
|
SOURCEDIR = .
|
||||||
|
BUILDDIR = _build
|
||||||
|
|
||||||
|
# Put it first so that "make" without argument is like "make help".
|
||||||
|
help:
|
||||||
|
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
|
|
||||||
|
.PHONY: help Makefile
|
||||||
|
|
||||||
|
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||||
|
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||||
|
%: Makefile
|
||||||
|
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
55
docs/conf.py
Normal file
55
docs/conf.py
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
# Configuration file for the Sphinx documentation builder.
|
||||||
|
#
|
||||||
|
# This file only contains a selection of the most common options. For a full
|
||||||
|
# list see the documentation:
|
||||||
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||||
|
|
||||||
|
# -- Path setup --------------------------------------------------------------
|
||||||
|
|
||||||
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
|
#
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, os.path.abspath('..'))
|
||||||
|
|
||||||
|
# -- Project information -----------------------------------------------------
|
||||||
|
|
||||||
|
project = 'Tornado OpenAPI 3'
|
||||||
|
copyright = '2021, Correl Roush'
|
||||||
|
author = 'Correl Roush'
|
||||||
|
|
||||||
|
|
||||||
|
# -- General configuration ---------------------------------------------------
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||||
|
# ones.
|
||||||
|
extensions = [
|
||||||
|
"sphinx.ext.autodoc",
|
||||||
|
"sphinx_rtd_theme",
|
||||||
|
]
|
||||||
|
|
||||||
|
autodoc_member_order = 'groupwise'
|
||||||
|
|
||||||
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
templates_path = ['_templates']
|
||||||
|
|
||||||
|
# List of patterns, relative to source directory, that match files and
|
||||||
|
# directories to ignore when looking for source files.
|
||||||
|
# This pattern also affects html_static_path and html_extra_path.
|
||||||
|
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTML output -------------------------------------------------
|
||||||
|
|
||||||
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
|
# a list of builtin themes.
|
||||||
|
#
|
||||||
|
html_theme = 'sphinx_rtd_theme'
|
||||||
|
|
||||||
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
|
html_static_path = ['_static']
|
9
docs/handler.rst
Normal file
9
docs/handler.rst
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Handling Incoming Requests
|
||||||
|
==========================
|
||||||
|
|
||||||
|
OpenAPIRequestHandler extends Tornado's RequestHandler class, providing
|
||||||
|
validation of incoming requests and translating errors into appropriate HTTP
|
||||||
|
responses.
|
||||||
|
|
||||||
|
.. automodule:: tornado_openapi3.handler
|
||||||
|
:members:
|
52
docs/index.rst
Normal file
52
docs/index.rst
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
.. Tornado OpenAPI 3 documentation master file, created by
|
||||||
|
sphinx-quickstart on Thu Feb 25 23:03:16 2021.
|
||||||
|
You can adapt this file completely to your liking, but it should at least
|
||||||
|
contain the root `toctree` directive.
|
||||||
|
|
||||||
|
Tornado OpenAPI 3
|
||||||
|
=================
|
||||||
|
|
||||||
|
.. image:: https://travis-ci.com/correl/tornado-openapi3.svg?branch=master
|
||||||
|
:target: https://travis-ci.com/correl/tornado-openapi3
|
||||||
|
.. image:: https://codecov.io/gh/correl/tornado-openapi3/branch/master/graph/badge.svg?token=CTYWWDXTL9
|
||||||
|
:target: https://codecov.io/gh/correl/tornado-openapi3
|
||||||
|
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||||
|
:target: https://github.com/psf/black
|
||||||
|
|
||||||
|
|
||||||
|
Tornado OpenAPI 3 request and response validation library.
|
||||||
|
|
||||||
|
Provides integration between the `Tornado`_ web framework and `Openapi-core`_
|
||||||
|
library for validating request and response objects against an `OpenAPI 3`_
|
||||||
|
specification.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
:caption: Getting Started
|
||||||
|
|
||||||
|
installation
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
:caption: Usage
|
||||||
|
|
||||||
|
handler
|
||||||
|
testing
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
:caption: Validators
|
||||||
|
|
||||||
|
validators
|
||||||
|
|
||||||
|
|
||||||
|
Indices and tables
|
||||||
|
==================
|
||||||
|
|
||||||
|
* :ref:`genindex`
|
||||||
|
* :ref:`modindex`
|
||||||
|
* :ref:`search`
|
||||||
|
|
||||||
|
.. _OpenAPI 3: https://swagger.io/specification/
|
||||||
|
.. _Openapi-core: https://github.com/p1c2u/openapi-core
|
||||||
|
.. _Tornado: https://www.tornadoweb.org/
|
11
docs/installation.rst
Normal file
11
docs/installation.rst
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
Installation
|
||||||
|
============
|
||||||
|
|
||||||
|
Tornado OpenAPI 3 is distributed on `PyPi`_ and can be installed via ``pip`` by
|
||||||
|
running:
|
||||||
|
|
||||||
|
.. code:: console
|
||||||
|
|
||||||
|
$ pip install tornado-openapi3
|
||||||
|
|
||||||
|
.. _PyPi: https://pypi.org/project/tornado-openapi3/
|
35
docs/make.bat
Normal file
35
docs/make.bat
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
@ECHO OFF
|
||||||
|
|
||||||
|
pushd %~dp0
|
||||||
|
|
||||||
|
REM Command file for Sphinx documentation
|
||||||
|
|
||||||
|
if "%SPHINXBUILD%" == "" (
|
||||||
|
set SPHINXBUILD=sphinx-build
|
||||||
|
)
|
||||||
|
set SOURCEDIR=.
|
||||||
|
set BUILDDIR=_build
|
||||||
|
|
||||||
|
if "%1" == "" goto help
|
||||||
|
|
||||||
|
%SPHINXBUILD% >NUL 2>NUL
|
||||||
|
if errorlevel 9009 (
|
||||||
|
echo.
|
||||||
|
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||||
|
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||||
|
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||||
|
echo.may add the Sphinx directory to PATH.
|
||||||
|
echo.
|
||||||
|
echo.If you don't have Sphinx installed, grab it from
|
||||||
|
echo.http://sphinx-doc.org/
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:help
|
||||||
|
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||||
|
|
||||||
|
:end
|
||||||
|
popd
|
8
docs/testing.rst
Normal file
8
docs/testing.rst
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
Testing API Responses
|
||||||
|
=====================
|
||||||
|
|
||||||
|
AsyncOpenAPITestCase extends Tornado's AsyncHTTPTestCase class, providing
|
||||||
|
validation of the responses from your application and raising errors in tests.
|
||||||
|
|
||||||
|
.. automodule:: tornado_openapi3.testing
|
||||||
|
:members:
|
11
docs/validators.rst
Normal file
11
docs/validators.rst
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
Requests
|
||||||
|
==========
|
||||||
|
|
||||||
|
.. automodule:: tornado_openapi3.requests
|
||||||
|
:members:
|
||||||
|
|
||||||
|
Responses
|
||||||
|
=========
|
||||||
|
|
||||||
|
.. automodule:: tornado_openapi3.responses
|
||||||
|
:members:
|
|
@ -28,6 +28,9 @@ pytest-black = "*"
|
||||||
pytest-cov = "*"
|
pytest-cov = "*"
|
||||||
pytest-flake8 = "*"
|
pytest-flake8 = "*"
|
||||||
pytest-mypy = "*"
|
pytest-mypy = "*"
|
||||||
|
sphinx = "^3.5.1"
|
||||||
|
sphinx-rtd-theme = "^0.5.1"
|
||||||
|
pylint = "^2.7.1"
|
||||||
|
|
||||||
[tool.coverage.report]
|
[tool.coverage.report]
|
||||||
fail_under = 100
|
fail_under = 100
|
||||||
|
|
|
@ -21,10 +21,62 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class OpenAPIRequestHandler(tornado.web.RequestHandler):
|
class OpenAPIRequestHandler(tornado.web.RequestHandler):
|
||||||
spec: dict = {}
|
@property
|
||||||
custom_media_type_deserializers: dict = {}
|
def spec(self) -> dict:
|
||||||
|
"""The OpenAPI 3 specification as a Python dictionary.
|
||||||
|
|
||||||
|
Override this in your request handlers to load or define your OpenAPI 3
|
||||||
|
spec.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return dict()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def custom_media_type_deserializers(self) -> dict:
|
||||||
|
"""A dictionary mapping media types to deserializing functions.
|
||||||
|
|
||||||
|
If your endpoints make use of content types beyond ``application/json``,
|
||||||
|
you must add them to this dictionary with a deserializing method that
|
||||||
|
converts the raw body (as ``bytes`` or ``str``) to Python objects.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return dict()
|
||||||
|
|
||||||
async def prepare(self) -> None:
|
async def prepare(self) -> None:
|
||||||
|
"""Called at the beginning of a request before *get/post/etc*.
|
||||||
|
|
||||||
|
Performs OpenAPI validation of the incoming request. Problems
|
||||||
|
encountered while validating the request are translated to HTTP error
|
||||||
|
codes:
|
||||||
|
|
||||||
|
+--------------------------+----------+----------------------------------------+
|
||||||
|
|OpenAPI Errors |Error Code|Description |
|
||||||
|
+--------------------------+----------+----------------------------------------+
|
||||||
|
|``PathNotFound`` |``404`` |Could not find the path for this request|
|
||||||
|
| | |in the OpenAPI specification. |
|
||||||
|
+--------------------------+----------+----------------------------------------+
|
||||||
|
|``OperationNotFound`` |``405`` |Could not find the operation specified |
|
||||||
|
| | |for this request in the OpenAPI |
|
||||||
|
| | |specification. |
|
||||||
|
+--------------------------+----------+----------------------------------------+
|
||||||
|
|``DeserializeError``, |``400`` |The message body could not be decoded or|
|
||||||
|
|``ValidateError`` | |did not validate against the specified |
|
||||||
|
| | |schema. |
|
||||||
|
+--------------------------+----------+----------------------------------------+
|
||||||
|
|``InvalidSecurity`` |``401`` |Required authorization was missing from |
|
||||||
|
| | |the request. |
|
||||||
|
+--------------------------+----------+----------------------------------------+
|
||||||
|
|``InvalidContentType`` |``415`` |The content type of the request did not |
|
||||||
|
| | |match any of the types in the OpenAPI |
|
||||||
|
| | |specification. |
|
||||||
|
+--------------------------+----------+----------------------------------------+
|
||||||
|
|Any other ``OpenAPIError``|``500`` |An unexpected error occurred. |
|
||||||
|
+--------------------------+----------+----------------------------------------+
|
||||||
|
|
||||||
|
To provide content in these error requests, you may override
|
||||||
|
:meth:`on_openapi_error`.
|
||||||
|
|
||||||
|
"""
|
||||||
maybe_coro = super().prepare()
|
maybe_coro = super().prepare()
|
||||||
if maybe_coro and asyncio.iscoroutine(maybe_coro): # pragma: no cover
|
if maybe_coro and asyncio.iscoroutine(maybe_coro): # pragma: no cover
|
||||||
await maybe_coro
|
await maybe_coro
|
||||||
|
@ -52,5 +104,11 @@ class OpenAPIRequestHandler(tornado.web.RequestHandler):
|
||||||
self.validated = result
|
self.validated = result
|
||||||
|
|
||||||
def on_openapi_error(self, status_code: int, error: OpenAPIError) -> None:
|
def on_openapi_error(self, status_code: int, error: OpenAPIError) -> None:
|
||||||
|
"""Sets an HTTP status code and finishes the request.
|
||||||
|
|
||||||
|
By default, no content is returned. To provide more informative
|
||||||
|
responses, you may override this method.
|
||||||
|
|
||||||
|
"""
|
||||||
self.set_status(status_code)
|
self.set_status(status_code)
|
||||||
self.finish()
|
self.finish()
|
||||||
|
|
|
@ -16,8 +16,15 @@ from .util import parse_mimetype
|
||||||
|
|
||||||
|
|
||||||
class TornadoRequestFactory:
|
class TornadoRequestFactory:
|
||||||
|
"""Factory for converting Tornado requests to OpenAPI request objects."""
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, request: Union[HTTPRequest, HTTPServerRequest]) -> OpenAPIRequest:
|
def create(cls, request: Union[HTTPRequest, HTTPServerRequest]) -> OpenAPIRequest:
|
||||||
|
"""Creates an OpenAPI request from Tornado request objects.
|
||||||
|
|
||||||
|
Supports both :class:`tornado.httpclient.HTTPRequest` and
|
||||||
|
:class:`tornado.httputil.HTTPServerRequest` objects.
|
||||||
|
|
||||||
|
"""
|
||||||
if isinstance(request, HTTPRequest):
|
if isinstance(request, HTTPRequest):
|
||||||
if request.url:
|
if request.url:
|
||||||
path, _, querystring = request.url.partition("?")
|
path, _, querystring = request.url.partition("?")
|
||||||
|
@ -55,9 +62,11 @@ class TornadoRequestFactory:
|
||||||
|
|
||||||
|
|
||||||
class RequestValidator(validators.RequestValidator):
|
class RequestValidator(validators.RequestValidator):
|
||||||
|
"""Validator for Tornado HTTP Requests."""
|
||||||
def validate(
|
def validate(
|
||||||
self, request: Union[HTTPRequest, HTTPServerRequest]
|
self, request: Union[HTTPRequest, HTTPServerRequest]
|
||||||
) -> RequestValidationResult:
|
) -> RequestValidationResult:
|
||||||
|
"""Validate a Tornado HTTP request object."""
|
||||||
return super().validate(TornadoRequestFactory.create(request))
|
return super().validate(TornadoRequestFactory.create(request))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,10 @@ from .util import parse_mimetype
|
||||||
|
|
||||||
|
|
||||||
class TornadoResponseFactory:
|
class TornadoResponseFactory:
|
||||||
|
"""Factory for converting Tornado responses to OpenAPI response objects."""
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, response: HTTPResponse) -> OpenAPIResponse:
|
def create(cls, response: HTTPResponse) -> OpenAPIResponse:
|
||||||
|
"""Creates an OpenAPI response from Tornado response objects."""
|
||||||
mimetype = parse_mimetype(response.headers.get("Content-Type", "text/html"))
|
mimetype = parse_mimetype(response.headers.get("Content-Type", "text/html"))
|
||||||
return OpenAPIResponse(
|
return OpenAPIResponse(
|
||||||
data=response.body if response.body else b"",
|
data=response.body if response.body else b"",
|
||||||
|
@ -21,7 +23,9 @@ class TornadoResponseFactory:
|
||||||
|
|
||||||
|
|
||||||
class ResponseValidator(validators.ResponseValidator):
|
class ResponseValidator(validators.ResponseValidator):
|
||||||
|
"""Validator for Tornado HTTP Responses."""
|
||||||
def validate(self, response: HTTPResponse) -> ResponseValidationResult:
|
def validate(self, response: HTTPResponse) -> ResponseValidationResult:
|
||||||
|
"""Validate a Tornado HTTP response object."""
|
||||||
return super().validate(
|
return super().validate(
|
||||||
TornadoRequestFactory.create(response.request),
|
TornadoRequestFactory.create(response.request),
|
||||||
TornadoResponseFactory.create(response),
|
TornadoResponseFactory.create(response),
|
||||||
|
|
|
@ -8,10 +8,34 @@ from tornado_openapi3.responses import ResponseValidator
|
||||||
|
|
||||||
|
|
||||||
class AsyncOpenAPITestCase(tornado.testing.AsyncHTTPTestCase):
|
class AsyncOpenAPITestCase(tornado.testing.AsyncHTTPTestCase):
|
||||||
spec: dict = {}
|
@property
|
||||||
custom_media_type_deserializers: dict = {}
|
def spec(self) -> dict:
|
||||||
|
"""The OpenAPI 3 specification as a Python dictionary.
|
||||||
|
|
||||||
|
Override this in your request handlers to load or define your OpenAPI 3
|
||||||
|
spec.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return dict()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def custom_media_type_deserializers(self) -> dict:
|
||||||
|
"""A dictionary mapping media types to deserializing functions.
|
||||||
|
|
||||||
|
If your endpoints make use of content types beyond ``application/json``,
|
||||||
|
you must add them to this dictionary with a deserializing method that
|
||||||
|
converts the raw body (as ``bytes`` or ``str``) to Python objects.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return dict()
|
||||||
|
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
|
"""Hook method for setting up the test fixture before exercising it.
|
||||||
|
|
||||||
|
Instantiates the :class:`tornado_openapi3.responses.ResponseValidator`
|
||||||
|
for this test case.
|
||||||
|
|
||||||
|
"""
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.validator = ResponseValidator(
|
self.validator = ResponseValidator(
|
||||||
create_spec(self.spec),
|
create_spec(self.spec),
|
||||||
|
@ -21,6 +45,15 @@ class AsyncOpenAPITestCase(tornado.testing.AsyncHTTPTestCase):
|
||||||
def fetch(
|
def fetch(
|
||||||
self, path: str, raise_error: bool = False, **kwargs: Any
|
self, path: str, raise_error: bool = False, **kwargs: Any
|
||||||
) -> tornado.httpclient.HTTPResponse:
|
) -> tornado.httpclient.HTTPResponse:
|
||||||
|
"""Convenience methiod to synchronously fetch a URL.
|
||||||
|
|
||||||
|
Extends the fetch method in Tornado's
|
||||||
|
:class:``tornado.testing.AsyncHTTPTestCase`` to perform OpenAPI 3
|
||||||
|
validation on the response received before returning it. If validation
|
||||||
|
fails, an :class:`openapi_core.exceptions.OpenAPIError` will be raised
|
||||||
|
describing the failure.
|
||||||
|
|
||||||
|
"""
|
||||||
response = super().fetch(path, raise_error=False, **kwargs)
|
response = super().fetch(path, raise_error=False, **kwargs)
|
||||||
result = self.validator.validate(response)
|
result = self.validator.validate(response)
|
||||||
result.raise_for_errors()
|
result.raise_for_errors()
|
||||||
|
|
Loading…
Reference in a new issue