Skip to content

Cleanup & Yield Dependencies

Yield Dependencies

Use yield to create dependencies that need cleanup:

def get_db_connection():
    print("Opening connection")
    connection = open_database()
    try:
        yield connection
    finally:
        print("Closing connection")
        connection.close()

async def query_users(db = Depends(get_db_connection)):
    return db.query("SELECT * FROM users")

Automatic Cleanup

When you call call_fn(), cleanup runs automatically after the function returns:

# Cleanup runs automatically after call_fn returns
result = await empty_di_ctx.call_fn(query_users)
# Connection is already closed here

This is the recommended pattern. You don't need to manage context manually.

Async Yield Dependencies

For async cleanup operations:

async def get_async_resource():
    print("Acquiring resource")
    resource = await acquire_resource()
    try:
        yield resource
    finally:
        print("Releasing resource")
        await release_resource(resource)

Cleanup Order (LIFO)

Cleanup happens in reverse order (Last In, First Out):

def resource_a():
    print("Setup A")
    yield "A"
    print("Cleanup A")

def resource_b():
    print("Setup B")
    yield "B"
    print("Cleanup B")

async def my_function(a = Depends(resource_a), b = Depends(resource_b)):
    return f"{a}{b}"

await empty_di_ctx.call_fn(my_function)

# Output:
# Setup A
# Setup B
# Cleanup B  (reversed order)
# Cleanup A

Exception Handling

All cleanups run even if exceptions occur. Exceptions are chained:

def resource_a():
    yield "A"
    raise ValueError("Error in A cleanup")

def resource_b():
    yield "B"
    raise TypeError("Error in B cleanup")

async def my_function(a = Depends(resource_a), b = Depends(resource_b)):
    return f"{a}{b}"

try:
    await empty_di_ctx.call_fn(my_function)
except ValueError as e:
    # ValueError is raised (last cleanup to fail)
    # e.__context__ is TypeError (previous cleanup failure)
    print(f"Error: {e}")
    print(f"Caused by: {e.__context__}")

This matches Python's context manager behavior - all cleanup runs, and exceptions are chained via __context__.

Yield Exactly Once

Dependencies must yield exactly once. The code before yield runs during setup, and the code after yield runs during cleanup.

# Correct: yields exactly once
def get_resource():
    resource = acquire()
    try:
        yield resource
    finally:
        release(resource)

# Wrong: yields twice - cleanup code won't run correctly
def bad_resource():
    yield "first"
    yield "second"  # This breaks cleanup
    actual_cleanup()  # Never runs

If a dependency yields more than once, the cleanup code after the second yield will not execute.