From f6f4c7d0dae134e5d1cf147ba64f9222d7a789ae Mon Sep 17 00:00:00 2001 From: Contributor Date: Sat, 28 Mar 2026 12:13:47 +0530 Subject: [PATCH] fix: include method in operation_id to prevent duplicates with multi-method routes When a route is defined with multiple HTTP methods (e.g., methods=['POST', 'DELETE']), the operation_id generation was only using the first method name, causing all methods to get the same operation_id suffix. This resulted in duplicate operation IDs in the OpenAPI schema and incorrect warnings. Changes: - Modified generate_unique_id() to accept an optional 'method' parameter - When a specific method is provided, it includes that method in the operation_id - Updated get_openapi_operation_metadata() in openapi/utils.py to pass the method - Each HTTP method now gets its own unique operation_id Fixes #13175 --- fastapi/openapi/utils.py | 3 ++- fastapi/utils.py | 10 ++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index 8f1852b0cc..07791fca47 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -33,6 +33,7 @@ from fastapi.types import ModelNameMap from fastapi.utils import ( deep_dict_update, generate_operation_id_for_path, + generate_unique_id, is_body_allowed_for_status_code, ) from pydantic import BaseModel @@ -242,7 +243,7 @@ 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 + operation_id = route.operation_id or generate_unique_id(route, method=method) 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/fastapi/utils.py b/fastapi/utils.py index 12eaa2bf08..575af49cda 100644 --- a/fastapi/utils.py +++ b/fastapi/utils.py @@ -92,11 +92,17 @@ def generate_operation_id_for_path( return operation_id -def generate_unique_id(route: "APIRoute") -> str: +def generate_unique_id(route: "APIRoute", method: str | None = None) -> str: operation_id = f"{route.name}{route.path_format}" operation_id = re.sub(r"\W", "_", operation_id) assert route.methods - operation_id = f"{operation_id}_{list(route.methods)[0].lower()}" + if method: + # Include the specific method being generated + operation_id = f"{operation_id}_{method.lower()}" + else: + # When no specific method is provided, include all methods (for backwards compatibility) + methods_str = "_".join(sorted(m.lower() for m in route.methods)) + operation_id = f"{operation_id}_{methods_str}" return operation_id