Ensure functions have docstrings for documentation
def ensure_multipart_is_installed() -> None:
1import dataclasses2import inspect3import sys4from collections.abc import (5 AsyncGenerator,6 AsyncIterable,7 AsyncIterator,8 Callable,9 Generator,10 Iterable,11 Iterator,12 Mapping,13 Sequence,14)15from contextlib import AsyncExitStack, contextmanager16from copy import copy, deepcopy17from dataclasses import dataclass18from typing import (19 Annotated,20 Any,21 ForwardRef,22 Literal,23 Union,24 cast,25 get_args,26 get_origin,27)2829from fastapi import params30from fastapi._compat import (31 ModelField,32 RequiredParam,33 Undefined,34 copy_field_info,35 create_body_model,36 evaluate_forwardref,37 field_annotation_is_scalar,38 field_annotation_is_scalar_sequence,39 field_annotation_is_sequence,40 get_cached_model_fields,41 get_missing_field_error,42 is_bytes_or_nonable_bytes_annotation,43 is_bytes_sequence_annotation,44 is_scalar_field,45 is_uploadfile_or_nonable_uploadfile_annotation,46 is_uploadfile_sequence_annotation,47 lenient_issubclass,48 sequence_types,49 serialize_sequence_value,50 value_is_sequence,51)52from fastapi.background import BackgroundTasks53from fastapi.concurrency import (54 asynccontextmanager,55 contextmanager_in_threadpool,56)57from fastapi.dependencies.models import (58 Dependant,59 _get_cache_key,60 _get_computed_scope,61 _get_oauth_scopes,62 _is_async_gen_callable,63 _is_coroutine_callable,64 _is_gen_callable,65 _UsesScopesCache,66)67from fastapi.exceptions import DependencyScopeError68from fastapi.logger import logger69from fastapi.security.oauth2 import SecurityScopes70from fastapi.types import DependencyCacheKey71from fastapi.utils import create_model_field, get_path_param_names72from pydantic import BaseModel, Json73from pydantic.fields import FieldInfo74from starlette.background import BackgroundTasks as StarletteBackgroundTasks75from starlette.concurrency import run_in_threadpool76from starlette.datastructures import (77 FormData,78 Headers,79 ImmutableMultiDict,80 QueryParams,81 UploadFile,82)83from starlette.requests import HTTPConnection, Request84from starlette.responses import Response85from starlette.websockets import WebSocket86from typing_inspection.typing_objects import is_typealiastype8788multipart_not_installed_error = (89 'Form data requires "python-multipart" to be installed. \n'90 'You can install "python-multipart" with: \n\n'91 "pip install python-multipart\n"92)93multipart_incorrect_install_error = (94 'Form data requires "python-multipart" to be installed. '95 'It seems you installed "multipart" instead. \n'96 'You can remove "multipart" with: \n\n'97 "pip uninstall multipart\n\n"98 'And then install "python-multipart" with: \n\n'99 "pip install python-multipart\n"100)101102103def ensure_multipart_is_installed() -> None:104 try:105 from python_multipart import __version__106107 # Import an attribute that can be mocked/deleted in testing108 assert __version__ > "0.0.12"109 except (ImportError, AssertionError):110 try:111 # __version__ is available in both multiparts, and can be mocked112 from multipart import ( # type: ignore[no-redef,import-untyped]113 __version__,114 )115116 assert __version__117 try:118 # parse_options_header is only available in the right multipart119 from multipart.multipart import ( # type: ignore[import-untyped]120 parse_options_header,121 )122123 assert parse_options_header124 except ImportError:125 logger.error(multipart_incorrect_install_error)126 raise RuntimeError(multipart_incorrect_install_error) from None127 except ImportError:128 logger.error(multipart_not_installed_error)129 raise RuntimeError(multipart_not_installed_error) from None130131132def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant:133 assert callable(depends.dependency), (134 "A parameter-less dependency must have a callable dependency"135 )136 own_oauth_scopes: list[str] = []137 if isinstance(depends, params.Security) and depends.scopes:138 own_oauth_scopes.extend(depends.scopes)139 return get_dependant(140 path=path,141 call=depends.dependency,142 scope=depends.scope,143 own_oauth_scopes=own_oauth_scopes,144 )145146147def _get_flat_body_params(dependant: Dependant) -> list[ModelField]:148 body_params: list[ModelField] = []149 dependants = [dependant]150 while dependants:151 current_dependant = dependants.pop()152 body_params.extend(current_dependant.body_params)153 dependants.extend(reversed(current_dependant.dependencies))154 return body_params155156157def _get_flat_fields_from_params(fields: list[ModelField]) -> list[ModelField]:158 if not fields:159 return fields160 first_field = fields[0]161 if len(fields) == 1 and lenient_issubclass(162 first_field.field_info.annotation, BaseModel163 ):164 fields_to_extract = get_cached_model_fields(first_field.field_info.annotation)165 return fields_to_extract166 return fields167168169def get_flat_params(dependant: Dependant) -> list[ModelField]:170 path_params: list[ModelField] = []171 query_params: list[ModelField] = []172 header_params: list[ModelField] = []173 cookie_params: list[ModelField] = []174 visited: list[DependencyCacheKey] = []175 uses_scopes_cache: _UsesScopesCache = {}176 dependants = [dependant]177 while dependants:178 current_dependant = dependants.pop()179 cache_key = _get_cache_key(180 dependant=current_dependant,181 uses_scopes_cache=uses_scopes_cache,182 )183 if cache_key in visited:184 continue185 visited.append(cache_key)186 path_params.extend(current_dependant.path_params)187 query_params.extend(current_dependant.query_params)188 header_params.extend(current_dependant.header_params)189 cookie_params.extend(current_dependant.cookie_params)190 dependants.extend(reversed(current_dependant.dependencies))191 path_params = _get_flat_fields_from_params(path_params)192 query_params = _get_flat_fields_from_params(query_params)193 header_params = _get_flat_fields_from_params(header_params)194 cookie_params = _get_flat_fields_from_params(cookie_params)195 return path_params + query_params + header_params + cookie_params196197198def _get_signature(call: Callable[..., Any]) -> inspect.Signature:199 try:200 signature = inspect.signature(call, eval_str=True)201 except NameError:202 # Handle type annotations with if TYPE_CHECKING, not used by FastAPI203 # e.g. dependency return types204 if sys.version_info >= (3, 14):205 from annotationlib import Format206207 signature = inspect.signature(call, annotation_format=Format.FORWARDREF)208 else:209 signature = inspect.signature(call)210 return signature211212213def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:214 signature = _get_signature(call)215 unwrapped = inspect.unwrap(call)216 globalns = getattr(unwrapped, "__globals__", {})217 typed_params = [218 inspect.Parameter(219 name=param.name,220 kind=param.kind,221 default=param.default,222 annotation=get_typed_annotation(param.annotation, globalns),223 )224 for param in signature.parameters.values()225 ]226 typed_signature = inspect.Signature(typed_params)227 return typed_signature228229230def get_typed_annotation(annotation: Any, globalns: dict[str, Any]) -> Any:231 if isinstance(annotation, str):232 annotation = ForwardRef(annotation)233 annotation = evaluate_forwardref(annotation, globalns, globalns)234 if annotation is type(None):235 return None236 return annotation237238239def get_typed_return_annotation(call: Callable[..., Any]) -> Any:240 signature = _get_signature(call)241 unwrapped = inspect.unwrap(call)242 annotation = signature.return_annotation243244 if annotation is inspect.Signature.empty:245 return None246247 globalns = getattr(unwrapped, "__globals__", {})248 return get_typed_annotation(annotation, globalns)249250251_STREAM_ORIGINS = {252 AsyncIterable,253 AsyncIterator,254 AsyncGenerator,255 Iterable,256 Iterator,257 Generator,258}259260261def get_stream_item_type(annotation: Any) -> Any | None:262 origin = get_origin(annotation)263 if origin is not None and origin in _STREAM_ORIGINS:264 type_args = get_args(annotation)265 if type_args:266 return type_args[0]267 return Any268 return None269270271def get_dependant(272 *,273 path: str,274 call: Callable[..., Any],275 name: str | None = None,276 own_oauth_scopes: list[str] | None = None,277 parent_oauth_scopes: list[str] | None = None,278 use_cache: bool = True,279 scope: Literal["function", "request"] | None = None,280) -> Dependant:281 dependant = Dependant(282 call=call,283 name=name,284 path=path,285 use_cache=use_cache,286 scope=scope,287 own_oauth_scopes=own_oauth_scopes,288 parent_oauth_scopes=parent_oauth_scopes,289 )290 current_scopes = (parent_oauth_scopes or []) + (own_oauth_scopes or [])291 path_param_names = get_path_param_names(path)292 endpoint_signature = get_typed_signature(call)293 signature_params = endpoint_signature.parameters294 for param_name, param in signature_params.items():295 is_path_param = param_name in path_param_names296 param_details = analyze_param(297 param_name=param_name,298 annotation=param.annotation,299 value=param.default,300 is_path_param=is_path_param,301 )302 if param_details.depends is not None:303 assert param_details.depends.dependency304 if (305 (306 _is_gen_callable(dependant.call)307 or _is_async_gen_callable(dependant.call)308 )309 and _get_computed_scope(dependant=dependant) == "request"310 and param_details.depends.scope == "function"311 ):312 assert dependant.call313 call_name = getattr(dependant.call, "__name__", "<unnamed_callable>")314 raise DependencyScopeError(315 f'The dependency "{call_name}" has a scope of '316 '"request", it cannot depend on dependencies with scope "function".'317 )318 sub_own_oauth_scopes: list[str] = []319 if isinstance(param_details.depends, params.Security):320 if param_details.depends.scopes:321 sub_own_oauth_scopes = list(param_details.depends.scopes)322 sub_dependant = get_dependant(323 path=path,324 call=param_details.depends.dependency,325 name=param_name,326 own_oauth_scopes=sub_own_oauth_scopes,327 parent_oauth_scopes=current_scopes,328 use_cache=param_details.depends.use_cache,329 scope=param_details.depends.scope,330 )331 dependant.dependencies.append(sub_dependant)332 continue333 if add_non_field_param_to_dependency(334 param_name=param_name,335 type_annotation=param_details.type_annotation,336 dependant=dependant,337 ):338 assert param_details.field is None, (339 f"Cannot specify multiple FastAPI annotations for {param_name!r}"340 )341 continue342 assert param_details.field is not None343 if isinstance(param_details.field.field_info, params.Body):344 dependant.body_params.append(param_details.field)345 else:346 add_param_to_fields(field=param_details.field, dependant=dependant)347 return dependant348349350def add_non_field_param_to_dependency(351 *, param_name: str, type_annotation: Any, dependant: Dependant352) -> bool | None:353 if lenient_issubclass(type_annotation, Request):354 dependant.request_param_name = param_name355 return True356 elif lenient_issubclass(type_annotation, WebSocket):357 dependant.websocket_param_name = param_name358 return True359 elif lenient_issubclass(type_annotation, HTTPConnection):360 dependant.http_connection_param_name = param_name361 return True362 elif lenient_issubclass(type_annotation, Response):363 dependant.response_param_name = param_name364 return True365 elif lenient_issubclass(type_annotation, StarletteBackgroundTasks):366 dependant.background_tasks_param_name = param_name367 return True368 elif lenient_issubclass(type_annotation, SecurityScopes):369 dependant.security_scopes_param_name = param_name370 return True371 return None372373374@dataclass375class ParamDetails:376 type_annotation: Any377 depends: params.Depends | None378 field: ModelField | None379380381def analyze_param(382 *,383 param_name: str,384 annotation: Any,385 value: Any,386 is_path_param: bool,387) -> ParamDetails:388 field_info = None389 depends = None390 type_annotation: Any = Any391 use_annotation: Any = Any392 if is_typealiastype(annotation):393 # unpack in case PEP 695 type syntax is used394 annotation = annotation.__value__395 if annotation is not inspect.Signature.empty:396 use_annotation = annotation397 type_annotation = annotation398 # Extract Annotated info399 if get_origin(use_annotation) is Annotated:400 annotated_args = get_args(annotation)401 type_annotation = annotated_args[0]402 fastapi_annotations = [403 arg404 for arg in annotated_args[1:]405 if isinstance(arg, (FieldInfo, params.Depends))406 ]407 fastapi_specific_annotations = [408 arg409 for arg in fastapi_annotations410 if isinstance(411 arg,412 (413 params.Param,414 params.Body,415 params.Depends,416 ),417 )418 ]419 if fastapi_specific_annotations:420 fastapi_annotation: FieldInfo | params.Depends | None = (421 fastapi_specific_annotations[-1]422 )423 else:424 fastapi_annotation = None425 # Set default for Annotated FieldInfo426 if isinstance(fastapi_annotation, FieldInfo):427 # Copy `field_info` because we mutate `field_info.default` below.428 field_info = copy_field_info(429 field_info=fastapi_annotation,430 annotation=use_annotation,431 )432 assert (433 field_info.default == Undefined or field_info.default == RequiredParam434 ), (435 f"`{field_info.__class__.__name__}` default value cannot be set in"436 f" `Annotated` for {param_name!r}. Set the default value with `=` instead."437 )438 if value is not inspect.Signature.empty:439 assert not is_path_param, "Path parameters cannot have default values"440 field_info.default = value441 else:442 field_info.default = RequiredParam443 # Get Annotated Depends444 elif isinstance(fastapi_annotation, params.Depends):445 depends = fastapi_annotation446 # Get Depends from default value447 if isinstance(value, params.Depends):448 assert depends is None, (449 "Cannot specify `Depends` in `Annotated` and default value"450 f" together for {param_name!r}"451 )452 assert field_info is None, (453 "Cannot specify a FastAPI annotation in `Annotated` and `Depends` as a"454 f" default value together for {param_name!r}"455 )456 depends = value457 # Get FieldInfo from default value458 elif isinstance(value, FieldInfo):459 assert field_info is None, (460 "Cannot specify FastAPI annotations in `Annotated` and default value"461 f" together for {param_name!r}"462 )463 field_info = value464 if isinstance(field_info, FieldInfo):465 field_info.annotation = type_annotation466467 # Get Depends from type annotation468 if depends is not None and depends.dependency is None:469 # Copy `depends` before mutating it470 depends = copy(depends)471 depends = dataclasses.replace(depends, dependency=type_annotation)472473 # Handle non-param type annotations like Request474 # Only apply special handling when there's no explicit Depends - if there's a Depends,475 # the dependency will be called and its return value used instead of the special injection476 if depends is None and lenient_issubclass(477 type_annotation,478 (479 Request,480 WebSocket,481 HTTPConnection,482 Response,483 StarletteBackgroundTasks,484 SecurityScopes,485 ),486 ):487 assert field_info is None, (488 f"Cannot specify FastAPI annotation for type {type_annotation!r}"489 )490 # Handle default assignations, neither field_info nor depends was not found in Annotated nor default value491 elif field_info is None and depends is None:492 default_value = value if value is not inspect.Signature.empty else RequiredParam493 if is_path_param:494 # We might check here that `default_value is RequiredParam`, but the fact is that the same495 # parameter might sometimes be a path parameter and sometimes not. See496 # `tests/test_infer_param_optionality.py` for an example.497 field_info = params.Path(annotation=use_annotation)498 elif is_uploadfile_or_nonable_uploadfile_annotation(499 type_annotation500 ) or is_uploadfile_sequence_annotation(type_annotation):501 field_info = params.File(annotation=use_annotation, default=default_value)502 elif not field_annotation_is_scalar(annotation=type_annotation):503 field_info = params.Body(annotation=use_annotation, default=default_value)504 else:505 field_info = params.Query(annotation=use_annotation, default=default_value)506507 field = None508 # It's a field_info, not a dependency509 if field_info is not None:510 # Handle field_info.in_511 if is_path_param:512 assert isinstance(field_info, params.Path), (513 f"Cannot use `{field_info.__class__.__name__}` for path param"514 f" {param_name!r}"515 )516 elif (517 isinstance(field_info, params.Param)518 and getattr(field_info, "in_", None) is None519 ):520 field_info.in_ = params.ParamTypes.query521 use_annotation_from_field_info = use_annotation522 if isinstance(field_info, params.Form):523 ensure_multipart_is_installed()524 if not field_info.alias and getattr(field_info, "convert_underscores", None):525 alias = param_name.replace("_", "-")526 else:527 alias = field_info.alias or param_name528 field_info.alias = alias529 field = create_model_field(530 name=param_name,531 type_=use_annotation_from_field_info,532 default=field_info.default,533 alias=alias,534 field_info=field_info,535 )536 if is_path_param:537 assert is_scalar_field(field=field), (538 "Path params must be of one of the supported types"539 )540 elif isinstance(field_info, params.Query):541 assert (542 is_scalar_field(field)543 or field_annotation_is_scalar_sequence(field.field_info.annotation)544 or lenient_issubclass(field.field_info.annotation, BaseModel)545 ), f"Query parameter {param_name!r} must be one of the supported types"546547 return ParamDetails(type_annotation=type_annotation, depends=depends, field=field)548549550def add_param_to_fields(*, field: ModelField, dependant: Dependant) -> None:551 field_info = field.field_info552 field_info_in = getattr(field_info, "in_", None)553 if field_info_in == params.ParamTypes.path:554 dependant.path_params.append(field)555 elif field_info_in == params.ParamTypes.query:556 dependant.query_params.append(field)557 elif field_info_in == params.ParamTypes.header:558 dependant.header_params.append(field)559 else:560 assert field_info_in == params.ParamTypes.cookie, (561 f"non-body parameters must be in path, query, header or cookie: {field.name}"562 )563 dependant.cookie_params.append(field)564565566async def _solve_generator(567 *, dependant: Dependant, stack: AsyncExitStack, sub_values: dict[str, Any]568) -> Any:569 assert dependant.call570 if _is_async_gen_callable(dependant.call):571 cm = asynccontextmanager(dependant.call)(**sub_values)572 elif _is_gen_callable(dependant.call):573 cm = contextmanager_in_threadpool(contextmanager(dependant.call)(**sub_values))574 return await stack.enter_async_context(cm)575576577@dataclass578class SolvedDependency:579 values: dict[str, Any]580 errors: list[Any]581 background_tasks: StarletteBackgroundTasks | None582 response: Response583 dependency_cache: dict[DependencyCacheKey, Any]584585586async def solve_dependencies(587 *,588 request: Request | WebSocket,589 dependant: Dependant,590 body: dict[str, Any] | FormData | bytes | None = None,591 background_tasks: StarletteBackgroundTasks | None = None,592 response: Response | None = None,593 dependency_overrides_provider: Any | None = None,594 dependency_cache: dict[DependencyCacheKey, Any] | None = None,595 # TODO: remove this parameter later, no longer used, not removing it yet as some596 # people might be monkey patching this function (although that's not supported)597 async_exit_stack: AsyncExitStack,598 embed_body_fields: bool,599 _uses_scopes_cache: _UsesScopesCache | None = None,600) -> SolvedDependency:601 request_astack = request.scope.get("fastapi_inner_astack")602 assert isinstance(request_astack, AsyncExitStack), (603 "fastapi_inner_astack not found in request scope"604 )605 function_astack = request.scope.get("fastapi_function_astack")606 assert isinstance(function_astack, AsyncExitStack), (607 "fastapi_function_astack not found in request scope"608 )609 values: dict[str, Any] = {}610 errors: list[Any] = []611 if response is None:612 response = Response()613 del response.headers["content-length"]614 response.status_code = None # type: ignore615 if dependency_cache is None:616 dependency_cache = {}617 if _uses_scopes_cache is None:618 _uses_scopes_cache = {}619 for sub_dependant in dependant.dependencies:620 sub_dependant.call = cast(Callable[..., Any], sub_dependant.call)621 call = sub_dependant.call622 use_sub_dependant = sub_dependant623 if (624 dependency_overrides_provider625 and dependency_overrides_provider.dependency_overrides626 ):627 original_call = sub_dependant.call628 call = getattr(629 dependency_overrides_provider, "dependency_overrides", {}630 ).get(original_call, original_call)631 use_path: str = sub_dependant.path # type: ignore632 use_sub_dependant = get_dependant(633 path=use_path,634 call=call,635 name=sub_dependant.name,636 parent_oauth_scopes=_get_oauth_scopes(dependant=sub_dependant),637 scope=sub_dependant.scope,638 )639640 solved_result = await solve_dependencies(641 request=request,642 dependant=use_sub_dependant,643 body=body,644 background_tasks=background_tasks,645 response=response,646 dependency_overrides_provider=dependency_overrides_provider,647 dependency_cache=dependency_cache,648 async_exit_stack=async_exit_stack,649 embed_body_fields=embed_body_fields,650 _uses_scopes_cache=_uses_scopes_cache,651 )652 background_tasks = solved_result.background_tasks653 if solved_result.errors:654 errors.extend(solved_result.errors)655 continue656 sub_dependant_cache_key = _get_cache_key(657 dependant=sub_dependant,658 uses_scopes_cache=_uses_scopes_cache,659 )660 if sub_dependant.use_cache and sub_dependant_cache_key in dependency_cache:661 solved = dependency_cache[sub_dependant_cache_key]662 elif _is_gen_callable(use_sub_dependant.call) or _is_async_gen_callable(663 use_sub_dependant.call664 ):665 use_astack = request_astack666 if sub_dependant.scope == "function":667 use_astack = function_astack668 solved = await _solve_generator(669 dependant=use_sub_dependant,670 stack=use_astack,671 sub_values=solved_result.values,672 )673 elif _is_coroutine_callable(use_sub_dependant.call):674 solved = await call(**solved_result.values)675 else:676 solved = await run_in_threadpool(call, **solved_result.values)677 if sub_dependant.name is not None:678 values[sub_dependant.name] = solved679 if sub_dependant_cache_key not in dependency_cache:680 dependency_cache[sub_dependant_cache_key] = solved681 path_values, path_errors = request_params_to_args(682 dependant.path_params, request.path_params683 )684 query_values, query_errors = request_params_to_args(685 dependant.query_params, request.query_params686 )687 header_values, header_errors = request_params_to_args(688 dependant.header_params, request.headers689 )690 cookie_values, cookie_errors = request_params_to_args(691 dependant.cookie_params, request.cookies692 )693 values.update(path_values)694 values.update(query_values)695 values.update(header_values)696 values.update(cookie_values)697 errors += path_errors + query_errors + header_errors + cookie_errors698 if dependant.body_params:699 (700 body_values,701 body_errors,702 ) = await request_body_to_args( # body_params checked above703 body_fields=dependant.body_params,704 received_body=body,705 embed_body_fields=embed_body_fields,706 )707 values.update(body_values)708 errors.extend(body_errors)709 if dependant.http_connection_param_name:710 values[dependant.http_connection_param_name] = request711 if dependant.request_param_name and isinstance(request, Request):712 values[dependant.request_param_name] = request713 elif dependant.websocket_param_name and isinstance(request, WebSocket):714 values[dependant.websocket_param_name] = request715 if dependant.background_tasks_param_name:716 if background_tasks is None:717 background_tasks = BackgroundTasks()718 values[dependant.background_tasks_param_name] = background_tasks719 if dependant.response_param_name:720 values[dependant.response_param_name] = response721 if dependant.security_scopes_param_name:722 values[dependant.security_scopes_param_name] = SecurityScopes(723 scopes=_get_oauth_scopes(dependant=dependant)724 )725 return SolvedDependency(726 values=values,727 errors=errors,728 background_tasks=background_tasks,729 response=response,730 dependency_cache=dependency_cache,731 )732733734def _validate_value_with_model_field(735 *, field: ModelField, value: Any, values: dict[str, Any], loc: tuple[str, ...]736) -> tuple[Any, list[Any]]:737 if value is None:738 if field.field_info.is_required():739 return None, [get_missing_field_error(loc=loc)]740 else:741 return deepcopy(field.default), []742 return field.validate(value, values, loc=loc)743744745def _is_json_field(field: ModelField) -> bool:746 return any(type(item) is Json for item in field.field_info.metadata)747748749def _get_multidict_value(750 field: ModelField, values: Mapping[str, Any], alias: str | None = None751) -> Any:752 alias = alias or get_validation_alias(field)753 if (754 (not _is_json_field(field))755 and field_annotation_is_sequence(field.field_info.annotation)756 and isinstance(values, (ImmutableMultiDict, Headers))757 ):758 value = values.getlist(alias)759 else:760 value = values.get(alias, None)761 if (762 value is None763 or (764 isinstance(field.field_info, params.Form)765 and isinstance(value, str) # For type checks766 and value == ""767 )768 or (769 field_annotation_is_sequence(field.field_info.annotation)770 and len(value) == 0771 )772 ):773 if field.field_info.is_required():774 return775 else:776 return deepcopy(field.default)777 return value778779780def request_params_to_args(781 fields: Sequence[ModelField],782 received_params: Mapping[str, Any] | QueryParams | Headers,783) -> tuple[dict[str, Any], list[Any]]:784 values: dict[str, Any] = {}785 errors: list[dict[str, Any]] = []786787 if not fields:788 return values, errors789790 first_field = fields[0]791 fields_to_extract = fields792 single_not_embedded_field = False793 default_convert_underscores = True794 if len(fields) == 1 and lenient_issubclass(795 first_field.field_info.annotation, BaseModel796 ):797 fields_to_extract = get_cached_model_fields(first_field.field_info.annotation)798 single_not_embedded_field = True799 # If headers are in a Pydantic model, the way to disable convert_underscores800 # would be with Header(convert_underscores=False) at the Pydantic model level801 default_convert_underscores = getattr(802 first_field.field_info, "convert_underscores", True803 )804805 params_to_process: dict[str, Any] = {}806807 processed_keys = set()808809 for field in fields_to_extract:810 alias = None811 if isinstance(received_params, Headers):812 # Handle fields extracted from a Pydantic Model for a header, each field813 # doesn't have a FieldInfo of type Header with the default convert_underscores=True814 convert_underscores = getattr(815 field.field_info, "convert_underscores", default_convert_underscores816 )817 if convert_underscores:818 alias = get_validation_alias(field)819 if alias == field.name:820 alias = alias.replace("_", "-")821 value = _get_multidict_value(field, received_params, alias=alias)822 if value is not None:823 params_to_process[get_validation_alias(field)] = value824 processed_keys.add(alias or get_validation_alias(field))825 # For headers with convert_underscores=True, mark both the converted826 # header name and the original field alias as processed to avoid827 # accepting the original alias as an extra header.828 processed_keys.add(get_validation_alias(field))829830 for key in received_params.keys():831 if key not in processed_keys:832 if isinstance(received_params, (ImmutableMultiDict, Headers)):833 value = received_params.getlist(key)834 if isinstance(value, list) and (len(value) == 1):835 params_to_process[key] = value[0]836 else:837 params_to_process[key] = value838 else:839 params_to_process[key] = received_params.get(key)840841 if single_not_embedded_field:842 field_info = first_field.field_info843 assert isinstance(field_info, params.Param), (844 "Params must be subclasses of Param"845 )846 loc: tuple[str, ...] = (field_info.in_.value,)847 v_, errors_ = _validate_value_with_model_field(848 field=first_field, value=params_to_process, values=values, loc=loc849 )850 return {first_field.name: v_}, errors_851852 for field in fields:853 value = _get_multidict_value(field, received_params)854 field_info = field.field_info855 assert isinstance(field_info, params.Param), (856 "Params must be subclasses of Param"857 )858 loc = (field_info.in_.value, get_validation_alias(field))859 v_, errors_ = _validate_value_with_model_field(860 field=field, value=value, values=values, loc=loc861 )862 if errors_:863 errors.extend(errors_)864 else:865 values[field.name] = v_866 return values, errors867868869def is_union_of_base_models(field_type: Any) -> bool:870 """Check if field type is a Union where all members are BaseModel subclasses."""871 from fastapi.types import UnionType872873 origin = get_origin(field_type)874875 # Check if it's a Union type (covers both typing.Union and types.UnionType in Python 3.10+)876 if origin is not Union and origin is not UnionType:877 return False878879 union_args = get_args(field_type)880881 for arg in union_args:882 if not lenient_issubclass(arg, BaseModel):883 return False884885 return True886887888def _should_embed_body_fields(fields: list[ModelField]) -> bool:889 if not fields:890 return False891 # More than one dependency could have the same field, it would show up as multiple892 # fields but it's the same one, so count them by name893 body_param_names_set = {field.name for field in fields}894 # A top level field has to be a single field, not multiple895 if len(body_param_names_set) > 1:896 return True897 first_field = fields[0]898 # If it explicitly specifies it is embedded, it has to be embedded899 if getattr(first_field.field_info, "embed", None):900 return True901 # If it's a Form (or File) field, it has to be a BaseModel (or a union of BaseModels) to be top level902 # otherwise it has to be embedded, so that the key value pair can be extracted903 if (904 isinstance(first_field.field_info, params.Form)905 and not lenient_issubclass(first_field.field_info.annotation, BaseModel)906 and not is_union_of_base_models(first_field.field_info.annotation)907 ):908 return True909 return False910911912async def _extract_form_body(913 body_fields: list[ModelField],914 received_body: FormData,915) -> dict[str, Any]:916 values = {}917918 for field in body_fields:919 value = _get_multidict_value(field, received_body)920 field_info = field.field_info921 if (922 isinstance(field_info, params.File)923 and is_bytes_or_nonable_bytes_annotation(field.field_info.annotation)924 and isinstance(value, UploadFile)925 ):926 value = await value.read()927 elif (928 is_bytes_sequence_annotation(field.field_info.annotation)929 and isinstance(field_info, params.File)930 and value_is_sequence(value)931 ):932 # For types933 assert isinstance(value, sequence_types)934 results: list[bytes | str] = []935 for sub_value in value:936 results.append(await sub_value.read())937 value = serialize_sequence_value(field=field, value=results)938 if value is not None:939 values[get_validation_alias(field)] = value940 field_aliases = {get_validation_alias(field) for field in body_fields}941 for key in received_body.keys():942 if key not in field_aliases:943 param_values = received_body.getlist(key)944 if len(param_values) == 1:945 values[key] = param_values[0]946 else:947 values[key] = param_values948 return values949950951async def request_body_to_args(952 body_fields: list[ModelField],953 received_body: dict[str, Any] | FormData | bytes | None,954 embed_body_fields: bool,955) -> tuple[dict[str, Any], list[dict[str, Any]]]:956 values: dict[str, Any] = {}957 errors: list[dict[str, Any]] = []958 assert body_fields, "request_body_to_args() should be called with fields"959 single_not_embedded_field = len(body_fields) == 1 and not embed_body_fields960 first_field = body_fields[0]961 body_to_process = received_body962963 fields_to_extract: list[ModelField] = body_fields964965 if (966 single_not_embedded_field967 and lenient_issubclass(first_field.field_info.annotation, BaseModel)968 and isinstance(received_body, FormData)969 ):970 fields_to_extract = get_cached_model_fields(first_field.field_info.annotation)971972 if isinstance(received_body, FormData):973 body_to_process = await _extract_form_body(fields_to_extract, received_body)974975 if single_not_embedded_field:976 loc: tuple[str, ...] = ("body",)977 v_, errors_ = _validate_value_with_model_field(978 field=first_field, value=body_to_process, values=values, loc=loc979 )980 return {first_field.name: v_}, errors_981 for field in body_fields:982 loc = ("body", get_validation_alias(field))983 value: Any | None = None984 if body_to_process is not None and not isinstance(body_to_process, bytes):985 try:986 value = body_to_process.get(get_validation_alias(field))987 # If the received body is a list, not a dict988 except AttributeError:989 errors.append(get_missing_field_error(loc))990 continue991 v_, errors_ = _validate_value_with_model_field(992 field=field, value=value, values=values, loc=loc993 )994 if errors_:995 errors.extend(errors_)996 else:997 values[field.name] = v_998 return values, errors99910001001def _get_body_field(1002 *, body_params: list[ModelField], name: str, embed_body_fields: bool1003) -> ModelField | None:1004 """1005 Get a ModelField representing the request body for a path operation, combining1006 all body parameters into a single field if necessary.10071008 Used to check if it's form data (with `isinstance(body_field, params.Form)`)1009 or JSON and to generate the JSON Schema for a request body.10101011 This is **not** used to validate/parse the request body, that's done with each1012 individual body parameter.1013 """1014 if not body_params:1015 return None1016 first_param = body_params[0]1017 if not embed_body_fields:1018 return first_param1019 model_name = "Body_" + name1020 BodyModel = create_body_model(fields=body_params, model_name=model_name)1021 required = any(True for f in body_params if f.field_info.is_required())1022 BodyFieldInfo_kwargs: dict[str, Any] = {1023 "annotation": BodyModel,1024 "alias": "body",1025 }1026 if not required:1027 BodyFieldInfo_kwargs["default"] = None1028 if any(isinstance(f.field_info, params.File) for f in body_params):1029 BodyFieldInfo: type[params.Body] = params.File1030 elif any(isinstance(f.field_info, params.Form) for f in body_params):1031 BodyFieldInfo = params.Form1032 else:1033 BodyFieldInfo = params.Body10341035 body_param_media_types = [1036 f.field_info.media_type1037 for f in body_params1038 if isinstance(f.field_info, params.Body)1039 ]1040 if len(set(body_param_media_types)) == 1:1041 BodyFieldInfo_kwargs["media_type"] = body_param_media_types[0]1042 final_field = create_model_field(1043 name="body",1044 type_=BodyModel,1045 alias="body",1046 field_info=BodyFieldInfo(**BodyFieldInfo_kwargs),1047 )1048 return final_field104910501051def get_validation_alias(field: ModelField) -> str:1052 va = getattr(field, "validation_alias", None)1053 return va or field.alias
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.
Generated by searchcode.com · powered by code_get_file + code_get_findings