Scalable approach to defining query parameters #11860
-
First Check
Commit to Help
Example Codefrom dataclasses import dataclass
from enum import Enum
from typing import Annotated
from fastapi import APIRouter, Depends, FastAPI, Query
from pydantic import BaseModel, Field
from pydantic.dataclasses import dataclass as pdataclass
app = FastAPI()
router = APIRouter(prefix="/api")
class Animal(str, Enum):
CAT = "CAT"
DOG = "DOG"
class CommonQueryParams1(BaseModel):
# Types working
# Descriptions NOT working
# Example working
q: str | None = Query(
default=None, description="Some string query param.", example="fooooo"
)
animal: Animal = Query(default=Animal.CAT, description="The animal.")
skip: int = Query(default=0, description="Skips results.")
limit: int = Query(default=50, ge=1, le=100, description="Show this many.")
class CommonQueryParams2(BaseModel):
# Types working
# Descriptions working
# Example working
q: str | None = Field(
Query(default=None, description="Some string query param.", example="fooooo")
)
animal: Animal = Field(Query(default=Animal.CAT, description="The animal."))
skip: int = Field(Query(default=0, description="Skips results."))
limit: int = Field(Query(default=50, ge=1, le=100, description="Show this many."))
class CommonQueryParams3:
# Types working
# Descriptions working
# Example working
def __init__(
self,
q: str = Query(
default=None, description="Some string query param.", example="fooooo"
),
animal: Animal = Query(default=Animal.CAT, description="The animal."),
skip: int = Query(default=0, description="Skips results."),
limit: int = Query(default=50, ge=1, le=100, description="Show this many."),
):
self.q = q
self.animal = animal
self.skip = skip
self.limit = limit
@dataclass
class CommonQueryParams4:
# Types working
# Descriptions working
# Example working
q: str | None = Query(
default=None,
description="Some string query param.",
example="fooooo",
)
animal: Animal = Query(
default=Animal.CAT,
# enum=Animal,
description="The animal.",
)
skip: int = Query(default=0, description="Skips results.")
limit: int = Query(default=50, ge=1, le=100, description="Show this many.")
@pdataclass
class CommonQueryParams5:
# Types working
# Descriptions NOT working
# Example working
q: str | None = Query(
default=None,
description="Some string query param.",
examples=["foo", "bar", "baz"],
example="fooooo",
)
animal: Animal = Query(
default=Animal.CAT,
# enum=Animal,
description="The animal.",
)
skip: int = Query(default=0, description="Skips results.")
limit: int = Query(default=50, ge=1, le=100, description="Show this many.")
class CommonQueryParams6(BaseModel):
# Types working
# Descriptions NOT working
# Example working
q: str | None = Field(
default=None,
description="Some string query param.",
examples=["fooooo"],
)
animal: Animal = Field(
default=Animal.CAT,
description="The animal.",
)
skip: int = Field(default=0, description="Skips results.")
limit: int = Field(default=50, ge=1, le=100, description="Show this many.")
@router.get("/widgets")
def get_widgets(q: CommonQueryParams1 = Depends()):
print(q, type(q))
return {"data": "got widgets"}
@router.get("/widgets2")
def get_widgets2(q: CommonQueryParams2 = Depends()):
print(q, type(q))
return {"data": "got widgets"}
@router.get("/widgets3")
def get_widgets3(q: CommonQueryParams3 = Depends()):
print(q, type(q))
return {"data": "got widgets"}
@router.get("/widgets4")
def get_widgets4(q: CommonQueryParams4 = Depends()):
print(q, type(q))
return {"data": "got widgets"}
@router.get("/widgets5")
def get_widgets5(q: CommonQueryParams5 = Depends()):
print(q, type(q))
return {"data": "got widgets"}
@router.get("/widgets6")
def get_widgets6(q: CommonQueryParams6 = Depends()):
print(q, type(q))
return {"data": "got widgets"}
@router.get("/widgets7")
def get_widgets7(q: Annotated[CommonQueryParams2, Depends()]):
print(q, type(q))
return {"data": "got widgets"}
app.include_router(router) DescriptionI think there is a lack of clarity in how to define query parameters in a way that is scalable and that works. Some approaches appear to work, but actually fail to show the parameters' descriptions in the swagger. I was expecting that I would simply be able to define my query params as I did in As you can see from my main example code, we currently can:
The mentioned thread ends without a conclusion, and the contributions are from a few years ago now. And there is not a clear recommendation from the docs but it would be helpful if we could get some clarity on it. Currently I am leaning towards using either the dataclass approach or the vanilla python class approach. It also works with a pydantic basemodel as long as we wrap the query objects in pydantic's For reference, my installed libs are:
Apologies if this isn't actually a FastAPI issue, although it feels like it might be. Operating SystemWindows Operating System DetailsI've tried this on Mac too, but it is not anything to do with the OS. FastAPI Version0.110.2 Pydantic Version2.7.1 Python Version3.12.4 Additional ContextHave tried this with other versions of python and also the latest fastapi and it seems to still be problematic. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 2 replies
-
Don't use Pydantic models for non-body parameters (Query, Form, Path, ...), it's not officially supported by FastAPI. |
Beta Was this translation helpful? Give feedback.
Don't use Pydantic models for non-body parameters (Query, Form, Path, ...), it's not officially supported by FastAPI.
Take a look at the answer of Sebastián Ramírez to this comment: #9709 (comment), also #9709 (comment)