ThreadSafe DataStore
Simple, convenient thread safe data store.
Features
- Thread-safe: All operations are atomic and synchronized using locks
- Nested access: Support for deep nested dictionary operations via key paths
- Atomic operations: Safe read-modify-write operations on values
- Context manager: Direct unlocked access to data while holding the lock
- Utility methods: Convenient methods for common operations (increment, append, etc.)
Why?
...because I kept rebuilding this feature to pass around context within AI agents working on the same data across ultiple threads. I kept wanting a simple atomic datastore that wasn't a pain to use. You can see that this is clearly inspired by my earlier version within the arkaine AI library. So - here it is as a stand alone package for easier use. Hopefully it helps you out.
Installation
Install from PyPI:
pip install threadsafe-datastore
Or install from source:
git clone https://github.com/hlfshell/threadsafe-datastore.git
cd threadsafe-datastore
pip install -e .
Quick Start
from threadsafe_datastore import Datastore
# Create a store
store = Datastore()
# Basic set / get operation
store["counter"] = 0
store["counter"] # 0
# The helper functions provide common features that
# are thread safe
store.increment("counter", 5) # Returns 5
store["counter"] # 5
# Nested dictionary support while supporting thread safety:
store["nested"] = {"items": []}
store.append(["nested", "items"], "value1")
store.append(["nested", "items"], "value2")
store["nested"]["items"] # ["value1", "value2"]
# Context manager to perform multiple step or batch
# operations all while maintaining a lock:
with store as unlocked:
unlocked["a"] = 1
unlocked["b"] = unlocked.get("a", 0) + 1
# You can also access the raw dict here, though
# this is dangerous and should only be used with
# careful forethought.
raw_data = unlocked.data
⚠️ Important: While all dictionary operations are thread-safe, if you retrieve mutable objects (lists, dicts, custom objects) via get() or __getitem__(), you must NOT mutate them directly outside of the store's atomic operations.
Or, in other words, never do something like this:
my_list = store["my_list"]
my_list.append("item")
Instead use the helper functions:
store.append("my_list", "item") # Thread-safe
# or
store.operate("my_list", lambda x: x + ["item"]) # Thread-safe
# or
with store as unlocked:
list = store["my_list"]
list.append("item")
API Reference
Core Operations
store[key]- Get a valuestore[key] = value- Set a valuedel store[key]- Delete a keykey in store- Check if key existslen(store)- Get number of itemsiter(store)- Iterate over keys
Methods
get(key, default=None)
Get a value with a default if key doesn't exist.
operate(keys, operation)
Perform an atomic operation on a value. Supports nested paths.
# Single key
store.operate("counter", lambda x: x + 1)
# Nested path
store.operate(["user", "profile", "score"], lambda x: x + 10)
update(key, operation)
Update a top-level key atomically.
store.update("counter", lambda x: x * 2)
init(key, value)
Initialize a key only if it doesn't exist.
increment(key, amount=1) / decrement(key, amount=1)
Atomically increment or decrement a numeric value.
append(keys, value)
Append to a list atomically. Supports nested paths.
store.append("items", "new_item")
store.append(["nested", "items"], "new_item")
concat(keys, value)
Concatenate to a string or list atomically.
Context Manager
Use the store as a context manager to get an unlocked datastore view with direct access.
with store as unlocked:
# Perform multiple operations atomically
unlocked["a"] = 1
unlocked["b"] = unlocked.get("a", 0) + 1
raw = unlocked.data # Get raw dict reference via .data property
to_json() / from_json(data)
Serialize/deserialize the store to/from JSON. A recursive function is used to dive into nested functions.
Examples
Nested Operations
store = Datastore()
# Create nested structure
store["config"] = {
"database": {
"host": "localhost",
"port": 5432
}
}
# Atomic operation on nested value
store.operate(["config", "database", "port"], lambda x: x + 1)
Context Manager for Batch Operations
import time
store = Datastore()
# Use context manager for multiple atomic operations
with store as unlocked:
unlocked["total"] = unlocked.get("total", 0) + 10
unlocked["count"] = unlocked.get("count", 0) + 1
unlocked["last_updated"] = time.time()
# All operations happen while lock is held
License
This code is provided under the MIT licesne - see LICENSE.
Metadata
Release files for threadsafe-datastore 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| threadsafe_datastore-1.0.0.tar.gz | 15.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| threadsafe_datastore-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 24.6 kB
Release files / threadsafe_datastore-1.0.0.tar.gz
| Download URL | threadsafe_datastore-1.0.0.tar.gz |
|---|---|
| Size | 15.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fcee7ce0b212fd70897934797ba735662b723307c75dbfea841ec3d2d0e61a8d
|
|
BLAKE2b-256 checksum How to use checksums |
953c54654b3baaf91bc48ee3ac92cfa392d70b4da589721fdfb69d8dce61e774
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.10
|
Release files / threadsafe_datastore-1.0.0-py3-none-any.whl
| Download URL | threadsafe_datastore-1.0.0-py3-none-any.whl |
|---|---|
| Size | 9.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4bc5f359d0c6c947192128206e39b60f2588ac0d5f08e7ee450d8c62736c1d13
|
|
BLAKE2b-256 checksum How to use checksums |
1799cc8d1fd856e59848b2fc42cf4dfa4ca651b416888ac1973e3f59f5dac750
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.10
|