← Python Code FastAPI
Browse Python Concepts

Depends() — FastAPI Dependency Injection Caching

Mental Model

FastAPI's dependency injection acts like a smart memoization system per request. Once a dependency function is executed for a given request, its result is cached and reused for all subsequent calls to Depends() on that same function within the same request lifecycle.

Rule: When you need unique, isolated instances of dynamic objects within a single request context, disable caching with use_cache=False.

The Setup

You build a dependency injection system where a unique transaction context or a configuration dictionary is generated for audit trails. Multiple sub-dependencies require this tracking object, and they assume they are working with clean, unmutated instances.

What Does This Print?

Broken code
Python
from fastapi import FastAPI, Depends
import uuid

app = FastAPI()

async def get_transaction_id():
    return uuid.uuid4()

async def process_step_one(tx_id: uuid.UUID = Depends(get_transaction_id)):
    # Mutating string value to simulate step execution logic modification
    return {"step": 1, "tx_id": tx_id}

@app.get("/checkout")
async def checkout(
    step_one: dict = Depends(process_step_one),
    raw_tx_id: uuid.UUID = Depends(get_transaction_id)
):
    return {"step_one": step_one, "raw_tx_id": raw_tx_id}
Predict whether step_one['tx_id'] and raw_tx_id will have different values or the same UUID value.

The Output

What actually happens
{ "step_one": { "step": 1, "tx_id": "b83c706d-bc16-43e6-9963-f22295553e1a" }, "raw_tx_id": "b83c706d-bc16-43e6-9963-f22295553e1a" }

Both fields return the exact same UUID value: FastAPI cached the execution of get_transaction_id during the request evaluation and returned the same cached UUID instance to both parameter injectors.

Why Python Does This

FastAPI uses dependency caching by default (use_cache=True). When resolving route signatures, it traverses the dependency DAG (Directed Acyclic Graph). For each node, it checks if a dependency has already been resolved for the current request context. If so, it skips execution of that dependency function and injects the cached result. This prevents duplicate database connections or auth verifications, but if your dependency returns a mutable connection state or expects initialization per-injection, this caching creates shared state contamination.

The Fix

Corrected pattern
Python
from fastapi import FastAPI, Depends
import uuid

app = FastAPI()

async def get_transaction_id():
    return uuid.uuid4()

async def process_step_one(tx_id: uuid.UUID = Depends(get_transaction_id, use_cache=False)):
    return {"step": 1, "tx_id": tx_id}

@app.get("/checkout")
async def checkout(
    step_one: dict = Depends(process_step_one),
    # Disabling caching forces FastAPI to execute the dependency function on each injection
    raw_tx_id: uuid.UUID = Depends(get_transaction_id, use_cache=False)
):
    return {"step_one": step_one, "raw_tx_id": raw_tx_id}

Setting use_cache=False in Depends() explicitly instructs FastAPI to always execute the dependency function get_transaction_id regardless of whether it has been called before in the current request. This ensures that a new, unique UUID is generated each time get_transaction_id is used as a dependency.

How This Fails in Real Systems

A multi-tenant application injected a database session with different access permissions for separate execution blocks within a single request. Due to dependency caching, the restricted user token inherited the write access privileges of the administrative sub-dependency, allowing unauthorized database writes.

Key Takeaway

When you need unique, isolated instances of dynamic objects within a single request context, disable caching with use_cache=False.
Common mistake: Assuming Depends() will always re-execute the dependency function for every parameter that declares it, even within the same request scope.