Python’s dbm module provides a dict-like interface to a bytes-key → bytes-value persistent database backed by the best available dbm implementation on the platform. import dbm. open: db = dbm.open("mydb", "c") — flag "c" create/open (default), "r" read-only, "w" write existing, "n" always-new; returns a Shelf-like object. Keys and values must be bytes or str (converted to bytes). db[b"key"] = b"value"; db[b"key"] → bytes. del db[b"key"]. b"key" in db. db.keys() → list of bytes keys. db.get(b"key", default). db.setdefault(b"key", b"default"). db.close(). Context manager: with dbm.open("store", "c") as db:. whichdb: dbm.whichdb("store") → "dbm.gnu" / "dbm.ndbm" / "dbm.dumb" / None. Backends: dbm.gnu (gdbm, best performance, most platforms); dbm.ndbm (ndbm/Berkeley DB, macOS); dbm.dumb (pure Python fallback, always available). dbm.dumb stores .dir + .dat + .bak files. Errors: dbm.error. Values must be serialized to bytes — use JSON, pickle, struct, or msgpack. Claude Code generates fast bytes caches, binary record stores, CLI key-value registries, and serialization-aware dbm wrappers.
CLAUDE.md for dbm
## dbm Stack
- Stdlib: import dbm, json
- Open: with dbm.open("store", "c") as db:
- Write: db[b"key"] = json.dumps(obj).encode()
- Read: v = db.get(b"key"); obj = json.loads(v) if v else None
- Keys: list(db.keys())
- Which: dbm.whichdb("store") # detect backend
dbm Bytes Store Pipeline
# app/dbmutil.py — open, CRUD, JSON/binary codecs, batch, scan, backend info
from __future__ import annotations
import dbm
import json
import struct
import time
from contextlib import contextmanager
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Callable, Generator, Iterator
# ─────────────────────────────────────────────────────────────────────────────
# 1. Open helpers
# ─────────────────────────────────────────────────────────────────────────────
@contextmanager
def open_db(
path: str | Path,
flag: str = "c",
) -> Generator[Any, None, None]:
"""
Open a dbm database as a context manager, closing it on exit.
flag: "c" create/open (default), "r" read-only, "n" new, "w" open-existing.
Example:
with open_db("mystore") as db:
db[b"hello"] = b"world"
"""
db = dbm.open(str(path), flag)
try:
yield db
finally:
db.close()
def backend_info(path: str | Path) -> str:
"""
Return the dbm backend name for an existing store, or the default backend.
Example:
print(backend_info("mystore")) # e.g. "dbm.gnu"
"""
result = dbm.whichdb(str(path))
if result is None:
return dbm.library if hasattr(dbm, "library") else "unknown"
return result or "dbm.dumb"
# ─────────────────────────────────────────────────────────────────────────────
# 2. JSON codec layer
# ─────────────────────────────────────────────────────────────────────────────
class JsonDbm:
"""
dbm wrapper that serializes / deserializes values as JSON.
Keys are str; values are any JSON-serializable Python object.
Example:
with JsonDbm.open("data/config") as db:
db["settings"] = {"debug": True, "timeout": 30}
print(db["settings"]) # {"debug": True, "timeout": 30}
"""
def __init__(self, path: str | Path, flag: str = "c") -> None:
self._db = dbm.open(str(path), flag)
@classmethod
@contextmanager
def open(cls, path: str | Path, flag: str = "c") -> Generator["JsonDbm", None, None]:
inst = cls(path, flag)
try:
yield inst
finally:
inst.close()
def close(self) -> None:
self._db.close()
def _enc_key(self, key: str) -> bytes:
return key.encode("utf-8")
def __setitem__(self, key: str, value: Any) -> None:
self._db[self._enc_key(key)] = json.dumps(value, separators=(",", ":")).encode()
def __getitem__(self, key: str) -> Any:
raw = self._db[self._enc_key(key)]
return json.loads(raw)
def __delitem__(self, key: str) -> None:
del self._db[self._enc_key(key)]
def __contains__(self, key: str) -> bool:
return self._enc_key(key) in self._db
def get(self, key: str, default: Any = None) -> Any:
raw = self._db.get(self._enc_key(key))
return json.loads(raw) if raw is not None else default
def setdefault(self, key: str, default: Any) -> Any:
if key not in self:
self[key] = default
return default
return self[key]
def keys(self) -> list[str]:
return [k.decode("utf-8") if isinstance(k, bytes) else k
for k in self._db.keys()]
def items(self) -> list[tuple[str, Any]]:
return [(k, self[k]) for k in self.keys()]
def update(self, data: dict[str, Any]) -> None:
for k, v in data.items():
self[k] = v
def pop(self, key: str, default: Any = None) -> Any:
val = self.get(key, default)
if key in self:
del self[key]
return val
def __len__(self) -> int:
return len(self._db.keys())
# ─────────────────────────────────────────────────────────────────────────────
# 3. Binary record store using struct
# ─────────────────────────────────────────────────────────────────────────────
# A fixed-size binary record: uint64 id, float64 value, uint32 flags, 16-byte label
_RECORD_FORMAT = "!QdI16s"
_RECORD_SIZE = struct.calcsize(_RECORD_FORMAT)
@dataclass
class BinaryRecord:
id: int
value: float
flags: int
label: str # up to 16 ASCII bytes
def pack(self) -> bytes:
label_bytes = self.label.encode("ascii")[:16].ljust(16, b"\x00")
return struct.pack(_RECORD_FORMAT, self.id, self.value, self.flags, label_bytes)
@classmethod
def unpack(cls, data: bytes) -> "BinaryRecord":
id_, value, flags, label_bytes = struct.unpack(_RECORD_FORMAT, data[:_RECORD_SIZE])
return cls(id=id_, value=value, flags=flags,
label=label_bytes.rstrip(b"\x00").decode("ascii", errors="replace"))
class BinaryDbm:
"""
dbm store for fixed-size binary records packed with struct.
Keys are str; values are BinaryRecord instances.
Example:
with BinaryDbm.open("records/sensor") as db:
db.put(BinaryRecord(id=1, value=98.6, flags=0, label="temp"))
rec = db.get_by_id(1)
"""
def __init__(self, path: str | Path, flag: str = "c") -> None:
self._db = dbm.open(str(path), flag)
@classmethod
@contextmanager
def open(cls, path: str | Path, flag: str = "c") -> Generator["BinaryDbm", None, None]:
inst = cls(path, flag)
try:
yield inst
finally:
inst._db.close()
def put(self, record: BinaryRecord) -> None:
"""Store a record by id."""
self._db[struct.pack("!Q", record.id)] = record.pack()
def get_by_id(self, id_: int) -> BinaryRecord | None:
"""Retrieve a record by id."""
raw = self._db.get(struct.pack("!Q", id_))
return BinaryRecord.unpack(raw) if raw else None
def delete(self, id_: int) -> bool:
key = struct.pack("!Q", id_)
if key in self._db:
del self._db[key]
return True
return False
def all_records(self) -> list[BinaryRecord]:
return [BinaryRecord.unpack(self._db[k]) for k in self._db.keys()]
def count(self) -> int:
return len(self._db.keys())
# ─────────────────────────────────────────────────────────────────────────────
# 4. Batch and scan helpers
# ─────────────────────────────────────────────────────────────────────────────
def dbm_batch_write(path: str | Path, data: dict[str | bytes, bytes]) -> int:
"""
Write multiple bytes key-value pairs to a dbm store in one open.
Returns number of entries written.
Example:
dbm_batch_write("store", {b"a": b"1", b"b": b"2"})
"""
with open_db(path) as db:
for k, v in data.items():
db[k] = v
return len(data)
def dbm_scan(
path: str | Path,
predicate: Callable[[bytes, bytes], bool] | None = None,
) -> Iterator[tuple[bytes, bytes]]:
"""
Iterate over all (key, value) bytes pairs in a dbm store.
Optional predicate to filter entries.
Example:
for k, v in dbm_scan("store"):
print(k, v)
for k, v in dbm_scan("store", lambda k, v: b"prefix:" in k):
print(k)
"""
with open_db(path, "r") as db:
for key in db.keys():
val = db[key]
if predicate is None or predicate(key, val):
yield key, val
def dbm_copy(src: str | Path, dst: str | Path) -> int:
"""
Copy all entries from src dbm store to dst. Returns count copied.
Example:
dbm_copy("store_old", "store_new")
"""
count = 0
with open_db(str(src), "r") as src_db, open_db(str(dst)) as dst_db:
for key in src_db.keys():
dst_db[key] = src_db[key]
count += 1
return count
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
import tempfile
print("=== dbm demo ===")
with tempfile.TemporaryDirectory() as tmpdir:
store = Path(tmpdir) / "demo"
# ── raw bytes CRUD ─────────────────────────────────────────────────────
print("\n--- raw bytes CRUD ---")
with open_db(store) as db:
db[b"name"] = b"Alice"
db[b"score"] = struct.pack("!f", 99.5)
db[b"active"] = b"1"
with open_db(store, "r") as db:
print(f" name: {db[b'name']!r}")
score = struct.unpack("!f", db[b"score"])[0]
print(f" score: {score:.1f}")
print(f" keys: {list(db.keys())}")
print(f" backend: {backend_info(store)}")
# ── JsonDbm ────────────────────────────────────────────────────────────
print("\n--- JsonDbm ---")
json_path = Path(tmpdir) / "jsonstore"
with JsonDbm.open(json_path) as db:
db.update({
"user:1": {"name": "Alice", "score": 100, "tags": ["admin"]},
"user:2": {"name": "Bob", "score": 85, "tags": []},
"config": {"debug": False, "version": 2},
})
print(f" user:1: {db['user:1']}")
db["user:1"]["score"] = 110 # won't work without re-assign
db["user:1"] = {**db["user:1"], "score": 110}
print(f" updated user:1: {db['user:1']}")
print(f" keys: {db.keys()}")
print(f" count: {len(db)}")
# ── BinaryDbm ──────────────────────────────────────────────────────────
print("\n--- BinaryDbm ---")
bin_path = Path(tmpdir) / "sensors"
with BinaryDbm.open(bin_path) as db:
records = [
BinaryRecord(id=1, value=98.6, flags=0, label="temp"),
BinaryRecord(id=2, value=1013.25, flags=1, label="pressure"),
BinaryRecord(id=3, value=45.0, flags=0, label="humidity"),
]
for r in records:
db.put(r)
print(f" stored {db.count()} records")
r1 = db.get_by_id(1)
print(f" id=1: {r1}")
# ── batch write + scan ─────────────────────────────────────────────────
print("\n--- batch write + scan ---")
batch_path = Path(tmpdir) / "batch"
written = dbm_batch_write(batch_path, {
b"key:1": b"val1",
b"key:2": b"val2",
b"other": b"skip",
})
print(f" wrote {written} entries")
matches = list(dbm_scan(batch_path, lambda k, v: b"key:" in k))
print(f" scan filter 'key:': {matches}")
# ── copy ───────────────────────────────────────────────────────────────
print("\n--- dbm_copy ---")
copy_path = Path(tmpdir) / "copy"
n = dbm_copy(batch_path, copy_path)
print(f" copied {n} entries")
with open_db(copy_path, "r") as db:
print(f" copy keys: {list(db.keys())}")
print("\n=== done ===")
For the shelve alternative — shelve wraps dbm to provide automatic pickle serialization, so you can store arbitrary Python objects with db["key"] = {"any": "value"} without writing encode/decode code — use shelve when your values are Python objects (dicts, dataclasses, lists) and you want zero-boilerplate persistence; use dbm directly when you need explicit control over serialization format (JSON for human-readable output, struct for fixed-size binary records, msgpack for compact binary), when working with other systems that write to the dbm file, or when you want to avoid pickle’s security implications for untrusted data. For the sqlite3 alternative — sqlite3 provides full SQL with indexes, joins, transactions, and a stable cross-platform file format (.db files readable by any SQLite tool) — use sqlite3 when you need queries beyond key-lookup (range scans, aggregation, full-text search), multi-process access, or when the data must be portable to other languages and tools; use dbm when you only need exact-key get/set access patterns and want the simplest possible persistent bytes mapping with no schema overhead. The Claude Skills 360 bundle includes dbm skill sets covering open_db() context manager with backend_info(), JsonDbm class with full dict API (get/setitem/delitem/contains/keys/items/update/pop/len), BinaryRecord struct-packed dataclass with BinaryDbm (put/get_by_id/delete/all_records/count), dbm_batch_write()/dbm_scan()/dbm_copy() bulk helpers. Start with the free tier to try bytes key-value store patterns and dbm pipeline code generation.