Embedded Document Store

MonaDB

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.

pip install monadb copy
monadb ❯ console v0.2
import monadb

db = monadb.open("app.db")

users = db.collection("users")
users["alice"] = {"age": 30}
users["alice"]
Embedded
In-process, one file, no server
Schemaless
BSON documents, nested, untyped
Pythonic
Collections are MutableMappings
01

Dict

MutableMapping

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
02

Keys

tuples
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.

03

Models

Documents

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

Install MonaDB.

Python 3.9 and up. One import, one file on disk, no server to run.

pip install monadb copy