]> git.ipfire.org Git - thirdparty/fastapi/fastapi.git/commitdiff
✨ Deep merge OpenAPI responses (#1577)
authorSebastián Ramírez <tiangolo@gmail.com>
Mon, 15 Jun 2020 11:12:12 +0000 (13:12 +0200)
committerGitHub <noreply@github.com>
Mon, 15 Jun 2020 11:12:12 +0000 (13:12 +0200)
* override successful response

* ✨ Add deep_dict_udpate

* ✨ Merge additional responses with generated responses

* 🍱 Update docs screenshot

Co-authored-by: rkbeatss <rkaus053@uottawa.ca>
docs/en/docs/img/tutorial/additional-responses/image01.png
fastapi/openapi/utils.py
fastapi/utils.py
tests/test_tutorial/test_additional_responses/test_tutorial002.py
tests/test_tutorial/test_additional_responses/test_tutorial003.py

index b69b9c7df2c98ef6c472ee5e7339ed82d5219111..c348d07cbd305969b8dda66e2ab33b6060c30d02 100644 (file)
Binary files a/docs/en/docs/img/tutorial/additional-responses/image01.png and b/docs/en/docs/img/tutorial/additional-responses/image01.png differ
index 5a0c89a894cb30dd31c1bc52435ba59a67c164eb..ad1a9d83bb87f91154e1eceb6e9b715b3de0d672 100644 (file)
@@ -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(
                 [
index 89bf861a57cd6ec276d5c12610cc47f7a7817de1..2f2cee3a1ad600494d8a8dd9fffc922c9315150c 100644 (file)
@@ -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]
index 274c95663f2569f37414d7fd3add8984e3a72b2f..83c77cef5ab1f0786c71fc1b5095c39408ed263e 100644 (file)
@@ -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": {
index 6787f9d29afff61b259acb262a0caeb97a0f2c1f..a1b0a7fb52611306f0525e9f208da32a5ed22a80 100644 (file)
@@ -20,7 +20,7 @@ openapi_schema = {
                         },
                     },
                     "200": {
-                        "description": "Successful Response",
+                        "description": "Item requested by ID",
                         "content": {
                             "application/json": {
                                 "schema": {"$ref": "#/components/schemas/Item"},