diff --git a/docs/en/docs/img/tutorial/additional-responses/image01.png b/docs/en/docs/img/tutorial/additional-responses/image01.png index b69b9c7df..c348d07cb 100644 Binary files a/docs/en/docs/img/tutorial/additional-responses/image01.png and b/docs/en/docs/img/tutorial/additional-responses/image01.png differ diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index 5a0c89a89..ad1a9d83b 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -14,6 +14,7 @@ from fastapi.openapi.constants import ( from fastapi.openapi.models import OpenAPI from fastapi.params import Body, Param from fastapi.utils import ( + deep_dict_update, generate_operation_id_for_path, get_field_info, get_model_definitions, @@ -201,33 +202,6 @@ def get_openapi_path( ) callbacks[callback.name] = {callback.path: cb_path} operation["callbacks"] = callbacks - if route.responses: - for (additional_status_code, response) in route.responses.items(): - process_response = response.copy() - assert isinstance( - process_response, dict - ), "An additional response must be a dict" - field = route.response_fields.get(additional_status_code) - if field: - response_schema, _, _ = field_schema( - field, model_name_map=model_name_map, ref_prefix=REF_PREFIX - ) - process_response.setdefault("content", {}).setdefault( - route_response_media_type or "application/json", {} - )["schema"] = response_schema - status_text: Optional[str] = status_code_ranges.get( - str(additional_status_code).upper() - ) or http.client.responses.get(int(additional_status_code)) - process_response.setdefault( - "description", status_text or "Additional Response" - ) - status_code_key = str(additional_status_code).upper() - if status_code_key == "DEFAULT": - status_code_key = "default" - process_response.pop("model", None) - operation.setdefault("responses", {})[ - status_code_key - ] = process_response status_code = str(route.status_code) operation.setdefault("responses", {}).setdefault(status_code, {})[ "description" @@ -251,7 +225,47 @@ def get_openapi_path( ).setdefault("content", {}).setdefault(route_response_media_type, {})[ "schema" ] = response_schema - + if route.responses: + operation_responses = operation.setdefault("responses", {}) + for ( + additional_status_code, + additional_response, + ) in route.responses.items(): + process_response = additional_response.copy() + process_response.pop("model", None) + status_code_key = str(additional_status_code).upper() + if status_code_key == "DEFAULT": + status_code_key = "default" + openapi_response = operation_responses.setdefault( + status_code_key, {} + ) + assert isinstance( + process_response, dict + ), "An additional response must be a dict" + field = route.response_fields.get(additional_status_code) + additional_field_schema: Optional[Dict[str, Any]] = None + if field: + additional_field_schema, _, _ = field_schema( + field, model_name_map=model_name_map, ref_prefix=REF_PREFIX + ) + media_type = route_response_media_type or "application/json" + additional_schema = ( + process_response.setdefault("content", {}) + .setdefault(media_type, {}) + .setdefault("schema", {}) + ) + deep_dict_update(additional_schema, additional_field_schema) + status_text: Optional[str] = status_code_ranges.get( + str(additional_status_code).upper() + ) or http.client.responses.get(int(additional_status_code)) + description = ( + process_response.get("description") + or openapi_response.get("description") + or status_text + or "Additional Response" + ) + deep_dict_update(openapi_response, process_response) + openapi_response["description"] = description http422 = str(HTTP_422_UNPROCESSABLE_ENTITY) if (all_route_params or route.body_field) and not any( [ diff --git a/fastapi/utils.py b/fastapi/utils.py index 89bf861a5..2f2cee3a1 100644 --- a/fastapi/utils.py +++ b/fastapi/utils.py @@ -172,3 +172,15 @@ def generate_operation_id_for_path(*, name: str, path: str, method: str) -> str: operation_id = re.sub("[^0-9a-zA-Z_]", "_", operation_id) operation_id = operation_id + "_" + method.lower() return operation_id + + +def deep_dict_update(main_dict: dict, update_dict: dict) -> None: + for key in update_dict: + if ( + key in main_dict + and isinstance(main_dict[key], dict) + and isinstance(update_dict[key], dict) + ): + deep_dict_update(main_dict[key], update_dict[key]) + else: + main_dict[key] = update_dict[key] diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial002.py b/tests/test_tutorial/test_additional_responses/test_tutorial002.py index 274c95663..83c77cef5 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial002.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial002.py @@ -15,7 +15,7 @@ openapi_schema = { "get": { "responses": { "200": { - "description": "Successful Response", + "description": "Return the JSON item or an image.", "content": { "image/png": {}, "application/json": { diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial003.py b/tests/test_tutorial/test_additional_responses/test_tutorial003.py index 6787f9d29..a1b0a7fb5 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial003.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial003.py @@ -20,7 +20,7 @@ openapi_schema = { }, }, "200": { - "description": "Successful Response", + "description": "Item requested by ID", "content": { "application/json": { "schema": {"$ref": "#/components/schemas/Item"},