-
Notifications
You must be signed in to change notification settings - Fork 1
docs: add fastsqla-session agent skill #34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| --- | ||
| name: fastsqla-session | ||
| description: > | ||
| Manages async SQLAlchemy sessions in FastSQLA endpoints and background tasks. | ||
| Covers the Session dependency (auto-commit/rollback lifecycle), flush vs commit | ||
| rules, IntegrityError handling, and the open_session() context manager. Use when | ||
| writing FastAPI endpoints or background tasks that interact with a database through | ||
| FastSQLA. | ||
| --- | ||
|
|
||
| # FastSQLA Session Management | ||
|
|
||
| FastSQLA provides two ways to get an async SQLAlchemy session: | ||
|
|
||
| 1. **`Session`** — A FastAPI dependency for endpoints. | ||
| 2. **`open_session()`** — An async context manager for non-endpoint code. | ||
|
|
||
| Both follow the same lifecycle: auto-commit on success, auto-rollback on exception, always close. | ||
|
|
||
| --- | ||
|
|
||
| ## Session Dependency | ||
|
|
||
| `Session` is a FastAPI dependency. Type-annotate an endpoint parameter as `Session` and FastAPI injects an async session automatically. | ||
|
|
||
| ```python | ||
| from fastsqla import Session, Item | ||
|
|
||
| @app.get("/users/{user_id}", response_model=Item[UserModel]) | ||
| async def get_user(session: Session, user_id: int): | ||
| user = await session.get(User, user_id) | ||
| return {"data": user} | ||
| ``` | ||
|
|
||
| ### Lifecycle | ||
|
|
||
| | Phase | What happens | | ||
| |-----------|------------------------------------------------------| | ||
| | Success | Session is **committed** automatically | | ||
| | Exception | Session is **rolled back** automatically | | ||
| | Always | Session is **closed**, connection returned to pool | | ||
|
|
||
| You do not need to manage any of this yourself. | ||
|
|
||
| --- | ||
|
|
||
| ## Critical: flush() vs commit() | ||
|
|
||
| **NEVER call `session.commit()` inside an endpoint.** FastSQLA commits automatically when the endpoint returns without error. Calling `commit()` manually breaks the transactional guarantee — if an error occurs after your manual commit, the already-committed changes cannot be rolled back. | ||
|
|
||
| Use `session.flush()` when you need server-generated data (e.g., auto-increment IDs) before the response is returned. Flushing sends pending changes to the database **within the current transaction** without finalizing it. | ||
|
|
||
| ### CORRECT — use flush() | ||
|
|
||
| ```python | ||
| from fastsqla import Session, Item | ||
|
|
||
| @app.post("/heroes", response_model=Item[HeroItem]) | ||
| async def create_hero(session: Session, new_hero: HeroBase): | ||
| hero = Hero(**new_hero.model_dump()) | ||
| session.add(hero) | ||
| await session.flush() # hero.id is now populated | ||
| return {"data": hero} | ||
| # FastSQLA auto-commits here | ||
| ``` | ||
|
|
||
| ### INCORRECT — do not call commit() | ||
|
|
||
| ```python | ||
| from fastsqla import Session, Item | ||
|
|
||
| @app.post("/heroes", response_model=Item[HeroItem]) | ||
| async def create_hero(session: Session, new_hero: HeroBase): | ||
| hero = Hero(**new_hero.model_dump()) | ||
| session.add(hero) | ||
| await session.commit() # WRONG: breaks auto-commit lifecycle | ||
| return {"data": hero} | ||
| ``` | ||
|
|
||
| If you call `commit()` and a later step raises an exception, the committed data **cannot** be rolled back. Let FastSQLA handle the commit. | ||
|
|
||
| --- | ||
|
|
||
| ## IntegrityError Handling | ||
|
|
||
| When a `flush()` triggers a constraint violation (unique, foreign key, etc.), SQLAlchemy raises `IntegrityError`. The session is **invalidated** after this — you cannot continue using it for further queries. | ||
|
|
||
| The correct pattern is to catch `IntegrityError` after `flush()` and re-raise it as an `HTTPException`. The raised exception triggers FastSQLA's automatic rollback. | ||
|
|
||
| ```python | ||
| from sqlalchemy.exc import IntegrityError | ||
| from fastapi import HTTPException | ||
| from fastsqla import Session, Item | ||
|
|
||
| @app.post("/heroes", response_model=Item[HeroItem]) | ||
| async def create_hero(session: Session, new_hero: HeroBase): | ||
| hero = Hero(**new_hero.model_dump()) | ||
| session.add(hero) | ||
| try: | ||
| await session.flush() | ||
| except IntegrityError: | ||
| raise HTTPException(status_code=409, detail="Hero already exists") | ||
| return {"data": hero} | ||
| ``` | ||
|
|
||
| ### Rules for IntegrityError | ||
|
|
||
| - **Always re-raise as an exception.** Do not catch and silently ignore — the session is broken after an `IntegrityError` and cannot be used for further operations. | ||
| - **Use `flush()`, not `commit()`**, so the error is caught within the transaction. | ||
| - The `HTTPException` propagates up, triggering the automatic rollback, which is the correct behavior. | ||
|
|
||
| --- | ||
|
|
||
| ## open_session() | ||
|
|
||
| For code that runs **outside FastAPI endpoints** (background tasks, CLI scripts, scheduled jobs), use `open_session()`: | ||
|
|
||
| ```python | ||
| from fastsqla import open_session | ||
|
|
||
| async def sync_external_data(): | ||
| async with open_session() as session: | ||
| result = await session.execute(select(Hero)) | ||
| heroes = result.scalars().all() | ||
| for hero in heroes: | ||
| hero.synced = True | ||
| # auto-commit on successful exit | ||
| ``` | ||
|
|
||
| ### Lifecycle | ||
|
|
||
| `open_session()` follows the same pattern as the `Session` dependency: | ||
|
|
||
| - **Context body succeeds** — session is committed, then closed. | ||
| - **Context body raises** — session is rolled back, then closed. The exception is re-raised. | ||
| - **Commit itself fails** — session is rolled back, then closed. The commit exception is re-raised. | ||
|
|
||
| The third case is important: if everything in your `async with` block succeeds but the `commit()` call at exit fails (e.g., a deferred constraint violation), `open_session()` rolls back and re-raises the commit exception. You do not get a silent failure. | ||
|
|
||
| --- | ||
|
|
||
| ## Summary Rules | ||
|
|
||
| 1. **Use `Session` for endpoints** — type-annotate a parameter and FastAPI injects it. Never instantiate sessions manually in endpoint code. | ||
| 2. **Never call `session.commit()` in endpoints** — FastSQLA auto-commits on success. Use `session.flush()` to get server-generated values. | ||
| 3. **Catch `IntegrityError` after `flush()` and re-raise as `HTTPException`** — the session is broken after an integrity error; do not attempt further operations on it. | ||
| 4. **Use `open_session()` outside endpoints** — background tasks, scripts, and other non-request code should use this async context manager. | ||
| 5. **Trust the lifecycle** — success commits, exceptions roll back, sessions always close. Do not add manual commit/rollback/close calls. | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use
http.HTTPStatusfrom standard library