Skip to content

Commit ef84b53

Browse files
committed
feat: add fastsqla-session agent skill
1 parent 6e98cf4 commit ef84b53

File tree

1 file changed

+148
-0
lines changed

1 file changed

+148
-0
lines changed

skills/fastsqla-session/SKILL.md

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
---
2+
name: fastsqla-session
3+
description: >
4+
Manages async SQLAlchemy sessions in FastSQLA endpoints and background tasks.
5+
Covers the Session dependency (auto-commit/rollback lifecycle), flush vs commit
6+
rules, IntegrityError handling, and the open_session() context manager. Use when
7+
writing FastAPI endpoints or background tasks that interact with a database through
8+
FastSQLA.
9+
---
10+
11+
# FastSQLA Session Management
12+
13+
FastSQLA provides two ways to get an async SQLAlchemy session:
14+
15+
1. **`Session`** — A FastAPI dependency for endpoints.
16+
2. **`open_session()`** — An async context manager for non-endpoint code.
17+
18+
Both follow the same lifecycle: auto-commit on success, auto-rollback on exception, always close.
19+
20+
---
21+
22+
## Session Dependency
23+
24+
`Session` is a FastAPI dependency. Type-annotate an endpoint parameter as `Session` and FastAPI injects an async session automatically.
25+
26+
```python
27+
from fastsqla import Session, Item
28+
29+
@app.get("/users/{user_id}", response_model=Item[UserModel])
30+
async def get_user(session: Session, user_id: int):
31+
user = await session.get(User, user_id)
32+
return {"data": user}
33+
```
34+
35+
### Lifecycle
36+
37+
| Phase | What happens |
38+
|-----------|------------------------------------------------------|
39+
| Success | Session is **committed** automatically |
40+
| Exception | Session is **rolled back** automatically |
41+
| Always | Session is **closed**, connection returned to pool |
42+
43+
You do not need to manage any of this yourself.
44+
45+
---
46+
47+
## Critical: flush() vs commit()
48+
49+
**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.
50+
51+
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.
52+
53+
### CORRECT — use flush()
54+
55+
```python
56+
from fastsqla import Session, Item
57+
58+
@app.post("/heroes", response_model=Item[HeroItem])
59+
async def create_hero(session: Session, new_hero: HeroBase):
60+
hero = Hero(**new_hero.model_dump())
61+
session.add(hero)
62+
await session.flush() # hero.id is now populated
63+
return {"data": hero}
64+
# FastSQLA auto-commits here
65+
```
66+
67+
### INCORRECT — do not call commit()
68+
69+
```python
70+
from fastsqla import Session, Item
71+
72+
@app.post("/heroes", response_model=Item[HeroItem])
73+
async def create_hero(session: Session, new_hero: HeroBase):
74+
hero = Hero(**new_hero.model_dump())
75+
session.add(hero)
76+
await session.commit() # WRONG: breaks auto-commit lifecycle
77+
return {"data": hero}
78+
```
79+
80+
If you call `commit()` and a later step raises an exception, the committed data **cannot** be rolled back. Let FastSQLA handle the commit.
81+
82+
---
83+
84+
## IntegrityError Handling
85+
86+
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.
87+
88+
The correct pattern is to catch `IntegrityError` after `flush()` and re-raise it as an `HTTPException`. The raised exception triggers FastSQLA's automatic rollback.
89+
90+
```python
91+
from sqlalchemy.exc import IntegrityError
92+
from fastapi import HTTPException
93+
from fastsqla import Session, Item
94+
95+
@app.post("/heroes", response_model=Item[HeroItem])
96+
async def create_hero(session: Session, new_hero: HeroBase):
97+
hero = Hero(**new_hero.model_dump())
98+
session.add(hero)
99+
try:
100+
await session.flush()
101+
except IntegrityError:
102+
raise HTTPException(status_code=409, detail="Hero already exists")
103+
return {"data": hero}
104+
```
105+
106+
### Rules for IntegrityError
107+
108+
- **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.
109+
- **Use `flush()`, not `commit()`**, so the error is caught within the transaction.
110+
- The `HTTPException` propagates up, triggering the automatic rollback, which is the correct behavior.
111+
112+
---
113+
114+
## open_session()
115+
116+
For code that runs **outside FastAPI endpoints** (background tasks, CLI scripts, scheduled jobs), use `open_session()`:
117+
118+
```python
119+
from fastsqla import open_session
120+
121+
async def sync_external_data():
122+
async with open_session() as session:
123+
result = await session.execute(select(Hero))
124+
heroes = result.scalars().all()
125+
for hero in heroes:
126+
hero.synced = True
127+
# auto-commit on successful exit
128+
```
129+
130+
### Lifecycle
131+
132+
`open_session()` follows the same pattern as the `Session` dependency:
133+
134+
- **Context body succeeds** — session is committed, then closed.
135+
- **Context body raises** — session is rolled back, then closed. The exception is re-raised.
136+
- **Commit itself fails** — session is rolled back, then closed. The commit exception is re-raised.
137+
138+
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.
139+
140+
---
141+
142+
## Summary Rules
143+
144+
1. **Use `Session` for endpoints** — type-annotate a parameter and FastAPI injects it. Never instantiate sessions manually in endpoint code.
145+
2. **Never call `session.commit()` in endpoints** — FastSQLA auto-commits on success. Use `session.flush()` to get server-generated values.
146+
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.
147+
4. **Use `open_session()` outside endpoints** — background tasks, scripts, and other non-request code should use this async context manager.
148+
5. **Trust the lifecycle** — success commits, exceptions roll back, sessions always close. Do not add manual commit/rollback/close calls.

0 commit comments

Comments
 (0)