Ok. Guys I can't figure it out. Is any option to add second Pydantic schema option, or switch between 2 Pydantic schemas according of parameter in request? What I want to archieve. If paremeter equals 'all' I would like to add 'date' field into schema. And if parameter is 'None' I would like to exclude 'data' field at all. With code below with no parameter at all got: "date": null
[
{
"unit": "Xeikon 1",
"color": 65817763,
"bw": 552903,
"date": null
},
{
"unit": "Xeikon 2",
"color": 65825687,
"bw": 552903,
"date": null
},
{
"unit": "Xeikon 3",
"color": 65825784,
"bw": 552903,
"date": null
}
]
This is part of my code
class Clicks(BaseModel):
unit: str
color: int
bw: int
date: Optional[date]
class Config:
orm_mode = True
@xeikon_api.get('/xeikon/clicks/', response_model=List[schema.Clicks])
async def xeikon_Clicks_data(details: Optional[str]=None) :
"""
endpoint: list all Clicks data \n
"""
try:
if details == 'all':
return db.session.query(Clicks).all()
if details is None:
from main import session_local
response = session_local.query(Clicks)
return reorganizeClicksData(response)
except NoResultFound as n:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=n.orig.args,
) from n
The way you describe it, that would be two distinct response schemas. Therefore you would have to define two distinct models for the response.
Let me quote the relevant section of the FastAPI docs:
You can declare a response to be the
Union
of two types, that means, that the response would be any of the two. It will be defined in OpenAPI withanyOf
. To do that, use the standard Python type hinttyping.Union
.
To simplify your example a bit, here is a possible solution:
from datetime import date
from typing import Optional, Union
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
class Clicks(BaseModel):
unit: str
color: int
bw: int
class Config:
orm_mode = True
class ExtendedClicks(Clicks):
date: date
AnyClicks = Union[list[ExtendedClicks], list[Clicks]]
app = FastAPI()
@app.get("/clicks")
async def endpoint(details: Optional[str] = None) -> AnyClicks:
# Get the clicks data:
test_data = [
{"unit": "foo", "color": 1, "bw": 1, "date": "2023-05-10"},
{"unit": "bar", "color": 0, "bw": 0, "date": "2023-01-01"},
]
if details == "all":
return [ExtendedClicks.parse_obj(obj) for obj in test_data]
if details is None:
return [Clicks.parse_obj(obj) for obj in test_data]
raise HTTPException(400)
The order in the type union is important. Since ExtendedClicks
is a subtype of Clicks
and it simply ignores extra values, if we were to switch the order in AnyClicks
, the response would always match Clicks
and you would never see the date
field.
The response in this example with the details=all
query:
[
{
"unit": "foo",
"color": 1,
"bw": 1,
"date": "2023-05-10"
},
{
"unit": "bar",
"color": 0,
"bw": 0,
"date": "2023-01-01"
}
]
With no query parameters:
[
{
"unit": "foo",
"color": 1,
"bw": 1
},
{
"unit": "bar",
"color": 0,
"bw": 0
}
]
I would still recommend not doing that, i.e. not using a union of return types. There is no way to express via the OpenAPI schema that the response schema depends on specific query parameters. So all that one can see from the endpoint schema is that it may return a list of Clicks
and it also may return a list of ExtendedClicks
.
From an API design standpoint I would recommend just having two separate endpoints for the two distinct schemas.