OpenAPI integration#
Starlite 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.
Pydantic-OpenAPI-schema#
Starlite generates the latest version of the OpenAPI specification using the pydantic-openapi-schema library, which is bundled as part of Starlite and is also maintained by the starlite-api GitHub organization.
This library offers a full implementation of the OpenAPI specification as pydantic models, and is as such a powerful and type correct foundation for schema generation using python.
See also
The pydantic-openapi-schema docs for a full reference regarding the library’s API.
OpenAPI schema generation config#
OpenAPI schema generation is enabled by default. To configure it you can pass an instance of
OpenAPIConfig
to the Starlite constructor
using the openapi_config
kwarg:
from starlite import Starlite, OpenAPIConfig
app = Starlite(
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 starlite import Starlite
app = Starlite(route_handlers=[...], openapi_config=None)
Route handler OpenAPI configuration#
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 starlite 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_id
An identifier used for the route’s schema operationId. Defaults to the
__name__
attribute of the wrapped function.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
starlite.HttpException
. This list should describe all exceptions raised within the route handler’s function/method. The StarliteValidationException
will be added automatically for the schema if any validation is involved (e.g. there are parameters specified in the method/function).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 starlite import ResponseSpec, get
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:
...
from datetime import datetime
from pydantic import BaseModel
from starlite import ResponseSpec, get
class Item(BaseModel):
...
class ItemNotFound(BaseModel):
was_removed: bool
removed_at: datetime | None
@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 starlite import Starlite, OpenAPIConfig, get
from pydantic_openapi_schema.v3_1_0 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 = Starlite(
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(
securitySchemes={
"BearerToken": SecurityScheme(
type="http",
scheme="bearer",
)
},
),
),
)
The OpenAPIController#
Starlite includes an OpenAPIController
class that is used as the
default controller in the OpenAPIConfig
.
This controller exposes the following endpoints:
/schema/openapi.yaml
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.
Additionally, the root /schema/
path is accessible, serving the site that is configured as the default in
the OpenAPIConfig
.
Subclassing OpenAPIController#
You can use your own subclass of OpenAPIController
by setting it as
then 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 starlite import Starlite, OpenAPIController, OpenAPIConfig
class MyOpenAPIController(OpenAPIController):
path = "/api-docs"
app = Starlite(
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 starlite import Starlite, OpenAPIController, OpenAPIConfig
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"
)
app = Starlite(
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 Starlite
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 starlite 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 starlite import Starlite, get
class IdModel(BaseModel):
__schema_name__ = "IdContainer"
id: UUID
@get("/id")
def retrieve_id_handler() -> IdModel:
"""
Returns: An IdModel
"""
return IdModel(id=uuid4())
app = Starlite(route_handlers=[retrieve_id_handler])
The above will result in an OpenAPI schema object that looks like this:
{
"openapi": "3.1.0",
"info": {"title": "Starlite 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": {
"media_type_schema": {
"ref": "#/components/schemas/IdContainer"
}
}
}
}
},
"deprecated": false
}
}
},
"components": {
"schemas": {
"IdContainer": {
"properties": {
"id": {"type": "string", "schema_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.