> ## Documentation Index
> Fetch the complete documentation index at: https://fastapi2day.codewithsiva.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Context Managers: Setup, Cleanup & the with Statement

> Learn how Python context managers work, how to build them with classes or generators, and how FastAPI uses the same yield-based mechanism.

A **context manager** is an object that automatically performs setup before a block of code runs and cleanup after it finishes — even if an exception occurs. You've almost certainly used one without realising it: `with open("file.txt") as f:` is Python's most common context manager. Understanding how they work under the hood helps you write safer resource-management code and opens the door to understanding how FastAPI's dependency injection and lifespan events operate.

## The Most Common Context Manager

```python theme={null}
with open("students.txt") as file:
    print(file.read())
```

What happens step by step:

1. `open("students.txt")` creates a **file object**.
2. Python calls `file.__enter__()`, which returns the file object assigned to `file`.
3. The code inside the `with` block executes.
4. When execution leaves the block (normally **or** via an exception), Python automatically calls `file.__exit__()`, which closes the file.

You never need to write `file.close()` — the context manager guarantees it.

## How `with` Works

Any object used with `with` must implement two special methods:

* `__enter__()` — performs setup; its return value is bound to the `as` variable.
* `__exit__(exc_type, exc_value, traceback)` — performs cleanup; receives exception info if one occurred.

Conceptually, this block:

```python theme={null}
with resource as value:
    print(value)
```

works like:

```python theme={null}
value = resource.__enter__()
try:
    print(value)
finally:
    resource.__exit__(None, None, None)
```

## Creating a Class-Based Context Manager

```python theme={null}
class MyContext:
    def __enter__(self):
        print("Setup")
        return "Hello"

    def __exit__(self, exc_type, exc_value, traceback):
        print("Cleanup")

with MyContext() as message:
    print(message)
# Setup
# Hello
# Cleanup
```

The variable after `as` receives whatever `__enter__()` returns.

## Generator-Based Context Managers

Writing a full class for simple set-up/tear-down is often unnecessary. Use the `@contextmanager` decorator from `contextlib` instead:

```python theme={null}
from contextlib import contextmanager

@contextmanager
def my_context():
    print("Setup")
    yield "Hello"
    print("Cleanup")

with my_context() as message:
    print(message)
# Setup
# Hello
# Cleanup
```

### Understanding `yield`

The `yield` divides the generator into two phases:

* **Before `yield`** — runs when entering the `with` block (setup).
* **The yielded value** — becomes the variable after `as`.
* **After `yield`** — runs when leaving the `with` block (cleanup).

## Exception Handling Inside the Generator

When an exception escapes the `with` block, Python does **not** call `next(generator)` to resume it normally. Instead it calls `generator.throw(exception)`, injecting the exception back at the point where the generator was paused.

```python theme={null}
from contextlib import contextmanager

@contextmanager
def my_context():
    try:
        yield "Hello"
        print("After Yield")   # only runs on normal exit
    except Exception as e:
        print("Generator caught:", e)
    finally:
        print("Cleanup")       # always runs

with my_context() as message:
    print(message)
    raise Exception("Error")

# Hello
# Generator caught: Error
# Cleanup
```

`"After Yield"` never prints because the exception was injected at the `yield`, jumping straight to the `except` block.

## Complete Walk-Through

```python theme={null}
from contextlib import contextmanager

@contextmanager
def my_context():
    try:
        data = "my data"
        yield data
        print("after yield")
    except Exception as e:
        print("from generator:", e)
    finally:
        print("in generator finally")

with my_context() as context_data:
    try:
        print(context_data)
        raise Exception("error")
    finally:
        print("in context finally")

# my data
# in context finally
# from generator: error
# in generator finally
```

Execution flow:

<Steps>
  <Step title="Generator is created">
    `generator = my_context()` — nothing executes yet.
  </Step>

  <Step title="Python enters the context">
    `next(generator)` runs until `yield data`. `context_data = "my data"`.
  </Step>

  <Step title="with block executes">
    `print(context_data)` runs, then `raise Exception("error")`.
  </Step>

  <Step title="Context finally runs">
    `"in context finally"` is printed before the exception propagates.
  </Step>

  <Step title="generator.throw() is called">
    Python injects the exception back into the generator at the suspended `yield`.
  </Step>

  <Step title="Generator handles the exception">
    The `except` block catches it and prints `"from generator: error"`.
  </Step>

  <Step title="Generator finally runs">
    `"in generator finally"` is always printed.
  </Step>
</Steps>

## FastAPI Uses the Same Mechanism

FastAPI's `yield`-based dependencies use exactly this pattern:

```python theme={null}
from fastapi import Depends, FastAPI

app = FastAPI()

def get_message():
    print("Creating resource")
    try:
        yield "Hello"
    finally:
        print("Cleaning resource")

@app.get("/")
def home(message: str = Depends(get_message)):
    return {"message": message}
```

FastAPI resumes the generator **after** the endpoint function returns, ensuring cleanup always runs before the HTTP response is sent to the client.

## Key Takeaways

<Accordion title="Summary of context manager rules">
  * A context manager performs setup and cleanup automatically.
  * The `with` statement works with any object implementing `__enter__()` and `__exit__()`.
  * The variable after `as` receives the return value of `__enter__()` (or the yielded value for generator-based managers).
  * `@contextmanager` is the concise way to create custom context managers using a generator function.
  * Normal completion resumes the generator with `next(generator)`.
  * Exceptions resume the generator with `generator.throw(exception)`.
  * Code after `yield` only runs during normal (non-exception) exit.
  * `finally` always runs — making it ideal for guaranteed resource clean-up.
  * FastAPI `yield` dependencies are built on this exact mechanism.
</Accordion>
