Configuring schema generation#
OpenAPI schema generation is enabled by default. To configure it you can pass an instance of
OpenAPIConfig
to the Litestar
class using the
openapi_config
kwarg:
from litestar import Litestar
from litestar.openapi import OpenAPIConfig
app = Litestar(
route_handlers=[...], openapi_config=OpenAPIConfig(title="My API", version="1.0.0")
)
Disabling schema generation#
If you wish to disable schema generation and not include the schema endpoints in your API, simply pass None
as the
value for openapi_config
:
from litestar import Litestar
app = Litestar(route_handlers=[...], openapi_config=None)
Configuring schema generation on a route handler#
By default, an operation schema is generated for all route
handlers. You can omit a route handler from the schema by setting include_in_schema=False
:
from litestar import get
@get(path="/some-path", include_in_schema=False)
def my_route_handler() -> None: ...
You can also modify the generated schema for the route handler using the following kwargs:
tags
A list of strings that correlate to the tag specification.
security
:A list of dictionaries that correlate to the security requirements specification. The values for this key are string keyed dictionaries with the values being a list of objects.
summary
Text used for the route’s schema summary section.
description
Text used for the route’s schema description section.
response_description
Text used for the route’s response schema description section.
operation_class
A subclass of
Operation
which can be used to fully customize the operation object for the handler.operation_id
A string or callable that returns a string, which servers as an identifier used for the route’s schema operationId.
deprecated
A boolean dictating whether this route should be marked as deprecated in the OpenAPI schema. Defaults to
False
.raises
A list of exception classes extending from
litestar.HttpException
. This list should describe all exceptions raised within the route handler’s function/method. The LitestarValidationException
will be added automatically for the schema if any validation is involved (e.g. there are parameters specified in the method/function). For custom exceptions, a detail class property should be defined, which will be integrated into the OpenAPI schema. If detail isn’t specified and the exception’s status code matches one from stdlib status code, a generic message will be applied.responses
A dictionary of additional status codes and a description of their expected content. The expected content should be based on a Pydantic model describing its structure. It can also include a description and the expected media type. For example:
Note
operation_id will be prefixed with the method name when function is decorated with HTTPRouteHandler and multiple http_method. Will also be prefixed with path strings used in Routers and Controllers to make sure id is unique.
from datetime import datetime
from typing import Optional
from pydantic import BaseModel
from litestar import get
from litestar.openapi.datastructures import ResponseSpec
class Item(BaseModel): ...
class ItemNotFound(BaseModel):
was_removed: bool
removed_at: Optional[datetime]
@get(
path="/items/{pk:int}",
responses={
404: ResponseSpec(
data_container=ItemNotFound, description="Item was removed or not found"
)
},
)
def retrieve_item(pk: int) -> Item: ...
from datetime import datetime
from pydantic import BaseModel
from litestar import get
from litestar.openapi.datastructures import ResponseSpec
class Item(BaseModel): ...
class ItemNotFound(BaseModel):
was_removed: bool
removed_at: datetime | None
@get(
path="/items/{pk:int}",
responses={
404: ResponseSpec(
data_container=ItemNotFound, description="Item was removed or not found"
)
},
)
def retrieve_item(pk: int) -> Item: ...
You can also specify security
and tags
on higher level of the application, e.g. on a controller, router, or the
app instance itself. For example:
from litestar import Litestar, get
from litestar.openapi import OpenAPIConfig
from litestar.openapi.spec import Components, SecurityScheme, Tag
@get(
"/public",
tags=["public"],
security=[{}], # this endpoint is marked as having optional security
)
def public_path_handler() -> dict[str, str]:
return {"hello": "world"}
@get("/other", tags=["internal"], security=[{"apiKey": []}])
def internal_path_handler() -> None: ...
app = Litestar(
route_handlers=[public_path_handler, internal_path_handler],
openapi_config=OpenAPIConfig(
title="my api",
version="1.0.0",
tags=[
Tag(name="public", description="This endpoint is for external users"),
Tag(name="internal", description="This endpoint is for internal users"),
],
security=[{"BearerToken": []}],
components=Components(
security_schemes={
"BearerToken": SecurityScheme(
type="http",
scheme="bearer",
)
},
),
),
)
Accessing the OpenAPI schema in code#
The OpenAPI schema is generated during the Litestar
app’s init method. Once init is finished,
its accessible as app.openapi_schema
. As such you can always access it inside route handlers, dependencies, etc. by
access the request instance:
from litestar import Request, get
@get(path="/")
def my_route_handler(request: Request) -> dict:
schema = request.app.openapi_schema
return schema.dict()
Customizing Pydantic model schemas#
You can customize the OpenAPI schemas generated for pydantic models by following the guidelines in the Pydantic docs.
Additionally, you can affect how pydantic models are translated into OpenAPI components
by settings a special dunder
attribute on the model called __schema_name__
:
from uuid import UUID, uuid4
from pydantic import BaseModel
from litestar import Litestar, get
class IdModel(BaseModel):
__schema_name__ = "IdContainer"
id: UUID
@get("/id", sync_to_thread=False)
def retrieve_id_handler() -> IdModel:
"""
Returns: An IdModel
"""
return IdModel(id=uuid4())
app = Litestar(route_handlers=[retrieve_id_handler])
The above will result in an OpenAPI schema object that looks like this:
{
"openapi": "3.1.0",
"info": {"title": "Litestar API", "version": "1.0.0"},
"servers": [{"url": "/"}],
"paths": {
"/id": {
"get": {
"operationId": "Retrieve Id Handler",
"responses": {
"200": {
"description": "Request fulfilled, document follows",
"headers": {},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/IdContainer"
}
}
}
}
},
"deprecated": false
}
}
},
"components": {
"schemas": {
"IdContainer": {
"properties": {
"id": {"type": "string", "format": "uuid", "title": "Id"}
},
"type": "object",
"required": ["id"],
"title": "IdContainer"
}
}
}
}
Attention
If you use multiple pydantic models that use the same name in the schema, you will need to use the __schema_name__ dunder to ensure each has a unique name in the schema, otherwise the schema components will be ambivalent.
Customizing Operation
class#
You can customize the operation object used for a path in
the generated OpenAPI schemas by creating a subclass of Operation
.
This option can be helpful in situations where request data needs to be manually parsed as Litestar will not know how to create the OpenAPI operation data by default.
from dataclasses import dataclass, field
from typing import Dict, List, Optional
from litestar import Litestar, MediaType, Request, post
from litestar.exceptions import HTTPException
from litestar.openapi.spec import OpenAPIMediaType, OpenAPIType, Operation, RequestBody, Schema
from litestar.status_codes import HTTP_400_BAD_REQUEST
@dataclass
class CustomOperation(Operation):
"""Custom Operation class which includes a non-standard field which is part of an OpenAPI extension."""
x_code_samples: Optional[List[Dict[str, str]]] = field(default=None, metadata={"alias": "x-codeSamples"})
def __post_init__(self) -> None:
self.tags = ["ok"]
self.description = "Requires OK, Returns OK"
self.request_body = RequestBody(
content={
"text": OpenAPIMediaType(
schema=Schema(
title="Body",
type=OpenAPIType.STRING,
example="OK",
)
),
},
description="OK is the only accepted value",
)
self.x_codeSamples = [
{"lang": "Python", "source": "import requests; requests.get('localhost/example')", "label": "Python"},
{"lang": "cURL", "source": "curl -XGET localhost/example", "label": "curl"},
]
@post("/", operation_class=CustomOperation, media_type=MediaType.TEXT)
async def route(request: Request) -> str:
"""
Returns: OK
"""
if (await request.body()) == b"OK":
return "OK"
raise HTTPException(status_code=HTTP_400_BAD_REQUEST, detail="request payload must be OK")
app = Litestar(route_handlers=[route])
from dataclasses import dataclass, field
from typing import Optional
from litestar import Litestar, MediaType, Request, post
from litestar.exceptions import HTTPException
from litestar.openapi.spec import OpenAPIMediaType, OpenAPIType, Operation, RequestBody, Schema
from litestar.status_codes import HTTP_400_BAD_REQUEST
@dataclass
class CustomOperation(Operation):
"""Custom Operation class which includes a non-standard field which is part of an OpenAPI extension."""
x_code_samples: Optional[list[dict[str, str]]] = field(default=None, metadata={"alias": "x-codeSamples"})
def __post_init__(self) -> None:
self.tags = ["ok"]
self.description = "Requires OK, Returns OK"
self.request_body = RequestBody(
content={
"text": OpenAPIMediaType(
schema=Schema(
title="Body",
type=OpenAPIType.STRING,
example="OK",
)
),
},
description="OK is the only accepted value",
)
self.x_codeSamples = [
{"lang": "Python", "source": "import requests; requests.get('localhost/example')", "label": "Python"},
{"lang": "cURL", "source": "curl -XGET localhost/example", "label": "curl"},
]
@post("/", operation_class=CustomOperation, media_type=MediaType.TEXT)
async def route(request: Request) -> str:
"""
Returns: OK
"""
if (await request.body()) == b"OK":
return "OK"
raise HTTPException(status_code=HTTP_400_BAD_REQUEST, detail="request payload must be OK")
app = Litestar(route_handlers=[route])
from dataclasses import dataclass, field
from litestar import Litestar, MediaType, Request, post
from litestar.exceptions import HTTPException
from litestar.openapi.spec import OpenAPIMediaType, OpenAPIType, Operation, RequestBody, Schema
from litestar.status_codes import HTTP_400_BAD_REQUEST
@dataclass
class CustomOperation(Operation):
"""Custom Operation class which includes a non-standard field which is part of an OpenAPI extension."""
x_code_samples: list[dict[str, str]] | None = field(default=None, metadata={"alias": "x-codeSamples"})
def __post_init__(self) -> None:
self.tags = ["ok"]
self.description = "Requires OK, Returns OK"
self.request_body = RequestBody(
content={
"text": OpenAPIMediaType(
schema=Schema(
title="Body",
type=OpenAPIType.STRING,
example="OK",
)
),
},
description="OK is the only accepted value",
)
self.x_codeSamples = [
{"lang": "Python", "source": "import requests; requests.get('localhost/example')", "label": "Python"},
{"lang": "cURL", "source": "curl -XGET localhost/example", "label": "curl"},
]
@post("/", operation_class=CustomOperation, media_type=MediaType.TEXT)
async def route(request: Request) -> str:
"""
Returns: OK
"""
if (await request.body()) == b"OK":
return "OK"
raise HTTPException(status_code=HTTP_400_BAD_REQUEST, detail="request payload must be OK")
app = Litestar(route_handlers=[route])
The above example will result in an OpenAPI schema object that looks like this:
{
"info": { "title": "Litestar API", "version": "1.0.0" },
"openapi": "3.0.3",
"servers": [{ "url": "/" }],
"paths": {
"/": {
"post": {
"tags": ["ok"],
"summary": "Route",
"description": "Requires OK, Returns OK",
"operationId": "Route",
"requestBody": {
"content": {
"text": {
"schema": { "type": "string", "title": "Body", "example": "OK" }
}
},
"description": "OK is the only accepted value",
"required": false
},
"responses": {
"201": {
"description": "Document created, URL follows",
"headers": {}
}
},
"deprecated": false,
"x-codeSamples": [
{
"lang": "Python",
"source": "import requests; requests.get('localhost/example')",
"label": "Python"
},
{
"lang": "cURL",
"source": "curl -XGET localhost/example",
"label": "curl"
}
]
}
}
},
"components": { "schemas": {} }
}
Attention
OpenAPI Vendor Extension fields need to start with x- and should not be processed with the default field name converter. To work around this, Litestar will honor an alias field provided to the dataclass.field metadata when generating the field name in the schema.