1

Based on this API definition, my api supports queries like:

GET http://my.api.url/posts?sort=["title","ASC"]&range=[0, 24]&filter={"q":"bar"}

where some of the checks needed are

  • sort[1] is either "asc" or "desc" (case should not matter)
  • filter has the key "q". filter can have other keys.
  • range is a list of two integers. range[0] is less then or equal to range[1]

In fastapi path definitions I currently define filter, sort, and range as strings as in the code below, convert them using json.loads, and do checks.

@r.get(
    "/users",
    response_model=List[User],
    response_model_exclude_none=True,
)
async def list_users(
    filter: Optional[str] = None,
    sort: Optional[str] = None,
    range: Optional[str] = None,
    ...
):
...

How can I use pydantic definitions for checks and API definition instead of just using str, such that checks are done by pydantic, and openapi schema definitions are more descriptive?

1
  • You can use a Depends to depend on a utilty function that receives a string, decodes the JSON and use parse_obj or parse_obj_as (depending on the root level structure of the request JSON) helper function from Pydantic to validate the submitted content against your basemodel. Commented May 15, 2022 at 19:17

1 Answer 1

0

There are some ways to do what you want. But FastAPI only allow sequence structures (list, tuples, set, sequence) for Query and Header params, so that &filter={"q":"bar"} it won't work on FastAPI at least for 0.75.0 version where I am working.

If you wish to support a Dictionary you should use POST method with Body params. And then you can wrap your values around a Pydantic model to support validation.

Below there are two ways to implement this in FastAPI.

Solution 1:

class FilterModel(BaseModel):
    filter: dict
    
@router.post(
    "/posts"
)
async def list_users(
    filters: FilterModel,
    sort: Tuple[str, Literal["DESC", "ASC", "desc", "asc"]] = Query(("title", "ASC")),
    ranges: Tuple[int, int] = Query((0, 24))
) -> None:
    print(filters)
    print(sort)
    print(ranges)
    model = MyModel(sort=sort, range=ranges)
    print(model)

This will allow you to call api like:

POST http://my.api.url/posts?sort=title&sort=ASC&ranges=0&ranges=24

With body:

{
    "filter"={"q":"bar"}
}

Solution 2:

class MyModel(BaseModel):
    sortBy: Optional[str]
    sortOrder: Optional[Literal["DESC", "ASC", "desc", "asc"]]
    min_range: Optional[int]
    max_range: Optional[int]

class FilterModel(BaseModel):
    filter: dict

@router.post(
    "/posts"
)
async def list_users(
    filters: FilterModel,
    model: MyModel = Depends()
) -> None:
    print(filters)
    print(model)

This will allow you to call api like:

POST http://my.api.url/posts?sortBy="title"&sortOrder="ASC"&min_range=0&max_range=24

With body:

{
    "filter"={"q":"bar"}
}

If you wish to use GET method and the filter you have to do some hacky things.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.