Browse Source

Fix duplicate operation IDs for routes with multiple methods

Fixes #13175. When a route is defined with multiple methods (e.g.,
methods=["POST", "DELETE"]), the generate_unique_id function was only
using the first method to generate the operation_id. This caused all
methods on the same route to have the same operation_id in the OpenAPI
schema, triggering duplicate operation_id warnings.

The fix modifies get_openapi_operation_metadata to generate unique
operation_ids per method when a route has multiple methods, by
appending the current method to the base unique_id.
pull/15074/head
04cb 4 months ago
parent
commit
9ccef4328c
  1. 11
      fastapi/openapi/utils.py
  2. 52
      tests/test_multiple_methods_unique_id.py

11
fastapi/openapi/utils.py

@ -243,7 +243,16 @@ 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
else:
# If the route has multiple methods, generate a unique operation_id
# using the current method to avoid duplicates
assert route.methods is not None
if len(route.methods) > 1:
operation_id = f"{route.unique_id}_{method.lower()}"
else:
operation_id = route.unique_id
if operation_id in operation_ids:
message = (
f"Duplicate Operation ID {operation_id} for function "

52
tests/test_multiple_methods_unique_id.py

@ -0,0 +1,52 @@
"""
Test that routes with multiple methods get unique operation IDs.
This is a regression test for issue #13175.
"""
import warnings
from fastapi import FastAPI
from fastapi.testclient import TestClient
def test_multiple_methods_generate_unique_operation_ids():
"""Test that add_api_route with multiple methods generates unique operation IDs."""
app = FastAPI()
def clear():
return {"cleared": True}
app.add_api_route("/clear", clear, methods=["POST", "DELETE"])
# Capture warnings to check for duplicate operation_id warnings
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
client = TestClient(app)
response = client.get("/openapi.json")
# There should be no duplicate operation_id warnings
dup_warnings = [
warning for warning in w
if "Duplicate Operation ID" in str(warning.message)
]
assert len(dup_warnings) == 0, f"Expected no duplicate warnings, got: {dup_warnings}"
openapi_schema = response.json()
# Get the operation IDs for POST and DELETE
post_operation_id = openapi_schema["paths"]["/clear"]["post"]["operationId"]
delete_operation_id = openapi_schema["paths"]["/clear"]["delete"]["operationId"]
# They should be different
assert post_operation_id != delete_operation_id, (
f"POST and DELETE should have different operation IDs. "
f"Got POST: {post_operation_id}, DELETE: {delete_operation_id}"
)
# Verify the operation IDs contain the correct method suffix
assert post_operation_id.endswith("_post"), f"POST operation_id should end with '_post', got: {post_operation_id}"
assert delete_operation_id.endswith("_delete"), f"DELETE operation_id should end with '_delete', got: {delete_operation_id}"
if __name__ == "__main__":
test_multiple_methods_generate_unique_operation_ids()
print("Test passed!")
Loading…
Cancel
Save