Skip to main content

cli-recipes

Use when the user asks about "Alembic commands", "database migrations", "flask db migrate", "flask db upgrade", "create migration", "rollback migration", "alembic check", "SQLAlchemy CLI", or needs ready-to-use database migration commands.

Source facts

Repository
Agents-Store/cursor-plugins
Last source activity
November 5, 2026 at 05:15
Detected SKILL.md language
English
Stars
0
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
2 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
cli-recipes
description
Use when the user asks about "Alembic commands", "database migrations", "flask db migrate", "flask db upgrade", "create migration", "rollback migration", "alembic check", "SQLAlchemy CLI", or needs ready-to-use database migration commands.
# Alembic / Flask-Migrate CLI Recipes Ready-to-use commands for database schema migrations. Alembic 1.20 needs Python 3.10+ and SQLAlchemy 2.0+. Every autogenerated migration must be read before it is applied: autogenerate misses some changes and misreads others (see Manual Migration Edits). ## Flask-Migrate (Flask Projects) ### Initial Setup ```bash pip install Flask-Migrate ``` ```python # extensions.py from flask_migrate import Migrate from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy(model_class=Base) # Base carries the naming convention (see model-patterns) migrate = Migrate() # app.py def create_app(): app = Flask(__name__) db.init_app(app) migrate.init_app(app, db) # add this line return app ``` ```bash # Initialize migrations directory (run once) flask db init ``` Put a `naming_convention` on `Base.metadata` before the first migration (see `model-patterns`): constraints and indexes then get stable, predictable names, and later `ALTER`/`DROP CONSTRAINT` statements can refer to them. Flask-Migrate 4.x already turns on `compare_type=True` (column type changes are detected) and `render_as_batch=True` (autogenerated migrations use `batch_alter_table`, which SQLite needs). The `env.py` that Flask-Migrate 4.1 generates calls `db.get_engine()` first, which Flask-SQLAlchemy 3.1 deprecates. It only shows as `DeprecationWarning: 'get_engine' is deprecated` when warnings are errors (pytest `filterwarnings`, `python -W error`). In `migrations/env.py` make the helper read the engine directly: ```python def get_engine(): return current_app.extensions['migrate'].db.engine ``` ### Migration Workflow ```bash # 1. Make model changes in models.py # 2. Generate migration script flask db migrate -m "Add phone column to clients" # 3. Read the generated file in migrations/versions/ and fix what autogenerate got wrong # 4. Apply migration flask db upgrade # Verify current state flask db current # CI gate: exits non-zero when the models and the migrations disagree flask db check ``` ### Common Operations ```bash # Apply all pending migrations flask db upgrade # Rollback last migration flask db downgrade # Show migration history flask db history # Show current revision (the database) and the newest revision(s) in the scripts flask db current flask db heads # Join two heads that appeared after merging branches flask db merge -m "Merge heads" <rev1> <rev2> # Generate empty migration (for manual edits) flask db revision -m "Custom migration" # Mark the database as being at a revision WITHOUT running any migration flask db stamp <revision> ``` `stamp` changes only the `alembic_version` table. Use it deliberately: to adopt a database whose schema already matches a known revision, or after repairing a half-applied migration by hand. `flask db stamp head` on a database that is behind marks it current and silently skips every missing change; the next `upgrade` then does nothing and the application fails on missing columns. ## Standalone Alembic ### Initial Setup ```bash pip install alembic alembic init alembic # or keep the source-layout settings in pyproject.toml instead of alembic.ini (Alembic 1.16+): alembic init --template pyproject alembic ``` Edit `alembic/env.py` to point to your models and set the database URL from the environment, not from a committed `alembic.ini`: ```python # alembic/env.py from myapp.models import Base # importing the models populates Base.metadata target_metadata = Base.metadata # inside run_migrations_online(): # context.configure( # connection=connection, # target_metadata=target_metadata, # render_as_batch=True, # SQLite: emit batch_alter_table in autogenerated migrations # compare_server_default=True, # also detect server_default changes # ) ``` `compare_type` is already `True` by default since Alembic 1.12. ### Migration Workflow ```bash # Auto-generate from model changes alembic revision --autogenerate -m "Add phone column" # Apply all pending alembic upgrade head # Print the SQL instead of running it alembic upgrade head --sql # Rollback one step alembic downgrade -1 # Rollback to specific revision alembic downgrade abc123 # View history, newest revisions, current revision alembic history alembic heads alembic current # CI gate: after "alembic upgrade head" on a clean database, fails if autogenerate would produce a migration alembic check ``` ## Manual Migration Edits Autogenerate compares models with the live schema; it cannot know intent. It reports a renamed column or table as a drop plus an add (which loses the data), and it does not see everything (some constraint, enum and server-default changes). Read every generated file and edit it: ```python # In migrations/versions/xxxx_add_phone_column.py import sqlalchemy as sa from alembic import op def upgrade() -> None: op.add_column("clients", sa.Column("phone", sa.String(20), nullable=True)) op.create_index(op.f("ix_clients_phone"), "clients", ["phone"]) def downgrade() -> None: op.drop_index(op.f("ix_clients_phone"), table_name="clients") op.drop_column("clients", "phone") ``` `op.f(...)` marks a name as final, so the naming convention is not applied to it a second time; autogenerate writes it for you. ### Common Migration Operations ```python import sqlalchemy as sa from alembic import op # Add column op.add_column("clients", sa.Column("phone", sa.String(20), nullable=True)) # Drop column op.drop_column("clients", "phone") # Rename column (keeps the data; autogenerate would drop and re-add) op.alter_column("clients", "name", new_column_name="full_name") # Change column type (PostgreSQL needs postgresql_using when there is no implicit cast) op.alter_column("clients", "age", type_=sa.Integer(), postgresql_using="age::integer") # Add index op.create_index(op.f("ix_clients_phone"), "clients", ["phone"]) # Add unique constraint op.create_unique_constraint(op.f("uq_clients_email"), "clients", ["email"]) # Add foreign key op.create_foreign_key( op.f("fk_appointments_client_id_clients"), "appointments", "clients", ["client_id"], ["id"] ) # Create table op.create_table( "new_table", sa.Column("id", sa.Integer, primary_key=True), sa.Column("name", sa.String(100)), ) # Drop table op.drop_table("old_table") ``` SQLite cannot alter or drop most constraints in place. Wrap such changes in batch mode, which rebuilds the table: ```python with op.batch_alter_table("clients") as batch_op: batch_op.add_column(sa.Column("nickname", sa.String(50))) batch_op.create_unique_constraint(op.f("uq_clients_nickname"), ["nickname"]) batch_op.alter_column("name", existing_type=sa.String(100), type_=sa.String(200)) ``` ### Data Migrations Do not import application models into a migration (the models change, the migration must not). Describe just the columns you touch: ```python clients = sa.table("clients", sa.column("id", sa.Integer), sa.column("status", sa.String)) op.execute(clients.update().where(clients.c.status == "regular").values(status="standard")) ``` ## Troubleshooting Migrations | Issue | Fix | |-------|-----| | "Target database is not up to date" | Run `flask db upgrade` first | | "Can't locate revision identified by 'x'" | The database's `alembic_version` names a revision that has no file in `migrations/versions/` (deleted file, another branch, stale checkout). Run `flask db heads` and `flask db history`, restore the missing file (merge or check out the branch that has it), then `flask db upgrade`. Re-stamp only to a revision whose schema you have verified: `flask db stamp <revision>` | | "Multiple head revisions are present" | Two branches both added a migration: `flask db heads`, then `flask db merge -m "Merge heads" <rev1> <rev2>`, then `flask db upgrade` | | Autogenerate misses changes | Edit the migration manually; check `compare_type` / `compare_server_default` in `env.py` | | Autogenerate drops and re-adds a renamed column | Replace the pair with `op.alter_column(..., new_column_name=...)` | | "No changes in schema detected" with a new model | The models module is not imported in `env.py` / the app, or `db.create_all()` already created the table | | Migration fails midway | PostgreSQL runs the migration's DDL in one transaction and rolls it back: fix the migration and rerun. For MySQL and SQLite Alembic assumes non-transactional DDL (its log says so), so the part that ran stays: undo it by hand, then rerun | | `DeprecationWarning: 'get_engine' is deprecated` | Edit `get_engine()` in `migrations/env.py` (see Initial Setup) | | Need to start fresh (development only) | Delete the database, delete `migrations/`, run `flask db init` and `flask db migrate`. Never on a shared or production database |
View on GitHub