Search code examples
getfastapipydantic

How to validate a FastAPI GET parameter with Pydantic, without a model


I have a very simple FastAPI GET endpoint. It does not accept any request body - it only accepts a single, mandatory GET parameter. For example:

/v1/app/end?foo=abcdef1234567890

This foo parameter absolutely must conform to some regex. I've simplified the regex for the purposes of this question, but it's nothing crazy.

When it worked

This was easy enough to do, and in fact, I had it working in Pydantic 1.9:

# Works perfectly with Pydantic v1.9
from pydantic import constr

MY_REGEX = r"^[a-z0-9]{16}$"

@router.get(
    "/v1/app/end",
    response_model=src.schemas.foo.Bar,
)
def get_with_parameter(
    foo: constr(regex=MY_REGEX),
) -> src.schemas.foo.Bar:

When it broke

Now, moving to Pydantic 2.5, I can no longer do this. Note that I have renamed regex to pattern, but it should still do the same thing.

# Validation is completely ignored!
def get_with_parameter(
    foo: constr(pattern=MY_REGEX),
) -> src.schemas.foo.Bar:

Following the suggestions in the official documentation and source code does not work either:

# Validation is completely ignored!
from typing_extensions import Annotated
from pydantic import StringConstraints

def get_with_parameter(
    foo: Annotated[str, StringConstraints(pattern=MY_REGEX)],
) -> src.schemas.foo.Bar:

Glitchy fix

I found that I can make it work again, but only if I make it Optional, Final, or some other weird type, which I do not want to do:

from typing import Optional, Final

# Validation works, but is now Optional
def get_with_parameter(
    foo: Optional[constr(pattern=MY_REGEX)],
) -> src.schemas.foo.Bar:


# Validation works, but is now Final
def get_with_parameter(
    foo: Final[constr(pattern=MY_REGEX)],
) -> src.schemas.foo.Bar:

--

How can I get this to work like it used to?


Solution

  • I found a fix, which was to use the Query type from FastAPI

    from typing import Annotated
    from fastapi import Query
    
    @router.get(
        "/v1/app/end",
        response_model=src.schemas.foo.Bar,
    )
    def get_with_parameter(
        foo: Annotated[str, Query(pattern=MY_REGEX)]),
    ) -> src.schemas.foo.Bar: