Browse Source

Exported some duplicated logic into a common _BaseDependant.

pull/12529/head
Nir Schulman 9 months ago
parent
commit
8ca136455a
  1. 60
      fastapi/dependencies/models.py
  2. 15
      fastapi/dependencies/utils.py
  3. 7
      fastapi/params.py
  4. 5
      fastapi/types.py

60
fastapi/dependencies/models.py

@ -6,8 +6,14 @@ from typing import Any, Callable, List, Optional, Sequence, Union, Tuple, cast
from fastapi._compat import ModelField from fastapi._compat import ModelField
from fastapi.security.base import SecurityBase from fastapi.security.base import SecurityBase
from fastapi.types import EndpointDependencyCacheKey, LifespanDependencyCacheKey from fastapi.types import (
from typing_extensions import Literal, TypeAlias EndpointDependencyCacheKey,
LifespanDependencyCacheKey,
DependencyScope,
LifespanDependencyScope,
EndpointDependencyScope
)
from typing_extensions import TypeAlias
if sys.version_info >= (3, 13): # pragma: no cover if sys.version_info >= (3, 13): # pragma: no cover
from inspect import iscoroutinefunction from inspect import iscoroutinefunction
@ -21,14 +27,13 @@ class SecurityRequirement:
scopes: Optional[Sequence[str]] = None scopes: Optional[Sequence[str]] = None
@dataclass @dataclass(kw_only=True, slots=True)
class LifespanDependant: class _BaseDependant:
call: Callable[..., Any]
caller: Callable[..., Any]
dependencies: List["LifespanDependant"] = field(default_factory=list)
name: Optional[str] = None name: Optional[str] = None
use_cache: bool = True
index: Optional[int] = None index: Optional[int] = None
call: Optional[Callable[..., Any]] = None
scope: DependencyScope = None
use_cache: bool = True
@cached_property @cached_property
def is_gen_callable(self) -> bool: def is_gen_callable(self) -> bool:
@ -53,6 +58,14 @@ class LifespanDependant:
dunder_call = getattr(self.call, "__call__", None) # noqa: B004 dunder_call = getattr(self.call, "__call__", None) # noqa: B004
return iscoroutinefunction(dunder_call) return iscoroutinefunction(dunder_call)
@dataclass
class LifespanDependant(_BaseDependant):
scope: LifespanDependencyScope
caller: Callable[..., Any]
call: Callable[..., Any]
dependencies: List["LifespanDependant"] = field(default_factory=list)
@cached_property @cached_property
def cache_key(self) -> LifespanDependencyCacheKey: def cache_key(self) -> LifespanDependencyCacheKey:
if self.use_cache: if self.use_cache:
@ -66,7 +79,8 @@ class LifespanDependant:
return self.caller, self.index return self.caller, self.index
@dataclass @dataclass
class EndpointDependant: class EndpointDependant(_BaseDependant):
scope: EndpointDependencyScope = None
path_params: List[ModelField] = field(default_factory=list) path_params: List[ModelField] = field(default_factory=list)
query_params: List[ModelField] = field(default_factory=list) query_params: List[ModelField] = field(default_factory=list)
header_params: List[ModelField] = field(default_factory=list) header_params: List[ModelField] = field(default_factory=list)
@ -75,9 +89,6 @@ class EndpointDependant:
endpoint_dependencies: List["EndpointDependant"] = field(default_factory=list) endpoint_dependencies: List["EndpointDependant"] = field(default_factory=list)
lifespan_dependencies: List[LifespanDependant] = field(default_factory=list) lifespan_dependencies: List[LifespanDependant] = field(default_factory=list)
security_requirements: List[SecurityRequirement] = field(default_factory=list) security_requirements: List[SecurityRequirement] = field(default_factory=list)
name: Optional[str] = None
call: Optional[Callable[..., Any]] = None
index: Optional[int] = None
request_param_name: Optional[str] = None request_param_name: Optional[str] = None
websocket_param_name: Optional[str] = None websocket_param_name: Optional[str] = None
http_connection_param_name: Optional[str] = None http_connection_param_name: Optional[str] = None
@ -85,9 +96,7 @@ class EndpointDependant:
background_tasks_param_name: Optional[str] = None background_tasks_param_name: Optional[str] = None
security_scopes_param_name: Optional[str] = None security_scopes_param_name: Optional[str] = None
security_scopes: Optional[List[str]] = None security_scopes: Optional[List[str]] = None
use_cache: bool = True
path: Optional[str] = None path: Optional[str] = None
scope: Union[Literal["function", "request"], None] = None
@cached_property @cached_property
def cache_key(self) -> EndpointDependencyCacheKey: def cache_key(self) -> EndpointDependencyCacheKey:
@ -97,29 +106,6 @@ class EndpointDependant:
self.computed_scope or "", self.computed_scope or "",
) )
@cached_property
def is_gen_callable(self) -> bool:
if inspect.isgeneratorfunction(self.call):
return True
dunder_call = getattr(self.call, "__call__", None) # noqa: B004
return inspect.isgeneratorfunction(dunder_call)
@cached_property
def is_async_gen_callable(self) -> bool:
if inspect.isasyncgenfunction(self.call):
return True
dunder_call = getattr(self.call, "__call__", None) # noqa: B004
return inspect.isasyncgenfunction(dunder_call)
@cached_property
def is_coroutine_callable(self) -> bool:
if inspect.isroutine(self.call):
return iscoroutinefunction(self.call)
if inspect.isclass(self.call):
return False
dunder_call = getattr(self.call, "__call__", None) # noqa: B004
return iscoroutinefunction(dunder_call)
@cached_property @cached_property
def computed_scope(self) -> Union[str, None]: def computed_scope(self) -> Union[str, None]:
if self.scope: if self.scope:

15
fastapi/dependencies/utils.py

@ -59,7 +59,7 @@ from fastapi.logger import logger
from fastapi.security.base import SecurityBase from fastapi.security.base import SecurityBase
from fastapi.security.oauth2 import OAuth2, SecurityScopes from fastapi.security.oauth2 import OAuth2, SecurityScopes
from fastapi.security.open_id_connect_url import OpenIdConnect from fastapi.security.open_id_connect_url import OpenIdConnect
from fastapi.types import EndpointDependencyCacheKey, LifespanDependencyCacheKey from fastapi.types import EndpointDependencyCacheKey, LifespanDependencyCacheKey, LifespanDependencyScope
from fastapi.utils import create_model_field, get_path_param_names from fastapi.utils import create_model_field, get_path_param_names
from pydantic import BaseModel from pydantic import BaseModel
from pydantic.fields import FieldInfo from pydantic.fields import FieldInfo
@ -143,6 +143,7 @@ def get_parameterless_sub_dependant(
call=depends.dependency, call=depends.dependency,
use_cache=depends.use_cache, use_cache=depends.use_cache,
index=index, index=index,
scope=depends.scope,
) )
else: else:
raise InvalidDependencyScope( raise InvalidDependencyScope(
@ -247,6 +248,7 @@ def get_lifespan_dependant(
*, *,
caller: Callable[..., Any], caller: Callable[..., Any],
call: Callable[..., Any], call: Callable[..., Any],
scope: LifespanDependencyScope,
name: Optional[str] = None, name: Optional[str] = None,
use_cache: bool = True, use_cache: bool = True,
index: Optional[int] = None, index: Optional[int] = None,
@ -254,7 +256,7 @@ def get_lifespan_dependant(
dependency_signature = get_typed_signature(call) dependency_signature = get_typed_signature(call)
signature_params = dependency_signature.parameters signature_params = dependency_signature.parameters
dependant = LifespanDependant( dependant = LifespanDependant(
call=call, name=name, use_cache=use_cache, caller=caller, index=index call=call, name=name, use_cache=use_cache, caller=caller, index=index, scope=scope
) )
for param_name, param in signature_params.items(): for param_name, param in signature_params.items():
param_details = analyze_param( param_details = analyze_param(
@ -265,7 +267,7 @@ def get_lifespan_dependant(
) )
if param_details.depends is None: if param_details.depends is None:
raise DependencyScopeError( raise DependencyScopeError(
f'Dependency "{dependant.name}" has "lifespan" scope, but was defined' f'Dependency "{dependant.name}" has "{scope}" scope, but was defined'
f'with an invalid argument: "{param_name}" which is ' 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." f"use lifespan scoped sub-dependencies."
@ -273,7 +275,7 @@ def get_lifespan_dependant(
if param_details.depends.scope != "lifespan": if param_details.depends.scope != "lifespan":
raise DependencyScopeError( raise DependencyScopeError(
f'Dependency "{dependant.name}" has "lifespan" scope, but was defined with the ' f'Dependency "{dependant.name}" has "{scope}" scope, but was defined with the '
f'sub-dependency "{param_name}" which has "{param_details.depends.scope}" scope. Lifespan scoped ' 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.'
) )
@ -285,6 +287,7 @@ def get_lifespan_dependant(
call=param_details.depends.dependency, call=param_details.depends.dependency,
use_cache=param_details.depends.use_cache, use_cache=param_details.depends.use_cache,
caller=call, caller=call,
scope=scope
) )
dependant.dependencies.append(sub_dependant) dependant.dependencies.append(sub_dependant)
@ -351,7 +354,8 @@ def get_endpoint_dependant(
call=param_details.depends.dependency, call=param_details.depends.dependency,
name=param_name, name=param_name,
use_cache=param_details.depends.use_cache, use_cache=param_details.depends.use_cache,
index=index index=index,
scope=param_details.depends.scope
)) ))
elif param_details.depends.scope in ("request", "function", None): elif param_details.depends.scope in ("request", "function", None):
dependant.endpoint_dependencies.append(get_endpoint_dependant( dependant.endpoint_dependencies.append(get_endpoint_dependant(
@ -672,6 +676,7 @@ async def solve_lifespan_dependant(
name=dependant.name, name=dependant.name,
use_cache=dependant.use_cache, use_cache=dependant.use_cache,
index=dependant.index, index=dependant.index,
scope=dependant.scope
) )
dependency_arguments: Dict[str, Any] = {} dependency_arguments: Dict[str, Any] = {}

7
fastapi/params.py

@ -13,10 +13,9 @@ from ._compat import (
Undefined, Undefined,
) )
from .exceptions import InvalidDependencyScope from .exceptions import InvalidDependencyScope
from .types import EndpointDependencyScope, DependencyScope
_Unset: Any = Undefined _Unset: Any = Undefined
_EndpointDependencyScope: TypeAlias = Literal["request", "function"]
DependencyScope: TypeAlias = Union[Literal["lifespan"], _EndpointDependencyScope]
class ParamTypes(Enum): class ParamTypes(Enum):
@ -769,7 +768,7 @@ class File(Form): # type: ignore[misc]
class Depends: class Depends:
dependency: Optional[Callable[..., Any]] = None dependency: Optional[Callable[..., Any]] = None
use_cache: bool = True use_cache: bool = True
scope: Union[DependencyScope, None] = None scope: DependencyScope = None
def __post_init__(self): def __post_init__(self):
if self.scope not in ("lifespan", "request", "function", None): if self.scope not in ("lifespan", "request", "function", None):
@ -778,5 +777,5 @@ class Depends:
@dataclass @dataclass
class Security(Depends): class Security(Depends):
scope: Union[_EndpointDependencyScope, None] = None scope: EndpointDependencyScope = None
scopes: Optional[Sequence[str]] = None scopes: Optional[Sequence[str]] = None

5
fastapi/types.py

@ -3,12 +3,17 @@ from enum import Enum
from typing import Any, Callable, Dict, Optional, Set, Tuple, Type, TypeVar, Union from typing import Any, Callable, Dict, Optional, Set, Tuple, Type, TypeVar, Union
from pydantic import BaseModel from pydantic import BaseModel
from typing_extensions import Literal, TypeAlias
DecoratedCallable = TypeVar("DecoratedCallable", bound=Callable[..., Any]) DecoratedCallable = TypeVar("DecoratedCallable", bound=Callable[..., Any])
UnionType = getattr(types, "UnionType", Union) UnionType = getattr(types, "UnionType", Union)
ModelNameMap = Dict[Union[Type[BaseModel], Type[Enum]], str] ModelNameMap = Dict[Union[Type[BaseModel], Type[Enum]], str]
IncEx = Union[Set[int], Set[str], Dict[int, Any], Dict[str, Any]] IncEx = Union[Set[int], Set[str], Dict[int, Any], Dict[str, Any]]
EndpointDependencyScope: TypeAlias = Union[Literal["request", "function"], None]
LifespanDependencyScope: TypeAlias = Literal["lifespan"]
DependencyScope: TypeAlias = Union[LifespanDependencyScope, EndpointDependencyScope]
LifespanDependencyCacheKey = Union[ LifespanDependencyCacheKey = Union[
Tuple[Callable[..., Any], Union[str, int]], Callable[..., Any] Tuple[Callable[..., Any], Union[str, int]], Callable[..., Any]
] ]

Loading…
Cancel
Save