Skill v1.0.1
currentAutomated scan100/1002 files
version: "1.0.1" 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:
- `Session` — A FastAPI dependency for endpoints.
- `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.
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()
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 populatedreturn {"data": hero}# FastSQLA auto-commits here
INCORRECT — do not call commit()
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 lifecyclereturn {"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.
from http import HTTPStatusfrom sqlalchemy.exc import IntegrityErrorfrom fastapi import HTTPExceptionfrom 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=HTTPStatus.CONFLICT, 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
IntegrityErrorand cannot be used for further operations. - Use `flush()`, not `commit()`, so the error is caught within the transaction.
- The
HTTPExceptionpropagates 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():
from fastsqla import open_sessionasync 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
- Use `Session` for endpoints — type-annotate a parameter and FastAPI injects it. Never instantiate sessions manually in endpoint code.
- Never call `session.commit()` in endpoints — FastSQLA auto-commits on success. Use
session.flush()to get server-generated values. - Catch `IntegrityError` after `flush()` and re-raise as `HTTPException` — the session is broken after an integrity error; do not attempt further operations on it.
- Use `open_session()` outside endpoints — background tasks, scripts, and other non-request code should use this async context manager.
- Trust the lifecycle — success commits, exceptions roll back, sessions always close. Do not add manual commit/rollback/close calls.