API documentation for aiohttp server based on Pydantic models
Project description
aiohttp-docs
Auto-generate OpenAPI 3.1 specification and
Swagger UI documentation for
aiohttp web servers.
Swagger version: v5.31.2
Annotate your route handlers with the @docs() decorator and call setup_docs() once at startup — the library
builds the full spec and serves both the JSON endpoint and the interactive Swagger UI.
Python >= 3.13 is required.
Installation
pip install aiohttp-docs
Quick start
from http import HTTPStatus
from aiohttp import web
from pydantic import BaseModel
from aiohttp_docs import Info, docs, setup_docs
class UserResponse(BaseModel):
id: int
name: str
@docs(
tags=['Users'],
summary='Get current user',
response_models={HTTPStatus.OK: UserResponse},
)
async def users_me(_: web.Request) -> web.Response:
"""Return the current user."""
return web.json_response({'id': 1, 'name': 'John'})
def main() -> None:
app = web.Application()
app.router.add_get('/users/me', users_me, allow_head=False)
setup_docs(
app,
info=Info(
title='My API',
version='0.1.0',
),
spec_url_path='/api/openapi.json', # URL to for OpenAPI Specification
swagger_url_path='/api/doc', # URL for Swagger
)
web.run_app(app)
if __name__ == '__main__':
main()
After starting the server, open http://localhost:8080/api/doc to see the Swagger UI.
Examples
Request body and response models
Use body_model to describe the JSON request body.
Use response_models to document possible responses — keys are HTTP status codes (as int or HTTPStatus),
values are Pydantic models or Response(model=..., description=...) dicts.
from http import HTTPStatus
from aiohttp import web
from pydantic import BaseModel, ValidationError
from aiohttp_docs import Info, Response, docs, setup_docs
class CreateUserRequest(BaseModel):
name: str
age: int
is_active: bool = True
class CreateUserResponse(BaseModel):
id: int
name: str
class ErrorResponse(BaseModel):
error_message: str
@docs(
tags=['Users'],
summary='Create a new user',
body_model=CreateUserRequest,
response_models={
HTTPStatus.CREATED: Response(model=CreateUserResponse, description='User created'),
HTTPStatus.BAD_REQUEST: ErrorResponse,
},
)
async def users_create(request: web.Request) -> web.Response:
try:
body = CreateUserRequest.model_validate_json(await request.content.read())
except ValidationError as e:
return web.json_response({'error_message': str(e)}, status=HTTPStatus.BAD_REQUEST)
return web.json_response({'id': 1, 'name': body.name}, status=201)
def main() -> None:
app = web.Application()
app.router.add_post('/users', users_create)
setup_docs(app, info=Info(title='My API', version='0.1.0'))
web.run_app(app)
if __name__ == '__main__':
main()
Path, query, header, and cookie parameters (and examples for them)
Define Pydantic models for each parameter location and pass them to the decorator.
from datetime import date, datetime, UTC
from decimal import Decimal
from http import HTTPStatus
from aiohttp import web
from pydantic import BaseModel, Field
from aiohttp_docs import Example, Info, docs, setup_docs
def utc_today() -> date:
return datetime.now(tz=UTC).date()
class UserOrdersInfo(BaseModel):
user_id: int
orders_count: int
total_amount: Decimal
date_from: date = Field(alias='from')
date_to: date = Field(alias='to')
class PathParams(BaseModel):
user_id: int
class QueryParams(BaseModel):
date_from: date = Field(
alias='from',
default_factory=utc_today,
description='Date from (including); Defaults to today',
examples=[ # examples for "from" field
Example(value='1970-01-01', summary='Start of UNIX Era'),
Example(value='2020-10-10'),
Example(value='2021-11-21'),
Example(value=utc_today().isoformat(), summary='Today'),
],
)
date_to: date = Field(
alias='to',
default_factory=utc_today,
description='Date to (including); Defaults to today',
)
@docs(
tags=['Admin', 'Users', 'Orders'],
summary='List user orders',
response_models={HTTPStatus.OK: UserOrdersInfo},
path_model=PathParams,
query_model=QueryParams,
)
async def admin_user_orders_info(request: web.Request) -> web.Response:
user_id = int(request.match_info['user_id'])
dates = QueryParams.model_validate(request.query)
return web.json_response(
{
'user_id': user_id,
'orders_count': 17,
'total_amount': 139.95,
'from': dates.date_from.isoformat(),
'to': dates.date_to.isoformat(),
}
)
def main() -> None:
app = web.Application()
app.router.add_get('/admin/user/{user_id}/orders', admin_user_orders_info, allow_head=False)
setup_docs(app, info=Info(title='My API', version='0.1.0'))
web.run_app(app)
if __name__ == '__main__':
main()
Class-based views
The @docs() decorator works on individual methods of aiohttp.web.View subclasses.
from http import HTTPStatus
from aiohttp import web
from pydantic import BaseModel
from aiohttp_docs import Info, Response, docs, setup_docs
class Item(BaseModel):
id: int
class ItemResponse(BaseModel):
id: int
title: str
class ItemBody(BaseModel):
title: str
class ItemView(web.View):
@docs(
tags=['Items'],
summary='Get item by ID',
path_model=Item,
response_models={HTTPStatus.OK: ItemResponse},
)
async def get(self) -> web.Response:
item_id = int(self.request.match_info['id'])
return web.json_response({'id': item_id, 'title': 'Thing'})
@docs(
tags=['Items'],
summary='Update item',
path_model=Item,
body_model=ItemBody,
response_models={
HTTPStatus.OK: Response(model=ItemResponse, description='Item updated'),
},
)
async def put(self) -> web.Response:
item_id = int(self.request.match_info['id'])
body = ItemBody.model_validate_json(await self.request.content.read())
return web.json_response({'id': item_id, 'title': body.title})
@docs(
tags=['Items'],
summary='Delete item',
path_model=Item,
response_models={
HTTPStatus.NO_CONTENT: Response(model=None, description='Item deleted'),
},
)
async def delete(self) -> web.Response:
return web.json_response(status=HTTPStatus.NO_CONTENT)
def main() -> None:
app = web.Application()
app.router.add_view('/items/{id}', ItemView)
setup_docs(app, info=Info(title='My API', version='0.1.0'))
web.run_app(app)
if __name__ == '__main__':
main()
Deprecating endpoints
Mark an endpoint as deprecated explicitly via the decorator or by using the standard @deprecated decorator from warnings.
from http import HTTPStatus
from warnings import deprecated
from aiohttp import web
from pydantic import BaseModel
from aiohttp_docs import Info, docs, setup_docs
class LegacyResponse(BaseModel):
status: str
@docs(
tags=['Legacy'],
deprecated=True, # this will mark the API method as deprecated only for the documentation
response_models={HTTPStatus.OK: LegacyResponse},
)
async def old_endpoint(_: web.Request) -> web.Response:
return web.json_response({'status': 'old'})
@deprecated('Use /v2/resource instead') # this will mark the function as deprecated, and it will be reflected in the documentation too
@docs(
tags=['Legacy'],
response_models={HTTPStatus.OK: LegacyResponse},
)
async def another_old_endpoint(_: web.Request) -> web.Response:
return web.json_response({'status': 'very old'})
def main() -> None:
app = web.Application()
app.add_routes(
[
web.get('/one', old_endpoint, allow_head=False),
web.get('/two', another_old_endpoint, allow_head=False),
],
)
setup_docs(app, info=Info(title='My API', version='0.1.0'))
web.run_app(app)
if __name__ == '__main__':
main()
Disabling docs in production
Pass enabled=False to setup_docs() to skip registration entirely.
import os
from aiohttp import web
from aiohttp_docs import Info, setup_docs
def main():
app = web.Application()
# disable docs for production
# `/api/openapi.json`, `/api/doc` and `/static/swagger` will return 404 Not Found
setup_docs(
app,
info=Info(title='My API', version='0.1.0'),
enabled=os.getenv('ENVIRONMENT', '') != 'PRODUCTION',
)
web.run_app(app)
if __name__ == '__main__':
main()
Plans
- Nested models, root models
- Links in responses
- Move from TypedDict to pydantic models (because of "termsOfService", and "Parameter.in" for instance)
- Authorization
- Automatic validation of the body, response, query, path etc (based on annotations)
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aiohttp_docs-0.0.2.tar.gz.
File metadata
- Download URL: aiohttp_docs-0.0.2.tar.gz
- Upload date:
- Size: 1.2 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dc4d17963e7f44d4adf90a31793fbf0ff2eefad181a76820e48be68d7b7219da
|
|
| MD5 |
56bf1950879abc2d253f5e663505a52b
|
|
| BLAKE2b-256 |
8cfdcc57440f832610b61ced8f1fe55456f6658f8182f0a2d3f6515262788e2e
|
File details
Details for the file aiohttp_docs-0.0.2-py3-none-any.whl.
File metadata
- Download URL: aiohttp_docs-0.0.2-py3-none-any.whl
- Upload date:
- Size: 1.2 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0c9ccee432cec1216cad41c7edb05f3c6cf08abf4775e138559b66f137d8ccf1
|
|
| MD5 |
af996e01fb9379bfb24d2665466c9fef
|
|
| BLAKE2b-256 |
c7f1881dda0f12700072af18108645f4c865b1152a4fd06c051010118b655c4e
|