UrsulaConnection
Last updated 3 November 2026
UrsulaConnection is the connection open() returns: what it adds to sqlite3.Connection, synced(), asynced(), status() and the reset callbacks, and what changes in the members it keeps.
It is a sqlite3.Connection subclass, so isinstance holds and any library that takes a sqlite3 connection takes it. Made with sqlite3.connect(..., factory=UrsulaConnection) outside open(), it binds nothing and works as a plain sqlite3.Connection.
UrsulaConnection
class UrsulaConnection(sqlite3.Connection):
name: str
path: str
async def asynced(self, timeout: float | None = None) -> None: ...
def off_reset(self, callback: ResetCallback) -> None: ...
def on_reset(self, callback: ResetCallback) -> ResetCallback: ...
def status(self) -> dict[str, Any]: ...
def synced(self, timeout: float | None = None) -> None: ...name
The database’s path on Tonbo, such as /acme/notes, or "" for a connection open() did not make.
path
The local database file the database is bound to, as status()["path"] reports it. A framework that opens its own sqlite3 connection by path uses the same database through it: its commits replicate too and synced() covers them. Keep db open while it does, since the binding retires once its last connection closes.
other = sqlite3.connect(db.path) # replicates too, and db.synced() covers itsynced()
def synced(self, timeout: float | None = None) -> None: ...db.execute("INSERT INTO notes(body) VALUES (?)", (body,))
db.commit()
db.synced() # returns once Tonbo holds the commit
send(reply)Returns once Tonbo holds every commit this process made on the database before the call, and a round trip that started after the call found this process still its writer. Call it before anything outside the process acts on what you wrote or read, such as a reply or a queue acknowledgement (see When a commit is durable). It covers every connection of the process on this database, a framework’s included, and no other database: call it on each one an output depends on, or use gate().
| Parameter | Type | Default | What it does |
|---|---|---|---|
timeout | float | None | None: no limit | How many seconds to wait, zero or more, at most threading.TIMEOUT_MAX. Past it, TimeoutError, while the commits keep replicating: call it again to keep waiting. Any other value raises invalid_argument. |
- Network failures never raise: it waits until Tonbo is reachable again. Ctrl-C interrupts it.
- It raises
TimeoutErroraftertimeout, UrsulaResetError once the database is reset, andforkedin a forked child. - On a reader cache, from a
read_onlyopen in a process that does not own the database, it returns at once. - It is safe to call any number of times, from any thread: each call waits for the commits made before it. After a
TimeoutError, call it again to keep waiting.
asynced()
async def asynced(self, timeout: float | None = None) -> None: ...db.execute("INSERT INTO notes(body) VALUES (?)", (body,))
db.commit()
await db.asynced(timeout=5) # waits on the event loop
await send(reply)synced() for asyncio: the same timeout, the same guarantee and the same errors, and it waits on the event loop instead of blocking it. Cancelling it ends the wait, while the commits keep replicating.
status()
def status(self) -> dict[str, Any]: ...status = db.status()
if status["commits"]["pending"]:
print("waiting for Tonbo:", status["replication"]["condition"])The database’s state, read locally with no network, as a dict (dict[str, Any]). It is the JSON the extension’s ursula_status returns to every host, so its keys are the fields Status lists, nested as there: such as commits.pending, the commits on this machine Tonbo does not hold yet, and replication.condition, why they wait. It raises forked in a forked child, and not_bound once the database retired.
on_reset()
def on_reset(self, callback: ResetCallback) -> ResetCallback: ...@db.on_reset
def reset(error: sqlite_ursula.UrsulaResetError) -> None:
log.warning("notes was reset: %s (%s)", error.kind, error.action)Runs callback once when the database is reset, after its pending synced() calls were rejected, on a thread of the package’s. It returns callback, so it serves as a decorator. A connection keeps its callbacks until it closes; a closed one runs none. What to do after a reset depends on its kind (see Troubleshooting).
| Parameter | Type | Default | What it does |
|---|---|---|---|
callback | ResetCallback | Required | Called once, with the UrsulaResetError, when the database is reset. What it returns is ignored, and an exception it raises is logged to the sqlite_ursula logger. |
off_reset()
def off_reset(self, callback: ResetCallback) -> None: ...Removes a callback on_reset() added. Both raise forked in a forked child.
| Parameter | Type | Default | What it does |
|---|---|---|---|
callback | ResetCallback | Required | A callback on_reset() added to this connection. Any other does nothing. |
The sqlite3 members
The members it keeps from sqlite3.Connection take the same parameters and return the same values. What changes:
| Member | What changes |
|---|---|
cursor | Returns an UrsulaCursor unless given a factory. |
execute | As sqlite3’s. A failure of the database raises an UrsulaError, a refused write or a reset; every other error is sqlite3’s, unchanged. |
executemany | As execute. |
executescript | As execute. |
commit | As sqlite3’s. A refused COMMIT raises UrsulaWriteRefusedError with nothing committed. It returns once the commit is on this machine: call synced() before anything outside the process acts on it. |
rollback | As sqlite3’s; after a refused write or a reset there is nothing to roll back, and it does nothing (see After a refused write). |
close | Closes the connection. Once the database’s last connection closes, the commits left keep replicating in the background and the binding retires; open() it again to use it. In a child forked after the parent made the connection it closes nothing, since the file’s locks are the parent’s. |
autocommit | On Python 3.12 or later, as sqlite3’s, except that setting it to False raises invalid_argument: in that mode the connection is always in a transaction, which would hide a refused commit. |
with db: commits or rolls back as sqlite3’s does, and raises as commit does. Its other members, such as backup, blobopen and serialize, are sqlite3’s, unchanged. Keep the default transaction control, isolation_level=None or autocommit=True.
After a refused write or a reset
A refused write and a reset are I/O errors on which SQLite rolls the whole transaction back, so no transaction is open after one. The connection then keeps the error until a statement succeeds: a ROLLBACK run through execute succeeds, since nothing is left to roll back, while a COMMIT or END that finds no transaction, a ROLLBACK TO or RELEASE that finds no savepoint, and a ROLLBACK inside executescript raise the kept error again. So a transaction helper’s own ROLLBACK or COMMIT never replaces the error you need. Run the whole transaction again after backlog_full, once synced() returned.
UrsulaCursor
class UrsulaCursor(sqlite3.Cursor):
def execute(self, sql: str, parameters: Any = (), /) -> UrsulaCursor: ...
def executemany(self, sql: str, parameters: Any, /) -> UrsulaCursor: ...
def executescript(self, script: str, /) -> UrsulaCursor: ...
def fetchall(self) -> list[Any]: ...
def fetchmany(self, size: int | None = None) -> list[Any]: ...
def fetchone(self) -> Any: ...The sqlite3.Cursor subclass cursor() returns. Its execute, executemany, executescript, fetchall, fetchmany and fetchone, and iterating it, take what sqlite3.Cursor’s take and raise as execute does. A cursor of a connection open() did not make works as a plain sqlite3.Cursor. For a first open, see Connect from Python.