Store Class¶
Container class for grouping observables and managing reactive state.
FynX Store - Reactive State Management Components¶
Consider a filing cabinet: each drawer holds related documents, and you can watch the cabinet to know when anything inside changes. That cabinet is a Store—a container that groups related reactive values together and provides unified change notification.
Stores organize reactive state into logical units. Instead of scattering observables throughout your codebase, Stores group related data together and provide methods for subscribing to changes, serializing state, and managing the reactive lifecycle. This gives you structured state management—each Store becomes a cohesive unit that you can observe, persist, and compose.
We apply that pattern to application architecture. Stores work well for application state like user preferences and theme settings, feature state like shopping carts and user profiles, component state that needs sharing across multiple components, and business logic that computes derived values from raw data. That organization reduces coupling and makes state changes predictable.
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, allowing
Store.attr = value syntax to work seamlessly with observables.
observable: Descriptor function that creates observable attributes on Store classes. Use this to define reactive properties in your Store subclasses. The metaclass converts these to descriptors that provide transparent reactive access.
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 automatically converts observable attributes to descriptors and provides type hint compatibility for mypy. This metaclass processes class definitions to wrap observables in descriptors and handles inheritance of observable attributes.
Key Features¶
- Automatic Observable Management: Store metaclass handles observable creation and descriptor wrapping, including support for inherited observables from parent classes
- Unified Subscriptions: Subscribe to all changes in a store with a single callback that receives a StoreSnapshot, or subscribe to individual observables directly
- State Serialization: Save and restore store state with
to_dict()andload_state(). Theto_dict()method serializes all observables including computed ones; use_get_primitive_observable_attrs()to filter out computed observables for persistence - Type Safety: Full type hint support for better IDE experience and static analysis
- Memory Efficient: Automatic cleanup through subscription context management and efficient change detection that avoids redundant updates
- Composable: Multiple stores operate independently, allowing you to organize state by domain without 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)
That pattern lets you define derived state alongside raw data. The computed observables participate in store subscriptions—when you subscribe to a store, computed values trigger updates just like primitive observables.
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 means you can compose complex applications from focused stores. Each store manages its own domain without creating cross-store dependencies.
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 groups related observable values together and manages their lifecycle
as a cohesive unit. Store subclasses define observable attributes using the
observable() descriptor, and Store provides methods for subscribing to changes,
serializing state, and managing reactive relationships.
The metaclass intercepts attribute assignment, allowing Store.attr = value syntax
to work seamlessly with observables. When you assign to a store attribute, the
metaclass delegates to the underlying observable's set() method, which triggers
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, allowing
Store.attr = value syntax to work seamlessly 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 to automatically convert observable attributes to descriptors and adjust type hints for mypy compatibility.
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.