Use db.table() and db.view() in tests, closes #838

This commit is contained in:
Simon Willison 2026-08-12 13:40:55 -07:00
commit 38fe466700
43 changed files with 1321 additions and 1254 deletions

View file

@ -45,30 +45,30 @@ def test_iter_complete_sql_statements(sql, expected):
def test_atomic_commits(fresh_db):
with fresh_db.atomic():
fresh_db["dogs"].insert({"id": 1, "name": "Cleo"}, pk="id")
fresh_db.table("dogs").insert({"id": 1, "name": "Cleo"}, pk="id")
assert list(fresh_db["dogs"].rows) == [{"id": 1, "name": "Cleo"}]
assert list(fresh_db.table("dogs").rows) == [{"id": 1, "name": "Cleo"}]
def test_atomic_rolls_back(fresh_db):
with pytest.raises(RuntimeError), fresh_db.atomic():
fresh_db["dogs"].insert({"id": 1, "name": "Cleo"}, pk="id")
fresh_db.table("dogs").insert({"id": 1, "name": "Cleo"}, pk="id")
raise RuntimeError("boom")
assert not fresh_db["dogs"].exists()
assert not fresh_db.table("dogs").exists()
def test_nested_atomic_rolls_back_to_savepoint(fresh_db):
fresh_db["dogs"].create({"id": int, "name": str}, pk="id")
fresh_db.table("dogs").create({"id": int, "name": str}, pk="id")
with fresh_db.atomic():
fresh_db["dogs"].insert({"id": 1, "name": "Cleo"})
fresh_db.table("dogs").insert({"id": 1, "name": "Cleo"})
with pytest.raises(RuntimeError), fresh_db.atomic():
fresh_db["dogs"].insert({"id": 2, "name": "Pancakes"})
fresh_db.table("dogs").insert({"id": 2, "name": "Pancakes"})
raise RuntimeError("boom")
fresh_db["dogs"].insert({"id": 3, "name": "Marnie"})
fresh_db.table("dogs").insert({"id": 3, "name": "Marnie"})
assert list(fresh_db["dogs"].rows) == [
assert list(fresh_db.table("dogs").rows) == [
{"id": 1, "name": "Cleo"},
{"id": 3, "name": "Marnie"},
]
@ -76,12 +76,12 @@ def test_nested_atomic_rolls_back_to_savepoint(fresh_db):
def test_outer_atomic_rolls_back_released_savepoint(fresh_db):
with pytest.raises(RuntimeError), fresh_db.atomic():
fresh_db["dogs"].insert({"id": 1, "name": "Cleo"}, pk="id")
fresh_db.table("dogs").insert({"id": 1, "name": "Cleo"}, pk="id")
with fresh_db.atomic():
fresh_db["dogs"].insert({"id": 2, "name": "Pancakes"})
fresh_db.table("dogs").insert({"id": 2, "name": "Pancakes"})
raise RuntimeError("boom")
assert not fresh_db["dogs"].exists()
assert not fresh_db.table("dogs").exists()
def test_executescript_does_not_commit_open_atomic_block(fresh_db):
@ -97,41 +97,41 @@ def test_executescript_does_not_commit_open_atomic_block(fresh_db):
""")
raise RuntimeError("boom")
assert not fresh_db["dogs"].exists()
assert not fresh_db.table("dogs").exists()
def test_transform_does_not_commit_open_atomic_block(fresh_db):
fresh_db["dogs"].insert({"id": 1, "name": "Cleo", "age": "5"}, pk="id")
fresh_db.table("dogs").insert({"id": 1, "name": "Cleo", "age": "5"}, pk="id")
with pytest.raises(RuntimeError), fresh_db.atomic():
fresh_db["dogs"].insert({"id": 2, "name": "Pancakes", "age": "6"})
fresh_db["dogs"].transform(rename={"age": "dog_age"})
fresh_db.table("dogs").insert({"id": 2, "name": "Pancakes", "age": "6"})
fresh_db.table("dogs").transform(rename={"age": "dog_age"})
raise RuntimeError("boom")
assert (
fresh_db["dogs"].schema
fresh_db.table("dogs").schema
== 'CREATE TABLE "dogs" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT,\n "age" TEXT\n)'
)
assert list(fresh_db["dogs"].rows) == [
assert list(fresh_db.table("dogs").rows) == [
{"id": 1, "name": "Cleo", "age": "5"},
]
def test_transform_parent_table_with_foreign_keys_in_atomic(fresh_db):
fresh_db.conn.execute("PRAGMA foreign_keys=ON")
fresh_db["authors"].insert({"id": 1, "name": "Tina"}, pk="id")
fresh_db["books"].insert(
fresh_db.table("authors").insert({"id": 1, "name": "Tina"}, pk="id")
fresh_db.table("books").insert(
{"id": 1, "title": "Book", "author_id": 1},
pk="id",
foreign_keys={"author_id"},
)
with fresh_db.atomic():
fresh_db["authors"].transform(rename={"name": "full_name"})
fresh_db.table("authors").transform(rename={"name": "full_name"})
assert fresh_db.conn.execute("PRAGMA foreign_keys").fetchone()[0]
assert (
fresh_db["authors"].schema
fresh_db.table("authors").schema
== 'CREATE TABLE "authors" (\n "id" INTEGER PRIMARY KEY,\n "full_name" TEXT\n)'
)
assert fresh_db.execute("PRAGMA foreign_key_check").fetchall() == []
@ -139,19 +139,19 @@ def test_transform_parent_table_with_foreign_keys_in_atomic(fresh_db):
def test_transform_parent_table_with_foreign_keys_rolls_back(fresh_db):
fresh_db.conn.execute("PRAGMA foreign_keys=ON")
fresh_db["authors"].insert({"id": 1, "name": "Tina"}, pk="id")
fresh_db["books"].insert(
fresh_db.table("authors").insert({"id": 1, "name": "Tina"}, pk="id")
fresh_db.table("books").insert(
{"id": 1, "title": "Book", "author_id": 1},
pk="id",
foreign_keys={"author_id"},
)
with pytest.raises(RuntimeError), fresh_db.atomic():
fresh_db["authors"].transform(rename={"name": "full_name"})
fresh_db.table("authors").transform(rename={"name": "full_name"})
raise RuntimeError("boom")
assert (
fresh_db["authors"].schema
fresh_db.table("authors").schema
== 'CREATE TABLE "authors" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT\n)'
)
assert fresh_db.conn.execute("PRAGMA foreign_keys").fetchone()[0]
@ -160,49 +160,51 @@ def test_transform_parent_table_with_foreign_keys_rolls_back(fresh_db):
def test_transform_detects_foreign_key_check_violations(fresh_db):
fresh_db.conn.execute("PRAGMA foreign_keys=ON")
fresh_db["authors"].insert({"id": 1, "name": "Tina"}, pk="id")
fresh_db["books"].insert({"id": 1, "author_id": 2}, pk="id")
fresh_db.table("authors").insert({"id": 1, "name": "Tina"}, pk="id")
fresh_db.table("books").insert({"id": 1, "author_id": 2}, pk="id")
with pytest.raises(sqlite3.IntegrityError):
fresh_db["books"].transform(add_foreign_keys=(("author_id", "authors", "id"),))
fresh_db.table("books").transform(
add_foreign_keys=(("author_id", "authors", "id"),)
)
assert fresh_db["books"].foreign_keys == []
assert fresh_db.table("books").foreign_keys == []
assert fresh_db.conn.execute("PRAGMA foreign_keys").fetchone()[0]
def test_atomic_inside_manual_transaction_uses_savepoint(fresh_db):
fresh_db["t"].insert({"id": 1}, pk="id")
fresh_db.table("t").insert({"id": 1}, pk="id")
fresh_db.execute("begin")
with fresh_db.atomic():
fresh_db["t"].insert({"id": 2}, pk="id")
fresh_db.table("t").insert({"id": 2}, pk="id")
# Nothing is committed until the user's own transaction commits
assert fresh_db.conn.in_transaction
fresh_db.rollback()
assert [r["id"] for r in fresh_db["t"].rows] == [1]
assert [r["id"] for r in fresh_db.table("t").rows] == [1]
# And with a commit instead, the atomic block's writes persist
fresh_db.execute("begin")
with fresh_db.atomic():
fresh_db["t"].insert({"id": 3}, pk="id")
fresh_db.table("t").insert({"id": 3}, pk="id")
fresh_db.commit()
assert [r["id"] for r in fresh_db["t"].rows] == [1, 3]
assert [r["id"] for r in fresh_db.table("t").rows] == [1, 3]
def test_begin_commit_rollback(tmpdir):
path = str(tmpdir / "test.db")
db = Database(path)
db["t"].insert({"id": 1}, pk="id")
db.table("t").insert({"id": 1}, pk="id")
db.begin()
db["t"].insert({"id": 2}, pk="id")
db.table("t").insert({"id": 2}, pk="id")
assert db.conn.in_transaction
db.rollback()
assert not db.conn.in_transaction
assert [r["id"] for r in db["t"].rows] == [1]
assert [r["id"] for r in db.table("t").rows] == [1]
db.begin()
db["t"].insert({"id": 3}, pk="id")
db.table("t").insert({"id": 3}, pk="id")
db.commit()
db.close()
db2 = Database(path)
assert [r["id"] for r in db2["t"].rows] == [1, 3]
assert [r["id"] for r in db2.table("t").rows] == [1, 3]
db2.close()
@ -222,7 +224,7 @@ def test_commit_and_rollback_without_transaction_are_noops(fresh_db):
def test_execute_write_commits_immediately(tmpdir):
path = str(tmpdir / "test.db")
db = Database(path)
db["t"].insert({"id": 1}, pk="id")
db.table("t").insert({"id": 1}, pk="id")
db.execute("insert into t (id) values (2)")
# No implicit transaction is left open
assert not db.conn.in_transaction
@ -234,24 +236,24 @@ def test_execute_write_commits_immediately(tmpdir):
def test_execute_write_respects_explicit_transaction(fresh_db):
fresh_db["t"].insert({"id": 1}, pk="id")
fresh_db.table("t").insert({"id": 1}, pk="id")
fresh_db.begin()
fresh_db.execute("insert into t (id) values (2)")
# Still inside the explicit transaction - not committed
assert fresh_db.conn.in_transaction
fresh_db.rollback()
assert [r["id"] for r in fresh_db["t"].rows] == [1]
assert [r["id"] for r in fresh_db.table("t").rows] == [1]
def test_execute_comment_prefixed_begin_leaves_transaction_open(fresh_db):
# A BEGIN hidden behind a leading comment must not be auto-committed
# out from under the caller
fresh_db["t"].insert({"id": 1}, pk="id")
fresh_db.table("t").insert({"id": 1}, pk="id")
fresh_db.execute("-- start a transaction\nbegin")
assert fresh_db.conn.in_transaction
fresh_db.execute("insert into t (id) values (2)")
fresh_db.rollback()
assert [r["id"] for r in fresh_db["t"].rows] == [1]
assert [r["id"] for r in fresh_db.table("t").rows] == [1]
def _sqlite_accepts_bom():
@ -269,12 +271,12 @@ def test_execute_prefixed_begin_leaves_transaction_open(fresh_db, begin_sql):
# out from under the caller
if begin_sql.startswith("\ufeff") and not _sqlite_accepts_bom():
pytest.skip("This SQLite version rejects a leading byte order mark")
fresh_db["t"].insert({"id": 1}, pk="id")
fresh_db.table("t").insert({"id": 1}, pk="id")
fresh_db.execute(begin_sql)
assert fresh_db.conn.in_transaction
fresh_db.execute("insert into t (id) values (2)")
fresh_db.rollback()
assert [r["id"] for r in fresh_db["t"].rows] == [1]
assert [r["id"] for r in fresh_db.table("t").rows] == [1]
def test_execute_failed_write_rolls_back_implicit_transaction(tmpdir):
@ -282,40 +284,40 @@ def test_execute_failed_write_rolls_back_implicit_transaction(tmpdir):
# that would silently disable auto-commit for every subsequent write
path = str(tmpdir / "test.db")
db = Database(path)
db["t"].insert({"id": 1}, pk="id")
db.table("t").insert({"id": 1}, pk="id")
with pytest.raises(sqlite3.IntegrityError):
db.execute("insert into t (id) values (1)")
assert not db.conn.in_transaction
# Subsequent writes commit as normal and survive closing the connection
db["other"].insert({"id": 2})
db.table("other").insert({"id": 2})
db.close()
db2 = Database(path)
assert db2["other"].exists()
assert db2.table("other").exists()
db2.close()
def test_execute_failed_write_preserves_explicit_transaction(fresh_db):
# A failed write inside an explicit transaction must not roll back
# the caller's earlier work - only the caller decides that
fresh_db["t"].insert({"id": 1}, pk="id")
fresh_db.table("t").insert({"id": 1}, pk="id")
fresh_db.begin()
fresh_db.execute("insert into t (id) values (2)")
with pytest.raises(sqlite3.IntegrityError):
fresh_db.execute("insert into t (id) values (1)")
assert fresh_db.conn.in_transaction
fresh_db.commit()
assert [r["id"] for r in fresh_db["t"].rows] == [1, 2]
assert [r["id"] for r in fresh_db.table("t").rows] == [1, 2]
def test_execute_failed_write_inside_atomic_preserves_block(fresh_db):
# A caught failure inside an atomic() block must leave the block's
# transaction open so its other work still commits
fresh_db["t"].insert({"id": 1}, pk="id")
fresh_db.table("t").insert({"id": 1}, pk="id")
with fresh_db.atomic():
fresh_db.execute("insert into t (id) values (2)")
with pytest.raises(sqlite3.IntegrityError):
fresh_db.execute("insert into t (id) values (1)")
assert [r["id"] for r in fresh_db["t"].rows] == [1, 2]
assert [r["id"] for r in fresh_db.table("t").rows] == [1, 2]
def test_query_returning_commits_after_iteration(tmpdir):
@ -325,7 +327,7 @@ def test_query_returning_commits_after_iteration(tmpdir):
_pytest.skip("RETURNING requires SQLite 3.35.0 or higher")
path = str(tmpdir / "test.db")
db = Database(path)
db["t"].insert({"id": 1}, pk="id")
db.table("t").insert({"id": 1}, pk="id")
rows = list(db.query("insert into t (id) values (2) returning id"))
assert rows == [{"id": 2}]
assert not db.conn.in_transaction
@ -375,7 +377,7 @@ def test_nested_atomic_preserves_error_from_transaction_destroying_trigger(
def test_atomic_preserves_error_from_insert_or_rollback(fresh_db):
fresh_db["t"].insert({"id": 1}, pk="id")
fresh_db.table("t").insert({"id": 1}, pk="id")
with pytest.raises(sqlite3.IntegrityError), fresh_db.atomic():
fresh_db.execute("insert or rollback into t (id) values (1)")
assert not fresh_db.conn.in_transaction