Document usage with examples

This commit is contained in:
Correl Roush 2021-03-19 11:17:18 -04:00 committed by Correl
parent d1cd04f3e3
commit a04edb6ff9
9 changed files with 214 additions and 10 deletions

39
docs/examples/cached.py Normal file
View file

@ -0,0 +1,39 @@
import logging
import pathlib
import tornado.ioloop
import tornado.web
from tornado_openapi3.handler import OpenAPIRequestHandler
import yaml
VERSION = "1.0.0"
class MyRequestHandler(OpenAPIRequestHandler):
@property
def spec_dict(self):
return yaml.safe_load(self.render_string("openapi.yaml", version=VERSION))
@property
def spec(self):
spec = getattr(self.application, "openapi_spec", None)
if not spec:
logging.info("Compiling OpenAPI spec")
spec = super().spec
setattr(self.application, "openapi_spec", spec)
return spec
class RootHandler(MyRequestHandler):
async def get(self):
self.finish("Hello, World!")
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
example_root = pathlib.Path(__file__).parent
app = tornado.web.Application(
[(r"/", RootHandler)], template_path=str(example_root / "templates")
)
app.listen(8888)
tornado.ioloop.IOLoop.current().start()

40
docs/examples/simple.py Normal file
View file

@ -0,0 +1,40 @@
import tornado.ioloop
import tornado.web
from tornado_openapi3.handler import OpenAPIRequestHandler
class MyRequestHandler(OpenAPIRequestHandler):
spec_dict = {
"openapi": "3.0.0",
"info": {
"title": "Simple Example",
"version": "1.0.0",
},
"paths": {
"/": {
"get": {
"responses": {
"200": {
"description": "Index",
"content": {
"text/html": {
"schema": {"type": "string"},
}
},
}
}
}
}
},
}
class RootHandler(MyRequestHandler):
async def get(self):
self.finish("Hello, World!")
if __name__ == "__main__":
app = tornado.web.Application([(r"/", RootHandler)])
app.listen(8888)
tornado.ioloop.IOLoop.current().start()

View file

@ -0,0 +1,15 @@
---
openapi: 3.0.0
info:
title: Simple Example
version: "{{ version }}"
paths:
"/":
get:
responses:
'200':
description: Index
content:
text/html:
schema:
type: string

47
docs/examples/test.py Normal file
View file

@ -0,0 +1,47 @@
import unittest
import tornado.web
from tornado_openapi3.testing import AsyncOpenAPITestCase
class RootHandler(tornado.web.RequestHandler):
async def get(self):
self.finish("Hello, World!")
class BaseTestCase(AsyncOpenAPITestCase):
spec_dict = {
"openapi": "3.0.0",
"info": {
"title": "Simple Example",
"version": "1.0.0",
},
"paths": {
"/": {
"get": {
"responses": {
"200": {
"description": "Index",
"content": {
"text/html": {
"schema": {"type": "string"},
}
},
}
}
}
}
},
}
def get_app(self):
return tornado.web.Application([(r"/", RootHandler)])
def test_root_endpoint(self):
response = self.fetch("/")
self.assertEqual(200, response.code)
self.assertEqual(b"Hello, World!", response.body)
if __name__ == "__main__":
unittest.main()

View file

@ -1,9 +1,42 @@
Handling Incoming Requests
==========================
Tornado OpenAPI 3 allows you to validate requests coming in to your application
against your OpenAPI 3.0 specification with little additional code.
Defining a base handler with your OpenAPI 3.0 specification
-----------------------------------------------------------
By extending :class:`~tornado_openapi3.handler.OpenAPIRequestHandler`, you can
define your own base request handler with your specification attached, and use
that for each of your specialized request handlers for your application.
.. literalinclude:: examples/simple.py
A more complex example
----------------------
Your specification doesn't need to be embedded in your code. You may wish to
store it separately in your repository, or even templatize some aspects of it
(like your application version). Doing so is as simple as overriding your
request handler's
:attr:`~tornado_openapi3.handler.OpenAPIRequestHandler.spec_dict` property to
load your specification however you see fit.
By default, the specification is compiled on every request. To achieve better
performance, you may also wish to override the request handler's
:attr:`~tornado_openapi3.handler.OpenAPIRequestHandler.spec_dict` property to
cache the result on your application object.
.. literalinclude:: examples/cached.py
Adding custom deserializers
---------------------------
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 :class:`bytes` or :class:`str`) to Python objects.
.. code-block:: python
import json
@ -21,6 +54,10 @@ Adding custom deserializers
Adding custom formatters
------------------------
If your schemas make use of format modifiers, you may specify them in this
dictionary paired with a :class:`~tornado_openapi3.types.Formatter` object that
provides methods to validate values and unmarshal them into Python objects.
.. code-block:: python
import datetime

View file

@ -21,20 +21,20 @@ library for validating request and response objects against an `OpenAPI 3`_
specification.
.. toctree::
:maxdepth: 2
:maxdepth: 1
:caption: Getting Started
installation
.. toctree::
:maxdepth: 2
:maxdepth: 1
:caption: Usage
handling_incoming_requests
testing_api_responses
.. toctree::
:maxdepth: 2
:maxdepth: 1
:caption: Modules
handler

View file

@ -1,9 +1,31 @@
Testing API Responses
=====================
Tornado OpenAPI 3 includes a base test class to help you validate each of your
application's responses while you test its behavior.
Making your tests aware of your API specification
-------------------------------------------------
By extending :class:`~tornado_openapi3.testing.AsyncOpenAPITestCase`, you can
define your test cases with your specification attached. Every response returned
by :meth:`~tornado_openapi3.testing.AsyncOpenAPITestCase.fetch` will be
automatically checked against your specification to ensure they match the
formats documented, and exceptions will be raised when they do not.
Because it extends :class:`tornado.testing.AsyncHTTPTestCase`, you can write
your application tests as you normally would with added confidence that your API
is behaving exactly as you expect it to.
.. literalinclude:: examples/test.py
Adding custom deserializers
---------------------------
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 :class:`bytes` or :class:`str`) to Python objects.
.. code-block:: python
import json
@ -21,6 +43,10 @@ Adding custom deserializers
Adding custom formatters
------------------------
If your schemas make use of format modifiers, you may specify them in this
dictionary paired with a :class:`~tornado_openapi3.types.Formatter` object that
provides methods to validate values and unmarshal them into Python objects.
.. code-block:: python
import datetime

View file

@ -33,7 +33,7 @@ logger = logging.getLogger(__name__)
class OpenAPIRequestHandler(tornado.web.RequestHandler):
"""Base class for HTTP request handlers.
A request handler extending :py:class:`tornado.web.RequestHandler` providing
A request handler extending :class:`tornado.web.RequestHandler` providing
OpenAPI spec validation on incoming requests and translating errors into
appropriate HTTP responses.
@ -58,7 +58,7 @@ class OpenAPIRequestHandler(tornado.web.RequestHandler):
Override this in your request handlers to customize how your OpenAPI 3
spec is loaded and validated.
:rtype: :py:class:`openapi_core.schema.specs.model.Spec`
:rtype: :class:`openapi_core.schema.specs.model.Spec`
"""
return create_spec(self.spec_dict, validate_spec=False)
@ -71,7 +71,7 @@ class OpenAPIRequestHandler(tornado.web.RequestHandler):
this dictionary paired with a Formatter object that provides methods to
validate values and unmarshal them into Python objects.
:rtype: Mapping[str, :py:class:`tornado_openapi3.types.Formatter`]
:rtype: Mapping[str, :class:`~tornado_openapi3.types.Formatter`]
"""
@ -85,7 +85,7 @@ class OpenAPIRequestHandler(tornado.web.RequestHandler):
you must add them to this dictionary with a deserializing method that
converts the raw body (as ``bytes`` or ``str``) to Python objects.
:rtype: Mapping[str, :py:attr:`tornado_openapi3.types.Deserializer`]
:rtype: Mapping[str, :attr:`~tornado_openapi3.types.Deserializer`]
"""
return dict()

View file

@ -11,7 +11,7 @@ from tornado_openapi3.responses import ResponseValidator
class AsyncOpenAPITestCase(tornado.testing.AsyncHTTPTestCase):
"""A test case that starts up an HTTP server.
An async test case extending :py:class:`tornado.testing.AsyncHTTPTestCase`,
An async test case extending :class:`tornado.testing.AsyncHTTPTestCase`,
providing OpenAPI spec validation on the responses from your application and
raising errors in tests.
@ -35,7 +35,7 @@ class AsyncOpenAPITestCase(tornado.testing.AsyncHTTPTestCase):
Override this in your test cases to customize how your OpenAPI 3 spec is
loaded and validated.
:rtype: :py:class:`openapi_core.schema.specs.model.Spec`
:rtype: :class:`openapi_core.schema.specs.model.Spec`
"""
return create_spec(self.spec_dict)
@ -65,7 +65,7 @@ class AsyncOpenAPITestCase(tornado.testing.AsyncHTTPTestCase):
def setUp(self) -> None:
"""Hook method for setting up the test fixture before exercising it.
Instantiates the :class:`tornado_openapi3.responses.ResponseValidator`
Instantiates the :class:`~tornado_openapi3.responses.ResponseValidator`
for this test case.
"""