)
-def get_flat_dependant(
- dependant: Dependant,
- *,
- skip_repeats: bool = False,
- visited: list[DependencyCacheKey] | None = None,
- parent_oauth_scopes: list[str] | None = None,
- _uses_scopes_cache: _UsesScopesCache | None = None,
-) -> Dependant:
- track_visited = (
- skip_repeats or visited is not None or _uses_scopes_cache is not None
- )
- if track_visited:
- if visited is None:
- visited = []
- if _uses_scopes_cache is None:
- _uses_scopes_cache = {}
- visited.append(
- _get_cache_key(
- dependant=dependant,
- uses_scopes_cache=_uses_scopes_cache,
- )
- )
- use_parent_oauth_scopes = (parent_oauth_scopes or []) + (
- _get_oauth_scopes(dependant=dependant)
- )
-
- flat_dependant = Dependant(
- path_params=dependant.path_params.copy(),
- query_params=dependant.query_params.copy(),
- header_params=dependant.header_params.copy(),
- cookie_params=dependant.cookie_params.copy(),
- body_params=dependant.body_params.copy(),
- name=dependant.name,
- call=dependant.call,
- request_param_name=dependant.request_param_name,
- websocket_param_name=dependant.websocket_param_name,
- http_connection_param_name=dependant.http_connection_param_name,
- response_param_name=dependant.response_param_name,
- background_tasks_param_name=dependant.background_tasks_param_name,
- security_scopes_param_name=dependant.security_scopes_param_name,
- own_oauth_scopes=dependant.own_oauth_scopes,
- parent_oauth_scopes=use_parent_oauth_scopes,
- use_cache=dependant.use_cache,
- path=dependant.path,
- scope=dependant.scope,
- )
- for sub_dependant in dependant.dependencies:
- if skip_repeats:
- assert visited is not None
- if (
- _get_cache_key(
- dependant=sub_dependant,
- uses_scopes_cache=_uses_scopes_cache,
- )
- in visited
- ):
- continue
- flat_sub = get_flat_dependant(
- sub_dependant,
- skip_repeats=skip_repeats,
- visited=visited,
- parent_oauth_scopes=_get_oauth_scopes(dependant=flat_dependant),
- _uses_scopes_cache=_uses_scopes_cache,
- )
- flat_dependant.dependencies.append(flat_sub)
- flat_dependant.path_params.extend(flat_sub.path_params)
- flat_dependant.query_params.extend(flat_sub.query_params)
- flat_dependant.header_params.extend(flat_sub.header_params)
- flat_dependant.cookie_params.extend(flat_sub.cookie_params)
- flat_dependant.body_params.extend(flat_sub.body_params)
- flat_dependant.dependencies.extend(flat_sub.dependencies)
-
- return flat_dependant
-
-
def _get_flat_body_params(dependant: Dependant) -> list[ModelField]:
body_params: list[ModelField] = []
dependants = [dependant]
import inspect
import warnings
from collections.abc import Sequence
+from dataclasses import dataclass, field
from typing import Any, Literal, cast
from fastapi import routing
from fastapi.datastructures import DefaultPlaceholder, _Unset
from fastapi.dependencies.models import (
Dependant,
+ _get_cache_key,
_get_oauth_scopes,
- _get_security_dependencies,
_get_security_scheme,
+ _is_security_scheme,
+ _UsesScopesCache,
)
from fastapi.dependencies.utils import (
_get_flat_fields_from_params,
- get_flat_dependant,
get_flat_params,
get_validation_alias,
)
from fastapi.params import Body, ParamTypes
from fastapi.responses import Response
from fastapi.sse import _SSE_EVENT_SCHEMA
-from fastapi.types import ModelNameMap
+from fastapi.types import DependencyCacheKey, ModelNameMap
from fastapi.utils import (
deep_dict_update,
generate_operation_id_for_path,
}
-def get_openapi_security_definitions(
- flat_dependant: Dependant,
+@dataclass
+class _OpenAPIDependencyData:
+ path_params: list[ModelField] = field(default_factory=list)
+ query_params: list[ModelField] = field(default_factory=list)
+ header_params: list[ModelField] = field(default_factory=list)
+ cookie_params: list[ModelField] = field(default_factory=list)
+ security_dependencies: list[tuple[Dependant, list[str]]] = field(
+ default_factory=list
+ )
+
+
+def _get_openapi_dependency_data(dependant: Dependant) -> _OpenAPIDependencyData:
+ dependency_data = _OpenAPIDependencyData()
+ visited: list[DependencyCacheKey] = []
+ uses_scopes_cache: _UsesScopesCache = {}
+ dependants: list[tuple[Dependant, list[str], bool]] = [(dependant, [], True)]
+ while dependants:
+ current_dependant, parent_oauth_scopes, is_root = dependants.pop()
+ cache_key = _get_cache_key(
+ dependant=current_dependant,
+ uses_scopes_cache=uses_scopes_cache,
+ )
+ if cache_key in visited:
+ continue
+ visited.append(cache_key)
+ dependency_data.path_params.extend(current_dependant.path_params)
+ dependency_data.query_params.extend(current_dependant.query_params)
+ dependency_data.header_params.extend(current_dependant.header_params)
+ dependency_data.cookie_params.extend(current_dependant.cookie_params)
+ oauth_scopes = parent_oauth_scopes.copy()
+ for scope in _get_oauth_scopes(dependant=current_dependant):
+ if scope not in oauth_scopes:
+ oauth_scopes.append(scope)
+ if not is_root and _is_security_scheme(dependant=current_dependant):
+ dependency_data.security_dependencies.append(
+ (current_dependant, oauth_scopes)
+ )
+ dependants.extend(
+ (sub_dependant, oauth_scopes, False)
+ for sub_dependant in reversed(current_dependant.dependencies)
+ )
+ return dependency_data
+
+
+def _get_openapi_security_definitions(
+ security_dependencies: list[tuple[Dependant, list[str]]],
) -> tuple[dict[str, Any], list[dict[str, Any]]]:
security_definitions = {}
# Use a dict to merge scopes for same security scheme
operation_security_dict: dict[str, list[str]] = {}
- for security_dependency in _get_security_dependencies(dependant=flat_dependant):
+ for security_dependency, oauth_scopes in security_dependencies:
security_scheme = _get_security_scheme(dependant=security_dependency)
security_definition = jsonable_encoder(
security_scheme.model,
# Merge scopes for the same security scheme
if security_name not in operation_security_dict:
operation_security_dict[security_name] = []
- for scope in _get_oauth_scopes(dependant=security_dependency):
+ for scope in oauth_scopes:
if scope not in operation_security_dict[security_name]:
operation_security_dict[security_name].append(scope)
operation_security = [
def _get_openapi_operation_parameters(
*,
- flat_dependant: Dependant,
+ dependency_data: _OpenAPIDependencyData,
model_name_map: ModelNameMap,
field_mapping: dict[
tuple[ModelField, Literal["validation", "serialization"]], dict[str, Any]
separate_input_output_schemas: bool = True,
) -> list[dict[str, Any]]:
parameters = []
- path_params = _get_flat_fields_from_params(flat_dependant.path_params)
- query_params = _get_flat_fields_from_params(flat_dependant.query_params)
- header_params = _get_flat_fields_from_params(flat_dependant.header_params)
- cookie_params = _get_flat_fields_from_params(flat_dependant.cookie_params)
+ path_params = _get_flat_fields_from_params(dependency_data.path_params)
+ query_params = _get_flat_fields_from_params(dependency_data.query_params)
+ header_params = _get_flat_fields_from_params(dependency_data.header_params)
+ cookie_params = _get_flat_fields_from_params(dependency_data.cookie_params)
parameter_groups = [
(ParamTypes.path, path_params),
(ParamTypes.query, query_params),
(ParamTypes.cookie, cookie_params),
]
default_convert_underscores = True
- if len(flat_dependant.header_params) == 1:
- first_field = flat_dependant.header_params[0]
+ if len(dependency_data.header_params) == 1:
+ first_field = dependency_data.header_params[0]
if lenient_issubclass(first_field.field_info.annotation, BaseModel):
default_convert_underscores = getattr(
first_field.field_info, "convert_underscores", True
assert current_response_class, "A response class is needed to generate OpenAPI"
route_response_media_type: str | None = current_response_class.media_type
if route.include_in_schema:
- flat_dependant = get_flat_dependant(route.dependant, skip_repeats=True)
+ dependency_data = _get_openapi_dependency_data(route.dependant)
all_route_params = [
field
for fields in (
- flat_dependant.path_params,
- flat_dependant.query_params,
- flat_dependant.header_params,
- flat_dependant.cookie_params,
+ dependency_data.path_params,
+ dependency_data.query_params,
+ dependency_data.header_params,
+ dependency_data.cookie_params,
)
for field in _get_flat_fields_from_params(fields)
]
route=route, method=method, operation_ids=operation_ids
)
parameters: list[dict[str, Any]] = []
- security_definitions, operation_security = get_openapi_security_definitions(
- flat_dependant=flat_dependant
+ security_definitions, operation_security = (
+ _get_openapi_security_definitions(
+ security_dependencies=dependency_data.security_dependencies
+ )
)
if operation_security:
operation.setdefault("security", []).extend(operation_security)
if security_definitions:
security_schemes.update(security_definitions)
operation_parameters = _get_openapi_operation_parameters(
- flat_dependant=flat_dependant,
+ dependency_data=dependency_data,
model_name_map=model_name_map,
field_mapping=field_mapping,
separate_input_output_schemas=separate_input_output_schemas,