Metadata-Version: 2.4
Name: larzdb
Version: 0.1.0
Summary: Crash-safe, single-file, embedded document + key-value database. ACID, atomic transactions, Mongo-style queries. Zero dependencies.
Author: larz-scripter
License: MIT
Project-URL: Homepage, https://github.com/larz-scripter/larzdb
Project-URL: Repository, https://github.com/larz-scripter/larzdb
Project-URL: Documentation, https://github.com/larz-scripter/larzdb#readme
Project-URL: Issues, https://github.com/larz-scripter/larzdb/issues
Keywords: database,embedded-database,document-database,key-value,nosql,acid,wal,single-file,sqlite-alternative,json,transactions,zero-dependency,pure-python
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Database
Classifier: Topic :: Database :: Database Engines/Servers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# larzdb

**A crash-safe, single-file, embedded document + key-value database. Zero dependencies.**

SQLite's best idea — *one file, no server, ACID* — applied to JSON documents, in
a few hundred lines of pure Python you can actually read.

```python
from larzdb import Database

db = Database("app.larz")

# key-value
db.put("config:theme", "dark")
db.get("config:theme")                      # "dark"

# documents
users = db.collection("users")
uid = users.insert({"name": "Ada", "age": 36, "role": "admin"})
users.find({"role": "admin", "age": {"$gte": 18}})

# atomic, all-or-nothing, durable
with db.transaction() as tx:
    tx.put("balance:a", 40)
    tx.put("balance:b", 60)
```

## Why

- **Durable** — every commit does `flush()` + `os.fsync()`; when a write returns, it's on disk.
- **Crash-safe** — a process killed mid-write leaves a torn tail frame that recovery simply drops. You never see half a transaction. *(There are on-disk tests that truncate and corrupt the log, then reopen and assert integrity.)*
- **Atomic transactions** — a whole batch of writes lands as one checksummed frame, or not at all.
- **Real queries** — Mongo-style filters (`$gt`, `$in`, `$or`, nested fields…) with optional in-memory secondary indexes.
- **One file** — trivial to back up, copy, or delete. `compact()` reclaims space from overwritten/deleted records.
- **Zero dependencies** — pure standard library. Nothing to install, nothing to compile, nothing to run as a server.

## Install

```bash
pip install larzdb
```

## How it stores data

larzdb is a **log-structured** store (the idea behind Bitcask and write-ahead
logs). Every change is appended to the file as a self-describing, CRC-checked
frame:

```
MAGIC(2) | payload_len(4) | crc32(4) | payload
```

Each frame is one committed transaction. On open, larzdb replays every valid
frame to rebuild its in-memory index and **stops at the first torn or
CRC-failing frame** — so an interrupted write can never corrupt earlier data.
`compact()` rewrites the file with just the live records, atomically via a
temp-file-and-rename so a crash during compaction leaves the original intact.

This makes writes fast (sequential appends) and recovery simple, at the cost of
keeping the key index in memory — a great fit for embedded app state, caches,
job queues, config, game saves, small services, and CLIs.

## Documents & queries

```python
users = db.collection("users")
users.insert({"name": "Ada", "age": 36, "role": "admin"})
users.insert({"name": "Bo",  "age": 17, "role": "user"})

users.find({"age": {"$gte": 18}})                       # ranges
users.find({"role": {"$in": ["admin", "user"]}})        # membership
users.find({"$or": [{"role": "admin"}, {"age": {"$lt": 18}}]})
users.find({"address.city": "Lagos"})                   # nested fields
users.find(sort="age", reverse=True, limit=10)          # sort + limit
users.find_one({"name": "Ada"})
users.count({"role": "admin"})
users.delete_many({"role": "user"})
```

Operators: `$eq $ne $gt $gte $lt $lte $in $nin $exists $regex` at the field
level, `$and $or $not` for logic.

### Indexes

```python
users.ensure_index("role")            # in-memory; rebuilt from the log on open
users.find({"role": "admin"})         # now uses the index instead of scanning
```

Indexes live in memory and are reconstructed when you reopen the database, so
they add nothing to the file and never get out of sync.

## Transactions

```python
with db.transaction() as tx:
    tx.put("a", 1)
    tx.delete("b")
    tx.collection("log").insert({"event": "transfer"})
# all three land atomically here — or none of them if the block raised
```

If the `with` block raises, nothing is written. Otherwise the whole batch is
persisted as a single durable frame.

## API at a glance

| key-value | documents (`db.collection(name)`) |
|---|---|
| `db.put(key, value)` | `.insert(doc, id=None) -> id` |
| `db.get(key, default=None)` | `.get(id)` |
| `db.delete(key)` | `.update(id, changes)` |
| `db.exists(key)` / `key in db` | `.delete(id)` |
| `db.keys(prefix="")` | `.find(query, limit, sort, reverse)` |
| `db.items(prefix="")` | `.find_one(query)` / `.count(query)` |
| `db.transaction()` | `.all()` / `.delete_many(query)` |
| `db.compact()` | `.ensure_index(field)` |
| `db.close()` / `with Database(...) as db` | |

## Scope & honesty

larzdb is an **embedded, single-writer** database (like SQLite), guarded by a
file lock so two processes won't open the same file at once. It keeps the key
index in RAM, so it's built for datasets that fit comfortably in memory — think
megabytes-to-gigabytes of app data, not a multi-terabyte warehouse. It is not a
networked/multi-master server and does not do SQL. For what it targets — local,
durable, queryable state with zero operational overhead — that's the point.

## Tests

```bash
python -m unittest discover -s tests -v      # 28 tests incl. crash recovery, zero deps
```

## The Larz stack

Pure-Python, zero-dependency building blocks:

- **[larz](https://github.com/larz-scripter/larz)** — money-native web framework
- **[larzchain](https://github.com/larz-scripter/larzchain)** — from-scratch PoW blockchain
- **[larzmoney](https://github.com/larz-scripter/larzmoney)** — exact, penny-perfect money
- **[larzcrypt](https://github.com/larz-scripter/larzcrypt)** — pure-Python cryptography toolkit
- **larzdb** — this database

## License

MIT © larz-scripter
