I am trying to have an endpoint like /services?status=New
status
is going to be either New
or Old
Here is my code:
from fastapi import APIRouter, Depends
from pydantic import BaseModel
from enum import Enum
router = APIRouter()
class ServiceStatusEnum(str, Enum):
new = "New"
old = "Old"
class ServiceStatusQueryParam(BaseModel):
status: ServiceStatusEnum
@router.get("/services")
def get_services(
status: ServiceStatusQueryParam = Query(..., title="Services", description="my desc"),
):
pass #my code for handling this route.....
The result is that I get an error that seems to be relevant to this issue here
The error says AssertionError: Param: status can only be a request body, using Body()
Then I found another solution explained here.
So, my code will be like this:
from fastapi import APIRouter, Depends
from pydantic import BaseModel
from enum import Enum
router = APIRouter()
class ServiceStatusEnum(str, Enum):
new = "New"
old = "Old"
class ServicesQueryParam(BaseModel):
status: ServiceStatusEnum
@router.get("/services")
def get_services(
q: ServicesQueryParam = Depends(),
):
pass #my code for handling this route.....
It is working (and I don't understand why) - but the question is how and where do I add the description and title?
To create a Pydantic model and use it to define query parameters, you would need to use Depends()
along with the parameter in your endpoint. To add description
, title
, etc., to query parameters, you could wrap the Query()
in a Field()
.
It should also be noted that one could use the Literal
type instead of Enum
, as described here and here. Additionally, if one would like to define a List
field inside a Pydantic model and use it as a query parameter, they would either need to implement this in a separate dependency class, as demonstrated here and here, or again wrap the Query()
in a Field()
, as shown below.
Moreover, to perform validation on query parameters inside a Pydnatic model, one could do this as usual using Pydantic's @validator
, as demonstrated here, as well as here and here. Note that in this case, where the BaseModel
is used for query parameters, raising ValueError
would cause an Internal Server Error
. Hence, you should instead raise an HTTPException
when a validation fails, or use a custom exception handler, in order to handle ValueError
exceptions, as shown in Option 2 of this answer. Besides @validator
, one could also have additional validations for Query
parameters, as described in FastAPI's documentation (see Query
class implementation as well).
As a side note, regarding defining optional parameters, the example below uses the Optional
type hint (accompanied by None
as the default value in Query
) from the typing
module; however, you may also would like to have a look at this answer and this answer, which describe all the available ways on how to do that.
from fastapi import FastAPI, Depends, Query, HTTPException
from pydantic import BaseModel, Field, validator
from typing import List, Optional, Literal
from enum import Enum
app = FastAPI()
class Status(str, Enum):
new = 'New'
old = 'Old'
class ServiceStatus(BaseModel):
status: Optional[Status] = Field (Query(None, description='Select service status'))
msg: Optional[str] = Field (Query(None, description='Type something'))
choice: Literal['a', 'b', 'c', 'd'] = Field (Query(..., description='Choose something'))
comments: List[str] = Field (Query(..., description='Add some comments'))
@validator('choice')
def check_choice(cls, v):
if v == 'b':
raise HTTPException(status_code=422, detail='Wrong choice')
return v
@app.get('/status')
def main(status: ServiceStatus = Depends()):
return status
@validator
)Please note that in Pydantic V2, @validator
has been deprecated and replaced by @field_validator
. Please have a look at this answer for more details and examples.
Query
parameters)As of FastAPI 0.115.0, one can declare Query
(as well as Header
and Cookie
) parameters with Pydantic models in the following way:
from typing import Annotated, Literal
from fastapi import FastAPI, Query
from pydantic import BaseModel, Field
app = FastAPI()
class FilterParams(BaseModel):
limit: int = Field(100, gt=0, le=100)
offset: int = Field(0, ge=0)
order_by: Literal["created_at", "updated_at"] = "created_at"
tags: list[str] = []
@app.get("/items")
async def read_items(filter_query: Annotated[FilterParams, Query()]):
return filter_query