diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py index 3d3567d03..618957894 100644 --- a/fastapi/dependencies/models.py +++ b/fastapi/dependencies/models.py @@ -1,8 +1,10 @@ from dataclasses import dataclass, field -from typing import Any, Callable, List, Optional, Sequence, Tuple +from functools import cached_property +from typing import Any, Callable, List, Optional, Sequence, Union from fastapi._compat import ModelField from fastapi.security.base import SecurityBase +from fastapi.types import DependencyCacheKey from typing_extensions import Literal @@ -32,14 +34,12 @@ class Dependant: security_scopes: Optional[List[str]] = None use_cache: bool = True path: Optional[str] = None - scope: Literal["function", "request"] = "request" - cache_key: Tuple[Optional[Callable[..., Any]], Tuple[str, ...], str] = field( - init=False - ) + scope: Union[Literal["function", "request"], None] = None - def __post_init__(self) -> None: - self.cache_key = ( + @cached_property + def cache_key(self) -> DependencyCacheKey: + return ( self.call, tuple(sorted(set(self.security_scopes or []))), - self.scope, + self.scope or "", ) diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 6e808cd19..5b28afc4f 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -60,6 +60,7 @@ from fastapi.logger import logger from fastapi.security.base import SecurityBase from fastapi.security.oauth2 import OAuth2, SecurityScopes from fastapi.security.open_id_connect_url import OpenIdConnect +from fastapi.types import DependencyCacheKey from fastapi.utils import create_model_field, get_path_param_names from pydantic import BaseModel from pydantic.fields import FieldInfo @@ -138,14 +139,11 @@ def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> De ) -CacheKey = Tuple[Optional[Callable[..., Any]], Tuple[str, ...]] - - def get_flat_dependant( dependant: Dependant, *, skip_repeats: bool = False, - visited: Optional[List[CacheKey]] = None, + visited: Optional[List[DependencyCacheKey]] = None, ) -> Dependant: if visited is None: visited = [] @@ -238,7 +236,7 @@ def get_dependant( name: Optional[str] = None, security_scopes: Optional[List[str]] = None, use_cache: bool = True, - scope: Literal["function", "request"] = "request", + scope: Union[Literal["function", "request"], None] = None, ) -> Dependant: dependant = Dependant( call=call, @@ -254,7 +252,7 @@ def get_dependant( if isinstance(call, SecurityBase): use_scopes: List[str] = [] if isinstance(call, (OAuth2, OpenIdConnect)): - use_scopes = security_scopes + use_scopes = security_scopes or use_scopes security_requirement = SecurityRequirement( security_scheme=call, scopes=use_scopes ) @@ -581,7 +579,7 @@ class SolvedDependency: errors: List[Any] background_tasks: Optional[StarletteBackgroundTasks] response: Response - dependency_cache: Dict[Tuple[Callable[..., Any], Tuple[str]], Any] + dependency_cache: Dict[DependencyCacheKey, Any] async def solve_dependencies( @@ -592,7 +590,7 @@ async def solve_dependencies( background_tasks: Optional[StarletteBackgroundTasks] = None, response: Optional[Response] = None, dependency_overrides_provider: Optional[Any] = None, - dependency_cache: Optional[Dict[Tuple[Callable[..., Any], Tuple[str]], Any]] = None, + dependency_cache: Optional[Dict[DependencyCacheKey, Any]] = None, # TODO: remove this parameter later, no longer used, not removing it yet as some # people might be monkey patching this function (although that's not supported) async_exit_stack: AsyncExitStack, @@ -616,9 +614,6 @@ async def solve_dependencies( dependency_cache = {} for sub_dependant in dependant.dependencies: sub_dependant.call = cast(Callable[..., Any], sub_dependant.call) - sub_dependant.cache_key = cast( - Tuple[Callable[..., Any], Tuple[str]], sub_dependant.cache_key - ) call = sub_dependant.call use_sub_dependant = sub_dependant if ( diff --git a/fastapi/param_functions.py b/fastapi/param_functions.py index f2bc98976..6c908bdcd 100644 --- a/fastapi/param_functions.py +++ b/fastapi/param_functions.py @@ -2246,7 +2246,7 @@ def Depends( # noqa: N802 ), ] = True, scope: Annotated[ - Literal["function", "request"], + Union[Literal["function", "request"], None], Doc( """ Mainly for dependencies with `yield`, define when the dependency function @@ -2264,7 +2264,7 @@ def Depends( # noqa: N802 function will be executed *around* the **request** and response cycle. """ ), - ] = "request", + ] = None, ) -> Any: """ Declare a FastAPI dependency. diff --git a/fastapi/params.py b/fastapi/params.py index 6a37091a3..6a58d5808 100644 --- a/fastapi/params.py +++ b/fastapi/params.py @@ -766,7 +766,7 @@ class File(Form): # type: ignore[misc] class Depends: dependency: Optional[Callable[..., Any]] = None use_cache: bool = True - scope: Literal["function", "request"] = "request" + scope: Union[Literal["function", "request"], None] = None @dataclass diff --git a/fastapi/types.py b/fastapi/types.py index 3205654c7..3f4e81a7c 100644 --- a/fastapi/types.py +++ b/fastapi/types.py @@ -1,6 +1,6 @@ import types from enum import Enum -from typing import Any, Callable, Dict, Set, Type, TypeVar, Union +from typing import Any, Callable, Dict, Optional, Set, Tuple, Type, TypeVar, Union from pydantic import BaseModel @@ -8,3 +8,4 @@ DecoratedCallable = TypeVar("DecoratedCallable", bound=Callable[..., Any]) UnionType = getattr(types, "UnionType", Union) ModelNameMap = Dict[Union[Type[BaseModel], Type[Enum]], str] IncEx = Union[Set[int], Set[str], Dict[int, Any], Dict[str, Any]] +DependencyCacheKey = Tuple[Optional[Callable[..., Any]], Tuple[str, ...], str]