$npx -y skills add ocbunknown/fastapi-claude-template --skill migrationUse when the user asks to create a database migration, change a SQLAlchemy model, add a column, add an index, or otherwise modify the schema. Enforces the rule that migrations are generated via make generate NAME="..." (the project's Makefile target that runs Alembic autogenera
| 1 | # Creating database migrations |
| 2 | |
| 3 | Migrations in this project are generated via the **Makefile**, which wraps Alembic's autogenerate. You never write migration files by hand, and you never edit an existing migration file. |
| 4 | |
| 5 | ## The three Makefile targets |
| 6 | |
| 7 | ```makefile |
| 8 | upgrade: alembic upgrade head # apply all pending migrations |
| 9 | downgrade: alembic downgrade -1 # roll back the latest migration |
| 10 | generate: alembic revision --autogenerate -m "$(NAME)" # create a new migration |
| 11 | ``` |
| 12 | |
| 13 | Run them from the repo root. The generate target requires a `NAME` variable — Alembic uses it as the migration message (and as part of the filename slug). |
| 14 | |
| 15 | ## The standard workflow |
| 16 | |
| 17 | When the user asks for a schema change, follow **this exact order**: |
| 18 | |
| 19 | 1. **Edit the SQLAlchemy model** under `src/database/psql/models/<entity>.py`. Add/remove columns, indices, constraints, relationships — whatever the request needs. |
| 20 | 2. **Update companion types** under `src/database/psql/types/<entity>.py`: |
| 21 | - If a new writable column: add it to `Create<Entity>Type` (required) and/or `Update<Entity>Type` (optional, `total=False`). |
| 22 | - If a new relationship: add its name to `<Entity>Loads = Literal[...]`. |
| 23 | 3. **Generate the migration** via the Makefile — do **not** call `alembic` directly: |
| 24 | ```bash |
| 25 | make generate NAME="add_is_verified_to_user" |
| 26 | ``` |
| 27 | Alembic autogenerate will diff the model metadata against the current DB schema and write a new file to `migrations/versions/NN_<hash>_add_is_verified_to_user.py`. The `NN_` numeric prefix is added by the project's naming convention — Alembic appends the hash + slug. |
| 28 | 4. **Inspect the generated file.** Autogenerate is imperfect — it often misses: |
| 29 | - Column type changes (e.g. `String(32)` → `String(64)` may require manual `alter_column` with `existing_type`) |
| 30 | - Index renames |
| 31 | - Enum value additions (Postgres `ALTER TYPE ... ADD VALUE`) |
| 32 | - `server_default` changes |
| 33 | - Data migrations (filling a new NOT NULL column for existing rows) |
| 34 | |
| 35 | Open the generated file and verify the `upgrade()` and `downgrade()` functions match your intent. If anything is missing or wrong, **edit the generated file now** — it is fresh, uncommitted, not yet applied. Once a migration is applied to dev/staging/prod, it becomes immutable. |
| 36 | 5. **Apply it locally**: |
| 37 | ```bash |
| 38 | make upgrade |
| 39 | ``` |
| 40 | 6. **Test it** — run the relevant integration tests under `tests/integration/` (they use `testcontainers` to spin up a real Postgres and apply migrations). |
| 41 | 7. **Commit the model change, the types change, and the migration file in one commit.** They must never be committed separately — a repo where `models.py` and `migrations/versions/` disagree is broken. |
| 42 | |
| 43 | ## Naming convention |
| 44 | |
| 45 | Pass a **snake_case**, imperative-ish, description to `NAME`: |
| 46 | |
| 47 | | Good | Bad | |
| 48 | |---|---| |
| 49 | | `add_is_verified_to_user` | `"added verified"` | |
| 50 | | `create_widget_table` | `"widget"` | |
| 51 | | `rename_user_email_to_login` | `"rename"` | |
| 52 | | `drop_legacy_permissions_column` | `"cleanup"` | |
| 53 | | `add_gin_index_on_user_login` | `"index"` | |
| 54 | |
| 55 | Keep it under ~50 chars. The slug ends up in the filename and in `git log`, so it should stand alone. |
| 56 | |
| 57 | ## What NOT to do |
| 58 | |
| 59 | - ❌ **Do not** write a new migration file by hand. Always use `make generate NAME="..."`. Autogenerate is the starting point even if you'll have to edit it. |
| 60 | - ❌ **Do not** call `alembic revision --autogenerate` directly. Use the Makefile target so the invocation is consistent across environments. |
| 61 | - ❌ **Do not** edit an existing migration file in `migrations/versions/` (`01_...py`, `02_...py`, etc.). These are **immutable history**. A PreToolUse hook (`.claude/hooks/guard_paths.py`) blocks any edit to these files — if you see the block, you're doing something wrong. If you need to fix a bug in a migration that was already applied, write a **new** migration that corrects it; don't rewrite history. |
| 62 | - ❌ **Do not** edit `migrations/env.py` casually — it's the Alembic bootstrap that the hook also protects. If you genuinely need to change it (e.g., to register a new metadata target), do it outside Claude's edit flow. |
| 63 | - ❌ **Do not** apply a migration without generating + inspecting the file first. Autogenerate output is your audit trail — if you skip it, you lose traceability. |
| 64 | - ❌ **Do not** autogenerate a migration against a schema that's out of sync with `head`. Run `make upgrade` first to bring the DB current, otherwise the diff will include changes from earlier, already-shipped migrations. |
| 65 | |
| 66 | ## When autogenerate is not enough |
| 67 | |
| 68 | Autogenerate is blind to: |
| 69 | |
| 70 | - **Data migrations** — if a new NOT NULL c |