From c8cdbd571ebafa391872c6ce4b6ce439a5557385 Mon Sep 17 00:00:00 2001 From: mohamad Date: Tue, 20 May 2025 01:19:37 +0200 Subject: [PATCH] Add FastAPI database transaction management strategy and update requirements Introduce a new technical specification for managing database transactions in FastAPI, ensuring ACID compliance through standardized practices. The specification outlines transaction handling for API endpoints, CRUD functions, and non-API operations, emphasizing the use of context managers and error handling. Additionally, update the requirements file to include new testing dependencies for async operations, enhancing the testing framework for the application. --- .cursor/rules/fastapi-db-strategy.mdc | 57 +++++++++++++++++++++++++++ be/requirements.txt | 7 +++- 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 .cursor/rules/fastapi-db-strategy.mdc 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