Avish021
Avish021

Reputation: 131

FastAPI - GET Request with Pydantic List field as Query Parameter

I'm new to FastAPI (migrating from Flask) and I'm trying to create a Pydantic model for my GET route:

from fastapi import APIRouter,Depends
from pydantic import BaseModel
from typing import Optional,List

router = APIRouter()

class SortModel(BaseModel):
    field:    Optional[str]
    directions: List[str]

@router.get("/pydanticmodel")
def get_sort(criteria: SortModel = Depends(SortModel)):
    pass #my code for handling this route.....

When I'm running :

curl -X GET http://localhost:XXXX/pydanticmodel?directions=up&directions=asc&field=id

I'm getting:

422 Unprocessable Entity: {"detail":[{"loc":["body"],"msg":"field required","type":"value_error.missing"}]}

But if I change directions:List[str] to directions: str I'm getting 200 OK with directions="asc".

What is the reason that str works for query param and List[str] does not? What am I doing wrong?

Thanks.

Upvotes: 13

Views: 11329

Answers (4)

Chris
Chris

Reputation: 34055

Update

You could now wrap the Query() in a Field(), which would allow you to define a Pydantic List field that will be interpreted as query parameter:

from fastapi import FastAPI, Query, Depends
from pydantic import BaseModel, Field
from typing import List, Optional

app = FastAPI()


class SortModel(BaseModel):
    field: Optional[str]
    directions: List[str] = Field(Query(...))


@app.get("/")
async def get_data(criteria: SortModel = Depends()):
    return criteria

See this answer for more details and another working example.

Original answer

It is not, as yet, possible to use a GET request with Pydantic List field as query parameter. When you declare a List field in the Pydantic model, it is interpreted as a request body parameter, instead of a query one (regardless of using Depends()—you can check that through Swagger UI docs at http://127.0.0.1:8000/docs, for instance). Additionally, as you are using a GET request, even if you added the List of directions in the body and attempted sending the request, it wouldn't work, as a POST request would be required for that operation.

The way to do this is to either define the List of directions explicitly with Query as a separate parameter in your endpoint, or implement your query parameter-parsing in a separate dependency class, as described here. Remember again to define the List field explicitly with Query, so that directions can be interpreted as a query parameter and appear multiple times in the URL (in others words, to receive multiple values). Example:

from typing import List, Optional
from fastapi import FastAPI, Depends, Query

app = FastAPI()


class SortModel:
    def __init__(
        self,
        field: Optional[str],
        directions: List[str] = Query(...)
    ):
        self.field = field
        self.directions = directions


@app.get("/")
async def get_data(criteria: SortModel = Depends()):
    return criteria

The above can be re-written using the @dataclass decorator, as shown below:

from typing import List, Optional
from fastapi import FastAPI, Depends, Query
from dataclasses import dataclass

app = FastAPI()


@dataclass
class SortModel:
    field: Optional[str]
    directions: List[str] = Query(...)


@app.get("/")
async def get_data(criteria: SortModel = Depends()):
    return criteria

Upvotes: 8

Tom McLean
Tom McLean

Reputation: 6297

With FastAPI 0.115.0, you can now use list fields in a BaseModel within a query, and it works with SwaggerUI:

from fastapi import FastAPI, Query
from pydantic import BaseModel
from typing import Annotated


class FilterParams(BaseModel):
    tags: list[str] = []


app = FastAPI()


@app.get("/hello")
def world(params: Annotated[FilterParams, Query()]):
    return params

Upvotes: 1

Jon E
Jon E

Reputation: 119

I'm running into the same issue. The following solution will work, but it isn't really what I want however maybe it's good enough for you:

from fastapi import APIRouter,Depends, Query
from pydantic import BaseModel
from typing import Optional,List

router = APIRouter()

class SortModel(BaseModel):
    field:    Optional[str]

@router.get("/pydanticmodel")
def get_sort(criteria: SortModel = Depends(SortModel), directions: List[str] = Query(...)):
    pass #my code for handling this route.....

Upvotes: 0

Yagiz Degirmenci
Yagiz Degirmenci

Reputation: 20598

It's not a Pydantic or FastAPI problem.

If you want to send an array with curl you should use -d flag.

In: curl -X GET "http://127.0.0.1:8000/pydanticmodel?field=123"  -d "[\"string\"]"
Out: {"field":"123","directions":["string"]}

Now your code should work perfectly.

Upvotes: -1

Related Questions