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
~/.cursor/mcp.json
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }.vscode/mcp.json
{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }MCP settings
{ "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.