Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
9 tasks done
Kludex opened this issue Mar 5, 2024 Discussed in #11250 · 2 comments
Open
9 tasks done
Labels
question Question or problem

Comments

@Kludex
Copy link
Member

Kludex commented Mar 5, 2024

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

@Kludex Kludex added the question Question or problem label Mar 5, 2024
@H-Plus-Time
Copy link

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).

@Htet-Inzali
Copy link

Based on the GitHub issue, the problem is:

In Pydantic v1 (version 1.10.14 mentioned in the details), path parameters with type annotations like int and UUID would correctly parse string inputs into those types when passed through URL paths.
After upgrading to Pydantic v2 (version 2.6.3), all path parameters are always interpreted as strings, regardless of the type annotation.

Looking at the example code:

`@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))`

With Pydantic v1, if you called /int/123, the path parameter would be parsed as an integer. With Pydantic v2, it's always a string.
This is indeed a breaking change that affects type validation and conversion in FastAPI applications. The issue reporter suspects this behavior applies to all FastAPI parameters (path, query, headers, body, etc.).
The most direct solution would be to manually convert the types in your route handlers:

@app.get("/int/{path}") async def int_(path: str): try: path_as_int = int(path) # Use path_as_int except ValueError: # Handle invalid format return str(type(path))

Alternatively, you might want to check if there are configuration options in Pydantic v2 or FastAPI to maintain backward compatibility with type parsing behavior.
If you're maintaining a large codebase with many endpoints, you might consider using FastAPI dependency injection to create reusable converters that can be applied across multiple endpoints.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Question or problem
Projects
None yet
Development

No branches or pull requests

3 participants