Metadata-Version: 2.4
Name: sqlite-callback-store
Version: 0.0.6
Summary: Typed callback transactions and exception-free SQLite storage
Author-email: Veya Fürst <ghgstefan@gmail.com>
License-Expression: MIT
Project-URL: PyPI, https://pypi.org/project/sqlite-callback-store/
Project-URL: Repository, https://github.com/0xveya/sqlite-callback-store
Project-URL: Issues, https://github.com/0xveya/sqlite-callback-store/issues
Keywords: sqlite,database,transactions,result
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Database
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: python-crimes<1,>=0
Requires-Dist: typed-errs<1,>=0
Provides-Extra: turso
Requires-Dist: pyturso<1,>=0.7; extra == "turso"
Dynamic: license-file

# sqlite-callback-store

[![PyPI](https://img.shields.io/pypi/v/sqlite-callback-store)](https://pypi.org/project/sqlite-callback-store/)
[![CI](https://github.com/0xveya/sqlite-callback-store/actions/workflows/ci.yml/badge.svg)](https://github.com/0xveya/sqlite-callback-store/actions/workflows/ci.yml)

**[View sqlite-callback-store on PyPI](https://pypi.org/project/sqlite-callback-store/)**

Short-lived SQLite connections wrapped in typed read and transaction callbacks.
Connections are cleaned up through
[python-crimes](https://github.com/0xveya/python-crimes)' `defer` stack, while
operation outcomes remain `typed-errs` `Result` values.

The same package offers `@deferred` for cleanup that belongs to application
work surrounding a store operation:

```python
from python_crimes import deferred
from typed_errs import Result, catch_bubble


@catch_bubble
@deferred
def import_notes(cleanup, store: SQLiteStore) -> Result[int, StorageError]:
    # Register any temporary resource cleanup before starting the transaction.
    # Store connections themselves are already deferred internally.
    return store.transaction(insert_notes)
```
An `Ok` commits, an `Err` rolls back, and database exceptions become
`StorageError` results.

## Turso

Install the optional Turso driver when the same callback API should use the
Turso database engine:

```bash
uv add "sqlite-callback-store[turso]"
```

Use it locally as a drop-in store:

```python
from sqlite_callback_store import TursoStore

store = TursoStore("notes.db")
```

Or attach the local database to Turso Cloud. Reads and writes stay local until
the store pulls on connection and pushes each successful transaction:

```python
store = TursoStore(
    "notes.db",
    remote_url="turso://database-organization.turso.io",
    auth_token="...",
)
```

Keep cloud credentials outside source code. `TursoStore` loads `pyturso` only
when a connection is opened, so normal `SQLiteStore` installations remain
small and unchanged.

```bash
uv add sqlite-callback-store
```

## Example

```python
from sqlite_callback_store import SQLiteStore, Transaction
from typed_errs import Ok

store = SQLiteStore("data/app.db")
store.initialize("CREATE TABLE IF NOT EXISTS notes (body TEXT NOT NULL)")


def insert(tx: Transaction):
    tx.conn.execute("INSERT INTO notes(body) VALUES (?)", ("hello",))
    return Ok(None)


store.transaction(insert)
rows = store.read(lambda conn: Ok(conn.execute("SELECT * FROM notes").fetchall()))
```

Applications keep their domain-specific typed query classes; this library owns
connection setup, pragmas, commit/rollback, directory creation, and error
conversion.

## Adding your own store methods

Subclass `SQLiteStore` and add methods named for your domain. Write methods call
`self.transaction(callback)`: returning `Ok` commits and returning `Err` rolls
back. Read methods call `self.read(callback)` and receive a short-lived
`sqlite3.Connection` configured to produce `sqlite3.Row` values. You may also
override `initialize()` to supply your schema by default while delegating to
`super().initialize(schema)`.

See the complete [typed notes store](examples/typed_notes_store.py). It defines
a `Note` model and a `NotesStore` with `initialize`, `add`, and `all` methods,
while the base library continues to own connections, pragmas, commits,
rollbacks, and error conversion.

## Where I use it

This is my internal SQLite foundation for 42 projects. It was extracted from
[RAG Against the Machine](https://github.com/0xveya/42-rag-against-the-machine),
where it manages the source/chunk index, FTS queries, short-lived read
connections, WAL configuration, and atomic indexing transactions. The
RAG-specific schema and query models stay in RAG; this package contains only
the reusable callback transaction layer.

## Dependencies

- Python 3.10+ and its standard-library `sqlite3`
- `typed-errs`

## Use and contributions

This is a personal library, but it is not private or locked to my projects.
You may use it in general Python work and in 42 projects under the MIT license;
just follow the rules that apply to your campus and assignment.

Contributions are welcome: open an issue or send a pull request. I do not care
whether a contribution is written by hand, AI-assisted, or generated another
way; I care about whether it is correct, tested, understandable, and a good fit.
Because this is opinionated personal infrastructure, pull requests are reviewed
selectively and are likely to be rejected unless they clearly improve the
library without making it harder to maintain.

## Development and release

Run `mise run check`. Every push to `master` publishes a unique `0.0.<CI run>` ZeroVer
version through PyPI Trusted Publishing. `mise run publish` remains available.
