State management library for Python (async RozRemembers + synchronous Store)
Project description
roz-remembers
A small, message-driven state management library for Python, inspired by the
predictable state-container pattern popularized by Redux. It gives you a
centralized, observable bag of state that you read and write through simple
dot-path keys (e.g. "job.bins"), with change events you can subscribe to
and optional JSON persistence.
It ships two front ends over the same dot-path engine:
Store— a synchronous store. Best for ordinary synchronous code that just wants central, observable runtime state without an event loop.RozRemembers— an asyncio, Redux-style store driven by an action queue and an event queue. Best for long-running async applications.
Both share the exported helpers get_nested_value(path, data) and
set_nested_value(path, value, data).
Installation
pip install roz-remembers
The import name is roz_remembers:
from roz_remembers import Store, RozRemembers, get_nested_value, set_nested_value
Dot-path convention
State is a nested dict, and every read/write addresses a value by a
dot-separated path:
"user.theme"→state["user"]["theme"]- numeric segments index into lists:
"items.0.name"→state["items"][0]["name"] - reads of a missing or non-traversable path return
None(or your supplied default onStore.get). - writes auto-create intermediate dicts as needed. A write fails
(returns
False) only if a segment can't be traversed or assigned — e.g. a list index out of range, or trying to descend into a scalar.
Quickstart — Store (synchronous)
from roz_remembers import Store
store = Store({"job": {"bins": 10, "sorted": 0}})
# read / write by dot-path
store.set("job.sorted", 1) # -> True
store.get("job.sorted") # -> 1
store.get("job.missing", default=0) # -> 0 (missing path falls back)
# writes auto-create intermediate dicts
store.set("machine.state", "CARD_READY")
store.get_state() # deep copy of the whole state
# subscribe to change events; subscribe() returns an unsubscribe function
def on_change(event):
print(event["path"], event["old_value"], "->", event["new_value"])
unsubscribe = store.subscribe(on_change)
store.set("job.sorted", 2) # -> on_change fires
unsubscribe() # stop receiving events
Persistence
Store reads and writes plain JSON:
store.save("state.json") # write current state to disk
restored = Store(state_file="state.json") # load state at construction
restored.get("job.bins") # -> 10
# a Store created with state_file remembers it, so save() needs no argument
live = Store(state_file="state.json")
live.set("job.sorted", 5)
live.save() # persists back to state.json
Loading a missing file or malformed JSON starts from an empty state (a warning is logged) rather than raising, so a first run "just works".
Quickstart — RozRemembers (asynchronous)
RozRemembers processes actions off a queue and emits events onto
another queue. You dispatch SET_STATE actions and consume STATE_CHANGED
events.
import asyncio
from roz_remembers import RozRemembers
async def main():
store = RozRemembers("initial_state.json")
await store.load_initial_state() # empty state if the file is absent
store.start_processing() # start the background action processor
# observe state changes
events = store.subscribe_events() # an asyncio.Queue of event dicts
await store.dispatch({
"type": RozRemembers.ACTION_TYPE_SET_STATE, # "SET_STATE"
"path": "user.theme",
"value": "dark",
})
event = await events.get()
# {'type': 'STATE_CHANGED', 'path': 'user.theme',
# 'old_value': None, 'new_value': 'dark', 'action_source': {...}}
print(event["path"], "->", event["new_value"])
print(store.get_current_state()) # deep copy of the whole state
await store.stop_processing()
asyncio.run(main())
The subscribe / events model
Storenotifies synchronously: eachset()that changes state calls every subscriber callback with aSTATE_CHANGEDevent ({"type", "path", "old_value", "new_value"}). A raising subscriber is logged and isolated — it won't break the store or other subscribers.RozRemembersis asynchronous:subscribe_events()returns anasyncio.Queue. Each appliedSET_STATEputs aSTATE_CHANGEDevent (which also carriesaction_source) on that queue for your consumer coroutine toawait. Actions with nopath, unknown action types, and writes that can't be applied are ignored and emit no event.
Development
This is a Poetry (PEP 621) project. Tests run under pytest with a coverage gate:
pip install pytest pytest-asyncio pytest-cov
pytest # runs the suite and enforces >=90% line coverage
pythonpath = ["src"] is set in pyproject.toml, so tests import
roz_remembers directly without a manual PYTHONPATH.
License
MIT
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 roz_remembers-0.2.0.tar.gz.
File metadata
- Download URL: roz_remembers-0.2.0.tar.gz
- Upload date:
- Size: 7.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"25.04","id":"plucky","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4d667db39dcd6e5fadbf88398e2e552e0a382839211d652c9a7103d817896a44
|
|
| MD5 |
06c5586e3d568f11bfd9be9fac0fde45
|
|
| BLAKE2b-256 |
10fbad8bee5042c75e3b87cd9a9c7568ef258d764e72027a976b52e32cb307fe
|
File details
Details for the file roz_remembers-0.2.0-py3-none-any.whl.
File metadata
- Download URL: roz_remembers-0.2.0-py3-none-any.whl
- Upload date:
- Size: 7.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"25.04","id":"plucky","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
13120f1cee4dd1d426d3509a8887140cb29f16d44f3d55f6a48f49bcd082b15e
|
|
| MD5 |
123b8b57e6b132d5ebdc88c57f6b7af5
|
|
| BLAKE2b-256 |
712e63a9d33d7f3960ff7ae1483591338808402552debc1984ae38a4ebea0fb9
|