OpenAPI Documentation¶
Kuí auto-generates OpenAPI 3.1.0 documentation from your code — type annotations, docstrings, and route metadata.
Setup¶
from kui.asgi import Kui, OpenAPI
app = Kui()
openapi = OpenAPI(
info={"title": "My API", "version": "1.0.0"},
template_name="swagger", # "swagger" | "redoc" | "rapidoc"
security_schemes={}, # Optional: custom OpenAPI security schemes
)
# Mount at /docs
app.router <<= "/docs" // openapi.routes
This serves:
| Path | Description |
|---|---|
GET /docs/ |
Interactive HTML UI |
GET /docs/json |
OpenAPI 3.1.0 JSON schema |
GET /docs/heartbeat |
SSE endpoint for hot reload |
UI Options¶
| Template | Description |
|---|---|
"swagger" |
Swagger UI — interactive, with "Try it out" |
"redoc" |
ReDoc — clean, read-only documentation |
"rapidoc" |
RapiDoc — modern, customizable |
You can also provide a custom HTML template:
openapi = OpenAPI(
info={"title": "My API", "version": "1.0.0"},
template="<html>...</html>", # Custom HTML string
)
Reload Mode¶
Set reload=True to regenerate the spec on each request (useful during development):
Auto-Generated Documentation¶
From Docstrings¶
The handler's docstring generates summary and description:
@app.router.http.get("/users")
async def list_users():
"""
List all users
Returns a paginated list of all registered users.
Supports filtering by status and role.
"""
return [...]
- First paragraph →
summary - Remaining text →
description
From Route Parameters¶
@app.router.http.get("/users", summary="List users", description="...", tags=["Users"])
async def list_users():
...
From Parameter Binding¶
Parameters declared with Annotated are automatically documented:
@app.router.http.get("/users")
async def list_users(
page: Annotated[int, Query(1, ge=1, description="Page number")],
size: Annotated[int, Query(20, ge=1, le=100, description="Items per page")],
):
...
Generates OpenAPI parameters entries with type, location, default, constraints, and description.
From Body Parameters¶
Pydantic models in Body() generate requestBody with JSON schema:
class UserCreate(BaseModel):
name: str
email: str
@app.router.http.post("/users")
async def create_user(user: Annotated[UserCreate, Body(exclusive=True)]):
...
From Dependencies¶
Parameters declared in dependency functions also appear in the docs:
async def verify_token(token: Annotated[str, Header(alias="authorization")]):
...
@app.router.http.get("/me")
async def me(user: Annotated[User, Depends(verify_token)]):
...
The authorization header parameter appears in the OpenAPI spec for /me.
Response Documentation¶
Document response types using return type annotations:
from typing import Any
from typing_extensions import Annotated
from kui.asgi import JSONResponse
@app.router.http.get("/users")
async def list_users() -> Annotated[Any, JSONResponse[200, {}, list[UserModel]]]:
return [...]
Response Subscript Syntax¶
# Status code only
JSONResponse[200]
# Status code + headers
JSONResponse[200, {"X-Request-Id": {"schema": {"type": "string"}}}]
# Status code + headers + body schema
JSONResponse[200, {}, UserModel]
Available response classes: JSONResponse, HTMLResponse, PlainTextResponse, FileResponse, RedirectResponse, SendEventResponse, StreamResponse.
Multiple Response Types¶
List multiple response annotations inside a single Annotated:
from http import HTTPStatus
@app.router.http.get("/users/{user_id:int}")
async def get_user() -> Annotated[
Any,
JSONResponse[200, {}, UserModel],
JSONResponse[HTTPStatus.NOT_FOUND],
]:
...
Tags¶
On Individual Routes¶
On Route Groups¶
Tag Descriptions and Paths¶
Configure tag descriptions and path mappings in the OpenAPI constructor:
openapi = OpenAPI(
info={"title": "My API", "version": "1.0.0"},
tags={
"Users": {"description": "User management endpoints"},
"Posts": {
"description": "Blog post endpoints",
"paths": ["/posts", "/posts/{post_id}"], # Auto-tag these paths
},
},
)
Each tag entry accepts:
description(required) — Tag description for the OpenAPI specpaths(optional) — List of path strings; routes matching these paths are automatically tagged
Security Schemes¶
Security dependencies (Bearer, Basic, API Key) are automatically documented:
from kui.asgi import Depends, bearer_auth
@app.router.http.get("/me")
async def me(token: Annotated[str, Depends(bearer_auth)]):
...
This generates a securitySchemes entry in the OpenAPI components and a security requirement on the operation.
See Security for all authentication options.
Extra Documentation¶
Use describe_extra_docs to supplement the auto-generated docs for an endpoint:
from kui.openapi import describe_extra_docs
@app.router.http.get("/users")
async def list_users():
...
describe_extra_docs(list_users, {
"responses": {
"500": {"description": "Internal server error"},
},
"deprecated": True,
})
describe_extra_docs(handler, info) takes a handler and a dict of OpenAPI operation fields. The info is deep-merged into the generated operation object.
Custom Content-Type¶
Override the documented request Content-Type by subclassing HttpRequest:
from typing import Any
from typing_extensions import Annotated
from baize.datastructures import ContentType
from kui.asgi import HttpRequest, FactoryClass, Kui
class MsgPackRequest(HttpRequest):
async def data(self) -> Annotated[Any, ContentType("application/x-msgpack")]:
body = await self.body
return msgpack.unpackb(body)
app = Kui(factory_class=FactoryClass(http=MsgPackRequest))
Schema Naming¶
When multiple Pydantic models share the same class name (e.g., different Item models in different modules), Kuí automatically prefixes schema names with the route path to avoid conflicts.