OpenAPI#
Litestar has first class OpenAPI support offering the following features:
Automatic OpenAPI 3.1.0 Schema generation, which is available as both YAML and JSON.
Builtin support for static documentation site generation using several different libraries.
Simple configuration using pydantic based classes.
Litestar includes a complete implementation of the latest version of the OpenAPI specification using Python dataclasses. This implementation is used as a basis for generating OpenAPI specs, supporting builtins including dataclasses and TypedDict, as well as Pydantic models and any 3rd party entities for which a plugin is implemented.
This is also highly configurable - and users can customize the OpenAPI spec in a variety of ways - ranging from passing configuration globally, to settings specific kwargs on route handler decorators.
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(
model=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",
)
},
),
),
)
Customizing schema endpoints with the OpenAPIController#
Litestar includes an OpenAPIController
class that is used as the
default controller in the OpenAPIConfig
.
This controller exposes the following endpoints:
/schema/openapi.yaml
or/schema/openapi.yml
allowing for download of the OpenAPI schema as YAML.
/schema/openapi.json
allowing for download of the OpenAPI schema as JSON.
/schema/redoc
Serves the docs using Redoc.
/schema/swagger
Serves the docs using Swagger-UI.
/schema/elements
Serves the docs using Stoplight Elements.
/schema/rapidoc
Serves the docs using RapiDoc.
Additionally, the root /schema/
path is accessible, serving the site that is configured as the default in
the OpenAPIConfig
.
You can use your own subclass of OpenAPIController
by setting it as
the controller to use in the OpenAPIConfig
openapi_controller
kwarg.
For example, lets say we wanted to change the base path of the OpenAPI related endpoints from /schema
to
/api-docs
, in this case we’d the following:
from litestar import Litestar
from litestar.openapi import OpenAPIConfig
from litestar.openapi import OpenAPIController
class MyOpenAPIController(OpenAPIController):
path = "/api-docs"
app = Litestar(
route_handlers=[...],
openapi_config=OpenAPIConfig(
title="My API", version="1.0.0", openapi_controller=MyOpenAPIController
),
)
OAuth2 in Swagger UI#
When using Swagger, OAuth2 settings can be configured via swagger_ui_init_oauth
, which can be set to a dictionary containing the parameters described in the Swagger UI documentation for OAuth2 here.
We that you can preset your clientId or enable PKCE support.
Example Usage
from litestar import Litestar
from litestar.openapi import OpenAPIConfig
from litestar.openapi import OpenAPIController
class MyOpenAPIController(OpenAPIController):
swagger_ui_init_oauth = {
"clientId": "your-client-id",
"appName": "your-app-name",
"scopeSeparator": " ",
"scopes": "openid profile",
"useBasicAuthenticationWithAccessCodeGrant": True,
"usePkceWithAuthorizationCodeGrant": True,
}
app = Litestar(
route_handlers=[...],
openapi_config=OpenAPIConfig(
title="My API", version="1.0.0", openapi_controller=MyOpenAPIController
),
)
CDN and offline file support#
You can change the default download paths for JS and CSS bundles as well as google fonts by subclassing
OpenAPIController
and setting any of the following class variables:
from litestar import Litestar
from litestar.openapi import OpenAPIConfig
from litestar.openapi import OpenAPIController
class MyOpenAPIController(OpenAPIController):
path = "/api-docs"
redoc_google_fonts = False
redoc_js_url = "https://offline_location/redoc.standalone.js"
swagger_css_url = "https://offline_location/swagger-ui-css"
swagger_ui_bundle_js_url = "https://offline_location/swagger-ui-bundle.js"
swagger_ui_standalone_preset_js_url = (
"https://offline_location/swagger-ui-standalone-preset.js"
)
stoplight_elements_css_url = "https://offline_location/spotlight-styles.mins.css"
stoplight_elements_js_url = (
"https://offline_location/spotlight-web-components.min.js"
)
rapidoc_js_url = "https://offline_location/rapidock-min.js"
app = Litestar(
route_handlers=[...],
openapi_config=OpenAPIConfig(
title="My API", version="1.0.0", openapi_controller=MyOpenAPIController
),
)
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])
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.