Skip to content

Commit 830d614

Browse files
committed
feat: add fastsqla-setup agent skill
1 parent 6e98cf4 commit 830d614

File tree

1 file changed

+151
-0
lines changed

1 file changed

+151
-0
lines changed

skills/fastsqla-setup/SKILL.md

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
---
2+
name: fastsqla-setup
3+
description: >
4+
How to install and configure FastSQLA with FastAPI. Covers pip installation,
5+
async driver selection, environment variable configuration via fastsqla.lifespan,
6+
programmatic configuration via new_lifespan(), and composing multiple lifespans
7+
with AsyncExitStack.
8+
---
9+
10+
# FastSQLA Setup
11+
12+
FastSQLA is an async SQLAlchemy 2.0+ extension for FastAPI. It provides session
13+
management, pagination, and deferred table reflection out of the box.
14+
15+
The entire library is a single module (`fastsqla`).
16+
17+
## Installation
18+
19+
```bash
20+
pip install FastSQLA
21+
```
22+
23+
### Optional: SQLModel support
24+
25+
```bash
26+
pip install FastSQLA[sqlmodel]
27+
```
28+
29+
When SQLModel is installed, FastSQLA automatically uses `sqlmodel.ext.asyncio.session.AsyncSession`
30+
instead of SQLAlchemy's `AsyncSession`.
31+
32+
### Requirements
33+
34+
- Python >= 3.12
35+
- FastAPI >= 0.115.6
36+
- SQLAlchemy[asyncio] >= 2.0.37
37+
38+
### Async database driver
39+
40+
You **must** install an async driver for your database. The driver determines the URL
41+
scheme:
42+
43+
| Database | Driver | Install | URL scheme |
44+
|------------|------------|------------------------|---------------------------|
45+
| PostgreSQL | asyncpg | `pip install asyncpg` | `postgresql+asyncpg://` |
46+
| SQLite | aiosqlite | `pip install aiosqlite`| `sqlite+aiosqlite:///` |
47+
| MySQL | aiomysql | `pip install aiomysql` | `mysql+aiomysql://` |
48+
49+
## Configuration
50+
51+
There are two ways to configure FastSQLA: environment variables or programmatic.
52+
53+
### Option 1: Environment variable configuration
54+
55+
Use `fastsqla.lifespan` for 12-factor app style configuration. All configuration is
56+
read from environment variables at startup.
57+
58+
```python
59+
from fastapi import FastAPI
60+
from fastsqla import lifespan
61+
62+
app = FastAPI(lifespan=lifespan)
63+
```
64+
65+
Set environment variables:
66+
67+
```bash
68+
export SQLALCHEMY_URL=postgresql+asyncpg://user:pass@localhost/mydb
69+
export SQLALCHEMY_POOL_SIZE=20
70+
export SQLALCHEMY_MAX_OVERFLOW=10
71+
export SQLALCHEMY_ECHO=true
72+
```
73+
74+
**How it works:** All environment variables prefixed with `SQLALCHEMY_` are collected
75+
(case-insensitive) and passed to SQLAlchemy's `async_engine_from_config()` with prefix
76+
`sqlalchemy_`. This means any `create_async_engine` parameter can be set via env var
77+
using the `SQLALCHEMY_` prefix.
78+
79+
**Required:** `SQLALCHEMY_URL` must be set. If missing, startup raises:
80+
`Missing sqlalchemy_url in environ.`
81+
82+
**Warning:** Stray `SQLALCHEMY_*` environment variables (e.g. from another app or a
83+
typo) will be passed to the engine factory and can cause unexpected errors. Keep your
84+
environment clean.
85+
86+
### Option 2: Programmatic configuration
87+
88+
Use `new_lifespan()` when you want to pass configuration directly in code. It accepts
89+
the same arguments as SQLAlchemy's `create_async_engine()`.
90+
91+
```python
92+
from fastapi import FastAPI
93+
from fastsqla import new_lifespan
94+
95+
lifespan = new_lifespan(
96+
"sqlite+aiosqlite:///app/db.sqlite",
97+
connect_args={"autocommit": False},
98+
)
99+
100+
app = FastAPI(lifespan=lifespan)
101+
```
102+
103+
When using `new_lifespan()` with arguments, environment variables are **not** read.
104+
105+
`new_lifespan()` called with no arguments returns the same env-var-based lifespan as
106+
`fastsqla.lifespan`.
107+
108+
## Composing multiple lifespans
109+
110+
If your app has multiple lifespan contexts (e.g. FastSQLA + another library), compose
111+
them with `AsyncExitStack`:
112+
113+
```python
114+
from collections.abc import AsyncGenerator
115+
from contextlib import AsyncExitStack, asynccontextmanager
116+
117+
from fastapi import FastAPI
118+
from fastsqla import lifespan as fastsqla_lifespan
119+
from other_library import lifespan as other_lifespan
120+
121+
122+
@asynccontextmanager
123+
async def lifespan(app: FastAPI) -> AsyncGenerator[dict, None]:
124+
async with AsyncExitStack() as stack:
125+
state1 = await stack.enter_async_context(fastsqla_lifespan(app))
126+
state2 = await stack.enter_async_context(other_lifespan(app))
127+
yield {**state1, **state2}
128+
129+
130+
app = FastAPI(lifespan=lifespan)
131+
```
132+
133+
Each lifespan returns a state dict. FastSQLA's lifespan returns
134+
`{"fastsqla_engine": <AsyncEngine>}`. Merge them so FastAPI's `request.state` has all
135+
keys.
136+
137+
## What the lifespan does
138+
139+
On **startup**:
140+
141+
1. Creates an `AsyncEngine` via `async_engine_from_config()` (env var path) or
142+
`create_async_engine()` (programmatic path).
143+
2. Calls `Base.prepare()` inside a connection — this triggers SQLAlchemy's
144+
`DeferredReflection`, reflecting table metadata from the database for any model
145+
inheriting from `fastsqla.Base`.
146+
3. Binds the engine to the shared `SessionFactory` (`async_sessionmaker`).
147+
148+
On **shutdown**:
149+
150+
1. Unbinds the `SessionFactory` (sets `bind=None`).
151+
2. Disposes the engine, closing all pooled connections.

0 commit comments

Comments
 (0)