Build and document REST APIs with aiohttp and apispec
Project description
aiohttp-apispec
Build and document REST APIs with aiohttp and apispec
aiohttp-apispec
key features:
docs
andrequest_schema
decorators to add swagger spec support out of the box;validation_middleware
middleware to enable validating with marshmallow schemas from those decorators;- SwaggerUI support.
- New from version 2.0 -
match_info_schema
,querystring_schema
,form_schema
,json_schema
,headers_schema
andcookies_schema
decorators for specific request parts validation. Look here for more info.
aiohttp-apispec
api is fully inspired by flask-apispec
library
Contents
- Install
- Quickstart
- Adding validation middleware
- More decorators
- Custom error handling
- Build swagger web client
- Versioning
Install
pip install aiohttp-apispec
Quickstart
Also you can read blog post about quickstart with aiohttp-apispec
from aiohttp_apispec import (
docs,
request_schema,
setup_aiohttp_apispec,
)
from aiohttp import web
from marshmallow import Schema, fields
class RequestSchema(Schema):
id = fields.Int()
name = fields.Str(description="name")
@docs(
tags=["mytag"],
summary="Test method summary",
description="Test method description",
)
@request_schema(RequestSchema(strict=True))
async def index(request):
return web.json_response({"msg": "done", "data": {}})
app = web.Application()
app.router.add_post("/v1/test", index)
# init docs with all parameters, usual for ApiSpec
setup_aiohttp_apispec(
app=app,
title="My Documentation",
version="v1",
url="/api/docs/swagger.json",
swagger_path="/api/docs",
)
# Now we can find spec on 'http://localhost:8080/api/docs/swagger.json'
# and docs on 'http://localhost:8080/api/docs'
web.run_app(app)
Class based views are also supported:
class TheView(web.View):
@docs(
tags=["mytag"],
summary="View method summary",
description="View method description",
)
@request_schema(RequestSchema(strict=True))
@response_schema(ResponseSchema(), 200)
def delete(self):
return web.json_response(
{"msg": "done", "data": {"name": self.request["data"]["name"]}}
)
app.router.add_view("/v1/view", TheView)
As alternative you can add responses info to docs
decorator, which is more compact way.
And it allows you not to use schemas for responses documentation:
@docs(
tags=["mytag"],
summary="Test method summary",
description="Test method description",
responses={
200: {
"schema": ResponseSchema,
"description": "Success response",
}, # regular response
404: {"description": "Not found"}, # responses without schema
422: {"description": "Validation error"},
},
)
@request_schema(RequestSchema(strict=True))
async def index(request):
return web.json_response({"msg": "done", "data": {}})
Adding validation middleware
from aiohttp_apispec import validation_middleware
...
app.middlewares.append(validation_middleware)
Now you can access all validated data in route from request['data']
like so:
@docs(
tags=["mytag"],
summary="Test method summary",
description="Test method description",
)
@request_schema(RequestSchema(strict=True))
@response_schema(ResponseSchema, 200)
async def index(request):
uid = request["data"]["id"]
name = request["data"]["name"]
return web.json_response(
{"msg": "done", "data": {"info": f"name - {name}, id - {uid}"}}
)
You can change Request
's 'data'
param to another with request_data_name
argument of
setup_aiohttp_apispec
function:
setup_aiohttp_apispec(
app=app,
request_data_name="validated_data",
)
...
@request_schema(RequestSchema(strict=True))
async def index(request):
uid = request["validated_data"]["id"]
...
Also you can do it for specific view using put_into
parameter (beginning from version 2.0):
@request_schema(RequestSchema(strict=True), put_into="validated_data")
async def index(request):
uid = request["validated_data"]["id"]
...
More decorators
Starting from version 2.0 you can use shortenings for documenting and validating specific request parts like cookies, headers etc using those decorators:
Decorator name | Default put_into param |
---|---|
match_info_schema | match_info |
querystring_schema | querystring |
form_schema | form |
json_schema | json |
headers_schema | headers |
cookies_schema | cookies |
And example:
@docs(
tags=["users"],
summary="Create new user",
description="Add new user to our toy database",
responses={
200: {"description": "Ok. User created", "schema": OkResponse},
401: {"description": "Unauthorized"},
422: {"description": "Validation error"},
500: {"description": "Server error"},
},
)
@headers_schema(AuthHeaders) # <- schema for headers validation
@json_schema(UserMeta) # <- schema for json body validation
@querystring_schema(UserParams) # <- schema for querystring params validation
async def create_user(request: web.Request):
headers = request["headers"] # <- validated headers!
json_data = request["json"] # <- validated json!
query_params = request["querystring"] # <- validated querystring!
...
Custom error handling
If you want to catch validation errors by yourself you
could use error_callback
parameter and create your custom error handler. Note that
it can be one of coroutine or callable and it should
have interface exactly like in examples below:
from marshmallow import ValidationError, Schema
from aiohttp import web
from typing import Optional, Mapping, NoReturn
def my_error_handler(
error: ValidationError,
req: web.Request,
schema: Schema,
error_status_code: Optional[int] = None,
error_headers: Optional[Mapping[str, str]] = None,
) -> NoReturn:
raise web.HTTPBadRequest(
body=json.dumps(error.messages),
headers=error_headers,
content_type="application/json",
)
setup_aiohttp_apispec(app, error_callback=my_error_handler)
Also you can create your own exceptions and create regular Request in middleware like so:
class MyException(Exception):
def __init__(self, message):
self.message = message
# It can be coroutine as well:
async def my_error_handler(
error, req, schema, error_status_code, error_headers
):
await req.app["db"].do_smth() # So you can use some async stuff
raise MyException({"errors": error.messages, "text": "Oops"})
# This middleware will handle your own exceptions:
@web.middleware
async def intercept_error(request, handler):
try:
return await handler(request)
except MyException as e:
return web.json_response(e.message, status=400)
setup_aiohttp_apispec(app, error_callback=my_error_handler)
# Do not forget to add your own middleware before validation_middleware
app.middlewares.extend([intercept_error, validation_middleware])
Build swagger web client
3.X SwaggerUI version
Just add swagger_path
parameter to setup_aiohttp_apispec
function.
For example:
setup_aiohttp_apispec(app, swagger_path="/docs")
Then go to /docs
and see awesome SwaggerUI
2.X SwaggerUI version
If you prefer older version you can use
aiohttp_swagger library.
aiohttp-apispec
adds swagger_dict
parameter to aiohttp web application
after initialization (with setup_aiohttp_apispec
function).
So you can use it easily like:
from aiohttp_apispec import setup_aiohttp_apispec
from aiohttp_swagger import setup_swagger
def create_app(app):
setup_aiohttp_apispec(app)
async def swagger(app):
setup_swagger(
app=app, swagger_url="/api/doc", swagger_info=app["swagger_dict"]
)
app.on_startup.append(swagger)
# now we can access swagger client on '/api/doc' url
...
return app
Versioning
This software follows Semantic Versioning.
Please star this repository if this project helped you!
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
File details
Details for the file aiohttp-apispec-2.2.3.tar.gz
.
File metadata
- Download URL: aiohttp-apispec-2.2.3.tar.gz
- Upload date:
- Size: 2.7 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.7.1 importlib_metadata/4.10.0 pkginfo/1.8.2 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.9.9
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | d70431e5f3ef5c6dc96dc9180ce10ddfd78fa054f178af8259707eb6d421ed05 |
|
MD5 | 2c7c46684f908b8bb53c557d8d053f23 |
|
BLAKE2b-256 | 4a4772e2a08bb9990264cd058d1fef90b7378b459ad5d530aabcf10fe837d8e2 |