tiangolo/fastapi

Breaking change with path parameters when updating to pydantic>=2 from pydantic<2

Kludex opened this issue · 1 comments

Discussed in #11250

Originally posted by ThirVondukr March 5, 2024

First Check

  • I added a very descriptive title here.
  • I used the GitHub search to find a similar question and didn't find it.
  • I searched the FastAPI documentation, with the integrated search.
  • I already searched in Google "How to X in FastAPI" and didn't find any information.
  • I already read and followed all the tutorial in the docs and didn't find an answer.
  • I already checked if it is not related to FastAPI but to Pydantic.
  • I already checked if it is not related to FastAPI but to Swagger UI.
  • I already checked if it is not related to FastAPI but to ReDoc.

Commit to Help

  • I commit to help with one of those options 👆

Example Code

from uuid import UUID

from fastapi import FastAPI

app = FastAPI()


@app.get("/int/{path}")
async def int_(path: int | str):
    return str(type(path))


@app.get("/uuid/{path}")
async def uuid_(path: UUID | str):
    return str(type(path))

Description

With pydantic<2 parameters are correctly parsed as int and UUID if an appropriate string is passed, with pydantic 2 they're always interpreted as str, I presume this will work the same way with all other types that you can pass into path parameters.
I also observed the same behavior with query, so I assume it behaves the same way with all FastAPI parameters (headers, body, etc).

Operating System

Windows

Operating System Details

No response

FastAPI Version

0.110.0

Pydantic Version

1.10.14 / 2.6.3

Python Version

Python 3.11.5

Additional Context

No response

A workaround for anyone facing this (NB: this assumes availability of typing.Annotated and pydantic.Field):

@app.get("/uuid/{path}")
-async def uuid_(path: UUID | str):
+async def uuid_(path: Annotated[UUID | str, Field(union_mode='left_to_right')]):
    return str(type(path))

Pydantic v2 switched to union_mode='smart' as the default, which in cases like this will always prefer str (since it's an exact type match).

It's also necessary to do this in any dependencies (as far as I can tell, type annotations set on direct path parameters and function parameters of dependencies aren't shared).