ProvenPin Install

Peewee models, migrations and transactions on SQLite: working versions (tested 2026-10-15)

Define Peewee models, evolve the schema with playhouse.migrate inside a migration context, and exercise atomic transactions against a SQLite file backed by pytest tmp_path.

passed5 passed · 0 failed2026-10-15 18:10 UTC
Status
passed
Last passed
2026-10-15 18:10 UTC
Tests
5 / 5
Language
Python
Runtime
Python 3.13.16
pinned versions2
peewee@4.5.3pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt
.venv/bin/python -m pytest -q
test environment
Python 3.13.16
linux 6.17.0-1022-azure
x64
runner: github-actions 38783276983

The code

Exactly the files that passed. Your agent can pull them with get_recipe("peewee-sqlite-migrations") (install), or see them on GitHub.

requirements.txt
peewee==4.5.3
pytest==9.1.1
models.py
from peewee import CharField, ForeignKeyField, IntegerField, Model, TextField


class BaseModel(Model):
    pass


class User(BaseModel):
    username = CharField(unique=True)
    email = CharField(default="")
    karma = IntegerField(default=0)


class Note(BaseModel):
    user = ForeignKeyField(User, backref="notes")
    body = TextField()
migrations.py
from contextlib import nullcontext

from peewee import CharField, IntegerField
from playhouse.migrate import SchemaMigrator, migrate


def add_profile_columns(database):
    migrator = SchemaMigrator.from_database(database)
    with migrator.migration_context():
        migrate(
            migrator.add_column("user", "email", CharField(default="")),
            migrator.add_column("user", "karma", IntegerField(default=0)),
        )


def run_broken_plan(database, *, transactional):
    migrator = SchemaMigrator.from_database(database)
    context = migrator.migration_context() if transactional else nullcontext()
    with context:
        migrate(
            migrator.add_column("user", "nickname", CharField(default="")),
            migrator.sql("SELECT * FROM no_such_table"),
        )
tests/test_migrations.py
import pytest
from peewee import IntegrityError, OperationalError, SqliteDatabase

from migrations import add_profile_columns, run_broken_plan
from models import Note, User

LEGACY_USER = (
    "CREATE TABLE user ("
    "id INTEGER PRIMARY KEY, "
    "username VARCHAR(255) NOT NULL UNIQUE)"
)


def columns(database, table="user"):
    return {column.name for column in database.get_columns(table)}


@pytest.fixture
def database(tmp_path):
    database = SqliteDatabase(str(tmp_path / "app.db"), pragmas={"foreign_keys": 1})
    with database.bind_ctx([User, Note]):
        database.execute_sql(LEGACY_USER)
        yield database
        database.close()


@pytest.fixture
def migrated(database):
    add_profile_columns(database)
    database.create_tables([Note])
    return database


def test_add_column_backfills_existing_rows(database):
    database.execute_sql("INSERT INTO user (username) VALUES ('ada')")
    add_profile_columns(database)
    assert {"email", "karma"} <= columns(database)
    row = User.get(User.username == "ada")
    assert row.email == "" and row.karma == 0


def test_migrate_is_not_transactional(database):
    with pytest.raises(OperationalError):
        run_broken_plan(database, transactional=False)
    assert "nickname" in columns(database)


def test_migration_context_rolls_back_failed_migration(database):
    with pytest.raises(OperationalError):
        run_broken_plan(database, transactional=True)
    assert "nickname" not in columns(database)


def test_nested_atomic_uses_savepoints(migrated):
    User.create(username="kept")
    with pytest.raises(RuntimeError):
        with migrated.atomic():
            User.create(username="outer")
            with migrated.atomic() as savepoint:
                User.create(username="inner")
                savepoint.rollback()
            User.create(username="after")
            raise RuntimeError("boom")
    assert sorted(user.username for user in User.select()) == ["kept"]


def test_foreign_key_pragma_blocks_orphaned_children(migrated):
    user = User.create(username="ada")
    Note.create(user=user, body="hello")
    with pytest.raises(IntegrityError):
        user.delete_instance()
    assert Note.select().count() == 1
pytest.ini
[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.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.