A thread-safe key-value data store with atomic operations and nested access
Project description
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file threadsafe_datastore-1.0.0.tar.gz.
File metadata
- Download URL: threadsafe_datastore-1.0.0.tar.gz
- Upload date:
- Size: 15.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fcee7ce0b212fd70897934797ba735662b723307c75dbfea841ec3d2d0e61a8d
|
|
| MD5 |
818f5b8daf1586566984cd284d83881c
|
|
| BLAKE2b-256 |
953c54654b3baaf91bc48ee3ac92cfa392d70b4da589721fdfb69d8dce61e774
|
File details
Details for the file threadsafe_datastore-1.0.0-py3-none-any.whl.
File metadata
- Download URL: threadsafe_datastore-1.0.0-py3-none-any.whl
- Upload date:
- Size: 9.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4bc5f359d0c6c947192128206e39b60f2588ac0d5f08e7ee450d8c62736c1d13
|
|
| MD5 |
a8a2b472d8a6596d5fcb070ef34c526d
|
|
| BLAKE2b-256 |
1799cc8d1fd856e59848b2fc42cf4dfa4ca651b416888ac1973e3f59f5dac750
|