]> git.ipfire.org Git - thirdparty/fastapi/fastapi.git/commitdiff
🐛 Fix handling of JSON Schema attributes named "$ref" (#14349)
authorSebastián Ramírez <tiangolo@gmail.com>
Thu, 13 Nov 2025 13:59:07 +0000 (14:59 +0100)
committerGitHub <noreply@github.com>
Thu, 13 Nov 2025 13:59:07 +0000 (14:59 +0100)
fastapi/_compat/v2.py
tests/test_schema_ref_pydantic_v2.py [new file with mode: 0644]

index 6a87b9ae9df12024eb8b57413e74f43323ca76ff..5cd49343b66fcfb40ee3910195be4641a3d696fc 100644 (file)
@@ -262,12 +262,12 @@ def _replace_refs(
     new_schema = deepcopy(schema)
     for key, value in new_schema.items():
         if key == "$ref":
-            ref_name = schema["$ref"].split("/")[-1]
-            if ref_name in old_name_to_new_name_map:
-                new_name = old_name_to_new_name_map[ref_name]
-                new_schema["$ref"] = REF_TEMPLATE.format(model=new_name)
-            else:
-                new_schema["$ref"] = schema["$ref"]
+            value = schema["$ref"]
+            if isinstance(value, str):
+                ref_name = schema["$ref"].split("/")[-1]
+                if ref_name in old_name_to_new_name_map:
+                    new_name = old_name_to_new_name_map[ref_name]
+                    new_schema["$ref"] = REF_TEMPLATE.format(model=new_name)
             continue
         if isinstance(value, dict):
             new_schema[key] = _replace_refs(
diff --git a/tests/test_schema_ref_pydantic_v2.py b/tests/test_schema_ref_pydantic_v2.py
new file mode 100644 (file)
index 0000000..119b76a
--- /dev/null
@@ -0,0 +1,72 @@
+from typing import Any
+
+import pytest
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
+from pydantic import BaseModel, ConfigDict, Field
+
+from tests.utils import needs_pydanticv2
+
+
+@pytest.fixture(name="client")
+def get_client():
+    app = FastAPI()
+
+    class ModelWithRef(BaseModel):
+        ref: str = Field(validation_alias="$ref", serialization_alias="$ref")
+        model_config = ConfigDict(validate_by_alias=True, serialize_by_alias=True)
+
+    @app.get("/", response_model=ModelWithRef)
+    async def read_root() -> Any:
+        return {"$ref": "some-ref"}
+
+    client = TestClient(app)
+    return client
+
+
+@needs_pydanticv2
+def test_get(client: TestClient):
+    response = client.get("/")
+    assert response.json() == {"$ref": "some-ref"}
+
+
+@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": {
+                "/": {
+                    "get": {
+                        "summary": "Read Root",
+                        "operationId": "read_root__get",
+                        "responses": {
+                            "200": {
+                                "description": "Successful Response",
+                                "content": {
+                                    "application/json": {
+                                        "schema": {
+                                            "$ref": "#/components/schemas/ModelWithRef"
+                                        }
+                                    }
+                                },
+                            }
+                        },
+                    }
+                }
+            },
+            "components": {
+                "schemas": {
+                    "ModelWithRef": {
+                        "properties": {"$ref": {"type": "string", "title": "$Ref"}},
+                        "type": "object",
+                        "required": ["$ref"],
+                        "title": "ModelWithRef",
+                    }
+                }
+            },
+        }
+    )