Browse Source

Exported some duplicated logic into a common _BaseDependant.

pull/12529/head
Nir Schulman 8 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.security.base import SecurityBase
from fastapi.types import EndpointDependencyCacheKey, LifespanDependencyCacheKey
from typing_extensions import Literal, TypeAlias
from fastapi.types import (
EndpointDependencyCacheKey,
LifespanDependencyCacheKey,
DependencyScope,
LifespanDependencyScope,
EndpointDependencyScope
)
from typing_extensions import TypeAlias
if sys.version_info >= (3, 13): # pragma: no cover
from inspect import iscoroutinefunction
@ -21,14 +27,13 @@ class SecurityRequirement:
scopes: Optional[Sequence[str]] = None
@dataclass
class LifespanDependant:
call: Callable[..., Any]
caller: Callable[..., Any]
dependencies: List["LifespanDependant"] = field(default_factory=list)
@dataclass(kw_only=True, slots=True)
class _BaseDependant:
name: Optional[str] = None
use_cache: bool = True
index: Optional[int] = None
call: Optional[Callable[..., Any]] = None
scope: DependencyScope = None
use_cache: bool = True
@cached_property
def is_gen_callable(self) -> bool:
@ -53,6 +58,14 @@ class LifespanDependant:
dunder_call = getattr(self.call, "__call__", None) # noqa: B004
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
def cache_key(self) -> LifespanDependencyCacheKey:
if self.use_cache:
@ -66,7 +79,8 @@ class LifespanDependant:
return self.caller, self.index
@dataclass
class EndpointDependant:
class EndpointDependant(_BaseDependant):
scope: EndpointDependencyScope = None
path_params: List[ModelField] = field(default_factory=list)
query_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)
lifespan_dependencies: List[LifespanDependant] = 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
websocket_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
security_scopes_param_name: Optional[str] = None
security_scopes: Optional[List[str]] = None
use_cache: bool = True
path: Optional[str] = None
scope: Union[Literal["function", "request"], None] = None
@cached_property
def cache_key(self) -> EndpointDependencyCacheKey:
@ -97,29 +106,6 @@ class EndpointDependant:
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
def computed_scope(self) -> Union[str, None]:
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.oauth2 import OAuth2, SecurityScopes
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 pydantic import BaseModel
from pydantic.fields import FieldInfo
@ -143,6 +143,7 @@ def get_parameterless_sub_dependant(
call=depends.dependency,
use_cache=depends.use_cache,
index=index,
scope=depends.scope,
)
else:
raise InvalidDependencyScope(
@ -247,6 +248,7 @@ def get_lifespan_dependant(
*,
caller: Callable[..., Any],
call: Callable[..., Any],
scope: LifespanDependencyScope,
name: Optional[str] = None,
use_cache: bool = True,
index: Optional[int] = None,
@ -254,7 +256,7 @@ def get_lifespan_dependant(
dependency_signature = get_typed_signature(call)
signature_params = dependency_signature.parameters
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():
param_details = analyze_param(
@ -265,7 +267,7 @@ def get_lifespan_dependant(
)
if param_details.depends is None:
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'not a valid sub-dependency. Lifespan scoped dependencies may only '
f"use lifespan scoped sub-dependencies."
@ -273,7 +275,7 @@ def get_lifespan_dependant(
if param_details.depends.scope != "lifespan":
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'dependencies may only use lifespan scoped sub-dependencies.'
)
@ -285,6 +287,7 @@ def get_lifespan_dependant(
call=param_details.depends.dependency,
use_cache=param_details.depends.use_cache,
caller=call,
scope=scope
)
dependant.dependencies.append(sub_dependant)
@ -351,7 +354,8 @@ def get_endpoint_dependant(
call=param_details.depends.dependency,
name=param_name,
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):
dependant.endpoint_dependencies.append(get_endpoint_dependant(
@ -672,6 +676,7 @@ async def solve_lifespan_dependant(
name=dependant.name,
use_cache=dependant.use_cache,
index=dependant.index,
scope=dependant.scope
)
dependency_arguments: Dict[str, Any] = {}

7
fastapi/params.py

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

Loading…
Cancel
Save