diff --git a/.cursor/rules/fastapi-db-strategy.mdc b/.cursor/rules/fastapi-db-strategy.mdc new file mode 100644 index 0000000..41fa743 --- /dev/null +++ b/.cursor/rules/fastapi-db-strategy.mdc @@ -0,0 +1,57 @@ +--- +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. \ No newline at end of file diff --git a/be/requirements.txt b/be/requirements.txt index 86a3eef..9731ae3 100644 --- a/be/requirements.txt +++ b/be/requirements.txt @@ -16,4 +16,9 @@ fastapi-users[sqlalchemy]>=12.1.2 email-validator>=2.0.0 fastapi-users[oauth]>=12.1.2 authlib>=1.3.0 -itsdangerous>=2.1.2 \ No newline at end of file +itsdangerous>=2.1.2 +pytest>=7.4.0 +pytest-asyncio>=0.21.0 +pytest-cov>=4.1.0 +httpx>=0.24.0 # For async HTTP testing +aiosqlite>=0.19.0 # For async SQLite support in tests \ No newline at end of file