Diagnosing Missing FastAPI Dependency Teardown After Request
Learn how to spot why a FastAPI dependency’s cleanup code (after `yield`) never runs, and follow ordered checks to fix it.
11 Jul 2025, 03:21 UTC

Recognizable condition
You have a FastAPI dependency that uses yield (or an async generator) to provide a resource and run cleanup code after the response is sent. When you call the endpoint, the cleanup code never executes – logs or assertions placed after the yield are silent.
Short cause/diagnostic table
| Condition | Possible cause | Quick check |
|---|---|---|
| Cleanup code never runs | Dependency is a regular function, not a generator | Inspect the dependency definition – does it contain yield or is it declared async def with yield? |
| Cleanup code never runs | Early return or raised exception before the yield point | Add a log or breakpoint at the start of the dependency; see if execution reaches the yield line. |
| Cleanup code never runs | Async dependency used in a sync path operation (or vice‑versa) causing the generator to be ignored | Confirm that the path operation’s async def matches the dependency’s sync/async nature. |
| Cleanup code never runs | Dependency overridden in tests with a non‑generator mock, and the override is leaked to production runs | Check app.dependency_overrides in the test suite; ensure overrides are cleared after each test. |
| Cleanup code never runs | Exception swallowed by a middleware or exception handler that returns a response before the dependency’s finally block runs | Look for middleware that returns early or exception handlers that return Response without re‑raising. |
Ordered checks
- Verify the dependency signature
Open the dependency definition. It must be either:
- A regular function with
yield(sync generator). - An
async deffunction withyield(async generator).
If it lacks
yield, FastAPI treats it as a simple callable and never executes post‑yield code.# Correct sync generator def get_db(): db = SessionLocal() try: yield db finally: db.close() # cleanup runs after response - A regular function with
- Check for early exits
Insert a temporary log or
printbefore theyield. Run a request and see if the log appears. If not, an exception orreturnstopped execution earlier.def get_db(): print("Entering dependency") # temporary check db = SessionLocal() try: yield db finally: print("Cleaning up DB") db.close() - Match sync/async contexts
If the path operation is
async def, a sync generator dependency works, but an async generator dependency must be declaredasync def. Mismatch can cause the generator to be ignored.# Async path operation with async generator dependency @app.get("/items/") async def read_items(db: AsyncSession = Depends(get_async_db)): ... async def get_async_db(): async with AsyncSessionLocal() as session: yield session - Inspect test overrides
In your test file, ensure any
app.dependency_overrides[get_db] = lambda: mock_dbis cleared after the test, e.g., using apytestfixture withapp.dependency_overrides.clear()in a finalizer.import pytest from fastapi.testclient import TestClient @pytest.fixture def client(): original = app.dependency_overrides.get(get_db) app.dependency_overrides[get_db] = lambda: MockSession() with TestClient(app) as c: yield c # teardown: restore original override if original is not None: app.dependency_overrides[get_db] = original else: app.dependency_overrides.pop(get_db, None) - Look for middleware that short‑circuits
Any middleware that returns a
Responsebefore callingcall_nextprevents the dependency’s finally block from running. Review middleware for early returns.# Problematic middleware async def block_middleware(request: Request, call_next): if request.url.path.startswith("/block"): return Response("Blocked", status_code=403) # skips dependencies return await call_next(request)
Fixes tied to findings
- Missing
yield: Convert the dependency to a generator (sync or async) as shown in the first check. - Early exit: Move cleanup logic into a
try: ... finally:block so it runs even if an exception occurs. - Sync/async mismatch: Align the dependency’s async nature with the path operation, or wrap a sync dependency in
Dependswithasync defif needed. - Test override leakage: Use fixtures to set and clear
app.dependency_overrides, or useoverride_dependencycontext manager fromfastapi.testclient. - Middleware short‑circuit: Ensure middleware calls
await call_next(request)for all paths that should execute dependencies, or move early‑return logic to a route‑level dependency.
Escalation criteria
If after performing all five checks the cleanup code still does not run:
- Enable FastAPI’s debug mode (
debug=True) and look for warnings about dependency resolution. - Check the version of FastAPI and any plugins (e.g.,
fastapi-utils,SQLModel) that might wrap dependencies. - Create a minimal reproducible example (a single file with the dependency, a route, and a test) and verify the behavior in isolation.
- If the issue persists, consider reporting it to the FastAPI repository with the minimal example, noting the FastAPI version and Python version.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.