Skip to main content

Caching and Dependency Scopes

When you use the same dependency in multiple places within a single request—for example, a database session used by both a security dependency and your path operation—FastAPI avoids redundant work by caching the result. By default, the dependency is executed once, and its return value is shared across all points of injection in that request.

Dependency Caching

FastAPI uses the use_cache parameter in the Depends class to control this behavior. Internally, the Dependant class (found in fastapi.dependencies.models) generates a cache_key based on the dependency callable and its security scopes. If a result for that key exists in the request's internal cache, FastAPI returns the cached value instead of calling the dependency again.

Disabling the Cache

If your dependency performs an action that must occur every time it is referenced—such as incrementing a counter or logging a specific event—you can disable caching by setting use_cache=False.

In this example from tests/test_dependency_cache.py, the dep_counter dependency is forced to run twice in a single request because the second injection explicitly opts out of the cache:

from fastapi import Depends, FastAPI

app = FastAPI()
counter_holder = {"counter": 0}

async def dep_counter():
counter_holder["counter"] += 1
return counter_holder["counter"]

async def super_dep(count: int = Depends(dep_counter)):
return count

@app.get("/sub-counter-no-cache/")
async def get_sub_counter_no_cache(
subcount: int = Depends(super_dep),
count: int = Depends(dep_counter, use_cache=False),
):
# subcount will be 1 (from the first call via super_dep)
# count will be 2 (because use_cache=False forced a second call)
return {"counter": count, "subcounter": subcount}

Dependency Scopes and Lifecycles

While standard dependencies return a value and finish, generator dependencies (using yield) have a lifecycle that spans the execution of your path operation. FastAPI manages this lifecycle using the scope parameter.

The Dependant.computed_scope property determines the effective scope:

  • Standard dependencies: Have no default scope (None).
  • Generator dependencies: Default to "request" scope.

Request Scope vs. Function Scope

The choice of scope is critical when dealing with StreamingResponse or background tasks.

  1. request scope (Default for generators): The dependency is torn down only after the entire response has been sent to the client. This is necessary if your response is a stream that relies on a resource (like a database connection) provided by the dependency.
  2. function scope: The dependency is torn down as soon as the path operation function returns, even if a StreamingResponse is still sending data.

You can specify the scope using Annotated for cleaner code, as seen in tests/test_dependency_yield_scope.py:

from typing import Annotated, Any
from fastapi import Depends, FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()

def dep_session():
s = Session()
yield s
s.close() # This runs based on the scope

# Define reusable dependencies with specific scopes
SessionFuncDep = Annotated[Session, Depends(dep_session, scope="function")]
SessionRequestDep = Annotated[Session, Depends(dep_session, scope="request")]

@app.get("/scopes")
def get_stream(
func_sess: SessionFuncDep,
req_sess: SessionRequestDep
):
def iter_data():
# func_sess is already closed here because the path operation returned
# req_sess is still open because it has "request" scope
yield "data"

return StreamingResponse(iter_data())

Scope Constraints and Errors

FastAPI enforces a hierarchy to prevent resource leaks and logic errors. A dependency with a longer-lived scope cannot depend on one with a shorter-lived scope.

Specifically, if a generator dependency has a "request" scope, it cannot depend on a dependency with a "function" scope. If you attempt this, FastAPI will raise a DependencyScopeError during application startup. This check is implemented in fastapi/dependencies/utils.py:

if (
(dependant.is_gen_callable or dependant.is_async_gen_callable)
and dependant.computed_scope == "request"
and param_details.depends.scope == "function"
):
raise DependencyScopeError(
f'The dependency "{call_name}" has a scope of '
'"request", it cannot depend on dependencies with scope "function".'
)

This ensures that a resource intended to last for the whole request doesn't accidentally rely on a resource that is destroyed as soon as the initial function call completes.