Browse Source

fix: generate unique operation ids for multi-method routes

pull/15552/head
Nisar k 2 weeks ago
parent
commit
571ff009b6
  1. 11
      fastapi/openapi/utils.py
  2. 10
      fastapi/utils.py
  3. 30
      tests/test_generate_unique_id_function.py

11
fastapi/openapi/utils.py

@ -31,6 +31,7 @@ from fastapi.responses import Response
from fastapi.sse import _SSE_EVENT_SCHEMA
from fastapi.types import ModelNameMap
from fastapi.utils import (
_generate_unique_id_for_method,
deep_dict_update,
generate_operation_id_for_path,
is_body_allowed_for_status_code,
@ -242,7 +243,15 @@ 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 is None
and isinstance(route.generate_unique_id_function, DefaultPlaceholder)
and route.methods
and len(route.methods) > 1
):
operation_id = _generate_unique_id_for_method(route, method=method)
else:
operation_id = route.operation_id or route.unique_id
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,14 +92,18 @@ def generate_operation_id_for_path(
return operation_id
def generate_unique_id(route: "APIRoute") -> str:
def _generate_unique_id_for_method(route: "APIRoute", method: str) -> 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()}"
operation_id = f"{operation_id}_{method.lower()}"
return operation_id
def generate_unique_id(route: "APIRoute") -> str:
assert route.methods
return _generate_unique_id_for_method(route, method=list(route.methods)[0])
def deep_dict_update(main_dict: dict[Any, Any], update_dict: dict[Any, Any]) -> None:
for key, value in update_dict.items():
if (

30
tests/test_generate_unique_id_function.py

@ -1697,3 +1697,33 @@ def test_warn_duplicate_operation_id():
]
assert len(duplicate_warnings) > 0
assert "Duplicate Operation ID" in str(duplicate_warnings[0].message)
def test_multi_method_route_unique_operation_ids():
app = FastAPI()
def clear():
return {"ok": True} # pragma: nocover
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 response.status_code == 200, response.text
duplicate_warnings = [
warning
for warning in w
if issubclass(warning.category, UserWarning)
and "Duplicate Operation ID" in str(warning.message)
]
assert duplicate_warnings == []
openapi_schema = response.json()
assert openapi_schema["paths"]["/clear"]["post"]["operationId"] == (
"clear_clear_post"
)
assert openapi_schema["paths"]["/clear"]["delete"]["operationId"] == (
"clear_clear_delete"
)

Loading…
Cancel
Save