diff --git a/docs/changelog.rst b/docs/changelog.rst index baff4af..f6f97b6 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -14,7 +14,7 @@ Breaking changes: - ``db.query()`` now executes its SQL as soon as it is called, rather than waiting until the returned generator is first iterated. Rows are still fetched lazily during iteration. SQL errors are now raised at the call site, statements such as ``INSERT ... RETURNING`` take effect without needing to iterate over their results, and passing a statement that returns no rows - previously a silent no-op - now raises a ``ValueError`` recommending ``db.execute()`` instead. - Python API validation errors now raise ``ValueError`` instead of ``AssertionError``. Previously invalid arguments - such as ``create_table()`` with no columns, ``transform()`` on a table that does not exist, or passing both ``ignore=True`` and ``replace=True`` - were rejected using bare ``assert`` statements, which are silently skipped when Python runs with the ``-O`` flag. Code that caught ``AssertionError`` for these cases should catch ``ValueError`` instead. - ``table.upsert()`` and ``table.upsert_all()`` now raise ``PrimaryKeyRequired`` if a record is missing a value for any primary key column, or has a value of ``None`` for one. Previously such records - which can never match an existing row - were quietly inserted as brand new rows, or triggered a confusing ``KeyError`` after the insert had already taken place. -- ``db.enable_wal()`` and ``db.disable_wal()`` now raise a ``RuntimeError`` if called while a transaction is open. Previously they would silently commit the open transaction as a side effect of changing the journal mode, breaking the rollback guarantee of ``db.atomic()`` and of user-managed transactions. +- ``db.enable_wal()`` and ``db.disable_wal()`` now raise a ``sqlite_utils.db.TransactionError`` if called while a transaction is open. Previously they would silently commit the open transaction as a side effect of changing the journal mode, breaking the rollback guarantee of ``db.atomic()`` and of user-managed transactions. - The ``View`` class no longer has an ``enable_fts()`` method. It existed only to raise ``NotImplementedError``, since full-text search is not supported for views - calling it now raises ``AttributeError`` instead, and the method no longer appears in the API reference. The ``sqlite-utils enable-fts`` command shows a clean error when pointed at a view. - The no-op ``-d/--detect-types`` flag has been removed from the ``insert`` and ``upsert`` commands. Type detection has been the default for CSV/TSV data since 4.0a1, so the flag did nothing - invocations using it should simply drop it. ``--no-detect-types`` remains available to disable detection. diff --git a/docs/python-api.rst b/docs/python-api.rst index 400c0fa..810f846 100644 --- a/docs/python-api.rst +++ b/docs/python-api.rst @@ -388,7 +388,7 @@ The library will never commit a transaction you opened. If you call write method Two related safeguards to be aware of: -- ``db.enable_wal()`` and ``db.disable_wal()`` raise a ``RuntimeError`` if called while a transaction is open, because changing the journal mode would commit it as a side effect. +- ``db.enable_wal()`` and ``db.disable_wal()`` raise a ``sqlite_utils.db.TransactionError`` if called while a transaction is open, because changing the journal mode would commit it as a side effect. - Closing the database - explicitly with ``db.close()``, or by exiting a ``with Database(...) as db:`` block - rolls back any transaction that is still open, see :ref:`python_api_close`. Supported connection modes @@ -2798,7 +2798,7 @@ You can disable WAL mode using ``.disable_wal()``: Database("my_database.db").disable_wal() -The journal mode can only be changed outside of a transaction. Calling either method while a transaction is open - inside a ``db.atomic()`` block, for example - raises a ``RuntimeError``, unless the database is already in the requested mode in which case the call is a no-op. +The journal mode can only be changed outside of a transaction. Calling either method while a transaction is open - inside a ``db.atomic()`` block, for example - raises a ``sqlite_utils.db.TransactionError``, unless the database is already in the requested mode in which case the call is a no-op. You can check the current journal mode for a database using the ``journal_mode`` property: diff --git a/docs/upgrading.rst b/docs/upgrading.rst index e6f4d4d..a18a88e 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -77,7 +77,7 @@ Python API changes **Transaction behavior is now well-defined.** 4.0 introduces the :ref:`db.atomic() ` context manager and uses it consistently for every write operation - the full model is described in :ref:`python_api_transactions`. Changes you may notice: - Multi-step operations such as ``table.transform()`` no longer commit an existing transaction you have open - they use savepoints inside it instead. -- ``db.enable_wal()`` and ``db.disable_wal()`` raise a ``RuntimeError`` if called while a transaction is open, instead of silently committing it. +- ``db.enable_wal()`` and ``db.disable_wal()`` raise a ``sqlite_utils.db.TransactionError`` if called while a transaction is open, instead of silently committing it. - Using ``Database`` as a context manager (``with Database(path) as db:``) closes the connection on exit *without* committing - uncommitted changes from raw ``db.execute()`` calls are rolled back. Packaging changes diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index aeafe56..d53e758 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -301,6 +301,10 @@ class InvalidColumns(Exception): "Specified columns do not exist" +class TransactionError(Exception): + "Operation cannot be performed while a transaction is open" + + class DescIndex(str): pass @@ -891,7 +895,7 @@ class Database: """ Sets ``journal_mode`` to ``'wal'`` to enable Write-Ahead Log mode. - :raises RuntimeError: if called while a transaction is open - the + :raises TransactionError: if called while a transaction is open - the journal mode can only be changed outside of a transaction """ if self.journal_mode != "wal": @@ -903,7 +907,7 @@ class Database: """ Sets ``journal_mode`` back to ``'delete'`` to disable Write-Ahead Log mode. - :raises RuntimeError: if called while a transaction is open - the + :raises TransactionError: if called while a transaction is open - the journal mode can only be changed outside of a transaction """ if self.journal_mode != "delete": @@ -916,7 +920,7 @@ class Database: # any open transaction as a side effect - breaking the rollback # guarantee of atomic() and of user-managed transactions if self.conn.in_transaction: - raise RuntimeError( + raise TransactionError( "{} cannot be used while a transaction is open".format(operation) ) diff --git a/tests/test_wal.py b/tests/test_wal.py index fa3c855..ee7ecf0 100644 --- a/tests/test_wal.py +++ b/tests/test_wal.py @@ -1,5 +1,6 @@ import pytest from sqlite_utils import Database +from sqlite_utils.db import TransactionError @pytest.fixture @@ -26,7 +27,7 @@ def test_enable_disable_wal(db_path_tmpdir): def test_enable_wal_inside_transaction_raises(db_path_tmpdir): db, path, tmpdir = db_path_tmpdir db["test"].insert({"id": 1}, pk="id") - with pytest.raises(RuntimeError): + with pytest.raises(TransactionError): with db.atomic(): db["test"].insert({"id": 2}, pk="id") db.enable_wal() @@ -40,7 +41,7 @@ def test_disable_wal_inside_transaction_raises(db_path_tmpdir): db, path, tmpdir = db_path_tmpdir db.enable_wal() db["test"].insert({"id": 1}, pk="id") - with pytest.raises(RuntimeError): + with pytest.raises(TransactionError): with db.atomic(): db["test"].insert({"id": 2}, pk="id") db.disable_wal()