Browse Source

Set dependency parallelization default opt in. (#639)

pull/13756/head
Fodor Zoltan 10 months ago
parent
commit
b123c5f2ef
  1. 11
      fastapi/applications.py
  2. 2
      fastapi/dependencies/models.py
  3. 23
      fastapi/dependencies/utils.py
  4. 33
      fastapi/param_functions.py
  5. 5
      fastapi/params.py
  6. 2
      tests/test_dependency_cache.py

11
fastapi/applications.py

@ -836,6 +836,16 @@ class FastAPI(Starlette):
"""
),
] = None,
depends_default_parallelizable: Annotated[
bool,
Doc(
"""
Default for whether dependencies are allowed to run in parallel when not
explicitly specified per dependency. Projects can opt in by setting this
to True when creating the application. The default is False.
"""
),
] = False,
**extra: Annotated[
Any,
Doc(
@ -865,6 +875,7 @@ class FastAPI(Starlette):
self.servers = servers or []
self.separate_input_output_schemas = separate_input_output_schemas
self.openapi_external_docs = openapi_external_docs
self.depends_default_parallelizable = depends_default_parallelizable
self.extra = extra
self.openapi_version: Annotated[
str,

2
fastapi/dependencies/models.py

@ -32,7 +32,7 @@ class Dependant:
use_cache: bool = True
path: Optional[str] = None
cache_key: Tuple[Optional[Callable[..., Any]], Tuple[str, ...]] = field(init=False)
parallelizable: bool = True
parallelizable: Optional[bool] = None
def __post_init__(self) -> None:
self.cache_key = (self.call, tuple(sorted(set(self.security_scopes or []))))

23
fastapi/dependencies/utils.py

@ -281,7 +281,7 @@ def get_dependant(
name: Optional[str] = None,
security_scopes: Optional[List[str]] = None,
use_cache: bool = True,
parallelizable: bool = True,
parallelizable: Optional[bool] = None,
) -> Dependant:
path_param_names = get_path_param_names(path)
endpoint_signature = get_typed_signature(call)
@ -589,13 +589,19 @@ class DependencySolveException(Exception):
self.errors = errors
def is_context_sensitive(dependant: Dependant) -> bool:
if dependant.parallelizable is False or (
def is_context_sensitive(dependant: Dependant, *, default_parallelizable: bool) -> bool:
effective_parallelizable = (
default_parallelizable if dependant.parallelizable is None else dependant.parallelizable
)
if effective_parallelizable is False or (
dependant.call is not None
and (is_gen_callable(dependant.call) or is_async_gen_callable(dependant.call))
):
return True
return any(is_context_sensitive(sub) for sub in dependant.dependencies)
return any(
is_context_sensitive(sub, default_parallelizable=default_parallelizable)
for sub in dependant.dependencies
)
def is_context_with_background_task(dependant: Dependant) -> bool:
@ -659,6 +665,7 @@ async def solve_dependencies(
call=call,
name=sub_dependant.name,
security_scopes=sub_dependant.security_scopes,
parallelizable=sub_dependant.parallelizable,
)
def resolve_cached() -> Optional["Future[Any]"]:
@ -750,10 +757,16 @@ async def solve_dependencies(
if name is not None:
values[name] = value
app = request.app # type: ignore[attr-defined]
default_parallelizable = bool(
getattr(app, "depends_default_parallelizable", False)
)
sequential_deps = []
parallel_deps = []
for sub in dependant.dependencies:
if is_context_sensitive(sub):
if is_context_sensitive(sub, default_parallelizable=default_parallelizable):
sequential_deps.append(sub)
else:
parallel_deps.append(sub)

33
fastapi/param_functions.py

@ -2244,6 +2244,17 @@ def Depends( # noqa: N802
"""
),
] = True,
parallelizable: Annotated[
Optional[bool],
Doc(
"""
Whether this dependency may be resolved in parallel.
If not provided, the application-wide default will be used (configured with
`FastAPI(depends_default_parallelizable=...)`).
"""
),
] = None,
) -> Any:
"""
Declare a FastAPI dependency.
@ -2274,7 +2285,9 @@ def Depends( # noqa: N802
return commons
```
"""
return params.Depends(dependency=dependency, use_cache=use_cache)
return params.Depends(
dependency=dependency, use_cache=use_cache, parallelizable=parallelizable
)
def Security( # noqa: N802
@ -2321,6 +2334,17 @@ def Security( # noqa: N802
"""
),
] = True,
parallelizable: Annotated[
Optional[bool],
Doc(
"""
Whether this dependency may be resolved in parallel.
If not provided, the application-wide default will be used (configured with
`FastAPI(depends_default_parallelizable=...)`).
"""
),
] = None,
) -> Any:
"""
Declare a FastAPI Security dependency.
@ -2357,4 +2381,9 @@ def Security( # noqa: N802
return [{"item_id": "Foo", "owner": current_user.username}]
```
"""
return params.Security(dependency=dependency, scopes=scopes, use_cache=use_cache)
return params.Security(
dependency=dependency,
scopes=scopes,
use_cache=use_cache,
parallelizable=parallelizable,
)

5
fastapi/params.py

@ -767,10 +767,11 @@ class Depends:
dependency: Optional[Callable[..., Any]] = None,
*,
use_cache: bool = True,
parallelizable: bool = True,
parallelizable: Optional[bool] = None,
):
self.dependency = dependency
self.use_cache = use_cache
# None means: use application default at resolution time
self.parallelizable = parallelizable
def __repr__(self) -> str:
@ -786,7 +787,7 @@ class Security(Depends):
*,
scopes: Optional[Sequence[str]] = None,
use_cache: bool = True,
parallelizable: bool = True,
parallelizable: Optional[bool] = None,
):
super().__init__(
dependency=dependency, use_cache=use_cache, parallelizable=parallelizable

2
tests/test_dependency_cache.py

@ -29,7 +29,7 @@ async def get_sub_counter(
@app.get("/sub-counter-no-cache/")
async def get_sub_counter_no_cache(
subcount: int = Depends(dep_counter),
subcount: int = Depends(super_dep),
count: int = Depends(dep_counter, use_cache=False),
):
return {"counter": count, "subcounter": subcount}

Loading…
Cancel
Save