I’m trying to create a GET route with Litestar that utilizes a Pydantic model as a query parameter. However, the serialization does not work as expected.
Here is a minimal example that reproduces my issue:
from pydantic import BaseModel
from litestar import Litestar, get, Controller
class Input(BaseModel):
foo: str
bar: str
class RootController(Controller):
path = "/"
@get()
def input(self, input: Input) -> str:
return input.foo + input.bar
app = Litestar(route_handlers=[RootController])
And the following GET request:
import httpx
import json
params = {
"input": {
"foo": "test",
"bar": "this"
}
}
def prepare_encode(params: dict) -> dict:
for key, value in params.items():
if isinstance(value, dict):
params[key] = json.dumps(value, indent=None)
return params
params = prepare_encode(params)
response = httpx.get("http://localhost:8000/", params=params)
response.json()
The GET request results in the following error:
{
"status_code": 400,
"detail": "Validation failed for GET /?input=%7B%22foo%22%3A%20%22test%22%2C%20%22bar%22%3A%20%22this%22%7D",
"extra": [
{
"message": "Input should be a valid dictionary or instance of Input"
}
]
}
It seems that the query parameter is not being properly serialized into the Input
Pydantic model.
What I've Tried:
json.dumps
to encode the dictionary before sending it as a parameter.Expected Behavior:
I expect the input
query parameter to be correctly parsed and serialized into the Input
model, allowing the GET request to succeed without validation errors.
Question: How can I correctly pass a Pydantic model as a query parameter in a Litestar GET route? What am I missing in the serialization process? Is it possible at all?
Additional Context:
Any help or guidance would be greatly appreciated.
There are actually problems with both how you make and how you process your request. First, I wasn't be able to find in the docs a possibility to use Pydantic
model for query params as FastAPI has. However you can implement similar logic yourself via DI mechanism:
def build_input(foo: str, bar: str) -> Input:
"""Prepare input model from query params."""""
return Input(foo=foo, bar=bar)
class RootController(Controller):
path = "/"
@get(dependencies={"input": Provide(build_input)})
def input(self, input: Input) -> str:
return input.foo + input.bar
Secondly, if you'd like to use nested structure as an input you should better use POST
method instead.
{
"input": {
"foo": "test",
"bar": "this"
}
}
Otherwise, the above dictionary will be converted to the following string when used as a query: %7B%22foo%22%3A%20%22test%22%2C%20%22bar%22%3A%20%22this%22%7D
I assume what you wanted to do was the following:
response = httpx.get(
"http://localhost:8000/",
params={
"foo": "test",
"bar": "this"
}
)
With these changes everything seems working!