ProvenPin Install

peewee JSONField queries on SQLite after SqliteExtDatabase removal: working versions (tested 2026-10-16)

Store and query JSON documents with peewee's core JSONField on SqliteDatabase, covering item access, as_text, has_key, contains and in-place mutations, plus the peewee 4 removal of playhouse SqliteExtDatabase.

passed6 passed · 0 failed2026-10-16 09:57 UTC
Status
passed
Last passed
2026-10-16 09:57 UTC
Tests
6 / 6
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 38883899161

The code

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

requirements.txt
peewee==4.5.3
pytest==9.1.1
models.py
from peewee import JSONField, Model, SqliteDatabase, TextField

db = SqliteDatabase(":memory:")

class Event(Model):
    slug = TextField(unique=True)
    payload = JSONField()

    class Meta:
        database = db

def init_db(rows=()):
    db.connect(reuse_if_open=True)
    db.create_tables([Event])
    for slug, payload in rows:
        Event.create(slug=slug, payload=payload)

def by_key(key, value):
    return Event.select().where(Event.payload[key] == value)

def append_tag(slug, tag):
    # json_append runs inside the UPDATE, no read-modify-write needed
    return (Event
            .update(payload=Event.payload["tags"].append(tag))
            .where(Event.slug == slug)
            .execute())
tests/test_json.py
import pytest
from peewee import SqliteDatabase

from models import Event, append_tag, by_key, db, init_db


@pytest.fixture
def events():
    init_db([
        ("a", {"kind": "push", "tags": ["x", "y"], "meta": {"ok": True, "note": None}, "n": 7}),
        ("b", {"kind": "pull", "tags": ["y"]}),
    ])
    yield
    db.drop_tables([Event])


def slugs(query):
    return [e.slug for e in query]


def test_item_access_compares_against_json(events):
    assert slugs(by_key("kind", "push")) == ["a"]
    # the right side is wrapped in json(), so a native Python value matches
    assert slugs(Event.select().where(Event.payload["n"] == 7)) == ["a"]


def test_as_text_yields_sql_scalars(events):
    # ->> returns the SQL representation: a JSON number comes back as an int
    assert slugs(Event.select().where(Event.payload["n"].as_text() == 7)) == ["a"]
    assert slugs(Event.select().where(Event.payload["n"].as_text() == "7")) == []


def test_has_key_sees_a_present_null(events):
    # a key whose value is JSON null still counts as present
    assert slugs(Event.select().where(Event.payload["meta"].has_key("note"))) == ["a"]
    assert slugs(Event.select().where(Event.payload["meta"].has_key("missing"))) == []


def test_contains_is_subset_or_array_membership(events):
    assert slugs(Event.select().where(Event.payload.contains({"kind": "push"}))) == ["a"]
    assert slugs(Event.select().where(Event.payload["tags"].contains("x"))) == ["a"]
    # on the root object, contains() looks at keys, never array items
    assert slugs(Event.select().where(Event.payload.contains("x"))) == []


def test_in_place_mutations(events):
    append_tag("a", "z")
    assert Event.get(Event.slug == "a").payload["tags"] == ["x", "y", "z"]
    # a mutator without .where() rewrites every row
    Event.update(payload=Event.payload.update({"seen": True})).execute()
    assert all(e.payload["seen"] is True for e in Event.select())


def test_sqlite_ext_database_is_removed():
    # peewee 4 dropped SqliteExtDatabase; JSON now lives on core SqliteDatabase
    with pytest.raises(ImportError):
        from playhouse.sqlite_ext import SqliteExtDatabase
    assert isinstance(db, SqliteDatabase)
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.