Lightweight atomic state management for IoT devices and embedded systems
Project description
StateQuark
Atomic state management for IoT and embedded systems. Inspired by Jotai.
Installation
pip install statequark
Quick Start
from statequark import quark, batch
# Basic state
temperature = quark(20.0)
print(temperature.value) # 20.0
temperature.set(25.5)
# Derived state (auto-updates when dependencies change)
temp_f = quark(lambda get: get(temperature) * 9/5 + 32, deps=[temperature])
print(temp_f.value) # 77.9
# Subscriptions (returns unsubscribe function)
unsub = temperature.subscribe(lambda q: print(f"Temp: {q.value}"))
temperature.set(30.0) # prints: Temp: 30.0
unsub() # stop listening
# Reset to initial value
temperature.reset() # back to 20.0
# Batch updates (single notification)
with batch():
sensor1.set(25.0)
sensor2.set(60.0)
Utilities
Storage (Persistent State)
from statequark import quark_with_storage
# State survives device reboot
config = quark_with_storage("device_config", {"threshold": 25.0})
config.set({"threshold": 30.0}) # Saved to .statequark/device_config.json
Reducer (Action-based Updates)
from statequark import quark_with_reducer
def motor_reducer(state, action):
match action["type"]:
case "START": return {**state, "running": True}
case "SET_SPEED": return {**state, "speed": action["value"]}
return state
motor = quark_with_reducer({"running": False, "speed": 0}, motor_reducer)
motor.dispatch({"type": "START"})
motor.dispatch({"type": "SET_SPEED", "value": 100})
Select (Partial Subscription)
from statequark import quark, select
sensor = quark({"temp": 25.0, "humidity": 60})
temp = select(sensor, lambda d: d["temp"]) # Only triggers when temp changes
temp.subscribe(lambda q: print(f"Temp changed: {q.value}"))
Family (Dynamic Quarks)
from statequark import quark, quark_family
sensors = quark_family(lambda id: quark(0.0))
living_room = sensors("living_room")
bedroom = sensors("bedroom")
kitchen = sensors("kitchen")
Debounce / Throttle (Noise Filtering)
from statequark import debounce, throttle
# Debounce: wait 100ms of silence before updating
temp = debounce(0.0, 0.1)
# Throttle: max 1 update per second
display = throttle(0, 1.0)
History (Undo/Redo)
from statequark import history
setting = history(50)
setting.set(60)
setting.set(70)
setting.undo() # 60
setting.undo() # 50
setting.redo() # 60
Validate (Value Validation)
from statequark import validate, in_range, clamp
# Raise error on invalid
temp = validate(25.0, in_range(0, 100))
# Auto-clamp on invalid
temp = validate(25.0, in_range(0, 100), clamp(0, 100))
temp.set(150) # Clamped to 100
Middleware (Extensible Hooks)
from statequark import middleware, logger, persist
storage = {}
counter = middleware(0)
counter.use(logger("counter")) # Log changes
counter.use(persist(storage, "count")) # Save to dict
counter.set(42) # [counter] 0 -> 42
Loadable (Async State)
from statequark import quark, loadable
data = quark(None)
state = loadable(data)
state.set_loading() # state.value.state == "loading"
state.set_data(42) # state.value.state == "hasData"
state.set_error(err) # state.value.state == "hasError"
IoT Example
from statequark import quark, quark_with_storage, validate, in_range, clamp
# Persistent config
config = quark_with_storage("config", {"threshold": 30})
# Validated sensor with auto-clamp
soil = validate(65.0, in_range(0, 100), clamp(0, 100))
# Derived alert
alert = quark(
lambda get: get(soil) < get(config)["threshold"],
deps=[soil, config]
)
# Hardware control
alert.subscribe(lambda q: gpio.output(PUMP_PIN, q.value))
API Reference
Core
| Function | Description |
|---|---|
quark(value) |
Create state |
quark(fn, deps=[...]) |
Create derived state |
batch() |
Batch updates context |
Quark Methods
| Method | Description |
|---|---|
.value |
Get current value |
.set(v) |
Set value (sync) |
await .set_async(v) |
Set value (async) |
.reset() |
Reset to initial |
unsub = .subscribe(fn) |
Subscribe, returns unsubscribe function |
.cleanup() |
Release resources |
Utilities
Create new quarks (pass initial value):
| Function | Description |
|---|---|
quark_with_storage(key, default) |
Persistent state |
quark_with_reducer(init, reducer) |
Action-based state |
quark_family(factory) |
Dynamic quark creation |
debounce(init, delay) |
Debounced updates |
throttle(init, interval) |
Throttled updates |
history(init, max_size) |
Undo/redo support |
validate(init, validator, on_invalid) |
Value validation |
middleware(init) |
Middleware pipeline |
Wrap existing quarks (pass source quark):
| Function | Description |
|---|---|
select(source, selector) |
Partial subscription (read-only) |
loadable(source) |
Async state wrapper |
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
statequark-0.5.2.tar.gz
(23.6 kB
view details)
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 statequark-0.5.2.tar.gz.
File metadata
- Download URL: statequark-0.5.2.tar.gz
- Upload date:
- Size: 23.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
128a2c8bce49295bf46ff4c4b7cf8efea05b7aae5433289071421c4b36fba996
|
|
| MD5 |
ab423b75dc510354169efd48d2ec108a
|
|
| BLAKE2b-256 |
7267a1efdd1a1ab1e5f7a2e63671767ad556365dd05e8d9fc86e65e434e113bb
|
File details
Details for the file statequark-0.5.2-py3-none-any.whl.
File metadata
- Download URL: statequark-0.5.2-py3-none-any.whl
- Upload date:
- Size: 20.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
489f68e60feb788851ec5d8e9feda92136e7b055671ba8e4862808b721cd6ff8
|
|
| MD5 |
fd24032aa348db67138789746c0a52b0
|
|
| BLAKE2b-256 |
85120547a7b16b5b4eb0c744d4854c3f6127799abbcdda44d3e333d4e0e42d70
|