dol is a pure-Python (no dependencies) toolkit for wrapping any storage backend (files, S3, databases, dicts) behind a uniform dict-like interface. Version 0.3.38. Python ≥ 3.10.
For a comprehensive agent-readable API reference, see llms-full.txt. For a quick orientation, see llms.txt.
| File | What's in it |
|---|---|
dol/base.py |
Collection, KvReader, KvPersister, Store — the class hierarchy |
dol/trans.py |
wrap_kvs (core), store_decorator, filt_iter, cached_keys, Codec, kv_wrap |
dol/kv_codecs.py |
ValueCodecs, KeyCodecs — ready-made codec namespaces |
dol/caching.py |
cache_this, cache_vals, store_cached, WriteBackChainMap |
dol/paths.py |
KeyTemplate, mk_relative_path_store, KeyPath, path_get/set/filter |
dol/filesys.py |
Files, TextFiles, JsonFiles, PickleFiles — filesystem stores |
dol/sources.py |
FlatReader, FanoutReader/Persister, CascadedStores |
dol/signatures.py |
Sig — signature arithmetic |
dol/util.py |
Pipe, lazyprop, partialclass, groupby |
dol/__init__.py |
Public API — all exports live here |
The fundamental operation is wrapping a backend with transforms:
from dol import wrap_kvs, Files
import json
# Add JSON serialization to a file store
JsonFileStore = wrap_kvs(Files, obj_of_data=json.loads, data_of_obj=json.dumps)
# Or wrap an instance
s = wrap_kvs(dict(), id_of_key=lambda k: k.upper(), key_of_id=str.lower)wrap_kvs parameters:
key_of_id/id_of_key— outgoing/incoming key transformsobj_of_data/data_of_obj— outgoing/incoming value transformspostget(key, data) → obj— value transform that knows the key (for reads)preset(key, obj) → data— value transform that knows the key (for writes)key_codec/value_codec—Codecobjects (encoder+decoder pair)
X_of_Ynaming:key_of_id= "give me a key, you give me an id" (outgoing).id_of_key= "give me an id, you give me a key" (incoming). Always pairs.- KvReader for read-only: subclass
KvReader(notKvPersister) when writes aren't needed. - KvPersister for read-write:
clear()is disabled — override only if you're sure. - Test with
dict, deploy with real backend:wrap_kvs(dict, ...)first, then swapdictforFiles, a DB store, etc. - Transforms are pure functions: they should be stateless and not have side effects.
from dol import wrap_kvs
MyStore = wrap_kvs(dict,
id_of_key=lambda k: k + '.json',
key_of_id=lambda _id: _id[:-5],
obj_of_data=json.loads,
data_of_obj=json.dumps,
)from dol.base import KvReader
class MyReader(KvReader):
def __getitem__(self, k): ...
def __iter__(self): ...
def __len__(self): ... # optional, falls back to iteration countfrom dol.base import Store
class MyStore(Store):
def _id_of_key(self, k): return k.upper()
def _key_of_id(self, _id): return _id.lower()
def _data_of_obj(self, obj): return json.dumps(obj)
def _obj_of_data(self, data): return json.loads(data)from dol import ValueCodecs, KeyCodecs, Pipe
# Common value codecs
ValueCodecs.pickle() # pickle.dumps / pickle.loads
ValueCodecs.json() # json.dumps / json.loads
ValueCodecs.gzip() # compress/decompress
ValueCodecs.str_to_bytes() # encode/decode
# Key codecs
KeyCodecs.suffixed('.pkl') # add/strip suffix
KeyCodecs.prefixed('ns:') # add/strip prefix
# Chain with Pipe
MyStore = Pipe(KeyCodecs.suffixed('.pkl'), ValueCodecs.pickle())(dict)Most tools in trans.py use @store_decorator, making them work 4 ways:
from dol import filt_iter, cached_keys
# As class decorator
@filt_iter(filt=lambda k: k.endswith('.json'))
class MyStore(dict): ...
# As instance wrapper
s = filt_iter(my_store, filt=lambda k: k.endswith('.json'))
# As factory
json_only = filt_iter(filt=lambda k: k.endswith('.json'))
s = json_only(my_store)from dol import cache_this, cache_vals, store_cached
# Cache a property or method
class MyClass:
@cache_this
def expensive(self): return sum(range(1_000_000))
# Cache fetched values from a slow store
fast = cache_vals(slow_store)
# Persist function results across sessions
@store_cached(JsonFiles('/cache'))
def compute(x, y): return slow_computation(x, y)Always prototype with dict as the backend:
# 1. Test logic with dict
s = wrap_kvs(dict(), obj_of_data=json.loads, data_of_obj=json.dumps)
s['key'] = {'a': 1}
assert s['key'] == {'a': 1}
# 2. Swap to real backend
from dol import Files
s = wrap_kvs(Files('/data'), obj_of_data=json.loads, data_of_obj=json.dumps)Run tests: pytest dol/tests/
| Document | Contents |
|---|---|
| general_design.md | Language-agnostic design: what dol is, the KV pipeline, layered composition, patterns |
| dol_design.md | Python architecture: class hierarchy, wrap_kvs deep dive, Codec/Sig/Pipe, critique |
| issues_and_discussions.md | GitHub issues/discussions themes, known limitations, open design questions |
| frontend_dol_ideas.md | zoddal design: TypeScript KV interface, adapters, Zod bridge, zod-collection-ui integration |
wrap_kvs+selfinside methods: When awrap_kvs-decorated class usesself[k]in its own methods,selfis the unwrapped instance. Re-apply the wrapper toselfif transforms are needed (Issue #18).clear()is disabled onKvPersister. Callensure_clear_to_kv_store(store)to re-enable.- No async support in core. Use synchronous wrappers for async backends (thread pool, etc.).
bytes.decodeasobj_of_datacauses issues — uselambda b: b.decode()instead (Issue #9).- Windows paths: Some path-related code has Unix assumptions. Issues #52, #58 track this.