diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index 808646cc2..95abc12bf 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -235,6 +235,8 @@ def get_openapi_operation_metadata( if route.description: operation["description"] = route.description operation_id = route.operation_id or route.unique_id + if len(route.methods) > 1: + operation_id = f"{operation_id}_{method.lower()}" if operation_id in operation_ids: message = ( f"Duplicate Operation ID {operation_id} for function " diff --git a/fastapi/utils.py b/fastapi/utils.py index 4c7350fea..607eb6c2a 100644 --- a/fastapi/utils.py +++ b/fastapi/utils.py @@ -180,7 +180,10 @@ 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}_{list(route.methods)[0].lower()}" + return operation_id diff --git a/tests/main.py b/tests/main.py index 6927eab61..6253422e8 100644 --- a/tests/main.py +++ b/tests/main.py @@ -194,6 +194,11 @@ def get_query_type_frozenset(query: FrozenSet[int] = Query(...)): return ",".join(map(str, sorted(query))) +@app.api_route("/multiple-methods", methods=["GET", "POST"]) +def multiple_methods(): + return {"message": "Hello World"} + + @app.get("/query/list") def get_query_list(device_ids: List[int] = Query()) -> List[int]: return device_ids diff --git a/tests/test_application.py b/tests/test_application.py index a7d50ea72..5f3d97c04 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -12,6 +12,7 @@ client = TestClient(app) [ ("/api_route", 200, {"message": "Hello World"}), ("/non_decorated_route", 200, {"message": "Hello World"}), + ("/multiple-methods", 200, {"message": "Hello World"}), ("/nonexistent", 404, {"detail": "Not Found"}), ], ) @@ -1163,6 +1164,28 @@ def test_openapi_schema(): }, } }, + "/multiple-methods": { + "get": { + "summary": "Multiple Methods", + "operationId": "multiple_methods_multiple_methods_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + }, + "post": { + "summary": "Multiple Methods", + "operationId": "multiple_methods_multiple_methods_post", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + }, + }, "/query/list": { "get": { "summary": "Get Query List", @@ -1251,6 +1274,17 @@ def test_openapi_schema(): }, "components": { "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, "ValidationError": { "title": "ValidationError", "required": ["loc", "msg", "type"], @@ -1267,17 +1301,6 @@ def test_openapi_schema(): "type": {"title": "Error Type", "type": "string"}, }, }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, } }, }