Metadata-Version: 2.5
Name: jotabase
Version: 0.8.0
Summary: Python client for jotaBase: read, write, local SQL with DuckDB and live changes.
Project-URL: Homepage, https://jotabase.com
Project-URL: Repository, https://github.com/jimv-dev/jbase
Author: jotaBase
License-Expression: ISC
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Database
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: pyarrow>=15
Requires-Dist: websockets>=13
Provides-Extra: app
Requires-Dist: duckdb>=1.1; extra == 'app'
Requires-Dist: pywebview>=5; extra == 'app'
Provides-Extra: pandas
Requires-Dist: pandas>=2.0; extra == 'pandas'
Provides-Extra: polars
Requires-Dist: polars>=1.0; extra == 'polars'
Provides-Extra: sql
Requires-Dist: duckdb>=1.1; extra == 'sql'
Provides-Extra: test
Requires-Dist: duckdb>=1.1; extra == 'test'
Requires-Dist: pandas>=2.0; extra == 'test'
Requires-Dist: polars>=1.0; extra == 'test'
Requires-Dist: pytest>=8; extra == 'test'
Description-Content-Type: text/markdown

# jotabase

Python client for [jotaBase](https://jotabase.com): sync, query and update your jotaBase databases
from servers, scripts, notebooks and desktop apps.

- **Read and write** a database with a secret key (servers and scripts) or a publishable key and a
  signed-in user (apps, where access rules apply).
- **Keep a copy on disk** that later syncs bring up to date, downloading only what changed.
- **Query it with SQL** through DuckDB, one table per collection, with results as pandas or Polars
  tables.
- **React to changes** as they happen.

```bash
pip install jotabase                      # read, write, local copy, live changes
pip install "jotabase[sql,pandas]"        # plus SQL with DuckDB, and pandas
```

Python 3.10 or newer.

## Quick start

```python
import jotabase

db = jotabase.connect("my-app", key="sk_live_...")   # your database's secret key

# Write
db.put("tasks", {"title": "Write the report", "points": 3}, id="t1")
results = db.put_many("measurements", batch)        # split under the 1 MB request limit
for r in results.failed:                            # refused by an access rule
    print(r.id, r.error)
db.delete("tasks", "t1")

# Read everything, or only what changed since last time
changes = db.fetch_changes()
docs = changes.latest()                             # newest version of each, no deleted ones
later = db.fetch_changes(since=changes.checkpoint)

# Keep a copy in ./.jotabase/my-app/ and read it with no server
db.pull()
tasks = db.local().documents("tasks")

# SQL over the copy (pip install "jotabase[sql]")
df = db.sql("SELECT status, count(*) AS n FROM tasks GROUP BY status").df()   # pandas
pl = db.sql("SELECT * FROM tasks WHERE points > ?", [3]).pl()                 # Polars

# React to changes as they happen (a background thread)
@db.on_change
def changed(change):
    for doc in change.docs:
        print(doc["id"], doc["data"])
```

## Keys and users

| You connect with | Who it is for | Access rules |
|---|---|---|
| `key="sk_..."` (secret) | servers, scripts, scheduled jobs | bypassed |
| `key="pk_..."` (publishable), then `db.sign_in(email, password)` | apps and desktop apps | applied to the user's roles |
| no key, `jotabase.connect("@handle/name")` | someone's public database | read-only, no live changes |

Never ship a secret key inside an app that runs on someone else's machine. Keys are created in the
dashboard at [app.jotabase.com](https://app.jotabase.com).

## Desktop app

The jotaBase dashboard in a window of its own, with SQL on this computer's DuckDB and each
database's copy kept on disk -- queries over a million rows answer in milliseconds.

```bash
pip install "jotabase[app]"
jotabase-app
```

Sign in with your [app.jotabase.com](https://app.jotabase.com) account. Copies live in
`~/.local/share/jotabase/app/databases/`; everything still syncs with the cloud.

## Errors

A refused request raises `jotabase.JotabaseError` with `.status` (401/403 means the key or the
login, anything else is worth a retry) and `.code`. A document an access rule refuses does **not**
raise: it comes back in `results.failed` with the reason, while the rest of the batch is written.

## Links

- [jotaBase](https://jotabase.com) and the [dashboard](https://app.jotabase.com)
- [Design notes for this package](https://github.com/jimv-dev/jbase/blob/main/docs/PYTHON-CLIENT.md)
- The JavaScript client: [`@jotabase/client`](https://www.npmjs.com/package/@jotabase/client)
