> Fetch the index of every Tonbo docs page as Markdown at https://tonbo.dev/llms.txt

# UrsulaConnection

Last updated 3 November 2026

`UrsulaConnection` is the connection `open()` returns: what it adds to [sqlite3.Connection](https://docs.python.org/3/library/sqlite3.html#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()](https://tonbo.dev/docs/reference/python/open.md#open), it binds nothing and works as a plain `sqlite3.Connection`.

## UrsulaConnection

```python
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.

```python
other = sqlite3.connect(db.path)  # replicates too, and db.synced() covers it
```

## synced()

```python
def synced(self, timeout: float | None = None) -> None: ...
```

```python
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](https://tonbo.dev/docs/databases/how-it-works.md#commit)). 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()](https://tonbo.dev/docs/reference/python/gate-and-import.md#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](https://tonbo.dev/docs/reference/python/errors.md#setup-invalid-argument). |

- Network failures never raise: it waits until Tonbo is reachable again. Ctrl-C interrupts it.
- It raises `TimeoutError` after `timeout`, [UrsulaResetError](https://tonbo.dev/docs/reference/python/errors.md#ursula-reset-error) once the database is reset, and [`forked`](https://tonbo.dev/docs/reference/python/errors.md#setup-forked) in a forked child.
- On a reader cache, from a `read_only` open 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()

```python
async def asynced(self, timeout: float | None = None) -> None: ...
```

```python
db.execute("INSERT INTO notes(body) VALUES (?)", (body,))
db.commit()
await db.asynced(timeout=5)  # waits on the event loop
await send(reply)
```

[synced()](#synced) for asyncio: the same [timeout](#synced-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()

```python
def status(self) -> dict[str, Any]: ...
```

```python
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](https://tonbo.dev/docs/reference/nodejs/status.md) lists, nested as there: such as [commits.pending](https://tonbo.dev/docs/reference/nodejs/status.md#field-commits-pending), the commits on this machine Tonbo does not hold yet, and [replication.condition](https://tonbo.dev/docs/reference/nodejs/status.md#field-replication-condition), why they wait. It raises [`forked`](https://tonbo.dev/docs/reference/python/errors.md#setup-forked) in a forked child, and [`not_bound`](https://tonbo.dev/docs/reference/python/errors.md#setup-not-bound) once the database retired.

## on_reset()

```python
def on_reset(self, callback: ResetCallback) -> ResetCallback: ...
```

```python
@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](https://tonbo.dev/docs/reference/python/errors.md#ursula-reset-error) (see [Troubleshooting](https://tonbo.dev/docs/databases/troubleshooting/reset.md#reset)).

| Parameter | Type | Default | What it does |
| --- | --- | --- | --- |
| `callback` | `ResetCallback` | Required | Called once, with the [UrsulaResetError](https://tonbo.dev/docs/reference/python/errors.md#ursula-reset-error), when the database is reset. What it returns is ignored, and an exception it raises is logged to the `sqlite_ursula` logger. |

## off_reset()

```python
def off_reset(self, callback: ResetCallback) -> None: ...
```

Removes a callback [on_reset()](#on-reset) added. Both raise [`forked`](https://tonbo.dev/docs/reference/python/errors.md#setup-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](https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection) take the same parameters and return the same values. What changes:

| Member | What changes |
| --- | --- |
| `cursor` | Returns an [UrsulaCursor](#ursula-cursor) unless given a `factory`. |
| `execute` | As `sqlite3`’s. A failure of the database raises an [UrsulaError](https://tonbo.dev/docs/reference/python/errors.md#ursula-error), 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](https://tonbo.dev/docs/reference/python/errors.md#ursula-write-refused-error) with nothing committed. It returns once the commit is on this machine: call [synced()](#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](#after-an-error)). |
| `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`](https://tonbo.dev/docs/reference/python/errors.md#setup-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`](https://tonbo.dev/docs/reference/python/errors.md#write-refused-backlog-full), once `synced()` returned.

## UrsulaCursor

```python
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](#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](https://tonbo.dev/docs/databases/python.md).
