This commit is contained in:
Gustav Larsson 2026-02-01 14:30:01 +00:00 committed by GitHub
commit 6e0c644f6c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 152 additions and 1 deletions

View File

@ -491,11 +491,25 @@ def normalize_name(name: str) -> str:
return re.sub(r"[^a-zA-Z0-9.\-_]", "_", name)
def get_long_model_name(model: TypeModelOrEnum) -> str:
return f"{model.__module__}__{model.__qualname__}".replace(".", "__")
def get_model_name_map(unique_models: TypeModelSet) -> dict[TypeModelOrEnum, str]:
name_model_map = {}
conflicting_names: set[str] = set()
for model in unique_models:
model_name = normalize_name(model.__name__)
name_model_map[model_name] = model
if model_name in conflicting_names:
model_name = get_long_model_name(model)
name_model_map[model_name] = model
elif model_name in name_model_map:
conflicting_names.add(model_name)
conflicting_model = name_model_map.pop(model_name)
name_model_map[get_long_model_name(conflicting_model)] = conflicting_model
name_model_map[get_long_model_name(model)] = model
else:
name_model_map[model_name] = model
return {v: k for k, v in name_model_map.items()}

View File

@ -0,0 +1,5 @@
from pydantic import BaseModel
class User(BaseModel):
a: int

View File

@ -0,0 +1,19 @@
from fastapi import FastAPI
from tests.test_duplicate_model_names_openapi.a.model import User as UserA
from tests.test_duplicate_model_names_openapi.b.model import User as UserB
from tests.test_duplicate_model_names_openapi.c.model import User as UserC
app = FastAPI()
@app.get("/a", response_model=UserA)
def user_a(): ...
@app.get("/b", response_model=UserB)
def user_b(): ...
@app.get("/c", response_model=UserC)
def user_c(): ...

View File

@ -0,0 +1,5 @@
from pydantic import BaseModel
class User(BaseModel):
b: int

View File

@ -0,0 +1,5 @@
from pydantic import BaseModel
class User(BaseModel):
c: int

View File

@ -0,0 +1,103 @@
from fastapi.testclient import TestClient
from .app import app
client = TestClient(app)
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
schema = response.json()
component_names = schema["components"]["schemas"].keys()
# Fully qualified names due to conflict
assert set(component_names) == {
"tests__test_duplicate_model_names_openapi__a__model__User",
"tests__test_duplicate_model_names_openapi__b__model__User",
"tests__test_duplicate_model_names_openapi__c__model__User",
}
assert schema == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/a": {
"get": {
"summary": "User A",
"operationId": "user_a_a_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/tests__test_duplicate_model_names_openapi__a__model__User"
}
}
},
}
},
}
},
"/b": {
"get": {
"summary": "User B",
"operationId": "user_b_b_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/tests__test_duplicate_model_names_openapi__b__model__User"
}
}
},
}
},
}
},
"/c": {
"get": {
"summary": "User C",
"operationId": "user_c_c_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/tests__test_duplicate_model_names_openapi__c__model__User"
}
}
},
}
},
}
},
},
"components": {
"schemas": {
"tests__test_duplicate_model_names_openapi__a__model__User": {
"properties": {"a": {"type": "integer", "title": "A"}},
"type": "object",
"required": ["a"],
"title": "User",
},
"tests__test_duplicate_model_names_openapi__b__model__User": {
"properties": {"b": {"type": "integer", "title": "B"}},
"type": "object",
"required": ["b"],
"title": "User",
},
"tests__test_duplicate_model_names_openapi__c__model__User": {
"properties": {"c": {"type": "integer", "title": "C"}},
"type": "object",
"required": ["c"],
"title": "User",
},
}
},
}