fastapi/dependencies/utils.py PYTHON 1,062 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 Dependant58from fastapi.exceptions import DependencyScopeError59from fastapi.logger import logger60from fastapi.security.oauth2 import SecurityScopes61from fastapi.types import DependencyCacheKey62from fastapi.utils import create_model_field, get_path_param_names63from pydantic import BaseModel, Json64from pydantic.fields import FieldInfo65from starlette.background import BackgroundTasks as StarletteBackgroundTasks66from starlette.concurrency import run_in_threadpool67from starlette.datastructures import (68    FormData,69    Headers,70    ImmutableMultiDict,71    QueryParams,72    UploadFile,73)74from starlette.requests import HTTPConnection, Request75from starlette.responses import Response76from starlette.websockets import WebSocket77from typing_inspection.typing_objects import is_typealiastype7879multipart_not_installed_error = (80    'Form data requires "python-multipart" to be installed. \n'81    'You can install "python-multipart" with: \n\n'82    "pip install python-multipart\n"83)84multipart_incorrect_install_error = (85    'Form data requires "python-multipart" to be installed. '86    'It seems you installed "multipart" instead. \n'87    'You can remove "multipart" with: \n\n'88    "pip uninstall multipart\n\n"89    'And then install "python-multipart" with: \n\n'90    "pip install python-multipart\n"91)929394def ensure_multipart_is_installed() -> None:95    try:96        from python_multipart import __version__9798        # Import an attribute that can be mocked/deleted in testing99        assert __version__ > "0.0.12"100    except (ImportError, AssertionError):101        try:102            # __version__ is available in both multiparts, and can be mocked103            from multipart import (  # type: ignore[no-redef,import-untyped]104                __version__,105            )106107            assert __version__108            try:109                # parse_options_header is only available in the right multipart110                from multipart.multipart import (  # type: ignore[import-untyped]111                    parse_options_header,112                )113114                assert parse_options_header115            except ImportError:116                logger.error(multipart_incorrect_install_error)117                raise RuntimeError(multipart_incorrect_install_error) from None118        except ImportError:119            logger.error(multipart_not_installed_error)120            raise RuntimeError(multipart_not_installed_error) from None121122123def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant:124    assert callable(depends.dependency), (125        "A parameter-less dependency must have a callable dependency"126    )127    own_oauth_scopes: list[str] = []128    if isinstance(depends, params.Security) and depends.scopes:129        own_oauth_scopes.extend(depends.scopes)130    return get_dependant(131        path=path,132        call=depends.dependency,133        scope=depends.scope,134        own_oauth_scopes=own_oauth_scopes,135    )136137138def get_flat_dependant(139    dependant: Dependant,140    *,141    skip_repeats: bool = False,142    visited: list[DependencyCacheKey] | None = None,143    parent_oauth_scopes: list[str] | None = None,144) -> Dependant:145    if visited is None:146        visited = []147    visited.append(dependant.cache_key)148    use_parent_oauth_scopes = (parent_oauth_scopes or []) + (149        dependant.oauth_scopes or []150    )151152    flat_dependant = Dependant(153        path_params=dependant.path_params.copy(),154        query_params=dependant.query_params.copy(),155        header_params=dependant.header_params.copy(),156        cookie_params=dependant.cookie_params.copy(),157        body_params=dependant.body_params.copy(),158        name=dependant.name,159        call=dependant.call,160        request_param_name=dependant.request_param_name,161        websocket_param_name=dependant.websocket_param_name,162        http_connection_param_name=dependant.http_connection_param_name,163        response_param_name=dependant.response_param_name,164        background_tasks_param_name=dependant.background_tasks_param_name,165        security_scopes_param_name=dependant.security_scopes_param_name,166        own_oauth_scopes=dependant.own_oauth_scopes,167        parent_oauth_scopes=use_parent_oauth_scopes,168        use_cache=dependant.use_cache,169        path=dependant.path,170        scope=dependant.scope,171    )172    for sub_dependant in dependant.dependencies:173        if skip_repeats and sub_dependant.cache_key in visited:174            continue175        flat_sub = get_flat_dependant(176            sub_dependant,177            skip_repeats=skip_repeats,178            visited=visited,179            parent_oauth_scopes=flat_dependant.oauth_scopes,180        )181        flat_dependant.dependencies.append(flat_sub)182        flat_dependant.path_params.extend(flat_sub.path_params)183        flat_dependant.query_params.extend(flat_sub.query_params)184        flat_dependant.header_params.extend(flat_sub.header_params)185        flat_dependant.cookie_params.extend(flat_sub.cookie_params)186        flat_dependant.body_params.extend(flat_sub.body_params)187        flat_dependant.dependencies.extend(flat_sub.dependencies)188189    return flat_dependant190191192def _get_flat_fields_from_params(fields: list[ModelField]) -> list[ModelField]:193    if not fields:194        return fields195    first_field = fields[0]196    if len(fields) == 1 and lenient_issubclass(197        first_field.field_info.annotation, BaseModel198    ):199        fields_to_extract = get_cached_model_fields(first_field.field_info.annotation)200        return fields_to_extract201    return fields202203204def get_flat_params(dependant: Dependant) -> list[ModelField]:205    flat_dependant = get_flat_dependant(dependant, skip_repeats=True)206    path_params = _get_flat_fields_from_params(flat_dependant.path_params)207    query_params = _get_flat_fields_from_params(flat_dependant.query_params)208    header_params = _get_flat_fields_from_params(flat_dependant.header_params)209    cookie_params = _get_flat_fields_from_params(flat_dependant.cookie_params)210    return path_params + query_params + header_params + cookie_params211212213def _get_signature(call: Callable[..., Any]) -> inspect.Signature:214    try:215        signature = inspect.signature(call, eval_str=True)216    except NameError:217        # Handle type annotations with if TYPE_CHECKING, not used by FastAPI218        # e.g. dependency return types219        if sys.version_info >= (3, 14):220            from annotationlib import Format221222            signature = inspect.signature(call, annotation_format=Format.FORWARDREF)223        else:224            signature = inspect.signature(call)225    return signature226227228def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:229    signature = _get_signature(call)230    unwrapped = inspect.unwrap(call)231    globalns = getattr(unwrapped, "__globals__", {})232    typed_params = [233        inspect.Parameter(234            name=param.name,235            kind=param.kind,236            default=param.default,237            annotation=get_typed_annotation(param.annotation, globalns),238        )239        for param in signature.parameters.values()240    ]241    typed_signature = inspect.Signature(typed_params)242    return typed_signature243244245def get_typed_annotation(annotation: Any, globalns: dict[str, Any]) -> Any:246    if isinstance(annotation, str):247        annotation = ForwardRef(annotation)248        annotation = evaluate_forwardref(annotation, globalns, globalns)249        if annotation is type(None):250            return None251    return annotation252253254def get_typed_return_annotation(call: Callable[..., Any]) -> Any:255    signature = _get_signature(call)256    unwrapped = inspect.unwrap(call)257    annotation = signature.return_annotation258259    if annotation is inspect.Signature.empty:260        return None261262    globalns = getattr(unwrapped, "__globals__", {})263    return get_typed_annotation(annotation, globalns)264265266_STREAM_ORIGINS = {267    AsyncIterable,268    AsyncIterator,269    AsyncGenerator,270    Iterable,271    Iterator,272    Generator,273}274275276def get_stream_item_type(annotation: Any) -> Any | None:277    origin = get_origin(annotation)278    if origin is not None and origin in _STREAM_ORIGINS:279        type_args = get_args(annotation)280        if type_args:281            return type_args[0]282        return Any283    return None284285286def get_dependant(287    *,288    path: str,289    call: Callable[..., Any],290    name: str | None = None,291    own_oauth_scopes: list[str] | None = None,292    parent_oauth_scopes: list[str] | None = None,293    use_cache: bool = True,294    scope: Literal["function", "request"] | None = None,295) -> Dependant:296    dependant = Dependant(297        call=call,298        name=name,299        path=path,300        use_cache=use_cache,301        scope=scope,302        own_oauth_scopes=own_oauth_scopes,303        parent_oauth_scopes=parent_oauth_scopes,304    )305    current_scopes = (parent_oauth_scopes or []) + (own_oauth_scopes or [])306    path_param_names = get_path_param_names(path)307    endpoint_signature = get_typed_signature(call)308    signature_params = endpoint_signature.parameters309    for param_name, param in signature_params.items():310        is_path_param = param_name in path_param_names311        param_details = analyze_param(312            param_name=param_name,313            annotation=param.annotation,314            value=param.default,315            is_path_param=is_path_param,316        )317        if param_details.depends is not None:318            assert param_details.depends.dependency319            if (320                (dependant.is_gen_callable or dependant.is_async_gen_callable)321                and dependant.computed_scope == "request"322                and param_details.depends.scope == "function"323            ):324                assert dependant.call325                call_name = getattr(dependant.call, "__name__", "<unnamed_callable>")326                raise DependencyScopeError(327                    f'The dependency "{call_name}" has a scope of '328                    '"request", it cannot depend on dependencies with scope "function".'329                )330            sub_own_oauth_scopes: list[str] = []331            if isinstance(param_details.depends, params.Security):332                if param_details.depends.scopes:333                    sub_own_oauth_scopes = list(param_details.depends.scopes)334            sub_dependant = get_dependant(335                path=path,336                call=param_details.depends.dependency,337                name=param_name,338                own_oauth_scopes=sub_own_oauth_scopes,339                parent_oauth_scopes=current_scopes,340                use_cache=param_details.depends.use_cache,341                scope=param_details.depends.scope,342            )343            dependant.dependencies.append(sub_dependant)344            continue345        if add_non_field_param_to_dependency(346            param_name=param_name,347            type_annotation=param_details.type_annotation,348            dependant=dependant,349        ):350            assert param_details.field is None, (351                f"Cannot specify multiple FastAPI annotations for {param_name!r}"352            )353            continue354        assert param_details.field is not None355        if isinstance(param_details.field.field_info, params.Body):356            dependant.body_params.append(param_details.field)357        else:358            add_param_to_fields(field=param_details.field, dependant=dependant)359    return dependant360361362def add_non_field_param_to_dependency(363    *, param_name: str, type_annotation: Any, dependant: Dependant364) -> bool | None:365    if lenient_issubclass(type_annotation, Request):366        dependant.request_param_name = param_name367        return True368    elif lenient_issubclass(type_annotation, WebSocket):369        dependant.websocket_param_name = param_name370        return True371    elif lenient_issubclass(type_annotation, HTTPConnection):372        dependant.http_connection_param_name = param_name373        return True374    elif lenient_issubclass(type_annotation, Response):375        dependant.response_param_name = param_name376        return True377    elif lenient_issubclass(type_annotation, StarletteBackgroundTasks):378        dependant.background_tasks_param_name = param_name379        return True380    elif lenient_issubclass(type_annotation, SecurityScopes):381        dependant.security_scopes_param_name = param_name382        return True383    return None384385386@dataclass387class ParamDetails:388    type_annotation: Any389    depends: params.Depends | None390    field: ModelField | None391392393def analyze_param(394    *,395    param_name: str,396    annotation: Any,397    value: Any,398    is_path_param: bool,399) -> ParamDetails:400    field_info = None401    depends = None402    type_annotation: Any = Any403    use_annotation: Any = Any404    if is_typealiastype(annotation):405        # unpack in case PEP 695 type syntax is used406        annotation = annotation.__value__407    if annotation is not inspect.Signature.empty:408        use_annotation = annotation409        type_annotation = annotation410    # Extract Annotated info411    if get_origin(use_annotation) is Annotated:412        annotated_args = get_args(annotation)413        type_annotation = annotated_args[0]414        fastapi_annotations = [415            arg416            for arg in annotated_args[1:]417            if isinstance(arg, (FieldInfo, params.Depends))418        ]419        fastapi_specific_annotations = [420            arg421            for arg in fastapi_annotations422            if isinstance(423                arg,424                (425                    params.Param,426                    params.Body,427                    params.Depends,428                ),429            )430        ]431        if fastapi_specific_annotations:432            fastapi_annotation: FieldInfo | params.Depends | None = (433                fastapi_specific_annotations[-1]434            )435        else:436            fastapi_annotation = None437        # Set default for Annotated FieldInfo438        if isinstance(fastapi_annotation, FieldInfo):439            # Copy `field_info` because we mutate `field_info.default` below.440            field_info = copy_field_info(441                field_info=fastapi_annotation,442                annotation=use_annotation,443            )444            assert (445                field_info.default == Undefined or field_info.default == RequiredParam446            ), (447                f"`{field_info.__class__.__name__}` default value cannot be set in"448                f" `Annotated` for {param_name!r}. Set the default value with `=` instead."449            )450            if value is not inspect.Signature.empty:451                assert not is_path_param, "Path parameters cannot have default values"452                field_info.default = value453            else:454                field_info.default = RequiredParam455        # Get Annotated Depends456        elif isinstance(fastapi_annotation, params.Depends):457            depends = fastapi_annotation458    # Get Depends from default value459    if isinstance(value, params.Depends):460        assert depends is None, (461            "Cannot specify `Depends` in `Annotated` and default value"462            f" together for {param_name!r}"463        )464        assert field_info is None, (465            "Cannot specify a FastAPI annotation in `Annotated` and `Depends` as a"466            f" default value together for {param_name!r}"467        )468        depends = value469    # Get FieldInfo from default value470    elif isinstance(value, FieldInfo):471        assert field_info is None, (472            "Cannot specify FastAPI annotations in `Annotated` and default value"473            f" together for {param_name!r}"474        )475        field_info = value476        if isinstance(field_info, FieldInfo):477            field_info.annotation = type_annotation478479    # Get Depends from type annotation480    if depends is not None and depends.dependency is None:481        # Copy `depends` before mutating it482        depends = copy(depends)483        depends = dataclasses.replace(depends, dependency=type_annotation)484485    # Handle non-param type annotations like Request486    # Only apply special handling when there's no explicit Depends - if there's a Depends,487    # the dependency will be called and its return value used instead of the special injection488    if depends is None and lenient_issubclass(489        type_annotation,490        (491            Request,492            WebSocket,493            HTTPConnection,494            Response,495            StarletteBackgroundTasks,496            SecurityScopes,497        ),498    ):499        assert field_info is None, (500            f"Cannot specify FastAPI annotation for type {type_annotation!r}"501        )502    # Handle default assignations, neither field_info nor depends was not found in Annotated nor default value503    elif field_info is None and depends is None:504        default_value = value if value is not inspect.Signature.empty else RequiredParam505        if is_path_param:506            # We might check here that `default_value is RequiredParam`, but the fact is that the same507            # parameter might sometimes be a path parameter and sometimes not. See508            # `tests/test_infer_param_optionality.py` for an example.509            field_info = params.Path(annotation=use_annotation)510        elif is_uploadfile_or_nonable_uploadfile_annotation(511            type_annotation512        ) or is_uploadfile_sequence_annotation(type_annotation):513            field_info = params.File(annotation=use_annotation, default=default_value)514        elif not field_annotation_is_scalar(annotation=type_annotation):515            field_info = params.Body(annotation=use_annotation, default=default_value)516        else:517            field_info = params.Query(annotation=use_annotation, default=default_value)518519    field = None520    # It's a field_info, not a dependency521    if field_info is not None:522        # Handle field_info.in_523        if is_path_param:524            assert isinstance(field_info, params.Path), (525                f"Cannot use `{field_info.__class__.__name__}` for path param"526                f" {param_name!r}"527            )528        elif (529            isinstance(field_info, params.Param)530            and getattr(field_info, "in_", None) is None531        ):532            field_info.in_ = params.ParamTypes.query533        use_annotation_from_field_info = use_annotation534        if isinstance(field_info, params.Form):535            ensure_multipart_is_installed()536        if not field_info.alias and getattr(field_info, "convert_underscores", None):537            alias = param_name.replace("_", "-")538        else:539            alias = field_info.alias or param_name540        field_info.alias = alias541        field = create_model_field(542            name=param_name,543            type_=use_annotation_from_field_info,544            default=field_info.default,545            alias=alias,546            field_info=field_info,547        )548        if is_path_param:549            assert is_scalar_field(field=field), (550                "Path params must be of one of the supported types"551            )552        elif isinstance(field_info, params.Query):553            assert (554                is_scalar_field(field)555                or field_annotation_is_scalar_sequence(field.field_info.annotation)556                or lenient_issubclass(field.field_info.annotation, BaseModel)557            ), f"Query parameter {param_name!r} must be one of the supported types"558559    return ParamDetails(type_annotation=type_annotation, depends=depends, field=field)560561562def add_param_to_fields(*, field: ModelField, dependant: Dependant) -> None:563    field_info = field.field_info564    field_info_in = getattr(field_info, "in_", None)565    if field_info_in == params.ParamTypes.path:566        dependant.path_params.append(field)567    elif field_info_in == params.ParamTypes.query:568        dependant.query_params.append(field)569    elif field_info_in == params.ParamTypes.header:570        dependant.header_params.append(field)571    else:572        assert field_info_in == params.ParamTypes.cookie, (573            f"non-body parameters must be in path, query, header or cookie: {field.name}"574        )575        dependant.cookie_params.append(field)576577578async def _solve_generator(579    *, dependant: Dependant, stack: AsyncExitStack, sub_values: dict[str, Any]580) -> Any:581    assert dependant.call582    if dependant.is_async_gen_callable:583        cm = asynccontextmanager(dependant.call)(**sub_values)584    elif dependant.is_gen_callable:585        cm = contextmanager_in_threadpool(contextmanager(dependant.call)(**sub_values))586    return await stack.enter_async_context(cm)587588589@dataclass590class SolvedDependency:591    values: dict[str, Any]592    errors: list[Any]593    background_tasks: StarletteBackgroundTasks | None594    response: Response595    dependency_cache: dict[DependencyCacheKey, Any]596597598async def solve_dependencies(599    *,600    request: Request | WebSocket,601    dependant: Dependant,602    body: dict[str, Any] | FormData | bytes | None = None,603    background_tasks: StarletteBackgroundTasks | None = None,604    response: Response | None = None,605    dependency_overrides_provider: Any | None = None,606    dependency_cache: dict[DependencyCacheKey, Any] | None = None,607    # TODO: remove this parameter later, no longer used, not removing it yet as some608    # people might be monkey patching this function (although that's not supported)609    async_exit_stack: AsyncExitStack,610    embed_body_fields: bool,611) -> SolvedDependency:612    request_astack = request.scope.get("fastapi_inner_astack")613    assert isinstance(request_astack, AsyncExitStack), (614        "fastapi_inner_astack not found in request scope"615    )616    function_astack = request.scope.get("fastapi_function_astack")617    assert isinstance(function_astack, AsyncExitStack), (618        "fastapi_function_astack not found in request scope"619    )620    values: dict[str, Any] = {}621    errors: list[Any] = []622    if response is None:623        response = Response()624        del response.headers["content-length"]625        response.status_code = None  # type: ignore626    if dependency_cache is None:627        dependency_cache = {}628    for sub_dependant in dependant.dependencies:629        sub_dependant.call = cast(Callable[..., Any], sub_dependant.call)630        call = sub_dependant.call631        use_sub_dependant = sub_dependant632        if (633            dependency_overrides_provider634            and dependency_overrides_provider.dependency_overrides635        ):636            original_call = sub_dependant.call637            call = getattr(638                dependency_overrides_provider, "dependency_overrides", {}639            ).get(original_call, original_call)640            use_path: str = sub_dependant.path  # type: ignore641            use_sub_dependant = get_dependant(642                path=use_path,643                call=call,644                name=sub_dependant.name,645                parent_oauth_scopes=sub_dependant.oauth_scopes,646                scope=sub_dependant.scope,647            )648649        solved_result = await solve_dependencies(650            request=request,651            dependant=use_sub_dependant,652            body=body,653            background_tasks=background_tasks,654            response=response,655            dependency_overrides_provider=dependency_overrides_provider,656            dependency_cache=dependency_cache,657            async_exit_stack=async_exit_stack,658            embed_body_fields=embed_body_fields,659        )660        background_tasks = solved_result.background_tasks661        if solved_result.errors:662            errors.extend(solved_result.errors)663            continue664        if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache:665            solved = dependency_cache[sub_dependant.cache_key]666        elif (667            use_sub_dependant.is_gen_callable or use_sub_dependant.is_async_gen_callable668        ):669            use_astack = request_astack670            if sub_dependant.scope == "function":671                use_astack = function_astack672            solved = await _solve_generator(673                dependant=use_sub_dependant,674                stack=use_astack,675                sub_values=solved_result.values,676            )677        elif use_sub_dependant.is_coroutine_callable:678            solved = await call(**solved_result.values)679        else:680            solved = await run_in_threadpool(call, **solved_result.values)681        if sub_dependant.name is not None:682            values[sub_dependant.name] = solved683        if sub_dependant.cache_key not in dependency_cache:684            dependency_cache[sub_dependant.cache_key] = solved685    path_values, path_errors = request_params_to_args(686        dependant.path_params, request.path_params687    )688    query_values, query_errors = request_params_to_args(689        dependant.query_params, request.query_params690    )691    header_values, header_errors = request_params_to_args(692        dependant.header_params, request.headers693    )694    cookie_values, cookie_errors = request_params_to_args(695        dependant.cookie_params, request.cookies696    )697    values.update(path_values)698    values.update(query_values)699    values.update(header_values)700    values.update(cookie_values)701    errors += path_errors + query_errors + header_errors + cookie_errors702    if dependant.body_params:703        (704            body_values,705            body_errors,706        ) = await request_body_to_args(  # body_params checked above707            body_fields=dependant.body_params,708            received_body=body,709            embed_body_fields=embed_body_fields,710        )711        values.update(body_values)712        errors.extend(body_errors)713    if dependant.http_connection_param_name:714        values[dependant.http_connection_param_name] = request715    if dependant.request_param_name and isinstance(request, Request):716        values[dependant.request_param_name] = request717    elif dependant.websocket_param_name and isinstance(request, WebSocket):718        values[dependant.websocket_param_name] = request719    if dependant.background_tasks_param_name:720        if background_tasks is None:721            background_tasks = BackgroundTasks()722        values[dependant.background_tasks_param_name] = background_tasks723    if dependant.response_param_name:724        values[dependant.response_param_name] = response725    if dependant.security_scopes_param_name:726        values[dependant.security_scopes_param_name] = SecurityScopes(727            scopes=dependant.oauth_scopes728        )729    return SolvedDependency(730        values=values,731        errors=errors,732        background_tasks=background_tasks,733        response=response,734        dependency_cache=dependency_cache,735    )736737738def _validate_value_with_model_field(739    *, field: ModelField, value: Any, values: dict[str, Any], loc: tuple[str, ...]740) -> tuple[Any, list[Any]]:741    if value is None:742        if field.field_info.is_required():743            return None, [get_missing_field_error(loc=loc)]744        else:745            return deepcopy(field.default), []746    return field.validate(value, values, loc=loc)747748749def _is_json_field(field: ModelField) -> bool:750    return any(type(item) is Json for item in field.field_info.metadata)751752753def _get_multidict_value(754    field: ModelField, values: Mapping[str, Any], alias: str | None = None755) -> Any:756    alias = alias or get_validation_alias(field)757    if (758        (not _is_json_field(field))759        and field_annotation_is_sequence(field.field_info.annotation)760        and isinstance(values, (ImmutableMultiDict, Headers))761    ):762        value = values.getlist(alias)763    else:764        value = values.get(alias, None)765    if (766        value is None767        or (768            isinstance(field.field_info, params.Form)769            and isinstance(value, str)  # For type checks770            and value == ""771        )772        or (773            field_annotation_is_sequence(field.field_info.annotation)774            and len(value) == 0775        )776    ):777        if field.field_info.is_required():778            return779        else:780            return deepcopy(field.default)781    return value782783784def request_params_to_args(785    fields: Sequence[ModelField],786    received_params: Mapping[str, Any] | QueryParams | Headers,787) -> tuple[dict[str, Any], list[Any]]:788    values: dict[str, Any] = {}789    errors: list[dict[str, Any]] = []790791    if not fields:792        return values, errors793794    first_field = fields[0]795    fields_to_extract = fields796    single_not_embedded_field = False797    default_convert_underscores = True798    if len(fields) == 1 and lenient_issubclass(799        first_field.field_info.annotation, BaseModel800    ):801        fields_to_extract = get_cached_model_fields(first_field.field_info.annotation)802        single_not_embedded_field = True803        # If headers are in a Pydantic model, the way to disable convert_underscores804        # would be with Header(convert_underscores=False) at the Pydantic model level805        default_convert_underscores = getattr(806            first_field.field_info, "convert_underscores", True807        )808809    params_to_process: dict[str, Any] = {}810811    processed_keys = set()812813    for field in fields_to_extract:814        alias = None815        if isinstance(received_params, Headers):816            # Handle fields extracted from a Pydantic Model for a header, each field817            # doesn't have a FieldInfo of type Header with the default convert_underscores=True818            convert_underscores = getattr(819                field.field_info, "convert_underscores", default_convert_underscores820            )821            if convert_underscores:822                alias = get_validation_alias(field)823                if alias == field.name:824                    alias = alias.replace("_", "-")825        value = _get_multidict_value(field, received_params, alias=alias)826        if value is not None:827            params_to_process[get_validation_alias(field)] = value828        processed_keys.add(alias or get_validation_alias(field))829        # For headers with convert_underscores=True, mark both the converted830        # header name and the original field alias as processed to avoid831        # accepting the original alias as an extra header.832        processed_keys.add(get_validation_alias(field))833834    for key in received_params.keys():835        if key not in processed_keys:836            if isinstance(received_params, (ImmutableMultiDict, Headers)):837                value = received_params.getlist(key)838                if isinstance(value, list) and (len(value) == 1):839                    params_to_process[key] = value[0]840                else:841                    params_to_process[key] = value842            else:843                params_to_process[key] = received_params.get(key)844845    if single_not_embedded_field:846        field_info = first_field.field_info847        assert isinstance(field_info, params.Param), (848            "Params must be subclasses of Param"849        )850        loc: tuple[str, ...] = (field_info.in_.value,)851        v_, errors_ = _validate_value_with_model_field(852            field=first_field, value=params_to_process, values=values, loc=loc853        )854        return {first_field.name: v_}, errors_855856    for field in fields:857        value = _get_multidict_value(field, received_params)858        field_info = field.field_info859        assert isinstance(field_info, params.Param), (860            "Params must be subclasses of Param"861        )862        loc = (field_info.in_.value, get_validation_alias(field))863        v_, errors_ = _validate_value_with_model_field(864            field=field, value=value, values=values, loc=loc865        )866        if errors_:867            errors.extend(errors_)868        else:869            values[field.name] = v_870    return values, errors871872873def is_union_of_base_models(field_type: Any) -> bool:874    """Check if field type is a Union where all members are BaseModel subclasses."""875    from fastapi.types import UnionType876877    origin = get_origin(field_type)878879    # Check if it's a Union type (covers both typing.Union and types.UnionType in Python 3.10+)880    if origin is not Union and origin is not UnionType:881        return False882883    union_args = get_args(field_type)884885    for arg in union_args:886        if not lenient_issubclass(arg, BaseModel):887            return False888889    return True890891892def _should_embed_body_fields(fields: list[ModelField]) -> bool:893    if not fields:894        return False895    # More than one dependency could have the same field, it would show up as multiple896    # fields but it's the same one, so count them by name897    body_param_names_set = {field.name for field in fields}898    # A top level field has to be a single field, not multiple899    if len(body_param_names_set) > 1:900        return True901    first_field = fields[0]902    # If it explicitly specifies it is embedded, it has to be embedded903    if getattr(first_field.field_info, "embed", None):904        return True905    # If it's a Form (or File) field, it has to be a BaseModel (or a union of BaseModels) to be top level906    # otherwise it has to be embedded, so that the key value pair can be extracted907    if (908        isinstance(first_field.field_info, params.Form)909        and not lenient_issubclass(first_field.field_info.annotation, BaseModel)910        and not is_union_of_base_models(first_field.field_info.annotation)911    ):912        return True913    return False914915916async def _extract_form_body(917    body_fields: list[ModelField],918    received_body: FormData,919) -> dict[str, Any]:920    values = {}921922    for field in body_fields:923        value = _get_multidict_value(field, received_body)924        field_info = field.field_info925        if (926            isinstance(field_info, params.File)927            and is_bytes_or_nonable_bytes_annotation(field.field_info.annotation)928            and isinstance(value, UploadFile)929        ):930            value = await value.read()931        elif (932            is_bytes_sequence_annotation(field.field_info.annotation)933            and isinstance(field_info, params.File)934            and value_is_sequence(value)935        ):936            # For types937            assert isinstance(value, sequence_types)938            results: list[bytes | str] = []939            for sub_value in value:940                results.append(await sub_value.read())941            value = serialize_sequence_value(field=field, value=results)942        if value is not None:943            values[get_validation_alias(field)] = value944    field_aliases = {get_validation_alias(field) for field in body_fields}945    for key in received_body.keys():946        if key not in field_aliases:947            param_values = received_body.getlist(key)948            if len(param_values) == 1:949                values[key] = param_values[0]950            else:951                values[key] = param_values952    return values953954955async def request_body_to_args(956    body_fields: list[ModelField],957    received_body: dict[str, Any] | FormData | bytes | None,958    embed_body_fields: bool,959) -> tuple[dict[str, Any], list[dict[str, Any]]]:960    values: dict[str, Any] = {}961    errors: list[dict[str, Any]] = []962    assert body_fields, "request_body_to_args() should be called with fields"963    single_not_embedded_field = len(body_fields) == 1 and not embed_body_fields964    first_field = body_fields[0]965    body_to_process = received_body966967    fields_to_extract: list[ModelField] = body_fields968969    if (970        single_not_embedded_field971        and lenient_issubclass(first_field.field_info.annotation, BaseModel)972        and isinstance(received_body, FormData)973    ):974        fields_to_extract = get_cached_model_fields(first_field.field_info.annotation)975976    if isinstance(received_body, FormData):977        body_to_process = await _extract_form_body(fields_to_extract, received_body)978979    if single_not_embedded_field:980        loc: tuple[str, ...] = ("body",)981        v_, errors_ = _validate_value_with_model_field(982            field=first_field, value=body_to_process, values=values, loc=loc983        )984        return {first_field.name: v_}, errors_985    for field in body_fields:986        loc = ("body", get_validation_alias(field))987        value: Any | None = None988        if body_to_process is not None and not isinstance(body_to_process, bytes):989            try:990                value = body_to_process.get(get_validation_alias(field))991            # If the received body is a list, not a dict992            except AttributeError:993                errors.append(get_missing_field_error(loc))994                continue995        v_, errors_ = _validate_value_with_model_field(996            field=field, value=value, values=values, loc=loc997        )998        if errors_:999            errors.extend(errors_)1000        else:1001            values[field.name] = v_1002    return values, errors100310041005def get_body_field(1006    *, flat_dependant: Dependant, name: str, embed_body_fields: bool1007) -> ModelField | None:1008    """1009    Get a ModelField representing the request body for a path operation, combining1010    all body parameters into a single field if necessary.10111012    Used to check if it's form data (with `isinstance(body_field, params.Form)`)1013    or JSON and to generate the JSON Schema for a request body.10141015    This is **not** used to validate/parse the request body, that's done with each1016    individual body parameter.1017    """1018    if not flat_dependant.body_params:1019        return None1020    first_param = flat_dependant.body_params[0]1021    if not embed_body_fields:1022        return first_param1023    model_name = "Body_" + name1024    BodyModel = create_body_model(1025        fields=flat_dependant.body_params, model_name=model_name1026    )1027    required = any(1028        True for f in flat_dependant.body_params if f.field_info.is_required()1029    )1030    BodyFieldInfo_kwargs: dict[str, Any] = {1031        "annotation": BodyModel,1032        "alias": "body",1033    }1034    if not required:1035        BodyFieldInfo_kwargs["default"] = None1036    if any(isinstance(f.field_info, params.File) for f in flat_dependant.body_params):1037        BodyFieldInfo: type[params.Body] = params.File1038    elif any(isinstance(f.field_info, params.Form) for f in flat_dependant.body_params):1039        BodyFieldInfo = params.Form1040    else:1041        BodyFieldInfo = params.Body10421043        body_param_media_types = [1044            f.field_info.media_type1045            for f in flat_dependant.body_params1046            if isinstance(f.field_info, params.Body)1047        ]1048        if len(set(body_param_media_types)) == 1:1049            BodyFieldInfo_kwargs["media_type"] = body_param_media_types[0]1050    final_field = create_model_field(1051        name="body",1052        type_=BodyModel,1053        alias="body",1054        field_info=BodyFieldInfo(**BodyFieldInfo_kwargs),1055    )1056    return final_field105710581059def get_validation_alias(field: ModelField) -> str:1060    va = getattr(field, "validation_alias", None)1061    return va or field.alias

Code quality findings 56

Ensure functions have docstrings for documentation
missing-docstring
def ensure_multipart_is_installed() -> None:
Ensure functions have docstrings for documentation
missing-docstring
def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant:
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(depends, params.Security) and depends.scopes:
Ensure functions have docstrings for documentation
missing-docstring
def get_flat_dependant(
Ensure functions have docstrings for documentation
missing-docstring
def get_flat_params(dependant: Dependant) -> list[ModelField]:
Ensure functions have docstrings for documentation
missing-docstring
def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
Ensure functions have docstrings for documentation
missing-docstring
def get_typed_annotation(annotation: Any, globalns: dict[str, Any]) -> Any:
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(annotation, str):
Use isinstance() for type checking instead of type()
type-check
if annotation is type(None):
Ensure functions have docstrings for documentation
missing-docstring
def get_typed_return_annotation(call: Callable[..., Any]) -> Any:
Ensure functions have docstrings for documentation
missing-docstring
def get_stream_item_type(annotation: Any) -> Any | None:
Ensure functions have docstrings for documentation
missing-docstring
def get_dependant(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(param_details.depends, params.Security):
Avoid unnecessary list conversions; use generators where possible
unnecessary-list
sub_own_oauth_scopes = list(param_details.depends.scopes)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(param_details.field.field_info, params.Body):
Ensure functions have docstrings for documentation
missing-docstring
def add_non_field_param_to_dependency(
Ensure functions have docstrings for documentation
missing-docstring
def analyze_param(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(arg, (FieldInfo, params.Depends))
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(fastapi_annotation, FieldInfo):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(fastapi_annotation, params.Depends):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(value, params.Depends):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(value, FieldInfo):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(field_info, FieldInfo):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
assert isinstance(field_info, params.Path), (
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
isinstance(field_info, params.Param)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(field_info, params.Form):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(field_info, params.Query):
Ensure functions have docstrings for documentation
missing-docstring
def add_param_to_fields(*, field: ModelField, dependant: Dependant) -> None:
Ensure functions have docstrings for documentation
missing-docstring
async def solve_dependencies(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
assert isinstance(request_astack, AsyncExitStack), (
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
assert isinstance(function_astack, AsyncExitStack), (
Avoid unless necessary; Python's garbage collector typically handles object deletion
unnecessary-del
del response.headers["content-length"]
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if dependant.request_param_name and isinstance(request, Request):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif dependant.websocket_param_name and isinstance(request, WebSocket):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
and isinstance(values, (ImmutableMultiDict, Headers))
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
isinstance(field.field_info, params.Form)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
and isinstance(value, str) # For type checks
Ensure functions have docstrings for documentation
missing-docstring
def request_params_to_args(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(received_params, Headers):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(received_params, (ImmutableMultiDict, Headers)):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(value, list) and (len(value) == 1):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
assert isinstance(field_info, params.Param), (
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
assert isinstance(field_info, params.Param), (
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
isinstance(first_field.field_info, params.Form)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
isinstance(field_info, params.File)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
and isinstance(value, UploadFile)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
and isinstance(field_info, params.File)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
assert isinstance(value, sequence_types)
Ensure functions have docstrings for documentation
missing-docstring
async def request_body_to_args(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
and isinstance(received_body, FormData)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(received_body, FormData):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if body_to_process is not None and not isinstance(body_to_process, bytes):
Ensure functions have docstrings for documentation
missing-docstring
def get_body_field(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(f.field_info, params.Body)
Ensure functions have docstrings for documentation
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.