---
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.