Rename ensure_autocommit_off() to ensure_autocommit_on(), closes #705

This commit is contained in:
Simon Willison 2026-07-05 21:54:30 -07:00 committed by GitHub
commit 50938ee6f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 27 additions and 7 deletions

View file

@ -13,6 +13,7 @@ Breaking changes:
- ``table.foreign_keys`` now returns ``ForeignKey`` objects that are dataclasses rather than ``namedtuple`` instances, so they can no longer be unpacked or indexed as ``(table, column, other_table, other_column)`` tuples - access their fields by name instead. Compound (multi-column) foreign keys are now represented as a single ``ForeignKey`` with ``is_compound=True`` and populated ``columns``/``other_columns`` tuples, where ``column`` and ``other_column`` are ``None``. Previously they were returned as one ``ForeignKey`` per column, misleadingly suggesting several independent foreign keys. See :ref:`upgrading_3_to_4` for details. (:issue:`594`)
- Removed support for using ``sqlean.py`` as a drop-in replacement for the Python standard library ``sqlite3`` module. ``sqlite-utils`` will now use ``pysqlite3`` if it is installed, otherwise it will use ``sqlite3`` from the standard library.
- The ``db.ensure_autocommit_off()`` context manager has been renamed to ``db.ensure_autocommit_on()``, because the old name described the opposite of what it did. The method temporarily puts the connection into driver-level autocommit mode - by setting ``isolation_level = None`` - so that statements such as ``PRAGMA journal_mode=wal`` can run outside of an implicit transaction. (:issue:`705`)
Compound foreign key support:

View file

@ -77,6 +77,8 @@ Python API changes
**table.convert() no longer skips falsey values.** Matching the CLI change above, ``table.convert()`` now converts every value. The ``skip_false`` parameter has been removed - previously it defaulted to ``True``, skipping empty strings and other falsey values.
**ensure_autocommit_off() is now ensure_autocommit_on().** The ``db.ensure_autocommit_off()`` context manager has been renamed to ``db.ensure_autocommit_on()``. The old name described the opposite of what the method did: it temporarily puts the connection into driver-level autocommit mode (by setting ``isolation_level = None``), so that statements such as ``PRAGMA journal_mode=wal`` can run outside of an implicit transaction. The behavior is unchanged - update any calls to use the new name.
**View.enable_fts() has been removed.** The ``View`` class previously had an ``enable_fts()`` method that existed only to raise ``NotImplementedError`` - full-text search is not supported for views. Calling it now raises ``AttributeError`` like any other missing method.
**ForeignKey is now a dataclass, not a namedtuple.** The ``ForeignKey`` objects returned by ``table.foreign_keys`` gained new fields - ``columns``, ``other_columns``, ``is_compound``, ``on_delete`` and ``on_update`` - so that compound (multi-column) foreign keys and foreign key actions can be represented. To make room for those fields cleanly ``ForeignKey`` is now a dataclass rather than a ``namedtuple``, so it can no longer be unpacked or indexed as a tuple. Access its fields by name instead:

View file

@ -610,16 +610,22 @@ class Database:
self.conn.execute("ROLLBACK")
@contextlib.contextmanager
def ensure_autocommit_off(self) -> Generator[None, None, None]:
def ensure_autocommit_on(self) -> Generator[None, None, None]:
"""
Ensure autocommit is off for this database connection.
Ensure the connection is in driver-level autocommit mode for the
duration of a block of code.
This temporarily sets ``isolation_level = None`` on the underlying
``sqlite3`` connection, so the driver does not open implicit
transactions. This is useful for statements such as
``PRAGMA journal_mode=wal`` which cannot run inside a transaction.
Example usage::
with db.ensure_autocommit_off():
with db.ensure_autocommit_on():
# do stuff here
This will reset to the previous autocommit state at the end of the block.
The previous ``isolation_level`` is restored at the end of the block.
"""
old_isolation_level = self.conn.isolation_level
try:
@ -783,7 +789,7 @@ class Database:
if self.conn.in_transaction:
cursor = self.conn.execute(sql, *args)
else:
with self.ensure_autocommit_off():
with self.ensure_autocommit_on():
cursor = self.conn.execute(sql, *args)
if cursor.description is None:
raise ValueError(message)
@ -1085,7 +1091,7 @@ class Database:
"""
if self.journal_mode != "wal":
self._ensure_no_open_transaction("enable_wal()")
with self.ensure_autocommit_off():
with self.ensure_autocommit_on():
self.execute("PRAGMA journal_mode=wal;")
def disable_wal(self) -> None:
@ -1097,7 +1103,7 @@ class Database:
"""
if self.journal_mode != "delete":
self._ensure_no_open_transaction("disable_wal()")
with self.ensure_autocommit_off():
with self.ensure_autocommit_on():
self.execute("PRAGMA journal_mode=delete;")
def _ensure_no_open_transaction(self, operation: str) -> None:

View file

@ -49,6 +49,17 @@ def test_disable_wal_inside_transaction_raises(db_path_tmpdir):
assert [r["id"] for r in db["test"].rows] == [1]
def test_ensure_autocommit_on(db_path_tmpdir):
db, path, tmpdir = db_path_tmpdir
previous_isolation_level = db.conn.isolation_level
assert previous_isolation_level is not None
with db.ensure_autocommit_on():
# isolation_level of None means driver-level autocommit mode
assert db.conn.isolation_level is None
# Restored afterwards
assert db.conn.isolation_level == previous_isolation_level
def test_enable_wal_noop_inside_transaction_is_allowed(db_path_tmpdir):
# Calling enable_wal() when WAL is already enabled is a no-op,
# so it is fine inside a transaction