Search code examples
pythonquery-stringpydanticlitestar

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:

  • Using json.dumps to encode the dictionary before sending it as a parameter.
  • Debugging the Litestar model implementation where the query parameter is provided as string into the msgspec conversion. This does obviously not fit to the required type.

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:

  • Litestar version: 2.10.0
  • Pydantic version: 2.8.2
  • httpx version: 0.27.0

Any help or guidance would be greatly appreciated.


Solution

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