» pip install openapi-pydantic
» pip install openapi-schema-pydantic
FastAPI will generate schemas for models that are used either as a Request Body or Response Model. When declaring query_args: FaultQueryParams = Depends() (using Depends), your endpoint would not expect a request body, but rather query parameters; hence, FaultQueryParams would not be included in the schemas of the OpenAPI docs.
To add additional schemas, you could extend/modify the OpenAPI schema. Example is given below (make sure to add the code for modifying the schema after all routes have been defined, i.e., at the end of your code).
class FaultQueryParams(BaseModel):
f_id: Optional[int] = Field(None, description="id for the host", example=12345, title="Fault ID")
hostname: Optional[str]
status: Literal["open", "closed", "all"] = Field("open")
...
@app.post('/predict')
def predict(query_args: FaultQueryParams = Depends()):
return query_args
def get_extra_schemas():
return {
"FaultQueryParams": {
"title": "FaultQueryParams",
"type": "object",
"properties": {
"f_id": {
"title": "Fault ID",
"type": "integer",
"description": "id for the host",
"example": 12345
},
"hostname": {
"title": "Hostname",
"type": "string"
},
"status": {
"title": "Status",
"enum": [
"open",
"closed",
"all"
],
"type": "string",
"default": "open"
},
...
}
}
}
from fastapi.openapi.utils import get_openapi
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title="FastAPI",
version="1.0.0",
description="This is a custom OpenAPI schema",
routes=app.routes,
)
new_schemas = openapi_schema["components"]["schemas"]
new_schemas.update(get_extra_schemas())
openapi_schema["components"]["schemas"] = new_schemas
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
Some Helpful Notes
Note 1
Instead of manually typing the schema for the extra models that you would like to add to the docs, you can have FastAPI do that for you by adding to your code an endpoint (which you would subsequently remove, after getting the schema) using that model as a request body or response model, for example:
@app.post('/predict')
def predict(query_args: FaultQueryParams):
return query_args
Then, you can get the generated JSON schema at http://127.0.0.1:8000/openapi.json, as described in the documentation. From there, you can either copy and paste the schema of the model to your code and use it directly (as shown in the get_extra_schema() method above) or save it to a file and load the JSON data from the file, as demonstrated below:
import json
...
new_schemas = openapi_schema["components"]["schemas"]
with open('extra_schemas.json') as f:
extra_schemas = json.load(f)
new_schemas.update(extra_schemas)
openapi_schema["components"]["schemas"] = new_schemas
...
Note 2
To declare metadata, such as description, example, etc, for your query parameter, you should define your parameter with Query instead of Field, and since you can't do that with Pydantic models, you either need to define the Query parameter(s) directly in the endpoint or use a custom dependency class, as shown below:
from fastapi import FastAPI, Query, Depends
from typing import Optional
class FaultQueryParams:
def __init__(
self,
f_id: Optional[int] = Query(None, description="id for the host", example=12345)
):
self.f_id = f_id
app = FastAPI()
@app.post('/predict')
def predict(query_args: FaultQueryParams = Depends()):
return query_args
The above could be re-written using the @dataclass decorator as follows:
from fastapi import FastAPI, Query, Depends
from typing import Optional
from dataclasses import dataclass
@dataclass
class FaultQueryParams:
f_id: Optional[int] = Query(None, description="id for the host", example=12345)
app = FastAPI()
@app.post('/predict')
def predict(query_args: FaultQueryParams = Depends()):
return query_args
Update
There is no need for using a custom dependency class anymore, as FastAPI now allows using a Pydantic BaseModel to define query parameters by wrapping the Query() in a Field(); hence, one should be able to use a Pydantic model to define multiple query parameters and declare metadata for them, i.e., description, example, etc. Related answers could be found here and here.
Thank to @Chris for the pointers which ultimately led me to use dataclasses for defining query params in bulk and it just worked fine.
@dataclass
class FaultQueryParams1:
f_id: Optional[int] = Query(None, description="id for the host", example=55555)
hostname: Optional[str] = Query(None, example="test-host1.domain.com")
status: Literal["open", "closed", "all"] = Query(
None, description="fetch open/closed or all records", example="all"
)
created_by: Optional[str] = Query(
None,
description="fetch records created by particular user",
example="user-id",
)
According to documentation, This OpenAPI-specific examples goes in another section in the OpenAPI specification. It goes in the details for each path operation, not inside each JSON Schema..
So, these examples relate to the path operation, not to the input parameter's schema.
You should pass these examples as a parameter of Path(), Query(), Header(), Cookie(), Body(), etc..
It's shown in the documentation how to use openapi_examples:
@app.put("/items/{item_id}")
async def update_item(
*,
item_id: int,
item: Annotated[
Item,
Body(
openapi_examples={
"normal": {
"summary": "A normal example",
"description": "A **normal** item works correctly.",
"value": {
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
},
},
"converted": {
"summary": "An example with converted data",
"description": "FastAPI can convert price `strings` to actual `numbers` automatically",
"value": {
"name": "Bar",
"price": "35.4",
},
},
"invalid": {
"summary": "Invalid data is rejected with an error",
"value": {
"name": "Baz",
"price": "thirty five point four",
},
},
},
),
],
):
results = {"item_id": item_id, "item": item}
return results
I think it's possible to store these examples in private field of pydantic model (class attribute) and pass it to Body() like:
@app.put("/items/{item_id}")
async def update_item(
*,
item_id: int,
item: Annotated[
Item,
Body(
openapi_examples=Item._openapi_examples,
),
],
):
results = {"item_id": item_id, "item": item}
return results
Here is docs link for @Yurii answer.
I found using sub class in the class bit nicer:
class CreateRequest(BaseModel):
class Config:
schema_extra = {}
async def create_item(CreateRequest: Annotated[CreateRequest, Body(openapi_examples = CreateRequest.Config.schema_extra)]):
pass