From 135010c19592f7685490497a6ce58a6b5b77ad71 Mon Sep 17 00:00:00 2001 From: aayushbaluni <73417844+aayushbaluni@users.noreply.github.com> Date: Tue, 17 Mar 2026 23:19:02 +0530 Subject: [PATCH] fix: unique operation IDs for routes with multiple methods (#13175) generate_unique_id used list(route.methods)[0] so all methods on a route shared the same operation_id. For multi-method routes, use method-specific IDs in get_openapi_operation_metadata. Made-with: Cursor --- fastapi/openapi/utils.py | 9 ++++++++- tests/test_generate_unique_id_function.py | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index 8f1852b0cc..f7238bac89 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -242,7 +242,14 @@ def get_openapi_operation_metadata( operation["summary"] = generate_operation_summary(route=route, method=method) if route.description: operation["description"] = route.description - operation_id = route.operation_id or route.unique_id + if route.operation_id: + operation_id = route.operation_id + elif route.methods and len(route.methods) > 1: + # Multi-method routes: unique_id uses first method only; use method-specific id + base = route.unique_id.rsplit("_", 1)[0] + operation_id = f"{base}_{method.lower()}" + else: + operation_id = route.unique_id if operation_id in operation_ids: endpoint_name = getattr(route.endpoint, "__name__", "") message = f"Duplicate Operation ID {operation_id} for function {endpoint_name}" diff --git a/tests/test_generate_unique_id_function.py b/tests/test_generate_unique_id_function.py index c56e6d5794..2a4038b00f 100644 --- a/tests/test_generate_unique_id_function.py +++ b/tests/test_generate_unique_id_function.py @@ -1697,3 +1697,24 @@ def test_warn_duplicate_operation_id(): ] assert len(duplicate_warnings) > 0 assert "Duplicate Operation ID" in str(duplicate_warnings[0].message) + + +def test_multiple_methods_unique_operation_id(): + """Regression test for #13175: routes with multiple methods get unique operation IDs.""" + app = FastAPI() + + def clear(): + return {"ok": True} + + app.add_api_route("/clear", clear, methods=["POST", "DELETE"]) + client = TestClient(app) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + response = client.get("/openapi.json") + assert len([x for x in w if "Duplicate Operation ID" in str(getattr(x, "message", ""))]) == 0 + paths = response.json()["paths"]["/clear"] + post_id = paths["post"]["operationId"] + delete_id = paths["delete"]["operationId"] + assert post_id != delete_id + assert post_id.endswith("_post") + assert delete_id.endswith("_delete")