Add support for OpenAPI 3.1.0 (#9770)

*  Update OpenAPI models for JSON Schema 2020-12 and OpenAPI 3.1.0

*  Add support for summary and webhooks

*  Update JSON Schema for UploadFiles

* ️ Revert making paths optional, to ensure always correctness

* ️ Keep UploadFile as format: binary for compatibility with the rest of Pydantic bytes fields in v1

*  Update version of OpenAPI generated to 3.1.0

*  Update the version of Swagger UI

* 📝 Update docs about extending OpenAPI

* 📝 Update docs and links to refer to OpenAPI 3.1.0

*  Update logic for handling webhooks

* ♻️ Update parameter functions and classes, deprecate example and make examples the main field

*  Update tests for OpenAPI 3.1.0

* 📝 Update examples for OpenAPI metadata

*  Add and update tests for OpenAPI metadata

* 📝 Add source example for webhooks

* 📝 Update docs for metadata

* 📝 Update docs for Schema extra

* 📝 Add docs for webhooks

* 🔧 Add webhooks docs to MkDocs

*  Update tests for extending OpenAPI

*  Add tests for webhooks

* ♻️ Refactor generation of OpenAPI and JSON Schema with params

* 📝 Update source examples for field examples

*  Update tests for examples

*  Make sure the minimum version of typing-extensions installed has deprecated() (already a dependency of Pydantic)

* ✏️ Fix typo in Webhooks example code

* 🔥 Remove commented out code of removed nullable field

* 🗑️ Add deprecation warnings for example argument

*  Update tests to check for deprecation warnings

*  Add test for webhooks with security schemes, for coverage

* 🍱 Update image for metadata, with new summary

* 🍱 Add docs image for Webhooks

* 📝 Update docs for webhooks, add docs UI image
This commit is contained in:
Sebastián Ramírez
2023-06-30 20:25:16 +02:00
committed by GitHub
parent 02fc9e8a63
commit 7dad5a820b
335 changed files with 1533 additions and 891 deletions

View File

@@ -106,9 +106,7 @@ def get_openapi_operation_parameters(
}
if field_info.description:
parameter["description"] = field_info.description
if field_info.examples:
parameter["examples"] = jsonable_encoder(field_info.examples)
elif field_info.example != Undefined:
if field_info.example != Undefined:
parameter["example"] = jsonable_encoder(field_info.example)
if field_info.deprecated:
parameter["deprecated"] = field_info.deprecated
@@ -134,9 +132,7 @@ def get_openapi_operation_request_body(
if required:
request_body_oai["required"] = required
request_media_content: Dict[str, Any] = {"schema": body_schema}
if field_info.examples:
request_media_content["examples"] = jsonable_encoder(field_info.examples)
elif field_info.example != Undefined:
if field_info.example != Undefined:
request_media_content["example"] = jsonable_encoder(field_info.example)
request_body_oai["content"] = {request_media_type: request_media_content}
return request_body_oai
@@ -392,9 +388,11 @@ def get_openapi(
*,
title: str,
version: str,
openapi_version: str = "3.0.2",
openapi_version: str = "3.1.0",
summary: Optional[str] = None,
description: Optional[str] = None,
routes: Sequence[BaseRoute],
webhooks: Optional[Sequence[BaseRoute]] = None,
tags: Optional[List[Dict[str, Any]]] = None,
servers: Optional[List[Dict[str, Union[str, Any]]]] = None,
terms_of_service: Optional[str] = None,
@@ -402,6 +400,8 @@ def get_openapi(
license_info: Optional[Dict[str, Union[str, Any]]] = None,
) -> Dict[str, Any]:
info: Dict[str, Any] = {"title": title, "version": version}
if summary:
info["summary"] = summary
if description:
info["description"] = description
if terms_of_service:
@@ -415,13 +415,14 @@ def get_openapi(
output["servers"] = servers
components: Dict[str, Dict[str, Any]] = {}
paths: Dict[str, Dict[str, Any]] = {}
webhook_paths: Dict[str, Dict[str, Any]] = {}
operation_ids: Set[str] = set()
flat_models = get_flat_models_from_routes(routes)
flat_models = get_flat_models_from_routes(list(routes or []) + list(webhooks or []))
model_name_map = get_model_name_map(flat_models)
definitions = get_model_definitions(
flat_models=flat_models, model_name_map=model_name_map
)
for route in routes:
for route in routes or []:
if isinstance(route, routing.APIRoute):
result = get_openapi_path(
route=route, model_name_map=model_name_map, operation_ids=operation_ids
@@ -436,11 +437,30 @@ def get_openapi(
)
if path_definitions:
definitions.update(path_definitions)
for webhook in webhooks or []:
if isinstance(webhook, routing.APIRoute):
result = get_openapi_path(
route=webhook,
model_name_map=model_name_map,
operation_ids=operation_ids,
)
if result:
path, security_schemes, path_definitions = result
if path:
webhook_paths.setdefault(webhook.path_format, {}).update(path)
if security_schemes:
components.setdefault("securitySchemes", {}).update(
security_schemes
)
if path_definitions:
definitions.update(path_definitions)
if definitions:
components["schemas"] = {k: definitions[k] for k in sorted(definitions)}
if components:
output["components"] = components
output["paths"] = paths
if webhook_paths:
output["webhooks"] = webhook_paths
if tags:
output["tags"] = tags
return jsonable_encoder(OpenAPI(**output), by_alias=True, exclude_none=True) # type: ignore