pythonfastapiopenapipydantic

How to define query parameters using Pydantic model in FastAPI?


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?


Solution

  • 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.

    Working Example

    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(..., 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
    

    Update (regarding @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.