diff --git a/docs/en/docs/advanced/advanced-dependencies.md b/docs/en/docs/advanced/advanced-dependencies.md index c71c11404..f157f49ed 100644 --- a/docs/en/docs/advanced/advanced-dependencies.md +++ b/docs/en/docs/advanced/advanced-dependencies.md @@ -1,5 +1,57 @@ # Advanced Dependencies { #advanced-dependencies } +## Parallel dependency resolution (opt-in) { #parallel-dependency-resolution-opt-in } + +FastAPI can resolve independent dependencies in parallel to reduce overall latency. + +This is disabled by default. You can opt in per-application, and you can also control +it per dependency. + +- Application-wide default (disabled by default): + - Set `depends_default_parallelizable=True` in `FastAPI(...)` to allow parallel + resolution by default. +- Per-dependency override: + - Use `Depends(..., parallelizable=True | False)` (and the same for `Security(...)`). + - If not provided, the application default is used. + +When parallelization is enabled, dependencies at the same level of the dependency graph +that are not "context sensitive" will be resolved concurrently. + +Context sensitive dependencies are always resolved sequentially: + +- Dependencies implemented with `yield` (generator or async generator dependencies). +- Dependencies explicitly marked with `parallelizable=False`. + +Dependency errors are still raised in the order of declaration, even if resolved in +parallel. Caching (`use_cache=True`, the default) still works per request: concurrent +callers share a single in-flight execution and the same result. + +### Enable globally + +{* ../../docs_src/dependencies/parallel_global_opt_in.py *} + +### Enable/disable per dependency + +{* ../../docs_src/dependencies/parallel_per_dep_enable.py *} + +You can also opt out for a specific dependency when the app default is parallel: + +{* ../../docs_src/dependencies/parallel_per_dep_disable.py *} + +### Security dependencies + +`Security(...)` supports the same `parallelizable` flag. Caching for security dependencies +respects OAuth2 scopes: dependencies with the same scopes share a cached value within +one request, different scopes result in separate executions. + +{* ../../docs_src/dependencies/parallel_security.py *} + +### Notes + +- Sync (regular `def`) dependencies participate via a threadpool. +- Generator (`yield`) dependencies are always sequential. +- Errors from dependencies are raised respecting the declaration order. + ## Parameterized dependencies { #parameterized-dependencies } All the dependencies we have seen are a fixed function or class. diff --git a/docs/en/docs/reference/dependencies.md b/docs/en/docs/reference/dependencies.md index 2959a21da..00b0da154 100644 --- a/docs/en/docs/reference/dependencies.md +++ b/docs/en/docs/reference/dependencies.md @@ -5,7 +5,6 @@ Dependencies are handled mainly with the special function `Depends()` that takes a callable. Here is the reference for it and its parameters. - You can import it directly from `fastapi`: ```python diff --git a/docs_src/dependencies/parallel_global_opt_in.py b/docs_src/dependencies/parallel_global_opt_in.py new file mode 100644 index 000000000..e38638337 --- /dev/null +++ b/docs_src/dependencies/parallel_global_opt_in.py @@ -0,0 +1,3 @@ +from fastapi import FastAPI + +app = FastAPI(depends_default_parallelizable=True) diff --git a/docs_src/dependencies/parallel_per_dep_disable.py b/docs_src/dependencies/parallel_per_dep_disable.py new file mode 100644 index 000000000..d9e32c117 --- /dev/null +++ b/docs_src/dependencies/parallel_per_dep_disable.py @@ -0,0 +1,13 @@ +from typing import Annotated +from fastapi import Depends, FastAPI + +app = FastAPI(depends_default_parallelizable=True) + +async def dep_seq() -> int: + return 1 + +@app.get("/users") +async def read_users( + x: Annotated[int, Depends(dep_seq, parallelizable=False)], +): + return {"x": x} diff --git a/docs_src/dependencies/parallel_per_dep_enable.py b/docs_src/dependencies/parallel_per_dep_enable.py new file mode 100644 index 000000000..cda3fa989 --- /dev/null +++ b/docs_src/dependencies/parallel_per_dep_enable.py @@ -0,0 +1,17 @@ +from typing import Annotated +from fastapi import Depends, FastAPI + +app = FastAPI(depends_default_parallelizable=False) + +async def dep_a() -> int: + return 1 + +async def dep_b() -> int: + return 2 + +@app.get("/items") +async def read_items( + a: Annotated[int, Depends(dep_a, parallelizable=True)], + b: Annotated[int, Depends(dep_b, parallelizable=True)], +): + return {"a": a, "b": b} diff --git a/docs_src/dependencies/parallel_security.py b/docs_src/dependencies/parallel_security.py new file mode 100644 index 000000000..c563d07be --- /dev/null +++ b/docs_src/dependencies/parallel_security.py @@ -0,0 +1,14 @@ +from typing import Annotated +from fastapi import FastAPI, Security + +app = FastAPI(depends_default_parallelizable=True) + +async def get_api_key() -> str: + return "secret" + +@app.get("/secure") +async def secure( + k1: Annotated[str, Security(get_api_key, scopes=["a"], parallelizable=True)], + k2: Annotated[str, Security(get_api_key, scopes=["b"], parallelizable=True)], +): + return {"ok": True, "k1": k1, "k2": k2}