RenameTableEvent, plus write connection track_event() mechanism (#2682)

* Add track_event callback to execute_write_fn and write_wrapper

Allows write functions and write_wrapper generators to queue events
during a write operation that are dispatched after successful commit.
The fn or wrapper can optionally accept a `track_event` parameter
(detected via call_with_supported_arguments). Events are discarded
if the write raises an exception.

Does not yet handle the block=False (non-blocking) case - events
queued during non-blocking writes are currently silently discarded.

Refs https://github.com/simonw/datasette/issues/2681

* Dispatch track_event events for non-blocking (block=False) writes

Spawns a background asyncio task that awaits the write thread's reply
queue and dispatches pending events after a successful non-blocking
write. Events are still discarded if the write raises an exception.

Refs https://github.com/simonw/datasette/issues/2681

* Warn that events won't fire for other processes

Refs https://github.com/simonw/datasette/issues/2681#issuecomment-4157118662
This commit is contained in:
Simon Willison 2026-03-30 11:20:46 -07:00 committed by GitHub
commit 312f41b0c2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 423 additions and 24 deletions

View file

@ -5,6 +5,8 @@ Datasette includes a mechanism for tracking events that occur while the software
The core Datasette application triggers events when certain things happen. This page describes those events.
Note that these events will *not* fire for changes made to a SQLite database by a process other than Datasette itself.
Plugins can listen for events using the {ref}`plugin_hook_track_event` plugin hook, which will be called with instances of the following classes - or additional classes {ref}`registered by other plugins <plugin_hook_register_events>`.
```{eval-rst}

View file

@ -1739,6 +1739,36 @@ For example:
except Exception as e:
print("An error occurred:", e)
Your function can optionally accept a ``track_event`` parameter in addition to ``conn``. If it does, it will be passed a callable that can be used to queue events for dispatch after the write transaction commits successfully. Events queued this way are discarded if the write raises an exception.
.. code-block:: python
from datasette.events import AlterTableEvent
def my_write(conn, track_event):
before_schema = conn.execute(
"select sql from sqlite_master where name = 'my_table'"
).fetchone()[0]
conn.execute(
"alter table my_table add column new_col text"
)
after_schema = conn.execute(
"select sql from sqlite_master where name = 'my_table'"
).fetchone()[0]
track_event(
AlterTableEvent(
actor=None,
database="mydb",
table="my_table",
before_schema=before_schema,
after_schema=after_schema,
)
)
await database.execute_write_fn(my_write)
The value returned from ``await database.execute_write_fn(...)`` will be the return value from your function.
If your function raises an exception that exception will be propagated up to the ``await`` line.

View file

@ -78,12 +78,14 @@ write_wrapper(datasette, database, request, transaction)
``transaction`` - bool
``True`` if the write will be wrapped in a database transaction.
Return a generator function that accepts a ``conn`` argument (a SQLite connection object). The generator should ``yield`` exactly once. Code before the ``yield`` runs before the write function executes; code after the ``yield`` runs after it completes.
Return a generator function that accepts a ``conn`` argument (a SQLite connection object) and optionally a ``track_event`` argument. The generator should ``yield`` exactly once. Code before the ``yield`` runs before the write function executes; code after the ``yield`` runs after it completes.
The result of the write function is sent back through the ``yield``, so you can capture it with ``result = yield``.
If the write function raises an exception, it is thrown into the generator so you can handle it with a ``try`` / ``except`` around the ``yield``.
If your generator accepts ``track_event``, you can call ``track_event(event)`` to queue an event that will be dispatched via :ref:`datasette.track_event() <datasette_track_event>` after the write commits successfully. Events are discarded if the write raises an exception.
Return ``None`` to skip wrapping for this particular write.
This example logs every write operation:

View file

@ -261,7 +261,8 @@ If you run ``datasette plugins --all`` it will include default plugins that ship
"templates": false,
"version": null,
"hooks": [
"register_events"
"register_events",
"write_wrapper"
]
},
{