--- description: FastAPI Database Transactions globs: alwaysApply: false --- ## FastAPI Database Transaction Management: Technical Specification **Objective:** Ensure atomic, consistent, isolated, and durable (ACID) database operations through a standardized transaction management strategy. **1. API Endpoint Transaction Scope (Primary Strategy):** * **Mechanism:** A FastAPI dependency `get_transactional_session` (from `app.database` or `app.core.dependencies`) wraps database-modifying API request handlers. * **Behavior:** * `async with AsyncSessionLocal() as session:` obtains a session. * `async with session.begin():` starts a transaction. * **Commit:** Automatic on successful completion of the `yield session` block (i.e., endpoint handler success). * **Rollback:** Automatic on any exception raised from the `yield session` block. * **Usage:** Endpoints performing CUD (Create, Update, Delete) operations **MUST** use `db: AsyncSession = Depends(get_transactional_session)`. * **Read-Only Endpoints:** May use `get_async_session` (alias `get_db`) or `get_transactional_session` (results in an empty transaction). **2. CRUD Layer Function Design:** * **Transaction Participation:** CRUD functions (in `app/crud/`) operate on the session provided by the caller. * **Composability Pattern:** Employ `async with db.begin_nested() if db.in_transaction() else db.begin():` to wrap database modification logic within the CRUD function. * If an outer transaction exists (e.g., from `get_transactional_session`), `begin_nested()` creates a **savepoint**. The `async with` block commits/rolls back this savepoint. * If no outer transaction exists (e.g., direct call from a script), `begin()` starts a **new transaction**. The `async with` block commits/rolls back this transaction. * **NO Direct `db.commit()` / `db.rollback()`:** CRUD functions **MUST NOT** call these directly. The `async with begin_nested()/begin()` block and the outermost transaction manager are responsible. * **`await db.flush()`:** Use only when necessary within the `async with` block to: 1. Obtain auto-generated IDs for subsequent operations in the *same* transaction. 2. Force database constraint checks mid-transaction. * **Error Handling:** Raise specific custom exceptions (e.g., `ListNotFoundError`, `DatabaseIntegrityError`). These exceptions will trigger rollbacks in the managing transaction contexts. **3. Non-API Operations (Background Tasks, Scripts):** * **Explicit Management:** These contexts **MUST** manage their own session and transaction lifecycles. * **Pattern:** ```python async with AsyncSessionLocal() as session: async with session.begin(): # Manages transaction for the task's scope try: # Call CRUD functions, which will participate via savepoints await crud_operation_1(db=session, ...) await crud_operation_2(db=session, ...) # Commit is handled by session.begin() context manager on success except Exception: # Rollback is handled by session.begin() context manager on error raise ``` **4. Key Principles Summary:** * **API:** `get_transactional_session` for CUD. * **CRUD:** Use `async with db.begin_nested() if db.in_transaction() else db.begin():`. No direct commit/rollback. Use `flush()` strategically. * **Background Tasks:** Explicit `AsyncSessionLocal()` and `session.begin()` context managers. This strategy ensures a clear separation of concerns, promotes composable CRUD operations, and centralizes final transaction control at the appropriate layer.