From 8f99a2b7347aeaff09ca3f06eaecf52a786ef2dc Mon Sep 17 00:00:00 2001 From: Sofie Van Landeghem Date: Tue, 2 Dec 2025 09:34:13 +0530 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Avoid=20accessing=20non-existing?= =?UTF-8?q?=20"$ref"=20key=20for=20Pydantic=20v2=20compat=20remapping=20(#?= =?UTF-8?q?14361)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- fastapi/_compat/v2.py | 2 +- tests/test_schema_compat_pydantic_v2.py | 92 +++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 tests/test_schema_compat_pydantic_v2.py diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 5cd49343b..7196a6190 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -304,7 +304,7 @@ def _remap_definitions_and_field_mappings( old_name_to_new_name_map = {} for field_key, schema in field_mapping.items(): model = field_key[0].type_ - if model not in model_name_map: + if model not in model_name_map or "$ref" not in schema: continue new_name = model_name_map[model] old_name = schema["$ref"].split("/")[-1] diff --git a/tests/test_schema_compat_pydantic_v2.py b/tests/test_schema_compat_pydantic_v2.py new file mode 100644 index 000000000..39626c0ec --- /dev/null +++ b/tests/test_schema_compat_pydantic_v2.py @@ -0,0 +1,92 @@ +import pytest +from fastapi import FastAPI +from fastapi.testclient import TestClient +from inline_snapshot import snapshot +from pydantic import BaseModel + +from tests.utils import needs_py310, needs_pydanticv2 + + +@pytest.fixture(name="client") +def get_client(): + from enum import Enum + + app = FastAPI() + + class PlatformRole(str, Enum): + admin = "admin" + user = "user" + + class OtherRole(str, Enum): ... + + class User(BaseModel): + username: str + role: PlatformRole | OtherRole + + @app.get("/users") + async def get_user() -> User: + return {"username": "alice", "role": "admin"} + + client = TestClient(app) + return client + + +@needs_py310 +@needs_pydanticv2 +def test_get(client: TestClient): + response = client.get("/users") + assert response.json() == {"username": "alice", "role": "admin"} + + +@needs_py310 +@needs_pydanticv2 +def test_openapi_schema(client: TestClient): + response = client.get("openapi.json") + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users": { + "get": { + "summary": "Get User", + "operationId": "get_user_users_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + } + }, + "components": { + "schemas": { + "PlatformRole": { + "type": "string", + "enum": ["admin", "user"], + "title": "PlatformRole", + }, + "User": { + "properties": { + "username": {"type": "string", "title": "Username"}, + "role": { + "anyOf": [ + {"$ref": "#/components/schemas/PlatformRole"}, + {"enum": [], "title": "OtherRole"}, + ], + "title": "Role", + }, + }, + "type": "object", + "required": ["username", "role"], + "title": "User", + }, + } + }, + } + )