Python’s shelve module provides a persistent dictionary backed by dbm that stores arbitrary picklable Python objects. import shelve. open: db = shelve.open("mystore") — creates/opens mystore.db (or platform-specific suffix); db["key"] = obj; obj = db["key"]; del db["key"]; db.close(). Context manager: with shelve.open("store") as db: — auto-closes. flag: "c" (default) create-or-open; "r" read-only; "n" always create fresh; "w" open existing for write. writeback: shelve.open("store", writeback=True) — tracks and re-writes mutable values (e.g. lists) automatically on sync()/close(); without writeback, you must re-assign: db["k"] = db["k"] after mutation. sync: db.sync() — flush pending writeback changes. keys/values/items: list(db.keys()) → list of str keys. "key" in db — membership test. get: db.get("key", default). update: db.update({"a": 1, "b": 2}). pop: db.pop("key", default). Shelf is not thread-safe; use a lock for concurrent access. Values must be picklable. Claude Code generates persistent caches, CLI session stores, incremental job result databases, and offline object stores.
CLAUDE.md for shelve
## shelve Stack
- Stdlib: import shelve
- Open: with shelve.open("store") as db:
- Write: db["key"] = {"any": "picklable", "value": True}
- Read: val = db.get("key", default)
- Mutate: db["k"] = db["k"]; db["k"].append(x) # or use writeback=True
- Keys: list(db.keys())
shelve Persistent Store Pipeline
# app/shelveutil.py — open, CRUD, batch, TTL cache, typed store, migration
from __future__ import annotations
import shelve
import threading
import time
from contextlib import contextmanager
from dataclasses import dataclass, asdict, field
from pathlib import Path
from typing import Any, Callable, Generator, Generic, Iterable, Iterator, TypeVar
V = TypeVar("V")
# ─────────────────────────────────────────────────────────────────────────────
# 1. Safe open helper
# ─────────────────────────────────────────────────────────────────────────────
@contextmanager
def open_shelf(
path: str | Path,
flag: str = "c",
writeback: bool = False,
) -> Generator[shelve.Shelf, None, None]:
"""
Open a shelf as a context manager, ensuring close() on exit.
flag: "c" create/open (default), "r" read-only, "n" new, "w" open existing.
writeback: if True, mutable items are tracked and re-written on sync/close.
Example:
with open_shelf("session") as db:
db["user"] = {"name": "Alice", "visits": 0}
"""
db = shelve.open(str(path), flag=flag, writeback=writeback)
try:
yield db
finally:
db.close()
# ─────────────────────────────────────────────────────────────────────────────
# 2. CRUD helpers
# ─────────────────────────────────────────────────────────────────────────────
def shelf_get(path: str | Path, key: str, default: Any = None) -> Any:
"""
Read a single value from a shelf.
Example:
user = shelf_get("session", "user")
"""
with open_shelf(path, "r") as db:
return db.get(key, default)
def shelf_set(path: str | Path, key: str, value: Any) -> None:
"""
Write a single value to a shelf (creates if absent).
Example:
shelf_set("session", "user", {"name": "Bob"})
"""
with open_shelf(path) as db:
db[key] = value
def shelf_delete(path: str | Path, key: str) -> bool:
"""
Delete a key from a shelf. Returns True if key existed.
Example:
shelf_delete("session", "temp_token")
"""
with open_shelf(path) as db:
if key in db:
del db[key]
return True
return False
def shelf_keys(path: str | Path) -> list[str]:
"""Return all keys in a shelf."""
with open_shelf(path, "r") as db:
return list(db.keys())
def shelf_update(path: str | Path, data: dict[str, Any]) -> None:
"""
Write multiple key-value pairs to a shelf at once.
Example:
shelf_update("config", {"debug": True, "timeout": 30})
"""
with open_shelf(path) as db:
db.update(data)
def shelf_load(path: str | Path) -> dict[str, Any]:
"""
Load all key-value pairs from a shelf into a plain dict.
Example:
data = shelf_load("config")
"""
with open_shelf(path, "r") as db:
return dict(db.items())
def shelf_dump(path: str | Path, data: dict[str, Any], clear: bool = False) -> None:
"""
Write all entries from data into a shelf.
If clear=True, remove all existing keys first.
Example:
shelf_dump("config", {"key": "val"}, clear=True)
"""
flag = "n" if clear else "c"
with open_shelf(path, flag) as db:
db.update(data)
# ─────────────────────────────────────────────────────────────────────────────
# 3. TTL-aware shelf cache
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class _TTLEntry:
value: Any
expires_at: float # unix timestamp; 0 = never
class ShelfCache:
"""
Persistent key-value cache with optional TTL (time-to-live) per entry.
Backed by shelve; thread-safe via a reentrant lock.
Example:
cache = ShelfCache("cache/results")
cache.set("expensive_key", result, ttl=3600) # expire in 1 hour
val = cache.get("expensive_key")
if val is None:
val = compute()
cache.set("expensive_key", val, ttl=3600)
"""
def __init__(self, path: str | Path, writeback: bool = False) -> None:
self._path = str(path)
self._writeback = writeback
self._lock = threading.RLock()
def get(self, key: str, default: Any = None) -> Any:
"""Return cached value or default (None if expired)."""
with self._lock, open_shelf(self._path, "r") as db:
entry: _TTLEntry | None = db.get(key)
if entry is None:
return default
if entry.expires_at and time.time() > entry.expires_at:
return default
return entry.value
def set(self, key: str, value: Any, ttl: float | None = None) -> None:
"""Store value with optional TTL seconds."""
expires_at = time.time() + ttl if ttl else 0.0
with self._lock, open_shelf(self._path) as db:
db[key] = _TTLEntry(value=value, expires_at=expires_at)
def delete(self, key: str) -> bool:
with self._lock, open_shelf(self._path) as db:
if key in db:
del db[key]
return True
return False
def clear_expired(self) -> int:
"""Remove all expired entries. Returns count removed."""
now = time.time()
removed = 0
with self._lock, open_shelf(self._path) as db:
expired = [k for k, v in db.items()
if isinstance(v, _TTLEntry) and v.expires_at and now > v.expires_at]
for k in expired:
del db[k]
removed += 1
return removed
def keys(self) -> list[str]:
with self._lock, open_shelf(self._path, "r") as db:
return list(db.keys())
def __contains__(self, key: str) -> bool:
return self.get(key) is not None
# ─────────────────────────────────────────────────────────────────────────────
# 4. Typed shelf
# ─────────────────────────────────────────────────────────────────────────────
class TypedShelf(Generic[V]):
"""
A shelf wrapper that encodes/decodes values via a transform function pair.
Useful for storing dataclasses or other typed objects as plain dicts.
Example:
@dataclass
class Job:
id: str
status: str
result: Any = None
store = TypedShelf[Job](
"jobs",
encode=lambda j: asdict(j),
decode=lambda d: Job(**d),
)
store.set("job-1", Job(id="job-1", status="pending"))
job = store.get("job-1")
"""
def __init__(
self,
path: str | Path,
encode: Callable[[V], Any],
decode: Callable[[Any], V],
) -> None:
self._path = str(path)
self._encode = encode
self._decode = decode
def get(self, key: str, default: V | None = None) -> V | None:
with open_shelf(self._path, "r") as db:
raw = db.get(key)
return self._decode(raw) if raw is not None else default
def set(self, key: str, value: V) -> None:
with open_shelf(self._path) as db:
db[key] = self._encode(value)
def delete(self, key: str) -> bool:
with open_shelf(self._path) as db:
if key in db:
del db[key]
return True
return False
def all(self) -> dict[str, V]:
with open_shelf(self._path, "r") as db:
return {k: self._decode(v) for k, v in db.items()}
def keys(self) -> list[str]:
with open_shelf(self._path, "r") as db:
return list(db.keys())
# ─────────────────────────────────────────────────────────────────────────────
# 5. Migration / inspection helpers
# ─────────────────────────────────────────────────────────────────────────────
def shelf_migrate(
src: str | Path,
dst: str | Path,
transform: Callable[[str, Any], tuple[str, Any] | None],
) -> int:
"""
Copy entries from src shelf to dst shelf, applying transform(key, value).
Return (new_key, new_value) to copy, None to skip an entry.
Returns number of entries written.
Example:
# Rename a key and drop entries where value is None
def xform(k, v):
if v is None:
return None
return k.removeprefix("old_"), v
shelf_migrate("store_v1", "store_v2", xform)
"""
written = 0
with open_shelf(src, "r") as src_db, open_shelf(dst) as dst_db:
for key in list(src_db.keys()):
result = transform(key, src_db[key])
if result is not None:
new_key, new_value = result
dst_db[new_key] = new_value
written += 1
return written
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
import tempfile
from dataclasses import dataclass as dc, asdict
print("=== shelve demo ===")
with tempfile.TemporaryDirectory() as tmpdir:
store_path = Path(tmpdir) / "demo"
# ── basic CRUD ─────────────────────────────────────────────────────────
print("\n--- basic CRUD ---")
shelf_update(str(store_path), {
"user:1": {"name": "Alice", "score": 100},
"user:2": {"name": "Bob", "score": 85},
"config": {"debug": False, "version": "1.2"},
})
print(f" keys: {shelf_keys(str(store_path))}")
print(f" user:1: {shelf_get(str(store_path), 'user:1')}")
shelf_set(str(store_path), "user:1", {"name": "Alice", "score": 110})
print(f" updated user:1: {shelf_get(str(store_path), 'user:1')}")
shelf_delete(str(store_path), "user:2")
print(f" after delete: {shelf_keys(str(store_path))}")
# ── writeback ─────────────────────────────────────────────────────────
print("\n--- writeback ---")
with open_shelf(str(store_path), writeback=True) as db:
db["items"] = [1, 2, 3]
db["items"].append(4) # writeback tracks this mutation
print(f" items after writeback: {shelf_get(str(store_path), 'items')}")
# ── ShelfCache TTL ─────────────────────────────────────────────────────
print("\n--- ShelfCache ---")
cache = ShelfCache(Path(tmpdir) / "cache")
cache.set("result", {"data": [1, 2, 3]}, ttl=60)
print(f" hit: {cache.get('result')}")
print(f" miss: {cache.get('missing', 'default')!r}")
# Simulate expiry
cache.set("old", "stale", ttl=0.001)
time.sleep(0.01)
print(f" expired: {cache.get('old')!r}")
removed = cache.clear_expired()
print(f" cleared {removed} expired entries")
# ── TypedShelf ─────────────────────────────────────────────────────────
print("\n--- TypedShelf ---")
@dc
class Task:
id: str
status: str
priority: int = 0
task_store: TypedShelf[Task] = TypedShelf(
Path(tmpdir) / "tasks",
encode=lambda t: asdict(t),
decode=lambda d: Task(**d),
)
task_store.set("t1", Task(id="t1", status="pending", priority=1))
task_store.set("t2", Task(id="t2", status="done", priority=2))
for k, v in task_store.all().items():
print(f" {k}: {v}")
# ── migration ──────────────────────────────────────────────────────────
print("\n--- shelf_migrate ---")
def _upgrade(k: str, v: Any) -> tuple[str, Any] | None:
if k.startswith("user:"):
uid = k.split(":")[1]
return f"u{uid}", {**v, "active": True}
return None
written = shelf_migrate(str(store_path), str(Path(tmpdir) / "store_v2"), _upgrade)
print(f" migrated {written} entries")
print(f" new store: {shelf_load(str(Path(tmpdir) / 'store_v2'))}")
print("\n=== done ===")
For the dbm alternative — dbm is the lower-level module that shelve builds on; dbm.open() returns a bytes-key → bytes-value mapping without automatic pickling, so you must serialize/deserialize values yourself — use dbm directly when you need raw bytes storage with minimal overhead, when you’re storing string or bytes values only, or when you want to control serialization format (JSON, msgpack) instead of pickle; use shelve when you want to store arbitrary Python objects with zero serialization boilerplate. For the sqlite3 alternative — sqlite3 stores data in a relational table with full SQL query support, ACID transactions, concurrent reads, and a stable on-disk format readable by any SQLite-compatible tool — use sqlite3 when you need queries on stored data, multi-process access, data durability guarantees, or when the store must be inspectable with external tools; use shelve for simple single-process persistent caches or session stores where the only access pattern is get / set by string key and you want Python objects stored without schema design. The Claude Skills 360 bundle includes shelve skill sets covering open_shelf() context manager, shelf_get()/shelf_set()/shelf_delete()/shelf_keys()/shelf_update()/shelf_load()/shelf_dump() CRUD helpers, ShelfCache TTL-aware persistent cache with clear_expired(), TypedShelf[V] generic typed wrapper with encode/decode, and shelf_migrate() versioned data migration helper. Start with the free tier to try persistent cache patterns and shelve pipeline code generation.