From b189030589f26759f6aa8f3056e4da9728a64780 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 8 Nov 2025 08:47:50 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20format?= =?UTF-8?q?=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs_src/dependencies/tutorial013c_an_py39.py | 4 +- fastapi/applications.py | 3 +- fastapi/dependencies/models.py | 3 +- fastapi/dependencies/utils.py | 68 ++++++++++++------- fastapi/exceptions.py | 8 +++ fastapi/params.py | 4 +- fastapi/utils.py | 20 +++--- .../test_dependency_overrides.py | 8 +-- .../test_endpoint_usage.py | 12 ++-- tests/test_params_repr.py | 1 - 10 files changed, 75 insertions(+), 56 deletions(-) diff --git a/docs_src/dependencies/tutorial013c_an_py39.py b/docs_src/dependencies/tutorial013c_an_py39.py index d9fcceed2..dbb790d17 100644 --- a/docs_src/dependencies/tutorial013c_an_py39.py +++ b/docs_src/dependencies/tutorial013c_an_py39.py @@ -32,9 +32,7 @@ async def get_configuration() -> dict: } -GlobalConfiguration = Annotated[ - dict, Depends(get_configuration, scope="lifespan") -] +GlobalConfiguration = Annotated[dict, Depends(get_configuration, scope="lifespan")] async def get_database_connection(configuration: GlobalConfiguration): diff --git a/fastapi/applications.py b/fastapi/applications.py index 864611121..0edc9ef05 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -36,9 +36,8 @@ from fastapi.openapi.utils import get_openapi from fastapi.params import Depends from fastapi.routing import merge_lifespan_context from fastapi.types import DecoratedCallable, IncEx -from fastapi.utils import generate_unique_id, call_asynchronously +from fastapi.utils import call_asynchronously, generate_unique_id from starlette.applications import Starlette -from starlette.concurrency import run_in_threadpool from starlette.datastructures import State from starlette.exceptions import HTTPException from starlette.middleware import Middleware diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py index 22df740c9..de22365fe 100644 --- a/fastapi/dependencies/models.py +++ b/fastapi/dependencies/models.py @@ -2,7 +2,7 @@ import inspect import sys from dataclasses import dataclass, field from functools import cached_property -from typing import Any, Callable, List, Optional, Sequence, Union, Tuple, cast +from typing import Any, Callable, List, Optional, Sequence, Tuple, Union, cast from fastapi._compat import ModelField from fastapi.security.base import SecurityBase @@ -65,6 +65,7 @@ class LifespanDependant: ) return self.caller, self.index + @dataclass class EndpointDependant: path_params: List[ModelField] = field(default_factory=list) diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index a58f24f56..0d61d2349 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -53,8 +53,16 @@ from fastapi.concurrency import ( asynccontextmanager, contextmanager_in_threadpool, ) -from fastapi.dependencies.models import EndpointDependant, LifespanDependant, SecurityRequirement -from fastapi.exceptions import DependencyScopeError, InvalidDependencyScope, UninitializedLifespanDependency +from fastapi.dependencies.models import ( + EndpointDependant, + LifespanDependant, + SecurityRequirement, +) +from fastapi.exceptions import ( + DependencyScopeError, + InvalidDependencyScope, + UninitializedLifespanDependency, +) from fastapi.logger import logger from fastapi.security.base import SecurityBase from fastapi.security.oauth2 import OAuth2, SecurityScopes @@ -122,7 +130,7 @@ def ensure_multipart_is_installed() -> None: def get_parameterless_sub_dependant( - *, depends: params.Depends, path: str, caller: Callable[..., Any], index: int + *, depends: params.Depends, path: str, caller: Callable[..., Any], index: int ) -> Union[EndpointDependant, LifespanDependant]: assert callable(depends.dependency), ( "A parameter-less dependency must have a callable dependency" @@ -135,7 +143,7 @@ def get_parameterless_sub_dependant( path=path, call=depends.dependency, security_scopes=use_security_scopes, - index=index + index=index, ) elif depends.scope == "lifespan": return get_lifespan_dependant( @@ -146,8 +154,7 @@ def get_parameterless_sub_dependant( ) else: raise InvalidDependencyScope( - f'Dependency "{index}" of {caller} has an invalid ' - f'scope: "{depends.scope}"' + f'Dependency "{index}" of {caller} has an invalid scope: "{depends.scope}"' ) @@ -267,7 +274,7 @@ def get_lifespan_dependant( raise DependencyScopeError( f'Dependency "{dependant.name}" has "lifespan" scope, but was defined' f'with an invalid argument: "{param_name}" which is ' - f'not a valid sub-dependency. Lifespan scoped dependencies may only ' + f"not a valid sub-dependency. Lifespan scoped dependencies may only " f"use lifespan scoped sub-dependencies." ) @@ -275,7 +282,7 @@ def get_lifespan_dependant( raise DependencyScopeError( f'Dependency "{dependant.name}" has "lifespan" scope, but was defined with the ' f'sub-dependency "{param_name}" which has "{param_details.depends.scope}" scope. Lifespan scoped ' - f'dependencies may only use lifespan scoped sub-dependencies.' + f"dependencies may only use lifespan scoped sub-dependencies." ) assert param_details.depends.dependency is not None @@ -346,23 +353,27 @@ def get_endpoint_dependant( if param_details.depends.scopes: use_security_scopes.extend(param_details.depends.scopes) if param_details.depends.scope == "lifespan": - dependant.lifespan_dependencies.append(get_lifespan_dependant( - caller=call, - call=param_details.depends.dependency, - name=param_name, - use_cache=param_details.depends.use_cache, - index=index - )) + dependant.lifespan_dependencies.append( + get_lifespan_dependant( + caller=call, + call=param_details.depends.dependency, + name=param_name, + use_cache=param_details.depends.use_cache, + index=index, + ) + ) elif param_details.depends.scope in ("request", "function", None): - dependant.endpoint_dependencies.append(get_endpoint_dependant( - path=path, - call=param_details.depends.dependency, - name=param_name, - security_scopes=use_security_scopes, - use_cache=param_details.depends.use_cache, - index=index, - scope=param_details.depends.scope - )) + dependant.endpoint_dependencies.append( + get_endpoint_dependant( + path=path, + call=param_details.depends.dependency, + name=param_name, + security_scopes=use_security_scopes, + use_cache=param_details.depends.use_cache, + index=index, + scope=param_details.depends.scope, + ) + ) else: raise InvalidDependencyScope( f'Dependency "{name}" of {call} has an invalid ' @@ -628,7 +639,10 @@ def add_param_to_fields(*, field: ModelField, dependant: EndpointDependant) -> N async def _solve_generator( - *, dependant: Union[EndpointDependant, LifespanDependant], stack: AsyncExitStack, sub_values: Dict[str, Any] + *, + dependant: Union[EndpointDependant, LifespanDependant], + stack: AsyncExitStack, + sub_values: Dict[str, Any], ) -> Any: assert dependant.call if dependant.is_gen_callable: @@ -690,7 +704,9 @@ async def solve_lifespan_dependant( if dependant_to_solve.is_gen_callable or dependant_to_solve.is_async_gen_callable: value = await _solve_generator( - dependant=dependant_to_solve, stack=async_exit_stack, sub_values=dependency_arguments + dependant=dependant_to_solve, + stack=async_exit_stack, + sub_values=dependency_arguments, ) elif dependant_to_solve.is_coroutine_callable: value = await call(**dependency_arguments) diff --git a/fastapi/exceptions.py b/fastapi/exceptions.py index 01b9bbe44..1afc808d6 100644 --- a/fastapi/exceptions.py +++ b/fastapi/exceptions.py @@ -146,31 +146,39 @@ class FastAPIError(RuntimeError): A generic, FastAPI-specific error. """ + class DependencyError(FastAPIError): """ A generic error regarding to dependencies. """ + pass + class InvalidDependencyScope(DependencyError): """ A dependency was declared with an unsupported scope value. """ + pass + class DependencyScopeError(DependencyError): """ A dependency declared that it depends on another dependency with an invalid (narrower) scope. """ + class UninitializedLifespanDependency(DependencyError): """ A bug in FastAPI caused a lifespan-scoped dependency to not initialize properly at the point where we handled a request that depended on it. """ + pass + class ValidationException(Exception): def __init__(self, errors: Sequence[Any]) -> None: self._errors = errors diff --git a/fastapi/params.py b/fastapi/params.py index 96bd67024..36a7877d2 100644 --- a/fastapi/params.py +++ b/fastapi/params.py @@ -773,7 +773,9 @@ class Depends: def __post_init__(self): if self.scope not in ("lifespan", "request", "function", None): - raise InvalidDependencyScope(f"Dependency received an invalid scope: \"{self.scope}\"") + raise InvalidDependencyScope( + f'Dependency received an invalid scope: "{self.scope}"' + ) @dataclass diff --git a/fastapi/utils.py b/fastapi/utils.py index 8be10ff15..6a4d04741 100644 --- a/fastapi/utils.py +++ b/fastapi/utils.py @@ -7,18 +7,18 @@ from dataclasses import is_dataclass from typing import ( TYPE_CHECKING, Any, + Awaitable, Dict, MutableMapping, Optional, Set, Type, + TypeVar, Union, - cast, Awaitable, TypeVar, + cast, ) from weakref import WeakKeyDictionary -from starlette.concurrency import run_in_threadpool - import fastapi from fastapi._compat import ( PYDANTIC_V2, @@ -35,7 +35,8 @@ from fastapi._compat import ( from fastapi.datastructures import DefaultPlaceholder, DefaultType from pydantic import BaseModel from pydantic.fields import FieldInfo -from typing_extensions import Literal, TypeIs, ParamSpec +from starlette.concurrency import run_in_threadpool +from typing_extensions import Literal, ParamSpec, TypeIs if TYPE_CHECKING: # pragma: nocover from .routing import APIRoute @@ -269,7 +270,7 @@ def get_value_or_default( def _is_coroutine_callable( - callable_: Callable[..., Any] + callable_: Callable[..., Any], ) -> TypeIs[Callable[..., Awaitable[Any]]]: if inspect.isroutine(callable_): return iscoroutinefunction(callable_) @@ -278,12 +279,13 @@ def _is_coroutine_callable( dunder_call = getattr(callable_, "__call__", None) # noqa: B004 return iscoroutinefunction(dunder_call) + async def call_asynchronously( - callable_: Union[Callable[_P, _T], Callable[_P, Awaitable[_T]]], - *args: _P.args, - **kwargs: _P.kwargs + callable_: Union[Callable[_P, _T], Callable[_P, Awaitable[_T]]], + *args: _P.args, + **kwargs: _P.kwargs, ) -> _T: if _is_coroutine_callable(callable_): return await callable_(*args, **kwargs) else: - return await run_in_threadpool(callable_, *args, **kwargs) \ No newline at end of file + return await run_in_threadpool(callable_, *args, **kwargs) diff --git a/tests/test_lifespan_scoped_dependencies/test_dependency_overrides.py b/tests/test_lifespan_scoped_dependencies/test_dependency_overrides.py index 3fd337c35..fb97bb738 100644 --- a/tests/test_lifespan_scoped_dependencies/test_dependency_overrides.py +++ b/tests/test_lifespan_scoped_dependencies/test_dependency_overrides.py @@ -494,9 +494,7 @@ def test_override_lifespan_scoped_dependency_cannot_use_endpoint_scoped_paramete router=app, path="/test", is_websocket=is_websocket, - annotation=Annotated[ - None, Depends(dependency_func, scope="lifespan") - ], + annotation=Annotated[None, Depends(dependency_func, scope="lifespan")], ) with pytest.raises(DependencyScopeError): @@ -567,9 +565,7 @@ def test_override_lifespan_scoped_dependency_cannot_use_endpoint_scoped_dependen router=app, path="/test", is_websocket=is_websocket, - annotation=Annotated[ - None, Depends(dependency_func, scope="lifespan") - ], + annotation=Annotated[None, Depends(dependency_func, scope="lifespan")], ) app.dependency_overrides[dependency_func] = override_dependency_func diff --git a/tests/test_lifespan_scoped_dependencies/test_endpoint_usage.py b/tests/test_lifespan_scoped_dependencies/test_endpoint_usage.py index 23875e978..df35686c1 100644 --- a/tests/test_lifespan_scoped_dependencies/test_endpoint_usage.py +++ b/tests/test_lifespan_scoped_dependencies/test_endpoint_usage.py @@ -453,9 +453,7 @@ def test_lifespan_scoped_dependency_cannot_use_endpoint_scoped_parameters( router=app, path="/test", is_websocket=is_websocket, - annotation=Annotated[ - None, Depends(dependency_func, scope="lifespan") - ], + annotation=Annotated[None, Depends(dependency_func, scope="lifespan")], ) @@ -519,7 +517,9 @@ def test_the_same_dependency_can_work_in_different_scopes( is_websocket=is_websocket, annotation1=Annotated[ int, - Depends(dependency_factory.get_dependency(), scope=endpoint_dependency_scope), + Depends( + dependency_factory.get_dependency(), scope=endpoint_dependency_scope + ), ], annotation2=Annotated[ int, @@ -659,9 +659,7 @@ def test_lifespan_scoped_dependency_cannot_use_endpoint_scoped_dependencies( router=app, path="/test", is_websocket=is_websocket, - annotation=Annotated[ - None, Depends(dependency_func, scope="lifespan") - ], + annotation=Annotated[None, Depends(dependency_func, scope="lifespan")], ) diff --git a/tests/test_params_repr.py b/tests/test_params_repr.py index 1529fe72f..baa172497 100644 --- a/tests/test_params_repr.py +++ b/tests/test_params_repr.py @@ -1,6 +1,5 @@ from typing import Any, List -import pytest from dirty_equals import IsOneOf from fastapi.params import Body, Cookie, Header, Param, Path, Query