Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aiohttp_docs-0.0.2.tar.gz (1.2 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aiohttp_docs-0.0.2-py3-none-any.whl (1.2 MB view details)

Uploaded Python 3

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

Hashes for aiohttp_docs-0.0.2.tar.gz
Algorithm Hash digest
SHA256 dc4d17963e7f44d4adf90a31793fbf0ff2eefad181a76820e48be68d7b7219da
MD5 56bf1950879abc2d253f5e663505a52b
BLAKE2b-256 8cfdcc57440f832610b61ced8f1fe55456f6658f8182f0a2d3f6515262788e2e

See more details on using hashes here.

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

Hashes for aiohttp_docs-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 0c9ccee432cec1216cad41c7edb05f3c6cf08abf4775e138559b66f137d8ccf1
MD5 af996e01fb9379bfb24d2665466c9fef
BLAKE2b-256 c7f1881dda0f12700072af18108645f4c865b1152a4fd06c051010118b655c4e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page