Browse Source

Fix duplicate operation IDs for routes with multiple methods

When a route is registered with multiple HTTP methods (e.g., methods=['POST', 'DELETE']),
the default generate_unique_id function only uses the first method, causing duplicate
operation IDs in the OpenAPI schema.

This fix ensures that when a route has multiple methods and no explicit operation_id,
each method gets a unique operation ID by appending the current method name.

Fixes #13175
pull/15194/head
OSC Agent 4 months ago
parent
commit
11dc280806
  1. 15
      fastapi/openapi/utils.py
  2. 27
      tests/test_generate_unique_id_function.py

15
fastapi/openapi/utils.py

@ -242,7 +242,20 @@ def get_openapi_operation_metadata(
operation["summary"] = generate_operation_summary(route=route, method=method) operation["summary"] = generate_operation_summary(route=route, method=method)
if route.description: if route.description:
operation["description"] = 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 len(route.methods) > 1:
# When a route handles multiple methods, the default unique_id is based on
# the first method only (via generate_unique_id). Append the actual method
# to avoid duplicate operation IDs across methods. See #13175.
base = route.unique_id
# Remove the trailing _<method> suffix that generate_unique_id appends
first_method = f"_{list(route.methods)[0].lower()}"
if base.endswith(first_method):
base = base[: -len(first_method)]
operation_id = f"{base}_{method.lower()}"
else:
operation_id = route.unique_id
if operation_id in operation_ids: if operation_id in operation_ids:
endpoint_name = getattr(route.endpoint, "__name__", "<unnamed_endpoint>") endpoint_name = getattr(route.endpoint, "__name__", "<unnamed_endpoint>")
message = f"Duplicate Operation ID {operation_id} for function {endpoint_name}" message = f"Duplicate Operation ID {operation_id} for function {endpoint_name}"

27
tests/test_generate_unique_id_function.py

@ -1697,3 +1697,30 @@ def test_warn_duplicate_operation_id():
] ]
assert len(duplicate_warnings) > 0 assert len(duplicate_warnings) > 0
assert "Duplicate Operation ID" in str(duplicate_warnings[0].message) assert "Duplicate Operation ID" in str(duplicate_warnings[0].message)
def test_unique_operation_id_for_multi_method_route():
"""A route with multiple methods should get unique operation IDs per method.
Regression test for https://github.com/fastapi/fastapi/issues/13175
"""
app = FastAPI()
def handler():
return {"msg": "ok"}
app.add_api_route("/clear", handler, methods=["POST", "DELETE"])
client = TestClient(app)
response = client.get("/openapi.json")
data = response.json()
paths = data["paths"]
op_ids = []
for methods in paths.values():
for details in methods.values():
op_ids.append(details["operationId"])
assert len(op_ids) == 2, f"Expected 2 operations, got {len(op_ids)}"
assert op_ids[0] != op_ids[1], f"Duplicate operation IDs: {op_ids}"
assert set(op_ids) == {"handler_clear_post", "handler_clear_delete"}

Loading…
Cancel
Save