From fd867282b3ae8ba05e60eee39471181008102c47 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 4 Jul 2026 19:06:44 +0000 Subject: [PATCH] Document atomic() deferred BEGIN, savepoint behavior and connection modes Documents that atomic() opens a deferred BEGIN, that calling it while the connection is already inside a transaction produces a savepoint whose changes only persist when the outer transaction commits, and that Python 3.12+ autocommit=True/False connections are not supported. Adds a test pinning the savepoint-inside-manual- BEGIN behavior. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01UnLnhsH25Nnv7LHhekUfPd --- docs/python-api.rst | 6 ++++++ tests/test_atomic.py | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/docs/python-api.rst b/docs/python-api.rst index d575ba6..e888b7d 100644 --- a/docs/python-api.rst +++ b/docs/python-api.rst @@ -274,6 +274,12 @@ If an exception is raised, changes made inside the block will be rolled back. pass db.table("dogs").insert({"id": 3, "name": "Marnie"}) +The transaction is opened with a deferred ``BEGIN`` - SQLite takes the necessary locks when the first statement inside the block runs. + +The same savepoint mechanism used for nesting applies if the connection is already inside a transaction when ``db.atomic()`` is called - for example if you executed ``BEGIN`` yourself, or made writes using raw ``db.execute()`` calls. In that case the block becomes a savepoint within your transaction: exiting the block releases the savepoint, but nothing is committed to disk until your outer transaction commits. + +``db.atomic()`` is designed for connections in Python's default transaction handling mode. Connections created with the Python 3.12+ ``sqlite3.connect(..., autocommit=True)`` or ``autocommit=False`` options are not supported, because ``commit()`` and ``rollback()`` behave differently on those connections. + .. _python_api_parameters: Passing parameters diff --git a/tests/test_atomic.py b/tests/test_atomic.py index acd5474..8c4d18d 100644 --- a/tests/test_atomic.py +++ b/tests/test_atomic.py @@ -172,3 +172,20 @@ def test_transform_detects_foreign_key_check_violations(fresh_db): assert fresh_db["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.execute("begin") + with fresh_db.atomic(): + fresh_db["t"].insert({"id": 2}, pk="id") + # Nothing is committed until the user's own transaction commits + assert fresh_db.conn.in_transaction + fresh_db.conn.rollback() + assert [r["id"] for r in fresh_db["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.conn.commit() + assert [r["id"] for r in fresh_db["t"].rows] == [1, 3]