Skip to content

Quick Start

A store wraps a piece of state — any structure of dicts, lists and sets — and tracks every change made through its mutations.

Define a store

Subclass Store and decorate the methods that change state with @mutation. Inside a mutation, self.state is writable; everywhere else it is a readonly proxy.

from reliev import Store, computed, mutation

class CounterStore(Store):
    @mutation(context="Bump count")
    def bump_count(self):
        self.state["count"] += 1

    @mutation(context=lambda self, amount: f"Adjust count to {amount}")
    def adjust_count(self, amount):
        self.state["count"] = amount

    @computed
    def count(self):
        return self.state["count"]

store = CounterStore({"count": 0})

Mutate, undo, redo

Every mutation call records one history entry:

store.bump_count()
assert store.count == 1

store.adjust_count(5)
assert store.count == 5

store.undo()
assert store.count == 1

store.redo()
assert store.count == 5

can_undo and can_redo tell you whether there is anything to walk back or forward — for instance to enable or disable menu items. The context values passed to the decorators come back through undo_context and redo_context, so building a label like "Undo Adjust count to 5" is a string format away.

React to changes

The state is an observ reactive proxy, so watchers pick up changes made by mutations — and by undo/redo — automatically:

from observ import watch

watcher = watch(
    lambda: store.state["count"],
    lambda value: print(f"Count is now: {value}"),
    sync=True,
)

store.bump_count()  # prints "Count is now: 6"
store.undo()        # prints "Count is now: 5"

That's the whole model. The guide covers each piece in depth, starting with what exactly a mutation records.