fastapi/dependencies/utils.py PYTHON 1,054 lines View on github.com → Search inside
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

Code quality findings 15

Ensure functions have docstrings for documentation
info maintainability missing-docstring
def ensure_multipart_is_installed() -> None:
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant:
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def get_flat_params(dependant: Dependant) -> list[ModelField]:
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def get_typed_annotation(annotation: Any, globalns: dict[str, Any]) -> Any:
Use isinstance() for type checking instead of type()
info correctness type-check
if annotation is type(None):
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def get_typed_return_annotation(call: Callable[..., Any]) -> Any:
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def get_stream_item_type(annotation: Any) -> Any | None:
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def get_dependant(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def add_non_field_param_to_dependency(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def analyze_param(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def add_param_to_fields(*, field: ModelField, dependant: Dependant) -> None:
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def request_params_to_args(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
async def request_body_to_args(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def get_validation_alias(field: ModelField) -> str:

Get this view in your editor

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