aiohttp.web OpenAPI 3 schema first server applications#
OpenAPI 3 is a powerful way of describing request / response specifications for API endpoints. There are two ways on using OpenAPI 3 within Python server applications:
Generate the schema from Python data structures (as done in Django REST Framework, FastAPI, aiohttp-apispec and others)
Rely on OpenAPI 3 schema file (as done in pyramid_openapi3)
While both ways have their pros & cons, rororo
library is heavily inspired
by pyramid_openapi3 and as
result requires valid OpenAPI 3 schema file to be provided.
In total, to build aiohttp.web OpenAPI 3 server applications with rororo you need to:
Provide valid OpenAPI 3 schema file
Map
operationId
withaiohttp.web
view handler viarororo.openapi.OperationTableDef
Call
rororo.openapi.setup_openapi()
to finish setup process
Below more details provided for all significant parts.
Part 1. Provide OpenAPI 3 schema file#
From one point of view, generating OpenAPI 3 schemas from Python data structures is more Pythonic way, but it results in several issues:
Which Python data structure use as a basis? For example,
Django REST Framework generates OpenAPI 3 schemas from their own serializers
While aiohttp-apispec built on top of APISpec library, which behind the scenes utilies marshmallow data structures
Data structure library need to support whole OpenAPI 3 specification on their own
Sharing OpenAPI 3 schema with other parts of your application (frontend, mobile application, etc) became a tricky task, which in most cases requires to be handled by specific CI/CD job
In same time, as rororo requires OpenAPI 3 schema file it allows to,
Use any Python data structure library for accessing valid request data and for providing valid response data
Track changes to OpenAPI specification file directly with source control management system as git or mercurial
Use all available OpenAPI 3 specification features
To start with OpenAPI 3 schema it is recommended to,
Check whole OpenAPI 3 specification
Browse through OpenAPI 3 Examples
Try Swagger Editor online tool
Supply schema & spec instances instead of schema path#
To simplify developer experience rororo expects only on OpenAPI 3 schema path.
However it is possible to pass predefined schema
dict and spec
instance
instead. Consult rororo.openapi.setup_openapi()
to check how to achieve
that.
Part 2. Map operation with view handler#
After OpenAPI 3 schema file is valid and ready to be used, it is needed to map OpenAPI operations with aiohttp.web view handlers.
As operationId field for the operation is,
Unique string used to identify the operation. The id MUST be unique among all operations described in the API.
It makes possible to tell aiohttp.web
to use specific view as a handler
for every given OpenAPI 3 operation.
For example,
OpenAPI 3 specification has
hello_world
operationapi.views
module hashello_world
view handler
To connect both of described parts rororo.openapi.OperationTableDef
need to be used as (in views.py
):
from aiohttp import web
from rororo import OperationTableDef
operations = OperationTableDef()
@operations.register
async def hello_world(request: web.Request) -> web.Response:
return web.json_response("Hello, world!")
In case, when operationId does not match view handler name it is needed to
to pass operation_id
string as first argument of @operations.register
decorator,
@operations.register("hello_world")
async def not_a_hello_world(
request: web.Request,
) -> web.Response:
return web.json_response("Hello, world!")
Class Based Views#
rororo supports class based views as well.
In basic mode it expects that OpenAPI schema contains operationId, which
equals to all view method qualified names. For example, code below expects
OpenAPI schema to declare UsersView.get
& UsersView.post
operation IDs,
@operations.register
class UsersView(web.View):
async def get(self) -> web.Response:
...
async def post(self) -> web.Response:
...
Next, it might be useful to provide different prefix instead of UsersView
.
In example below, rororo expects OpenAPI schema to provide users.get
&
users.post
operation IDs,
@operations.register("users")
class UsersView(web.View):
async def get(self) -> web.Response:
...
async def post(self) -> web.Response:
...
Finally, it might be useful to provide custom operationId instead of guessing
it from view or view method name. Example below, illustrates the case, when
OpenAPI schema contains list_users
& create_user
operation IDs,
@operations.register
class UsersView(web.View):
@operations.register("list_users")
async def get(self) -> web.Response:
...
@operations.register("create_user")
async def post(self) -> web.Response:
...
To access rororo.openapi.data.OpenAPIContext
in class based views you
need to pass self.request
into rororo.openapi.openapi_context()
or
rororo.openapi.get_openapi_context()
as done below,
@operations.register
class UserView(web.View):
async def patch(self) -> web.Response:
user = get_user_or_404(self.request)
with openapi_context(self.request) as context:
next_user = attr.evolve(user, **context.data)
save_user(next_user)
return web.json_response(next_user.to_api_dict())
Important
On registering class based views with multiple view methods (for example
with get
, patch
& put
) you need to ensure that all methods
could be mapped to operation ID in provided OpenAPI schema file.
Request Validation#
Decorating view handler with @operations.register
will ensure that it will
be executed only with valid request body & parameters according to OpenAPI 3
operation specification.
If any parameters are missed or invalid, as well as if request body does not pass validation it will result in 422 response.
Accessing Valid Request Data#
To access valid data for given request it is recommended to use
rororo.openapi.openapi_context()
context manager as follows,
@operations.register
async def add_pet(request: web.Request) -> web.Response:
with openapi_context(request) as context:
...
Resulted context instance will contain,
request
- untouchedaiohttp.web.Request
instanceapp
-aiohttp.web.Application
instanceconfig_dict
parameters
- valid parameters mappings (path
,query
,header
,cookie
)security
- security data, if operation is secureddata
- valid data from request body
Part 3. Finish setup process#
After the OpenAPI 3 schema is provided and view handlers is mapped to OpenAPI
operations it is a time to tell an aiohttp.web.Application
to use
given schema file and operations mapping(s) via
rororo.openapi.setup_openapi()
.
In most cases this setup should be done in application factory function as follows,
from pathlib import Path
from typing import List
from aiohttp import web
from rororo import setup_openapi
from .views import operations
OPENAPI_YAML_PATH = Path(__file__).parent / "openapi.yaml"
def create_app(argv: List[str] = None) -> web.Application:
app = web.Application()
setup_openapi(app, OPENAPI_YAML_PATH, operations)
return app
Note
It is recommended to store OpenAPI 3 schema file next to main application module, which semantically will mean: this is an OpenAPI 3 schema file for current application.
But it is not mandatory, and you might want to specify any accessible file path, you want.
Note
By default, OpenAPI schema, which is used for the application will be
available via GET requests to {server_url}/openapi.(json|yaml)
, but
it is possible to not serve the schema by passing
has_openapi_schema_handler
falsy flag to
rororo.openapi.setup_openapi()
Configuration & Operation Errors#
Setting up OpenAPI for aiohttp.web applicaitons via
rororo.openapi.setup_openapi()
may result in numerous errors as it relies
on many things. While most of the errors designed to be self-descriptive below
more information added about most possible cases.
OpenAPI 3 Schema file does not exist or not readable#
rororo expects that schema_path
is a path to a readable file with
OpenAPI schema. To fix the error, pass proper path.
Unable to read OpenAPI 3 Schema from the file#
rororo supports reading OpenAPI 3 schema from JSON & YAML files with
extensions: .json
, .yml
, .yaml
. If the schema_path
file
contains valid OpenAPI 3 schema, but has different extension, consider rename
it. Also, in same time rororo expects that .json
files contain valid
JSON, while .yml
/ .yaml
files contain valid YAML data.
OpenAPI 3 Schema is not valid#
rororo requires your OpenAPI 3 schema file to be a valid one. If the file is not valid consider running openapi-spec-validator against your file to find the issues.
Note
rororo depends on openapi-spec-validator (via openapi-core), which
means after installing rororo, virtual environment (or system) will
have openapi-spec-validator
script available
Operation not found#
Please, use valid operationId while mapping OpenAPI operation to aiohttp.web view handler.
Using invalid operationId will result in runtime error, which doesn’t allow aiohttp.web application to start up.
Accessing OpenAPI Schema & Spec#
After OpenAPI setting up for aiohttp.web.Application
it is possible
to access OpenAPI Schema & Spec inside of any view handler as follows,
from rororo import get_openapi_schema, get_openapi_spec
async def something(request: web.Request) -> web.Response:
# `Dict[str, Any]` with OpenAPI schema
schema = get_openapi_schema(request.app)
# `openapi_core.schemas.specs.models.Spec` instance
spec = get_openapi_spec(request.config_dict)
...
How it Works?#
Under the hood rororo heavily relies on openapi-core library.
rororo.openapi.setup_openapi()
Creates the Spec instance from OpenAPI schema source
Connects previously registered handlers and views to the application router (
aiohttp.web.UrlDispatcher
)Registers hidden
openapi_middleware
to handle request to registered handlers and views
On handling each OpenAPI request RequestValidator.validate(…) method called. Result of validation as
rororo.openapi.data.OpenAPIContext
supplied to currentaiohttp.web.Request
instanceIf enabled, ResponseValidator.validate(…) method called for each OpenAPI response
Swagger 2.0 Support#
While rororo designed to support only OpenAPI 3 Schemas due to openapi-core dependency it is technically able to support Swagger 2.0 for aiohttp.web applications in same manner as well.
Important
Swagger 2.0 support is not tested at all and rororo is not intended to provide it.
With that in mind please consider rororo only as a library to bring
OpenAPI 3 Schemas support for aiohttp.web
applications.