|
1 | 1 | # Alembic Setup |
2 | 2 |
|
3 | | -FastAPI Admin Kit does not bundle Alembic. This guide shows how to set up Alembic in your project to manage database migrations for both your models and the admin tables. |
| 3 | +FastAPI Admin Kit provides built-in models for authentication (users, roles, permissions) and audit logging. This guide shows how to set up Alembic to manage database migrations for both admin tables and your application models. |
4 | 4 |
|
5 | | -## Install Alembic |
| 5 | +## Quick Start with `fak init-alembic` |
| 6 | + |
| 7 | +The easiest way to get started is using the built-in CLI command: |
6 | 8 |
|
7 | 9 | ```bash |
8 | | -pip install alembic |
| 10 | +# For a new project |
| 11 | +fak init-alembic --app myapp:app --auto-migrate |
| 12 | + |
| 13 | +# For an existing database (baseline migration) |
| 14 | +fak init-alembic --app myapp:app --baseline |
9 | 15 | ``` |
10 | 16 |
|
11 | | -## Initialize Alembic |
| 17 | +This command: |
| 18 | +1. Creates `alembic.ini` with proper configuration |
| 19 | +2. Creates `alembic/env.py` that imports admin models from `fastapi_admin_kit.migrations.models` |
| 20 | +3. Creates `alembic/script.py.mako` template |
| 21 | +4. Optionally auto-generates the initial migration (`--auto-migrate`) |
| 22 | +5. Optionally creates a baseline migration for existing databases (`--baseline`) |
| 23 | + |
| 24 | +### What `init-alembic` Does |
12 | 25 |
|
13 | | -```bash |
14 | | -alembic init alembic |
| 26 | +``` |
| 27 | +myproject/ |
| 28 | +├── alembic.ini # Alembic configuration |
| 29 | +├── alembic/ |
| 30 | +│ ├── env.py # Migration environment (imports admin models) |
| 31 | +│ ├── script.py.mako # Migration template |
| 32 | +│ └── versions/ # Migration scripts |
15 | 33 | ``` |
16 | 34 |
|
17 | | -## Configure `alembic.ini` |
| 35 | +The generated `alembic/env.py` includes: |
18 | 36 |
|
19 | | -Set the database URL: |
| 37 | +```python |
| 38 | +# Import admin models (materialized from schemas) |
| 39 | +from fastapi_admin_kit.migrations.models import Base as AdminBase |
20 | 40 |
|
21 | | -```ini |
22 | | -[alembic] |
23 | | -script_location = alembic |
24 | | -sqlalchemy.url = sqlite+aiosqlite:///./your_database.db |
| 41 | +# Import your app models |
| 42 | +# from myapp.models import Base as AppBase |
| 43 | + |
| 44 | +# Combine metadata for autogenerate |
| 45 | +target_metadata = [AdminBase.metadata] |
| 46 | +# target_metadata.append(AppBase.metadata) # Add your models |
25 | 47 | ``` |
26 | 48 |
|
27 | | -For PostgreSQL: |
| 49 | +## Manual Alembic Setup |
28 | 50 |
|
29 | | -```ini |
30 | | -sqlalchemy.url = postgresql+asyncpg://user:password@localhost:5432/your_database |
| 51 | +If you prefer manual setup or have an existing Alembic configuration: |
| 52 | + |
| 53 | +### 1. Install Alembic |
| 54 | + |
| 55 | +```bash |
| 56 | +pip install alembic |
| 57 | +``` |
| 58 | + |
| 59 | +### 2. Initialize Alembic |
| 60 | + |
| 61 | +```bash |
| 62 | +alembic init alembic |
31 | 63 | ``` |
32 | 64 |
|
33 | | -## Configure `alembic/env.py` |
| 65 | +### 3. Configure `alembic/env.py` |
34 | 66 |
|
35 | 67 | Replace the contents of `alembic/env.py`: |
36 | 68 |
|
37 | 69 | ```python |
38 | 70 | import asyncio |
39 | 71 | import sys |
40 | | -from pathlib import Path |
41 | 72 | from logging.config import fileConfig |
| 73 | +from pathlib import Path |
42 | 74 |
|
43 | 75 | from sqlalchemy import pool |
44 | 76 | from sqlalchemy.engine import Connection |
45 | 77 | from sqlalchemy.ext.asyncio import async_engine_from_config |
46 | 78 |
|
47 | 79 | from alembic import context |
48 | 80 |
|
| 81 | +# Add project root to path |
49 | 82 | sys.path.insert(0, str(Path(__file__).resolve().parents[1])) |
50 | 83 |
|
51 | | -from models import Base as UserBase |
52 | | -from fastapi_admin_kit.models.base import Base as AdminBase |
| 84 | +# Import admin models (materialized from schemas) |
| 85 | +from fastapi_admin_kit.migrations.models import Base as AdminBase |
53 | 86 |
|
54 | | -target_metadata = UserBase.metadata |
55 | | -for table in AdminBase.metadata.tables.values(): |
56 | | - if table.name not in target_metadata.tables: |
57 | | - target_metadata._add_table(table.name, table.schema, table) |
| 87 | +# Import your application models |
| 88 | +# from myapp.models import Base as AppBase |
| 89 | + |
| 90 | +# Combine metadata for autogenerate |
| 91 | +target_metadata = [AdminBase.metadata] |
| 92 | +# target_metadata.append(AppBase.metadata) # Add your models |
58 | 93 |
|
59 | 94 | config = context.config |
60 | 95 |
|
@@ -101,63 +136,174 @@ else: |
101 | 136 | run_migrations_online() |
102 | 137 | ``` |
103 | 138 |
|
104 | | -Adjust the import to match your project: |
| 139 | +### 4. Configure Database URL |
105 | 140 |
|
106 | | -```python |
107 | | -# If models are in app/models.py |
108 | | -from app.models import Base as UserBase |
| 141 | +Edit `alembic.ini`: |
109 | 142 |
|
110 | | -# If models are in src/models/__init__.py |
111 | | -from src.models import Base as UserBase |
| 143 | +```ini |
| 144 | +[alembic] |
| 145 | +script_location = alembic |
| 146 | +sqlalchemy.url = sqlite+aiosqlite:///./your_database.db |
| 147 | +# For PostgreSQL: |
| 148 | +# sqlalchemy.url = postgresql+asyncpg://user:pass@localhost:5432/dbname |
112 | 149 | ``` |
113 | 150 |
|
114 | | -## Generate Initial Migration |
| 151 | +### 5. Generate Initial Migration |
115 | 152 |
|
116 | 153 | ```bash |
117 | | -alembic revision --autogenerate -m "initial schema" |
| 154 | +alembic revision --autogenerate -m "init admin models" |
118 | 155 | ``` |
119 | 156 |
|
120 | | -## Apply Migration |
| 157 | +### 6. Apply Migrations |
121 | 158 |
|
122 | 159 | ```bash |
123 | 160 | alembic upgrade head |
124 | 161 | ``` |
125 | 162 |
|
126 | | -## Sync Engine |
| 163 | +## Production vs Development Mode |
| 164 | + |
| 165 | +FastAPI Admin Kit supports two modes: |
| 166 | + |
| 167 | +| Mode | Setting | Behavior | |
| 168 | +|------|---------|----------| |
| 169 | +| **Development** (default) | `use_alembic=False` | Uses `create_all()` + auto-migration (adds missing columns) | |
| 170 | +| **Production** | `use_alembic=True` | Expects Alembic to manage schema; skips `create_all()` | |
127 | 171 |
|
128 | | -For synchronous engines, replace `run_migrations_online`: |
| 172 | +### In Your Application |
129 | 173 |
|
130 | 174 | ```python |
131 | | -from sqlalchemy import engine_from_config |
| 175 | +from fastapi_admin_kit import Admin |
| 176 | +from fastapi_admin_kit.config import BehaviorConfig |
| 177 | + |
| 178 | +admin = Admin( |
| 179 | + app=app, |
| 180 | + engine=engine, |
| 181 | + # ... other config ... |
| 182 | + behavior=BehaviorConfig(use_alembic=True), # Production mode |
| 183 | +) |
| 184 | +``` |
132 | 185 |
|
133 | | -def run_migrations_online() -> None: |
134 | | - connectable = engine_from_config( |
135 | | - config.get_section(config.config_ini_section, {}), |
136 | | - prefix="sqlalchemy.", |
137 | | - poolclass=pool.NullPool, |
138 | | - ) |
139 | | - with connectable.connect() as connection: |
140 | | - context.configure(connection=connection, target_metadata=target_metadata) |
141 | | - with context.begin_transaction(): |
142 | | - context.run_migrations() |
| 186 | +### In Lifespan (Production) |
| 187 | + |
| 188 | +```python |
| 189 | +@asynccontextmanager |
| 190 | +async def lifespan(app: FastAPI): |
| 191 | + # Run alembic upgrade head on startup |
| 192 | + from alembic.config import Config |
| 193 | + from alembic import command |
| 194 | + |
| 195 | + alembic_cfg = Config("alembic.ini") |
| 196 | + command.upgrade(alembic_cfg, "head") |
| 197 | + |
| 198 | + await admin.setup(app) |
| 199 | + yield |
143 | 200 | ``` |
144 | 201 |
|
145 | | -## Existing Tables |
| 202 | +## CLI Commands |
146 | 203 |
|
147 | | -If tables were created with `create_all()` before Alembic: |
| 204 | +### Initialize Alembic |
148 | 205 |
|
149 | 206 | ```bash |
| 207 | +# New project with auto-generated initial migration |
| 208 | +fak init-alembic --app myapp:app --auto-migrate |
| 209 | + |
| 210 | +# Existing project with database (creates baseline) |
| 211 | +fak init-alembic --app myapp:app --baseline |
| 212 | + |
| 213 | +# Force overwrite existing alembic config |
| 214 | +fak init-alembic --app myapp:app --force |
| 215 | +``` |
| 216 | + |
| 217 | +### Run Migrations (Production) |
| 218 | + |
| 219 | +```bash |
| 220 | +# Run all pending migrations (equivalent to alembic upgrade head) |
| 221 | +fak migrate-alembic |
| 222 | + |
| 223 | +# Run to specific revision |
| 224 | +fak migrate-alembic <revision> |
| 225 | + |
| 226 | +# Use custom app path to find alembic.ini |
| 227 | +fak migrate-alembic --app myapp:app |
| 228 | +``` |
| 229 | + |
| 230 | +### Dev Mode Migrations (Legacy) |
| 231 | + |
| 232 | +```bash |
| 233 | +# Add missing columns / recreate tables (dev only) |
| 234 | +fak migrate User Product |
| 235 | + |
| 236 | +# Convert old permissions format |
| 237 | +fak migrate-permissions |
| 238 | +``` |
| 239 | + |
| 240 | +## Existing Database Migration (Baseline) |
| 241 | + |
| 242 | +If you have an existing database created with `create_all()`: |
| 243 | + |
| 244 | +```bash |
| 245 | +# Create baseline migration and stamp as applied |
| 246 | +fak init-alembic --app myapp:app --baseline |
| 247 | + |
| 248 | +# Or manually: |
| 249 | +alembic revision -m "baseline_existing_schema" |
| 250 | +# Edit the migration to match your current schema |
150 | 251 | alembic stamp head |
151 | 252 | ``` |
152 | 253 |
|
153 | | -## Verify Admin Tables |
| 254 | +## Adding Your Models to Migrations |
| 255 | + |
| 256 | +In `alembic/env.py`, add your application's metadata: |
154 | 257 |
|
155 | 258 | ```python |
156 | | -from fastapi_admin_kit.models.base import Base as AdminBase |
157 | | -print(list(AdminBase.metadata.tables.keys())) |
| 259 | +from fastapi_admin_kit.migrations.models import Base as AdminBase |
| 260 | +from myapp.models import Base as AppBase # Your models |
| 261 | + |
| 262 | +target_metadata = [AdminBase.metadata, AppBase.metadata] |
158 | 263 | ``` |
159 | 264 |
|
| 265 | +Then autogenerate will include both admin and app tables: |
| 266 | + |
| 267 | +```bash |
| 268 | +alembic revision --autogenerate -m "add product table" |
| 269 | +``` |
| 270 | + |
| 271 | +## Junction Tables |
| 272 | + |
| 273 | +The admin models include two junction tables for many-to-many relationships: |
| 274 | +- `admin_user_roles` — User ↔ Role |
| 275 | +- `admin_role_permissions` — Role ↔ Permission |
| 276 | + |
| 277 | +These are automatically created via SQLAlchemy relationships and included in migrations. |
| 278 | + |
| 279 | +## Troubleshooting |
| 280 | + |
| 281 | +### "Table already exists" on initial migration |
| 282 | + |
| 283 | +If tables were created via `create_all()` before Alembic: |
| 284 | + |
| 285 | +```bash |
| 286 | +# Option 1: Baseline (recommended) |
| 287 | +fak init-alembic --baseline |
| 288 | + |
| 289 | +# Option 2: Stamp head manually |
| 290 | +alembic stamp head |
| 291 | +``` |
| 292 | + |
| 293 | +### Import errors in `env.py` |
| 294 | + |
| 295 | +Ensure your project root is in `sys.path`: |
| 296 | + |
| 297 | +```python |
| 298 | +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) |
| 299 | +``` |
| 300 | + |
| 301 | +### Async engine issues |
| 302 | + |
| 303 | +The generated `env.py` uses `async_engine_from_config` for async migrations. For sync engines, use the sync variant in the template. |
| 304 | + |
160 | 305 | ## Next Steps |
161 | 306 |
|
162 | | -- [Model Registration](model-registration.md) |
163 | | -- [Authentication & RBAC](auth-rbac.md) |
| 307 | +- [Model Registration](../guide/model-registration.md) — Register your models with the admin |
| 308 | +- [Authentication & RBAC](../guide/auth-rbac.md) — Set up roles and permissions |
| 309 | +- [Existing Alembic Integration](./existing-alembic-integration.md) — Integrate with existing Alembic setup |
0 commit comments