Reusable Components and References
To define reusable elements like schemas, request bodies, or security schemes in your OpenAPI document, you can leverage the Components and Reference models. This allows you to define a component once and reference it across multiple path operations.
Defining and Referencing Reusable Components
The following example demonstrates how to define a reusable RequestBody in the components section and reference it in a path operation using openapi_extra.
from fastapi import FastAPI, Request
from fastapi.openapi.utils import get_openapi
from fastapi.openapi.models import OpenAPI, Components, RequestBody, MediaType, Reference
from fastapi.encoders import jsonable_encoder
app = FastAPI()
# Reference the component in the route using the $ref key
@app.post(
"/items/",
openapi_extra={
"requestBody": {"$ref": "#/components/requestBodies/SharedItemBody"}
}
)
async def create_item(request: Request):
return {"message": "Item created"}
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
# Generate the base OpenAPI schema
openapi_schema = get_openapi(
title="Custom Components API",
version="1.0.0",
routes=app.routes,
)
# Define the reusable RequestBody component
shared_body = RequestBody(
content={
"application/json": MediaType(
schema={
"type": "object",
"properties": {
"name": {"type": "string"},
"price": {"type": "number"}
},
"required": ["name", "price"]
}
)
},
description="A shared request body for item creation"
)
# Initialize components if not present
if "components" not in openapi_schema:
openapi_schema["components"] = {}
# Add the component to the schema
# We use jsonable_encoder to ensure models are converted to dicts with correct aliases
components_dict = openapi_schema["components"]
components_dict.setdefault("requestBodies", {})["SharedItemBody"] = jsonable_encoder(
shared_body, exclude_none=True, by_alias=True
)
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
Using the Reference Class
While you can use raw dictionaries for references (e.g., {"$ref": "..."}), the Reference class provides a structured way to define JSON references. It uses a Pydantic alias to map the ref field to $ref.
from fastapi.openapi.models import Reference
from fastapi.encoders import jsonable_encoder
# Define a reference to a schema
ref = Reference(ref="#/components/schemas/Item")
# Serialization results in the correct OpenAPI key
# {"$ref": "#/components/schemas/Item"}
serialized_ref = jsonable_encoder(ref, by_alias=True)
Extending Components with Specification Extensions
Most OpenAPI models in this project, including Components and RequestBody, inherit from BaseModelWithConfig. This base class is configured with extra: "allow", enabling you to add custom "Specification Extensions" (fields starting with x-) directly to the objects.
from fastapi.openapi.models import Components, Info
# Adding a custom extension to the Info object
info = Info(
title="Extended API",
version="1.0.0",
description="An API with custom metadata"
)
# BaseModelWithConfig allows extra fields
info.x_logo = {"url": "https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png"}
# Adding extensions to Components
components = Components(schemas={})
components.x_generator = "FastAPI Custom Script"
Troubleshooting
Correct Alias Handling
The Reference class and many other OpenAPI models use aliases (like $ref for ref or in for in_). When manually manipulating the openapi_schema dictionary, always use jsonable_encoder(model, by_alias=True) to ensure the keys match the OpenAPI specification.
Reference Prefixes
When creating manual references, ensure you use the correct prefix for the component type:
- Schemas:
#/components/schemas/(Available asREF_PREFIXinfastapi.openapi.constants) - Responses:
#/components/responses/ - Parameters:
#/components/parameters/ - Request Bodies:
#/components/requestBodies/ - Security Schemes:
#/components/securitySchemes/
Schema Caching
When overriding app.openapi, always check if app.openapi_schema is already set. This prevents unnecessary re-generation of the schema on every request to the /openapi.json endpoint.
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
# ... generation logic ...
app.openapi_schema = openapi_schema
return app.openapi_schema