Browse Source

Merge 0226b66f38 into 313723494b

pull/13767/merge
JustKiddingCode 2 days ago
committed by GitHub
parent
commit
6077dbb9eb
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 38
      fastapi/applications.py
  2. 30
      fastapi/openapi/utils.py
  3. 47
      fastapi/routing.py
  4. 1
      pyproject.toml

38
fastapi/applications.py

@ -141,7 +141,7 @@ class FastAPI(Starlette):
),
] = None,
description: Annotated[
str,
Optional[str],
Doc(
'''
A description of the API. Supports Markdown (using
@ -1064,7 +1064,7 @@ class FastAPI(Starlette):
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
response_description: Optional[str] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
methods: Optional[List[str]] = None,
@ -1122,7 +1122,7 @@ class FastAPI(Starlette):
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
response_description: Optional[str] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
methods: Optional[List[str]] = None,
@ -1574,7 +1574,7 @@ class FastAPI(Starlette):
),
] = None,
response_description: Annotated[
str,
Optional[str],
Doc(
"""
The description for the default response.
@ -1582,7 +1582,7 @@ class FastAPI(Starlette):
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
"""
),
] = "Successful Response",
] = None,
responses: Annotated[
Optional[Dict[Union[int, str], Dict[str, Any]]],
Doc(
@ -1947,7 +1947,7 @@ class FastAPI(Starlette):
),
] = None,
response_description: Annotated[
str,
Optional[str],
Doc(
"""
The description for the default response.
@ -1955,7 +1955,7 @@ class FastAPI(Starlette):
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
"""
),
] = "Successful Response",
] = None,
responses: Annotated[
Optional[Dict[Union[int, str], Dict[str, Any]]],
Doc(
@ -2325,7 +2325,7 @@ class FastAPI(Starlette):
),
] = None,
response_description: Annotated[
str,
Optional[str],
Doc(
"""
The description for the default response.
@ -2333,7 +2333,7 @@ class FastAPI(Starlette):
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
"""
),
] = "Successful Response",
] = None,
responses: Annotated[
Optional[Dict[Union[int, str], Dict[str, Any]]],
Doc(
@ -2703,7 +2703,7 @@ class FastAPI(Starlette):
),
] = None,
response_description: Annotated[
str,
Optional[str],
Doc(
"""
The description for the default response.
@ -2711,7 +2711,7 @@ class FastAPI(Starlette):
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
"""
),
] = "Successful Response",
] = None,
responses: Annotated[
Optional[Dict[Union[int, str], Dict[str, Any]]],
Doc(
@ -3076,7 +3076,7 @@ class FastAPI(Starlette):
),
] = None,
response_description: Annotated[
str,
Optional[str],
Doc(
"""
The description for the default response.
@ -3084,7 +3084,7 @@ class FastAPI(Starlette):
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
"""
),
] = "Successful Response",
] = None,
responses: Annotated[
Optional[Dict[Union[int, str], Dict[str, Any]]],
Doc(
@ -3449,7 +3449,7 @@ class FastAPI(Starlette):
),
] = None,
response_description: Annotated[
str,
Optional[str],
Doc(
"""
The description for the default response.
@ -3457,7 +3457,7 @@ class FastAPI(Starlette):
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
"""
),
] = "Successful Response",
] = None,
responses: Annotated[
Optional[Dict[Union[int, str], Dict[str, Any]]],
Doc(
@ -3822,7 +3822,7 @@ class FastAPI(Starlette):
),
] = None,
response_description: Annotated[
str,
Optional[str],
Doc(
"""
The description for the default response.
@ -3830,7 +3830,7 @@ class FastAPI(Starlette):
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
"""
),
] = "Successful Response",
] = None,
responses: Annotated[
Optional[Dict[Union[int, str], Dict[str, Any]]],
Doc(
@ -4200,7 +4200,7 @@ class FastAPI(Starlette):
),
] = None,
response_description: Annotated[
str,
Optional[str],
Doc(
"""
The description for the default response.
@ -4208,7 +4208,7 @@ class FastAPI(Starlette):
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
"""
),
] = "Successful Response",
] = None,
responses: Annotated[
Optional[Dict[Union[int, str], Dict[str, Any]]],
Doc(

30
fastapi/openapi/utils.py

@ -100,6 +100,7 @@ def _get_openapi_operation_parameters(
field_mapping: Dict[
Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue
],
field_docstring: Optional[Dict[str, str]] = None,
separate_input_output_schemas: bool = True,
) -> List[Dict[str, Any]]:
parameters = []
@ -115,6 +116,7 @@ def _get_openapi_operation_parameters(
(ParamTypes.cookie, cookie_params),
]
default_convert_underscores = True
field_docstring = field_docstring or {}
if len(flat_dependant.header_params) == 1:
first_field = flat_dependant.header_params[0]
if lenient_issubclass(first_field.type_, BaseModel):
@ -155,6 +157,8 @@ def _get_openapi_operation_parameters(
}
if field_info.description:
parameter["description"] = field_info.description
elif param.name in field_docstring:
parameter["description"] = field_docstring[param.name]
openapi_examples = getattr(field_info, "openapi_examples", None)
example = getattr(field_info, "example", None)
if openapi_examples:
@ -232,8 +236,17 @@ def get_openapi_operation_metadata(
if route.tags:
operation["tags"] = route.tags
operation["summary"] = generate_operation_summary(route=route, method=method)
if route.parsed_docstring:
operation["description"] = "\n\n".join(
[i.value for i in route.parsed_docstring if i.kind == "text"]
)
operation["description"] = operation["description"].split("\f")[0].strip()
if not operation["description"]:
del operation["description"]
if route.description:
operation["description"] = route.description
operation["description"] = operation["description"].split("\f")[0].strip()
operation_id = route.operation_id or route.unique_id
if operation_id in operation_ids:
message = (
@ -273,6 +286,10 @@ def get_openapi_path(
assert current_response_class, "A response class is needed to generate OpenAPI"
route_response_media_type: Optional[str] = current_response_class.media_type
if route.include_in_schema:
args = [i.value for i in route.parsed_docstring if i.kind == "parameters"]
args_docstring_mapping = (
{a.name: a.description for a in args[0]} if len(args) == 1 else {}
)
for method in route.methods:
operation = get_openapi_operation_metadata(
route=route, method=method, operation_ids=operation_ids
@ -292,6 +309,7 @@ def get_openapi_path(
model_name_map=model_name_map,
field_mapping=field_mapping,
separate_input_output_schemas=separate_input_output_schemas,
field_docstring=args_docstring_mapping,
)
parameters.extend(operation_parameters)
if parameters:
@ -348,9 +366,19 @@ def get_openapi_path(
if status_code_param is not None:
if isinstance(status_code_param.default, int):
status_code = str(status_code_param.default)
response_description = ""
if route.parsed_docstring:
ret = [i.value for i in route.parsed_docstring if i.kind == "returns"]
response_description = (
",".join(x.description for x in ret[0])
if len(ret) == 1
else "Successful Response"
)
if route.response_description:
response_description = route.response_description
operation.setdefault("responses", {}).setdefault(status_code, {})[
"description"
] = route.response_description
] = response_description
if route_response_media_type and is_body_allowed_for_status_code(
route.status_code
):

47
fastapi/routing.py

@ -1,7 +1,6 @@
import asyncio
import dataclasses
import email.message
import inspect
import json
from contextlib import AsyncExitStack, asynccontextmanager
from enum import Enum, IntEnum
@ -57,6 +56,7 @@ from fastapi.utils import (
get_value_or_default,
is_body_allowed_for_status_code,
)
from griffe import Docstring
from pydantic import BaseModel
from starlette import routing
from starlette.concurrency import run_in_threadpool
@ -438,7 +438,7 @@ class APIRoute(routing.Route):
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
response_description: Optional[str] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
name: Optional[str] = None,
@ -529,10 +529,13 @@ class APIRoute(routing.Route):
self.response_field = None # type: ignore
self.secure_cloned_response_field = None
self.dependencies = list(dependencies or [])
self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "")
self.description = description
self.docstring = self.endpoint.__doc__ or ""
self.parsed_docstring = Docstring(
self.docstring, parser="google", parser_options={"warnings": False}
).parsed
# if a "form feed" character (page break) is found in the description text,
# truncate description text to the content preceding the first "form feed"
self.description = self.description.split("\f")[0].strip()
response_fields = {}
for additional_status_code, response in self.responses.items():
assert isinstance(response, dict), "An additional response must be a dict"
@ -890,7 +893,7 @@ class APIRouter(routing.Router):
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
response_description: Optional[str] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
methods: Optional[Union[Set[str], List[str]]] = None,
@ -971,7 +974,7 @@ class APIRouter(routing.Router):
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
response_description: Optional[str] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
methods: Optional[List[str]] = None,
@ -1481,7 +1484,7 @@ class APIRouter(routing.Router):
),
] = None,
response_description: Annotated[
str,
Optional[str],
Doc(
"""
The description for the default response.
@ -1489,7 +1492,7 @@ class APIRouter(routing.Router):
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
"""
),
] = "Successful Response",
] = None,
responses: Annotated[
Optional[Dict[Union[int, str], Dict[str, Any]]],
Doc(
@ -1858,7 +1861,7 @@ class APIRouter(routing.Router):
),
] = None,
response_description: Annotated[
str,
Optional[str],
Doc(
"""
The description for the default response.
@ -1866,7 +1869,7 @@ class APIRouter(routing.Router):
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
"""
),
] = "Successful Response",
] = None,
responses: Annotated[
Optional[Dict[Union[int, str], Dict[str, Any]]],
Doc(
@ -2240,7 +2243,7 @@ class APIRouter(routing.Router):
),
] = None,
response_description: Annotated[
str,
Optional[str],
Doc(
"""
The description for the default response.
@ -2248,7 +2251,7 @@ class APIRouter(routing.Router):
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
"""
),
] = "Successful Response",
] = None,
responses: Annotated[
Optional[Dict[Union[int, str], Dict[str, Any]]],
Doc(
@ -2622,7 +2625,7 @@ class APIRouter(routing.Router):
),
] = None,
response_description: Annotated[
str,
Optional[str],
Doc(
"""
The description for the default response.
@ -2630,7 +2633,7 @@ class APIRouter(routing.Router):
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
"""
),
] = "Successful Response",
] = None,
responses: Annotated[
Optional[Dict[Union[int, str], Dict[str, Any]]],
Doc(
@ -2999,7 +3002,7 @@ class APIRouter(routing.Router):
),
] = None,
response_description: Annotated[
str,
Optional[str],
Doc(
"""
The description for the default response.
@ -3007,7 +3010,7 @@ class APIRouter(routing.Router):
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
"""
),
] = "Successful Response",
] = None,
responses: Annotated[
Optional[Dict[Union[int, str], Dict[str, Any]]],
Doc(
@ -3376,7 +3379,7 @@ class APIRouter(routing.Router):
),
] = None,
response_description: Annotated[
str,
Optional[str],
Doc(
"""
The description for the default response.
@ -3384,7 +3387,7 @@ class APIRouter(routing.Router):
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
"""
),
] = "Successful Response",
] = None,
responses: Annotated[
Optional[Dict[Union[int, str], Dict[str, Any]]],
Doc(
@ -3758,7 +3761,7 @@ class APIRouter(routing.Router):
),
] = None,
response_description: Annotated[
str,
Optional[str],
Doc(
"""
The description for the default response.
@ -3766,7 +3769,7 @@ class APIRouter(routing.Router):
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
"""
),
] = "Successful Response",
] = None,
responses: Annotated[
Optional[Dict[Union[int, str], Dict[str, Any]]],
Doc(
@ -4140,7 +4143,7 @@ class APIRouter(routing.Router):
),
] = None,
response_description: Annotated[
str,
Optional[str],
Doc(
"""
The description for the default response.
@ -4148,7 +4151,7 @@ class APIRouter(routing.Router):
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
"""
),
] = "Successful Response",
] = None,
responses: Annotated[
Optional[Dict[Union[int, str], Dict[str, Any]]],
Doc(

1
pyproject.toml

@ -46,6 +46,7 @@ dependencies = [
"starlette>=0.40.0,<0.48.0",
"pydantic>=1.7.4,!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0",
"typing-extensions>=4.8.0",
"griffe>=1.4.0",
]
[project.urls]

Loading…
Cancel
Save