Browse Source

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
pull/15245/head
Contributor 4 months ago
parent
commit
f6f4c7d0da
  1. 3
      fastapi/openapi/utils.py
  2. 10
      fastapi/utils.py

3
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__", "<unnamed_endpoint>")
message = f"Duplicate Operation ID {operation_id} for function {endpoint_name}"

10
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

Loading…
Cancel
Save