jsondb2.py
A JSON file wrapper with dictionary-style access, optional autosave, and lightweight async queue utilities.
Setup and storage model
Place jsondb2.py on the import path. JSON and queue operations use standard-library modules. Import also calls pmblue_update.self_update(); if the updater is absent, the bootstrap requires requests, downloads over the legacy HTTP URL, and writes pmblue_update.py in the working directory.
JsonFile(fp, **kwargs) reads the entire file into memory. A missing file is created with {}; its parent directory must already exist. Data is intended to be a JSON dictionary or list. Files are rewritten in full when saved, using compact JSON with ensure_ascii=False.
Explicit save example
from jsondb2 import JsonFile
settings = JsonFile("settings.json")
settings["theme"] = "dark"
settings["recent_files"] = ["report.csv"]
settings["recent_files"].append("summary.csv")
settings.save()
settings.reload()
print(settings["theme"])
print("theme" in settings)
Constructor options
| Option | Default | Behavior |
|---|---|---|
auto_save | False | Save on wrapper item assignment/deletion and data replacement. Existing nested dictionaries/lists are wrapped to intercept item assignment. |
async_save | False | Schedule save work through the internal asyncio queue; requires a running event loop. Disk writes still happen synchronously on that event loop. |
reload_data | False | Reload the file whenever the data property is accessed. Can discard unsaved in-memory edits. |
dump | Built-in compact serializer | Custom callable taking (data, file_object). It replaces serialization, not loading. |
Reading and writing
file.data exposes the loaded structure. Assigning data accepts only dictionaries/lists. file[key], assignment, deletion, membership, and iteration delegate to this structure. save() and reload() return None; reload() rereads disk without merging changes. Iteration uses the current memory state directly.
Autosave does not intercept every nested mutation. For example, file["items"].append(value) and nested deletion delegate directly to a list/dictionary and do not reliably trigger a write. Explicit save() after nested mutations is the predictable approach. Replacing the entire data value also does not recursively wrap the replacement immediately.
Current print(indent=2) only initializes its serialized value when auto_save=True; with defaults it raises an error. Use print(settings.data) or, when autosave is off, json.dumps(settings.data, indent=2).
Async queue utilities
Queue.add(coro) and Queue.add_sync(func, *args, **kwargs) return asyncio Tasks; await those tasks to observe completion and errors. await queue.add_and_wait(coro) runs a coroutine in the queue and returns its result. queue.qm() supplies a sync/async context manager that serializes entry within this process.
import asyncio
from jsondb2 import Queue
async def worker():
await asyncio.sleep(0)
return "finished"
async def main():
queue = Queue()
task = queue.add(worker())
print(await task)
asyncio.run(main())
await queue waits for IDs that were registered when waiting started; await queue.wait_until_empty() waits while registered entries remain. Neither guarantees that newly scheduled tasks have entered the queue yet. JsonFile.save() discards the internally created Task when async_save=True, so it does not expose a reliable public flush/completion handle. Prefer the default synchronous save for code that must know persistence has completed.
AsyncProperty is a descriptor for async getters accessed with await obj.property; its wrapper also supports indexing before awaiting. Descriptor coroutine state is shared on the descriptor, so overlapping accesses are not a concurrency guarantee.
Persistence limits
Writes truncate and rewrite the live JSON file. There is no atomic replacement, transaction rollback, cross-process file lock, or merge between independent instances. Invalid JSON and file/serialization errors propagate to the caller. Use this wrapper for small local state with a single writer; the module updater's transactional installation does not change JsonFile's persistence behavior.
For relational data, see sqliteObj and aiosqliteObj.
Objects and method contracts
JsonFile is a file-backed container. Its fp identifies the file; auto_save, async_save, and reload_data store the constructor options. The data property exposes the actual in-memory dictionary or list, so changing it changes the wrapper's state. With autosave enabled, nested values can be internal container wrappers rather than plain dict/list objects.
| Operation | Return / persistence |
|---|---|
file[key] | Returns the value (possibly a nested wrapper). Missing dictionary keys raise KeyError. In autosave mode, reads may also trigger a save after detecting changed in-memory state. |
file[key] = value, del file[key] | Mutate the loaded structure; save immediately only when autosave is enabled. |
file.data = { ... } | Replaces the entire root; accepts only dict or list. With autosave, saves the replacement. |
file.save() | Returns None. Default mode completes the full file rewrite before returning; async_save schedules it without returning the task. |
file.reload() | Returns None and replaces memory with file contents. Unsaved local changes are lost. |
for key in file | Iterates the current root: dictionary keys or list values. It does not implicitly merge external file edits. |
Queue and QueueManager
Queue() tracks active entry IDs in q. active is a boolean property and len(queue) counts registered entries. The ID is registered when a scheduled task actually enters its QueueManager, so these values may still be empty immediately after queue.add(). QueueManager is normally obtained through queue.qm(); both with and async with serialize access, and waiting too long raises TimeoutError.
add(coro) takes an already-created coroutine; add_sync(func, *args, **kwargs) takes a callable plus its arguments. Both return Tasks, and awaited task results are the callable/coroutine's return value. Synchronous work is executed on the event-loop thread, so the queue does not make file writes nonblocking.
Worked example: autosave plus nested edits
This uses a temporary file and explicitly saves after append. That final save is deliberate: autosave reliably intercepts top-level assignments, but not all delegated list methods.
import os
import tempfile
from jsondb2 import JsonFile
with tempfile.TemporaryDirectory() as directory:
path = os.path.join(directory, "preferences.json")
preferences = JsonFile(path, auto_save=True)
preferences["theme"] = "dark"
preferences["recent"] = ["report.csv"]
preferences["recent"].append("summary.csv")
preferences.save()
reread = JsonFile(path)
print(reread.data)
# {'theme': 'dark', 'recent': ['report.csv', 'summary.csv']}
del preferences["theme"]
reread.reload()
print("theme" in reread) # False
Worked example: await all queued work and errors
Keep the Task objects and await them with asyncio.gather. This observes completion even before those tasks have registered queue IDs, and propagates failures to the caller.
import asyncio
from jsondb2 import Queue
async def main():
queue = Queue()
events = []
async def record(label):
events.append("start " + label)
await asyncio.sleep(0)
events.append("end " + label)
return label.upper()
tasks = [queue.add(record(label)) for label in ("a", "b", "c")]
results = await asyncio.gather(*tasks)
print(results) # ['A', 'B', 'C']
print(events) # Each task finishes its critical section before the next enters.
asyncio.run(main())
Custom serialization
A dump callback is useful with the default auto_save=False: pass dump=lambda data, stream: json.dump(data, stream, indent=2) for readable files. When autosave is enabled, a custom callback receives the internal nested wrappers and must handle them itself. Loading always uses json.load, so the callback must still produce valid JSON.
Complete source API
Generated from modules/jsondb2.py; version 0.1.0.1. Includes public functions, classes, directly declared methods, properties, and Python protocol methods. Conditional APIs may require optional dependencies. Inherited members and dynamically assigned attributes are explained in the guide where relevant.
Signatures and docstrings are extracted without importing or running the module. A property or AsyncProperty decorator changes how a member is accessed; see its decorators and the guide.
Classes and objects
class AsyncProperty
Source line 32
Construct: AsyncProperty(func)
Fields assigned by the constructor: coro, func. Some assignments may be conditional; see the object guide for meaning and lifecycle.
Declared functions, properties, and nested objects:
AsyncProperty.__init__— methodAsyncProperty.__await__— methodAsyncProperty.__get__— methodAsyncProperty.__set__— methodAsyncProperty.setter— method
AsyncProperty.__init__(self, func)
Source line 33
| Parameter | Passing convention | Default / required |
|---|---|---|
func | positional or keyword | required |
AsyncProperty.__await__(self)
Source line 37
No caller-supplied parameters are declared.
AsyncProperty.__get__(self, instance, owner)
Source line 39
| Parameter | Passing convention | Default / required |
|---|---|---|
instance | positional or keyword | required |
owner | positional or keyword | required |
AsyncProperty.__set__(self, instance, value)
Source line 42
| Parameter | Passing convention | Default / required |
|---|---|---|
instance | positional or keyword | required |
value | positional or keyword | required |
AsyncProperty.setter(self, func)
Source line 44
| Parameter | Passing convention | Default / required |
|---|---|---|
func | positional or keyword | required |
class JsonFile
Source line 70
Construct: JsonFile(fp, **kwargs)
Fields assigned by the constructor: async_save, auto_save, dump, fp, reload_data. Some assignments may be conditional; see the object guide for meaning and lifecycle.
Declared functions, properties, and nested objects:
JsonFile.__init__— methodJsonFile.data— propertyJsonFile.data— property setterJsonFile.save— methodJsonFile.reload— methodJsonFile.__contains__— methodJsonFile.__getitem__— methodJsonFile.__setitem__— methodJsonFile.__delitem__— methodJsonFile.__iter__— methodJsonFile.print— method
JsonFile.__init__(self, fp, **kwargs)
Source line 109
| Parameter | Passing convention | Default / required |
|---|---|---|
fp | positional or keyword | required |
kwargs | extra keyword arguments (**kwargs) | optional collection |
JsonFile.data(self)
Source line 138
Decorators: @property
No caller-supplied parameters are declared.
JsonFile.data(self, value)
Source line 143
Decorators: @data.setter
| Parameter | Passing convention | Default / required |
|---|---|---|
value | positional or keyword | required |
JsonFile.save(self)
Source line 154
No caller-supplied parameters are declared.
JsonFile.reload(self)
Source line 159
No caller-supplied parameters are declared.
JsonFile.__contains__(self, value)
Source line 163
| Parameter | Passing convention | Default / required |
|---|---|---|
value | positional or keyword | required |
JsonFile.__getitem__(self, key)
Source line 165
| Parameter | Passing convention | Default / required |
|---|---|---|
key | positional or keyword | required |
JsonFile.__setitem__(self, key, value)
Source line 171
| Parameter | Passing convention | Default / required |
|---|---|---|
key | positional or keyword | required |
value | positional or keyword | required |
JsonFile.__delitem__(self, key)
Source line 178
| Parameter | Passing convention | Default / required |
|---|---|---|
key | positional or keyword | required |
JsonFile.__iter__(self)
Source line 182
No caller-supplied parameters are declared.
JsonFile.print(self, indent=2)
Source line 184
| Parameter | Passing convention | Default / required |
|---|---|---|
indent | positional or keyword | 2 |
class Queue
Source line 206
Construct: Queue()
Fields assigned by the constructor: q. Some assignments may be conditional; see the object guide for meaning and lifecycle.
Declared functions, properties, and nested objects:
Queue.__init__— methodQueue.qm— methodQueue.active— propertyQueue.__len__— methodQueue.add— methodQueue.add_and_wait— async methodQueue.add_sync— methodQueue.__await__— methodQueue.wait_until_empty— async method
Queue.__init__(self)
Source line 207
No caller-supplied parameters are declared.
Queue.qm(self)
Source line 209
No caller-supplied parameters are declared.
Queue.active(self)
Source line 212
Decorators: @property
No caller-supplied parameters are declared.
Queue.__len__(self)
Source line 214
No caller-supplied parameters are declared.
Queue.add(self, coro)
Source line 216
| Parameter | Passing convention | Default / required |
|---|---|---|
coro | positional or keyword | required |
async Queue.add_and_wait(self, coro)
Source line 222
| Parameter | Passing convention | Default / required |
|---|---|---|
coro | positional or keyword | required |
Declared with async def. Normal calls return an awaitable, or an async generator if the body yields. Decorated properties may be accessed differently; follow the object guide.
Queue.add_sync(self, func, *args, **kwargs)
Source line 226
| Parameter | Passing convention | Default / required |
|---|---|---|
func | positional or keyword | required |
args | extra positional arguments (*args) | optional collection |
kwargs | extra keyword arguments (**kwargs) | optional collection |
Queue.__await__(self)
Source line 232
No caller-supplied parameters are declared.
async Queue.wait_until_empty(self)
Source line 245
No caller-supplied parameters are declared.
Declared with async def. Normal calls return an awaitable, or an async generator if the body yields. Decorated properties may be accessed differently; follow the object guide.
class QueueManager
Source line 249
Construct: QueueManager(q)
Fields assigned by the constructor: q. Some assignments may be conditional; see the object guide for meaning and lifecycle.
Declared functions, properties, and nested objects:
QueueManager.__init__— methodQueueManager.__enter__— methodQueueManager.__exit__— methodQueueManager.__aenter__— async methodQueueManager.__aexit__— async method
QueueManager.__init__(self, q)
Source line 250
| Parameter | Passing convention | Default / required |
|---|---|---|
q | positional or keyword | required |
QueueManager.__enter__(self)
Source line 252
No caller-supplied parameters are declared.
QueueManager.__exit__(self, *args)
Source line 266
| Parameter | Passing convention | Default / required |
|---|---|---|
args | extra positional arguments (*args) | optional collection |
async QueueManager.__aenter__(self)
Source line 268
No caller-supplied parameters are declared.
Declared with async def. Normal calls return an awaitable, or an async generator if the body yields. Decorated properties may be accessed differently; follow the object guide.
async QueueManager.__aexit__(self, *args)
Source line 282
| Parameter | Passing convention | Default / required |
|---|---|---|
args | extra positional arguments (*args) | optional collection |
Declared with async def. Normal calls return an awaitable, or an async generator if the body yields. Decorated properties may be accessed differently; follow the object guide.