- Zero schema. Records are plain Python dicts. No migrations, no ORM.
- Minimal dependencies. Only
combo_lockis required for core functionality. - Dict compatibility. Storage classes are
dictsubclasses, so any code that works with a dict works withJsonStorage. - Optional layers. Encryption and XDG path resolution are opt-in; the core is usable without them.
dict
├── JsonStorage (persistent dict + file locking)
│ ├── EncryptedJsonStorage (AES-GCM encryption layer)
│ │ └── EncryptedJsonStorageXDG (XDG path resolution for data dir)
│ └── JsonStorageXDG (XDG path resolution for cache dir)
│ └── JsonConfigXDG (XDG path resolution for config dir)
└── JsonDatabase (list-of-records + search)
└── JsonDatabaseXDG (XDG path resolution for data dir)
Query (stateful filter builder, not a dict)
json_database/__init__.py
| Module | Role |
|---|---|
json_database/__init__.py |
All public storage and database classes |
json_database/search.py |
Query builder |
json_database/crypto.py |
AES-GCM encrypt/decrypt, zlib compress/decompress |
json_database/utils.py |
merge_dict, fuzzy matching, recursive search helpers, DummyLock |
json_database/xdg_utils.py |
XDG path resolution (adapted from Scott Stevenson's xdg library) |
json_database/exceptions.py |
Custom exception classes |
json_database/hpm.py |
HiveMind plugin adapter (JsonDB) |
json_database/version.py |
Version constants |
Construction
└── load_local(path)
└── load_commented_json(path) # strips // and # comments
└── json.loads(...)
└── dict.update(self, data)
store(path)
└── json.dump(self, file, indent=4) # writes full dict as JSON
File locking wraps both load_local and store via ComboLock (or
DummyLock). The lock file lives in /tmp/{basename}.lock.
Construction
└── load_local(path)
└── JsonStorage.load_local(path) # loads ciphertext JSON blob
└── decrypt_from_json(key, blob)
├── unhexlify ciphertext, tag, nonce
├── AES.new(key, GCM, nonce).decrypt_and_verify(...)
└── zlib.decompress(plaintext)
└── dict.update(self, plaintext)
store(path)
├── snapshot plaintext = dict(self)
├── encrypt_as_json(key, plaintext)
│ ├── zlib.compress(json.dumps(plaintext))
│ ├── AES.new(key, GCM).encrypt_and_digest(compressed)
│ └── json.dumps({"ciphertext": hex, "tag": hex, "nonce": hex})
├── dict.clear(self); dict.update(self, ciphertext_blob)
├── JsonStorage.store(path) # writes ciphertext JSON
└── dict.clear(self); dict.update(self, plaintext) # restore
JsonDatabase does not extend JsonStorage. It holds a JsonStorage instance
as self.db and treats the entry self.db[self.name] as its record list.
self.db = {
"users": [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25},
]
}
Operations on the database manipulate self.db[self.name] (a Python list)
directly. commit() calls self.db.store() to flush changes to disk.
Item IDs are stable list indices. remove_item writes None (a tombstone) into
the slot rather than popping it, so higher indices are never shifted. Tombstoned
slots are skipped by __iter__, __len__, search_by_key, search_by_value,
and __contains__; a direct db[item_id] on a tombstone raises InvalidItemID
(json_database/__init__.py:252).
Query is not a dict subclass. It holds a mutable list self.result
initialised from all records in a JsonDatabase. Each filter method applies
a list comprehension or loop to self.result in-place and returns self.
Query(db)
└── self.result = list(db) # shallow copy of all records
.equal("status", "active")
└── self.result = [r for r in self.result if r["status"] == "active"]
.build()
└── return self.result
Because each filter mutates the same list, chaining is zero-copy after the initial snapshot.
ComboLock (from the combo_lock package) provides both threading and
multiprocessing safety via a combination of a threading lock and a lockfile.
The lock file path is derived from the database file's basename and placed in
the system temp directory.
DummyLock (json_database/utils.py:5) is a no-op drop-in used when
disable_lock=True. Use only in single-threaded, single-process contexts.
json_database/hpm.py implements AbstractDB from hivemind-plugin-manager.
It wraps either JsonStorageXDG (plain) or EncryptedJsonStorageXDG (when a
password is provided) as a key-value store for HiveMind client credentials.
The entry point is registered as hivemind-json-db-plugin in the
hivemind.database group (setup.py:59).
jsonify_recursively (json_database/utils.py:317) converts arbitrary Python
objects to JSON-compatible structures before storage. It uses hasattr(thing, '__dict__')
(not try/except) to detect non-dict/list/scalar objects and reads thing.__dict__.
This means:
- Objects stored via
add_itemlose their class identity; they become plain dicts. - If you need typed objects on retrieval, implement your own deserialisation layer or use an ORM.
The commented JSON loader (load_commented_json) allows // and # line
comments in stored JSON files. Files produced by store() contain no comments
(standard JSON), but hand-edited files may use them.