Store Class¶
Container class for grouping observables and managing reactive state.
FynX Store - reactive state management components¶
A Store groups related observables into a single container with unified change notification, instead of scattering them through the codebase. It works well for application state (preferences, theme), feature state (shopping carts, user profiles), component state shared across several components, or derived values computed from raw data.
Core Components¶
Store: Base class for reactive state containers. Store classes define observable
attributes using the observable() descriptor, and Store provides methods for subscribing
to changes and managing state. The metaclass intercepts attribute assignment, so
Store.attr = value works directly with observables.
observable: Descriptor function that creates observable attributes on Store classes. Use this to define reactive properties in your Store subclasses. The returned Observable is itself a typed descriptor, so class access returns an ObservableValue with the right static type while preserving reactive operators.
StoreSnapshot: Immutable snapshot of store state at a specific point in time. Useful for debugging, logging, and ensuring consistent state access during reactive callbacks. Each snapshot captures all observable values at creation time.
StoreMeta: Metaclass that records observable attributes, provides class-level
assignment (Store.attr = value), and gives inherited Store fields owner-specific
backing observables so subclasses do not share mutable state accidentally.
Key Features¶
- Store's metaclass records observable descriptors and resolves inherited ones to owner-specific backing observables, so subclasses never share mutable state by accident
- Subscribe to every change in a store with one callback that receives a StoreSnapshot, or subscribe to individual observables directly
- Save and restore state with
to_dict()andload_state().to_dict()serializes every observable including computed ones; use_get_primitive_observable_attrs()to filter those out for persistence - Full type hints, so class access gets the right static type
- Stores operate independently, so you can organize state by domain without creating cross-store dependencies
Basic Usage¶
from fynx import Store, observable
class CounterStore(Store):
count = observable(0)
name = observable("My Counter")
# Access values like regular attributes
print(CounterStore.count) # 0
CounterStore.count = 5 # Updates the observable
# Subscribe to all changes in the store
def on_store_change(snapshot):
print(f"Store changed: count={snapshot.count}, name={snapshot.name}")
CounterStore.subscribe(on_store_change)
CounterStore.count = 10 # Triggers: "Store changed: count=10, name=My Counter"
# Unsubscribe when done
CounterStore.unsubscribe(on_store_change)
Alternative: use the @reactive decorator for a more convenient syntax:
from fynx import reactive
@reactive(CounterStore)
def on_store_change(snapshot):
print(f"Store changed: count={snapshot.count}, name={snapshot.name}")
CounterStore.count = 10 # Automatically triggers the function
Advanced Patterns¶
Computed Properties in Stores¶
Stores support computed observables that derive values from other observables. These update automatically when their dependencies change:
from fynx import Store, observable
class UserStore(Store):
first_name = observable("John")
last_name = observable("Doe")
age = observable(30)
# Computed properties using the >> operator
full_name = (first_name + last_name) >> (
lambda fname, lname: f"{fname} {lname}"
)
is_adult = age >> (lambda a: a >= 18)
print(UserStore.full_name) # "John Doe"
UserStore.first_name = "Jane"
print(UserStore.full_name) # "Jane Doe" (automatically updated)
Computed observables participate in store subscriptions the same way - subscribing to a store triggers updates for computed values just like primitive ones.
State Persistence¶
Stores can serialize their state to dictionaries and restore from them. This enables persistence across sessions or state transfer between components:
# Save store state
state = CounterStore.to_dict()
# state = {"count": 10, "name": "My Counter"}
# Restore state later
CounterStore.load_state(state)
print(CounterStore.count) # 10
Note that to_dict() includes all observables, including computed ones. For persistence,
you typically want only primitive observables since computed values derive from them.
Use _get_primitive_observable_attrs() to filter if needed, though the current implementation
serializes everything for simplicity.
Store Composition¶
Multiple stores operate independently, allowing you to organize state by domain:
class AppStore(Store):
theme = observable("light")
language = observable("en")
class UserStore(Store):
name = observable("Alice")
preferences = observable({})
# Use both stores independently
AppStore.theme = "dark"
UserStore.name = "Bob"
That independence lets you compose complex applications out of focused stores, each managing its own domain.
Common Patterns¶
Singleton Stores: Use class-level access for global state. Since Store attributes are class-level, each Store class acts as a singleton:
class GlobalStore(Store):
is_loading = observable(False)
current_user = observable(None)
# Access globally
GlobalStore.is_loading = True
Store Inheritance: Child classes inherit observable attributes from parent classes. The metaclass handles descriptor creation for inherited observables:
class BaseStore(Store):
created_at = observable(None)
class UserStore(BaseStore):
name = observable("")
# UserStore automatically has created_at from BaseStore
See Also¶
fynx.observable: Core observable classes and operatorsfynx.reactive: Reactive decorators for side effectsfynx.observable.computed: Creating computed properties
Store ¶
Base class for reactive state containers with observable attributes.
Store subclasses define observable attributes with the observable()
descriptor; Store provides methods for subscribing to changes,
serializing state, and managing reactive relationships.
The metaclass intercepts attribute assignment so Store.attr = value
delegates to the underlying observable's set() method, triggering
reactive updates.
Key Features:
- Automatic observable attribute detection and management through metaclass
- Unified subscription method that reacts to all observable changes in the store
- Serialization/deserialization support via to_dict() and load_state()
- Snapshot functionality through StoreSnapshot for consistent state access
Example
from fynx import Store, observable
class CounterStore(Store):
count = observable(0)
name = observable("Counter")
# Subscribe to all changes
def on_change(snapshot):
print(f"Counter: {snapshot.count}, Name: {snapshot.name}")
CounterStore.subscribe(on_change)
# Changes trigger reactions
CounterStore.count = 5 # Prints: Counter: 5, Name: Counter
CounterStore.name = "My Counter" # Prints: Counter: 5, Name: My Counter
# Unsubscribe when done
CounterStore.unsubscribe(on_change)
Note
Store uses a metaclass to intercept attribute assignment, so
Store.attr = value works directly with observables. The
subscribe() method is a classmethod that takes a function, not a decorator.
Use @reactive(Store) from fynx.reactive for decorator syntax.
StoreMeta ¶
Metaclass for Store observable registration and class-level assignment.
Observable instances are already typed descriptors. StoreMeta records them,
resolves inherited descriptors to owner-specific backing observables, and
intercepts class assignment so Store.attr = value delegates to .set().
StoreSnapshot ¶
Immutable snapshot of store observable values at a specific point in time.
observable ¶
Create an observable with an initial value, used as a descriptor in Store classes.