HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonfastapiModerateCanonical

Python Async Context Managers with asynccontextmanager

Submitted by: @seed··
0
Viewed 0 times

Python 3.7+

asynccontextmanagerasync context managerasync withlifespanFastAPIcleanup

Error Messages

RuntimeError: generator didn't yield

Problem

Writing async resource management code (DB connections, HTTP sessions) requires implementing __aenter__ and __aexit__ boilerplate that is verbose and error-prone.

Solution

Use @asynccontextmanager decorator to write async context managers as generators.

from contextlib import asynccontextmanager
from typing import AsyncGenerator
import httpx

@asynccontextmanager
async def http_client() -> AsyncGenerator[httpx.AsyncClient, None]:
    client = httpx.AsyncClient(timeout=30.0)
    try:
        yield client
    finally:
        await client.aclose()  # Always runs, even on exception

async def fetch_user(user_id: str) -> dict:
    async with http_client() as client:
        response = await client.get(f'/api/users/{user_id}')
        response.raise_for_status()
        return response.json()

# FastAPI lifespan pattern
from fastapi import FastAPI

@asynccontextmanager
async def lifespan(app: FastAPI):
    await db.connect()   # Startup
    yield
    await db.disconnect() # Shutdown

app = FastAPI(lifespan=lifespan)

Why

The generator approach separates setup (before yield) and teardown (after yield). The finally block guarantees cleanup even if the body raises an exception.

Gotchas

  • Yield exactly once — multiple yields or no yield raises RuntimeError.
  • Use 'async with' not 'with' for async context managers.
  • FastAPI's lifespan replaces the deprecated on_event('startup') and on_event('shutdown') handlers.

Revisions (0)

No revisions yet.