Browse Source

Deep merge OpenAPI responses (#1577)

* override successful response

*  Add deep_dict_udpate

*  Merge additional responses with generated responses

* 🍱 Update docs screenshot

Co-authored-by: rkbeatss <[email protected]>
pull/1578/head
Sebastián Ramírez 5 years ago
committed by GitHub
parent
commit
181a32236a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. BIN
      docs/en/docs/img/tutorial/additional-responses/image01.png
  2. 70
      fastapi/openapi/utils.py
  3. 12
      fastapi/utils.py
  4. 2
      tests/test_tutorial/test_additional_responses/test_tutorial002.py
  5. 2
      tests/test_tutorial/test_additional_responses/test_tutorial003.py

BIN
docs/en/docs/img/tutorial/additional-responses/image01.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 71 KiB

70
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(
[

12
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]

2
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": {

2
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"},

Loading…
Cancel
Save