Browse Source

Fix unique operation IDs for multi-method routes

pull/15222/head
giria660 4 months ago
parent
commit
8f87daac97
No known key found for this signature in database GPG Key ID: 3D8290B3CD372BA4
  1. 9
      fastapi/openapi/utils.py
  2. 3
      fastapi/utils.py
  3. 41
      tests/test_generate_unique_id_function.py

9
fastapi/openapi/utils.py

@ -233,6 +233,13 @@ def generate_operation_summary(*, route: routing.APIRoute, method: str) -> str:
return route.name.replace("_", " ").title()
def get_openapi_operation_id(*, route: routing.APIRoute, method: str) -> str:
operation_id = route.operation_id or route.unique_id
if route.operation_id is None and route.methods and len(route.methods) > 1:
operation_id = f"{operation_id}_{method.lower()}"
return operation_id
def get_openapi_operation_metadata(
*, route: routing.APIRoute, method: str, operation_ids: set[str]
) -> dict[str, Any]:
@ -242,7 +249,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 = get_openapi_operation_id(route=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}"

3
fastapi/utils.py

@ -96,7 +96,8 @@ def generate_unique_id(route: "APIRoute") -> 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 len(route.methods) == 1:
operation_id = f"{operation_id}_{sorted(route.methods)[0].lower()}"
return operation_id

41
tests/test_generate_unique_id_function.py

@ -1697,3 +1697,44 @@ 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()
@app.api_route("/clear", methods=["POST", "DELETE"])
def clear():
return None # pragma: nocover
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 == []
path = response.json()["paths"]["/clear"]
assert path["post"]["operationId"] == "clear_clear_post"
assert path["delete"]["operationId"] == "clear_clear_delete"
def test_multi_method_route_unique_operation_ids_with_custom_generate_unique_id():
app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
@app.api_route("/clear", methods=["POST", "DELETE"])
def clear():
return None # pragma: nocover
client = TestClient(app)
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
path = response.json()["paths"]["/clear"]
assert path["post"]["operationId"] == "foo_clear_post"
assert path["delete"]["operationId"] == "foo_clear_delete"

Loading…
Cancel
Save