Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 33

Managing Dependencies with `yield`

Recommended resources

Hello! Let's dive into the next piece of FastAPI's powerful Dependency Injection system.

In our last lesson, we saw how to chain dependencies together to compose complex logic, keeping our endpoints clean and focused. We built a dependency graph where one function could rely on the output of another. However, all our dependencies so far have performed an action and simply used return to pass a value.

This brings us to a crucial question: What if a dependency needs to not only set something up (like acquiring a resource) but also clean it up after the request is handled? This is a common requirement for managing database connections, file handles, or network clients.

Today's lesson addresses this directly, fulfilling the learning outcome: Create dependencies with yield for setup and teardown logic, such as managing database sessions. We'll explore how the yield keyword transforms a simple dependency into a powerful lifecycle manager.

Coming from Django, this pattern is conceptually similar to how middleware can wrap a request, performing actions before and after the view logic runs. However, FastAPI's approach allows you to apply this lifecycle management with granular, per-endpoint control, which is a key advantage of its DI system.

1. From return to yield: A New Kind of Dependency

So far, our dependencies have looked like this:

async def get_something():
    value = "some value"
    return value # The function ends here

When you replace return with yield, the function's behavior changes fundamentally. It becomes a generator.

async def manage_resource():
    print("Setting up resource...") # Code before yield runs first
    resource = "My precious resource"
    try:
        yield resource # Pauses and injects 'resource' into the endpoint
    finally:
        print("Cleaning up resource...") # Code after yield runs after the response

FastAPI executes the code up to and including the yield statement. The value yielded is injected into your path operation. The function then pauses. After your path operation has finished and a response is generated, FastAPI resumes the dependency function, executing the code that comes after yield.

This setup/teardown pattern is perfect for managing resources that must be reliably closed or released. The try...finally block is essential here, as it guarantees that the cleanup code in the finally block will run, even if an error occurs within the endpoint.

To get a solid foundation on this syntax, let's turn to the official FastAPI documentation.

Dependencies with yield - FastAPI

The official documentation page, 'Dependencies with yield', is the best place to start. It clearly explains the concept and syntax, showing how to use yield with a try...finally block to manage a database session.

Please read the introduction and the sections 'A database dependency with yield' and 'A dependency with yield and try'. Focus on how the try...finally structure ensures the db.close() call happens.


2. The Canonical Use Case: Managing Database Sessions

As the documentation shows, the most common and critical use case for yield dependencies is managing database sessions. You need to:

  1. Obtain a session from a session factory before the endpoint logic runs.
  2. Provide this session to the endpoint so it can execute queries.
  3. Close the session after the endpoint logic is complete to release the connection back to the pool.

Since FastAPI is built for async programming, let's see how this pattern works with an asynchronous database library like SQLAlchemy with asyncpg.

The following video provides an excellent, practical walkthrough of converting a synchronous database setup to an asynchronous one, and a key part of that process is creating a yield-based dependency for the async session.

Async SQLAlchemy - Async Database Engines, Sessions and FastAPI Dependencies

This video from BugBytes, 'Async SQLAlchemy', demonstrates exactly how to create an async database session dependency. It shows the modern async with syntax which simplifies the try...finally block.

Please watch the segment from 06:21 to 07:01. The narrator modifies the get_session dependency to use async with and yield, which is a concise and modern way to achieve the setup/teardown pattern for async resources.

The key piece of code from the video is:




# The dependency function is now async
async def get_session():



    # 'async with' handles the setup and teardown of the session context
    async with async_session_local() as session:



        # 'yield' provides the session to the path operation
        yield session

This pattern using an async with statement is equivalent to and often cleaner than an explicit async try...finally block for objects that support the async context manager protocol (like SQLAlchemy's async session). It elegantly handles the session's lifecycle.


3. Understanding the Execution Flow

It's vital to be certain about when the cleanup code runs. A blog post on LexxAi provides a very clear explanation of the execution flow, including what happens during an error.

FastAPI Dependency Injection: 21 Examples Using Depends() with ...

This article, 'FastAPI Dependency Injection: 21 Examples', has a great section that breaks down the execution flow of a yield-based dependency with clear print statements, demonstrating what happens during a normal request and an error.

Please read through the Q&A sections and examples. Start with 'Q: The decision of who will run when...'. Pay special attention to Example 06, which shows the normal execution flow, and then look at the 'Solution' section with Example 08, which demonstrates why the try/finally block is so crucial for guaranteeing cleanup during an error.

This resource should solidify your understanding that the finally block (or the exit part of a context manager) is your safety net. It ensures resources are never leaked, which is critical for the stability and scalability of your application.


4. Putting It All Together

Let's consolidate what we've learned into a single, runnable example. We will simulate an async database session dependency and see the print statements that prove the execution order.

import asyncio
from typing import Annotated
from fastapi import Depends, FastAPI

app = FastAPI()




# --- Mock Async Database Session ---
# This class simulates a resource that needs setup and teardown.
class MockAsyncSession:
    def __init__(self):
        self.is_closed = True

    async def connect(self):
        print("▶️ Opening database connection...")
        await asyncio.sleep(0.1) # Simulate network latency
        self.is_closed = False
        print("✅ Connection opened.")

    async def close(self):
        print("▶️ Closing database connection...")
        await asyncio.sleep(0.1)
        self.is_closed = True
        print("❌ Connection closed.")

    async def execute_query(self, query: str):
        if self.is_closed:
            raise RuntimeError("Database session is closed!")
        print(f"Executing query: '{query}'")
        return {"data": "Query results"}




# --- Dependency with yield ---
async def get_db_session():
    """
    Dependency that creates and yields a database session.
    It ensures the session is closed after the request is handled.
    """
    session = MockAsyncSession()
    try:
        await session.connect()
        yield session
    finally:
        await session.close()




# --- Path Operation ---
@app.get("/items")
async def read_items(db: Annotated[MockAsyncSession, Depends(get_db_session)]):
    """
    Endpoint that uses the database session provided by the dependency.
    """
    print("⚙️ Inside endpoint: using the database session.")
    result = await db.execute_query("SELECT * FROM items")
    return result

If you run this code and make a request to http://127.0.0.1:8000/items, you will see the following output in your terminal, demonstrating the exact order of operations:

▶️ Opening database connection...
✅ Connection opened.
⚙️ Inside endpoint: using the database session.
Executing query: 'SELECT * FROM items'
▶️ Closing database connection...
❌ Connection closed.

This clearly shows that the setup logic runs before the endpoint, and the teardown logic runs after.


Conclusion

You've now mastered one of the most important patterns for building robust FastAPI applications. Managing resource lifecycles explicitly within dependencies gives you fine-grained control and improves the reliability of your API.

Key Takeaways:

  • yield for Lifecycles: Use yield in a dependency to perform setup actions before the endpoint runs and teardown actions after it finishes.
  • Guaranteed Cleanup: Always wrap your yield statement in a try...finally block (or use a context manager with with / async with) to ensure the cleanup code runs, even if errors occur.
  • Database Session Management: This pattern is the standard, recommended way to manage database sessions per-request in FastAPI.
  • Analogy to Django: This provides a more explicit and granular alternative to some of Django's implicit request/response cycle hooks or middleware, tying resource management directly to the endpoints that need it.

Preview of the Next Lesson:

We've defined dependencies, chained them, and now made them manage lifecycles. But so far, we've only applied them directly to path operations. What if many endpoints in the same file need a database session? It would be repetitive to add Depends(get_db_session) to every single one.

In our next lesson, "Apply dependencies at different scopes: globally (app), on a router, or for a specific path operation," we will learn how to apply dependencies more efficiently to entire groups of routes, further reducing boilerplate and improving the organization of our project.