Embedded Document Store
An embedded document store for Python. Collections are ordered
MutableMappings of keys to documents; documents are
dicts, dataclasses, or pydantic models. A Rust core on redb, one file
on disk.
import monadb db = monadb.open("app.db") users = db.collection("users") users["alice"] = {"age": 30} users["alice"]
A collection is a MutableMapping of keys to documents.
Subscript, in, len, and iteration go to
storage; get, pop, setdefault,
and clear come from the ABC. Assignment is an upsert and
missing keys raise KeyError. Every operation is its own
commit, and update() commits a whole mapping at once.
import monadb db = monadb.open("app.db") users = db["users"] # a handle; made on first write users["alice"] = {"age": 30} # upsert, one commit users["alice"] # {'age': 30} "alice" in users # True len(users) # 1 users.update({"bob": {"age": 41}, # one commit for the mapping "cy": {"age": 9}}) del users["bob"] # KeyError if absent
events = db["events"] for ts in [3, 1, 2]: events[ts] = {"at": ts} list(events) # [1, 2, 3] list(reversed(events)) # [3, 2, 1] events.first() # (1, {'at': 1}) list(events.range(1, 3)) # half-open; None is unbounded [k for k, _ in db["logs"].prefix("2026-08")]
Keys are str, int, bytes, or a
flat tuple of those, held in an order-preserving encoding:
encode(a) < encode(b) exactly when a < b.
Iteration is key order, not insertion order, so range and prefix scans
are b-tree seeks rather than filters. Across types the order is
int < str < bytes.
A document is any mapping. Bind a handle to a dataclass or a pydantic
model and it converts on write and rebuilds instances on read. The
binding lives on the handle, not in the file, so the same documents
still read as dicts. Values store as BSON: None,
bool, int, float,
str, bytes, datetime,
list, and nested mappings.
from dataclasses import dataclass @dataclass class User: age: int email: str users = db.collection("users", User) users["gwen"] = User(age=44, email="g@corp.io") users["gwen"] # User(age=44, email='g@corp.io') db["users"]["gwen"] # {'age': 44, 'email': 'g@corp.io'}
Getting Started
Python 3.9 and up. One import, one file on disk, no server to run.