casabre
casabre

Reputation: 168

How to Use Pydantic Model as Query Parameter in Litestar GET Route

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:

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.

Upvotes: 2

Views: 521

Answers (1)

Victor Egiazarian
Victor Egiazarian

Reputation: 1116

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!

Upvotes: 1

Related Questions