mirror of https://github.com/tiangolo/fastapi.git
✨ 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 <rkaus053@uottawa.ca>
This commit is contained in:
parent
1f54a8e0a1
commit
181a32236a
Binary file not shown.
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 71 KiB |
|
|
@ -14,6 +14,7 @@ from fastapi.openapi.constants import (
|
||||||
from fastapi.openapi.models import OpenAPI
|
from fastapi.openapi.models import OpenAPI
|
||||||
from fastapi.params import Body, Param
|
from fastapi.params import Body, Param
|
||||||
from fastapi.utils import (
|
from fastapi.utils import (
|
||||||
|
deep_dict_update,
|
||||||
generate_operation_id_for_path,
|
generate_operation_id_for_path,
|
||||||
get_field_info,
|
get_field_info,
|
||||||
get_model_definitions,
|
get_model_definitions,
|
||||||
|
|
@ -201,33 +202,6 @@ def get_openapi_path(
|
||||||
)
|
)
|
||||||
callbacks[callback.name] = {callback.path: cb_path}
|
callbacks[callback.name] = {callback.path: cb_path}
|
||||||
operation["callbacks"] = callbacks
|
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)
|
status_code = str(route.status_code)
|
||||||
operation.setdefault("responses", {}).setdefault(status_code, {})[
|
operation.setdefault("responses", {}).setdefault(status_code, {})[
|
||||||
"description"
|
"description"
|
||||||
|
|
@ -251,7 +225,47 @@ def get_openapi_path(
|
||||||
).setdefault("content", {}).setdefault(route_response_media_type, {})[
|
).setdefault("content", {}).setdefault(route_response_media_type, {})[
|
||||||
"schema"
|
"schema"
|
||||||
] = response_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)
|
http422 = str(HTTP_422_UNPROCESSABLE_ENTITY)
|
||||||
if (all_route_params or route.body_field) and not any(
|
if (all_route_params or route.body_field) and not any(
|
||||||
[
|
[
|
||||||
|
|
|
||||||
|
|
@ -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 = re.sub("[^0-9a-zA-Z_]", "_", operation_id)
|
||||||
operation_id = operation_id + "_" + method.lower()
|
operation_id = operation_id + "_" + method.lower()
|
||||||
return operation_id
|
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]
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ openapi_schema = {
|
||||||
"get": {
|
"get": {
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Successful Response",
|
"description": "Return the JSON item or an image.",
|
||||||
"content": {
|
"content": {
|
||||||
"image/png": {},
|
"image/png": {},
|
||||||
"application/json": {
|
"application/json": {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ openapi_schema = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Successful Response",
|
"description": "Item requested by ID",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {"$ref": "#/components/schemas/Item"},
|
"schema": {"$ref": "#/components/schemas/Item"},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue