peewee-testing
**Scope**: In-memory SQLite databases, test isolation, fixture patterns, and migration testing with Peewee ORM. **Version range**: Peewee 3.x, Python 3.8+, pytest 7+ **Generated**: 2026-04-14 — verify against current pytest-peewee releases
$ npx -y skills add notque/vexjoy-agent --agent claude-codeHow it fires
How this agent gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
Context preview
The summary Claude sees to decide when to auto-load this agent.
**Scope**: In-memory SQLite databases, test isolation, fixture patterns, and migration testing with Peewee ORM. **Version range**: Peewee 3.x, Python 3.8+, pytest 7+ **Generated**: 2026-04-14 — verify against current pytest-peewee releases
Agent definition
peewee-testing.mdPeewee Testing Patterns
> **Scope**: In-memory SQLite databases, test isolation, fixture patterns, and migration testing with Peewee ORM. > **Version range**: Peewee 3.x, Python 3.8+, pytest 7+ > **Generated**: 2026-04-14 — verify against current pytest-peewee releases
---
Overview
Critical failure mode: shared database state bleeds between tests with module-level db instances. Fix: `:memory:` SQLite per-test via `bind_ctx()`. Works cleanly with pytest fixtures.
---
Pattern Table
| Pattern | Use When | Avoid When | |---------|----------|------------| | `SqliteDatabase(':memory:')` | Unit tests, fast isolation | Integration tests needing file persistence | | `db.bind_ctx(models)` | Per-test isolation via fixture | Module-level setup (state leaks) | | `db.create_tables(models)` | Schema creation in fixture | Production code (use migrations) | | `TestModel.truncate_table()` | Clearing data between tests | Full isolation (use bind_ctx instead) | | `atomic()` rollback | Preserving db state across tests | Tests that commit intentionally |
---
Correct Patterns
Per-Test Isolation with `bind_ctx()`
Fresh in-memory db per test. Models re-bind at test start, dropped at end.
import pytest
from peewee import SqliteDatabase
from myapp.models import User, Post, Comment
ALL_MODELS = [User, Post, Comment]
@pytest.fixture
def db():
"""Fresh in-memory database for each test."""
test_db = SqliteDatabase(':memory:', pragmas={'foreign_keys': 1})
with test_db.bind_ctx(ALL_MODELS):
test_db.create_tables(ALL_MODELS)
yield test_db
# Tables auto-dropped when in-memory db is garbage collected
def test_user_creation(db):
user = User.create(username='alice', email='alice@example.com')
assert User.select().count() == 1
assert user.id is not None
def test_post_belongs_to_user(db):
user = User.create(username='bob', email='bob@example.com')
post = Post.create(user=user, title='Hello')
# FK constraint enforced because foreign_keys=1 pragma is set
assert post.user_id == user.id**Why**: `bind_ctx()` temporarily rebinds models to test db without changing `Meta.database`. Thread-safe, works with parallel execution.
---
Fixture Factories for Related Data
Factory functions avoid repetitive setup:
@pytest.fixture
def make_user(db):
"""Factory for creating users with defaults."""
def factory(username='testuser', email=None, **kwargs):
email = email or f'{username}@example.com'
return User.create(username=username, email=email, **kwargs)
return factory
@pytest.fixture
def make_post(db, make_user):
"""Factory for creating posts, creates user if not provided."""
def factory(title='Test Post', user=None, **kwargs):
if user is None:
user = make_user()
return Post.create(title=title, user=user, **kwargs)
return factory
def test_post_count_per_user(db, make_user, make_post):
alice = make_user('alice')
make_post(user=alice)
make_post(user=alice)
make_post(user=alice)
# Verify the prefetch path works correctly
users = User.select().prefetch(Post)
user = [u for u in users if u.username == 'alice'][0]
assert len(list(user.posts)) == 3---
Testing Transactions and Rollback
def test_atomic_rollback_on_error(db):
"""Verify atomic() rolls back all changes when exception raised."""
user = User.create(username='alice', email='alice@example.com')
with pytest.raises(ValueError):
with db.atomic():
Post.create(user=user, title='First post')
Post.create(user=user, title='Second post')
raise ValueError('intentional rollback')
# Both posts should be rolled back
assert Post.select().count() == 0
def test_savepoint_partial_rollback(db):
"""Verify nested atomic() uses savepoints for partial rollback."""
user = User.create(username='alice', email='alice@example.com')
with db.atomic():
Post.create(user=user, title='Outer post')
try:
with db.atomic(): # Creates savepoint
Post.create(user=user, title='Inner post')
raise ValueError('rollback inner only')
except ValueError:
pass # Inner savepoint rolled back, outer continues
# Only the outer post committed
assert Post.select().count() == 1
assert Post.get().title == 'Outer post'---
Testing Migration Scripts
import pytest
from peewee import SqliteDatabase, Model, CharField, TextField
from playhouse.migrate import SqliteMigrator, migrate
@pytest.fixture
def pre_migration_db():
"""Database in the state before a migration runs."""
db = SqliteDatabase(':memory:')
class OldUser(Model):
username = CharField()
# No 'email' column yet — simulates pre-migration state
class Meta:
database = db
table_name = 'user'
db.create_tables([OldUser])
OldUser.create(username='alice')
yield db
def test_add_email_column_migration(pre_migration_db):
migrator = SqliteMigrator(pre_migration_db)
migrate(
migrator.add_column('user', 'email', TextField(null=True))
)
# Verify column exists and existing rows have NULL
cursor = pre_migration_db.execute_sql('SELECT email FROM user')
rows = cursor.fetchall()
assert len(rows) == 1
assert rows[0][0] is None # NULL for pre-existing rows---
Pattern Catalog
<!-- no-pair-required: section header with no content -->
Use Per-Test Database Fixtures (State Leakage)
**Detection**:
# Find test files using module-level database setup
grep -rn 'db = SqliteDatabase' --include="test_*.py"
grep -rn 'setUpClass\|setup_module' --include="test_*.py" -A 5 | grep 'create_tables'
rg 'SqliteDatabase.*test' --type py | grep -v 'fixture\|conftest'
**Preferred action:** Use a per-test `db` fixture with `bind_ctx()` so each test gets a f
Read more
Peewee Testing Patterns
> **Scope**: In-memory SQLite databases, test isolation, fixture patterns, and migration testing with Peewee ORM. > **Version range**: Peewee 3.x, Python 3.8+, pytest 7+ > **Generated**: 2026-04-14 — verify against current pytest-peewee releases
---
Overview
Critical failure mode: shared database state bleeds between tests with module-level db instances. Fix: `:memory:` SQLite per-test via `bind_ctx()`. Works cleanly with pytest fixtures.
---
Pattern Table
| Pattern | Use When | Avoid When | |---------|----------|------------| | `SqliteDatabase(':memory:')` | Unit tests, fast isolation | Integration tests needing file persistence | | `db.bind_ctx(models)` | Per-test isolation via fixture | Module-level setup (state leaks) | | `db.create_tables(models)` | Schema creation in fixture | Production code (use migrations) | | `TestModel.truncate_table()` | Clearing data between tests | Full isolation (use bind_ctx instead) | | `atomic()` rollback | Preserving db state across tests | Tests that commit intentionally |
---
Correct Patterns
Per-Test Isolation with `bind_ctx()`
Fresh in-memory db per test. Models re-bind at test start, dropped at end.
import pytest
from peewee import SqliteDatabase
from myapp.models import User, Post, Comment
ALL_MODELS = [User, Post, Comment]
@pytest.fixture
def db():
"""Fresh in-memory database for each test."""
test_db = SqliteDatabase(':memory:', pragmas={'foreign_keys': 1})
with test_db.bind_ctx(ALL_MODELS):
test_db.create_tables(ALL_MODELS)
yield test_db
# Tables auto-dropped when in-memory db is garbage collected
def test_user_creation(db):
user = User.create(username='alice', email='alice@example.com')
assert User.select().count() == 1
assert user.id is not None
def test_post_belongs_to_user(db):
user = User.create(username='bob', email='bob@example.com')
post = Post.create(user=user, title='Hello')
# FK constraint enforced because foreign_keys=1 pragma is set
assert post.user_id == user.id**Why**: `bind_ctx()` temporarily rebinds models to test db without changing `Meta.database`. Thread-safe, works with parallel execution.
---
Fixture Factories for Related Data
Factory functions avoid repetitive setup:
@pytest.fixture
def make_user(db):
"""Factory for creating users with defaults."""
def factory(username='testuser', email=None, **kwargs):
email = email or f'{username}@example.com'
return User.create(username=username, email=email, **kwargs)
return factory
@pytest.fixture
def make_post(db, make_user):
"""Factory for creating posts, creates user if not provided."""
def factory(title='Test Post', user=None, **kwargs):
if user is None:
user = make_user()
return Post.create(title=title, user=user, **kwargs)
return factory
def test_post_count_per_user(db, make_user, make_post):
alice = make_user('alice')
make_post(user=alice)
make_post(user=alice)
make_post(user=alice)
# Verify the prefetch path works correctly
users = User.select().prefetch(Post)
user = [u for u in users if u.username == 'alice'][0]
assert len(list(user.posts)) == 3---
Testing Transactions and Rollback
def test_atomic_rollback_on_error(db):
"""Verify atomic() rolls back all changes when exception raised."""
user = User.create(username='alice', email='alice@example.com')
with pytest.raises(ValueError):
with db.atomic():
Post.create(user=user, title='First post')
Post.create(user=user, title='Second post')
raise ValueError('intentional rollback')
# Both posts should be rolled back
assert Post.select().count() == 0
def test_savepoint_partial_rollback(db):
"""Verify nested atomic() uses savepoints for partial rollback."""
user = User.create(username='alice', email='alice@example.com')
with db.atomic():
Post.create(user=user, title='Outer post')
try:
with db.atomic(): # Creates savepoint
Post.create(user=user, title='Inner post')
raise ValueError('rollback inner only')
except ValueError:
pass # Inner savepoint rolled back, outer continues
# Only the outer post committed
assert Post.select().count() == 1
assert Post.get().title == 'Outer post'---
Testing Migration Scripts
import pytest
from peewee import SqliteDatabase, Model, CharField, TextField
from playhouse.migrate import SqliteMigrator, migrate
@pytest.fixture
def pre_migration_db():
"""Database in the state before a migration runs."""
db = SqliteDatabase(':memory:')
class OldUser(Model):
username = CharField()
# No 'email' column yet — simulates pre-migration state
class Meta:
database = db
table_name = 'user'
db.create_tables([OldUser])
OldUser.create(username='alice')
yield db
def test_add_email_column_migration(pre_migration_db):
migrator = SqliteMigrator(pre_migration_db)
migrate(
migrator.add_column('user', 'email', TextField(null=True))
)
# Verify column exists and existing rows have NULL
cursor = pre_migration_db.execute_sql('SELECT email FROM user')
rows = cursor.fetchall()
assert len(rows) == 1
assert rows[0][0] is None # NULL for pre-existing rows---
Pattern Catalog
<!-- no-pair-required: section header with no content -->
Use Per-Test Database Fixtures (State Leakage)
**Detection**:
# Find test files using module-level database setup grep -rn 'db = SqliteDatabase' --include="test_*.py" grep -rn 'setUpClass\|setup_module' --include="test_*.py" -A 5 | grep 'create_tables' rg 'SqliteDatabase.*test' --type py | grep -v 'fixture\|conftest'
**Preferred action:** Use a per-test `db` fixture with `bind_ctx()` so each test gets a f
Essays and writing behind this toolkit live at vexjoy.com. AI agents skip steps. "Looks correct" replaces running tests. "Trivial change" replaces verification.
Repo: notque/vexjoy-agent
Other agents on vexjoy-agent.
- ansible-automation-engineer
Ansible automation: playbooks, roles, collections, Molecule testing, Vault security.
Open agent - modules
**Scope**: Module selection patterns, builtin vs command/shell decisions, collection modules, and version-specific module changes **Version range**: ansible-core 2.14+ / Ansible Collections (community.general 7.0+) **Generated**: 2026-04-04 — verify against current Ansible
Open agent - testing
**Scope**: Molecule test scenarios, ansible-lint rules, idempotency validation, and check-mode patterns **Version range**: Molecule 6.0+ / ansible-lint 6.0+ / ansible-core 2.14+ **Generated**: 2026-04-04 — verify against current Molecule and ansible-lint documentation
Open agent - base-instructions
Universal operational rules injected by /do at agent dispatch. Domain-specific rules live in each agent's .md file.
Open agent - communication-patterns
**Scope**: Failure modes in agent output style — over-reporting, self-congratulation, verbose narration, and hedging. Covers what to detect and how to fix each. **Version range**: all versions **Generated**: 2026-05-11
Open agent - combat-effects-upgrade
Zero-dependency combat visual upgrades: CSS particle replacement, Framer Motion combat juice, CSS 3D card transforms.
Open agent

