SQLAlchemy 2 + Alembic programmatic migrations: working versions (tested 2026-10-15)
Alembic migrations driven from Python (upgrade, downgrade, autogenerate check) against SQLite, with SQLAlchemy 2 models.
passed2 passed · 0 failed2026-10-15 11:37 UTC- Status
- passed
- Last passed
- 2026-10-15 11:37 UTC
- Tests
- 2 / 2
- Language
- Python
- Runtime
- Python 3.13.16
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 38728226240
The code
Exactly the files that passed. Your agent can pull them with get_recipe("sqlalchemy-alembic-migrations") (install), or see them on GitHub.
sqlalchemy==2.1.4 alembic==1.20.0 pytest==9.1.1
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class Account(Base):
__tablename__ = "account"
id: Mapped[int] = mapped_column(primary_key=True)
email: Mapped[str] = mapped_column(String(255), unique=True)
from alembic import context
from sqlalchemy import engine_from_config, pool
from models import Base
config = context.config
connectable = engine_from_config(config.get_section(config.config_ini_section, {}), prefix="sqlalchemy.", poolclass=pool.NullPool)
with connectable.connect() as connection:
context.configure(connection=connection, target_metadata=Base.metadata, render_as_batch=True)
with context.begin_transaction():
context.run_migrations()
"""${message}"""
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
from alembic import op
import sqlalchemy as sa
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}
"""create account"""
revision = "0001"
down_revision = None
from alembic import op
import sqlalchemy as sa
def upgrade():
op.create_table("account", sa.Column("id", sa.Integer, primary_key=True), sa.Column("email", sa.String(255), nullable=False, unique=True))
def downgrade():
op.drop_table("account")
from alembic import command
from alembic.config import Config
from alembic.autogenerate import compare_metadata
from alembic.migration import MigrationContext
from sqlalchemy import create_engine, inspect
from models import Base
def cfg(url):
c = Config()
c.set_main_option("script_location", "migrations")
c.set_main_option("sqlalchemy.url", url)
return c
def test_upgrade_downgrade(tmp_path):
url = f"sqlite:///{tmp_path}/db.sqlite"
command.upgrade(cfg(url), "head")
assert "account" in inspect(create_engine(url)).get_table_names()
command.downgrade(cfg(url), "base")
assert "account" not in inspect(create_engine(url)).get_table_names()
def test_models_match_migrations(tmp_path):
url = f"sqlite:///{tmp_path}/db.sqlite"
command.upgrade(cfg(url), "head")
with create_engine(url).connect() as conn:
diff = compare_metadata(MigrationContext.configure(conn), Base.metadata)
assert diff == []
[pytest] pythonpath = .
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
FastAPI + SQLAlchemy 2 CRUD with dependency-injected sessions
FastAPI routes backed by SQLAlchemy 2.x typed models, a per-request session dependency, and TestClient tests on SQLite.
FastAPI with async SQLAlchemy 2 on aiosqlite, tested offline with per-test in-memory DBs
A FastAPI app using AsyncSession and async_sessionmaker on aiosqlite, tested with httpx.AsyncClient over ASGITransport, overriding the session dependency with a fresh in-memory database for each test.
Flask 3 app factory + Flask-SQLAlchemy
Flask application factory with Flask-SQLAlchemy 3, a JSON API and pytest fixtures using an in-memory database.
Flask API with Flask-SQLAlchemy and marshmallow-sqlalchemy, validation errors as 422
A Flask JSON API that loads and dumps SQLAlchemy models through SQLAlchemyAutoSchema, returns validation errors as 422, and is tested offline with the Flask test client on in-memory sqlite.