Skip to content

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() and load_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 operators
  • fynx.reactive: Reactive decorators for side effects
  • fynx.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.

load_state

load_state(state_dict)

Load state from a dictionary into the store's observables.

subscribe

subscribe(func)

Subscribe a function to react to all observable changes in the store.

to_dict

to_dict()

Serialize all observable values to a dictionary.

unsubscribe

unsubscribe(func)

Unsubscribe a function from all observables.

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().

__setattr__

__setattr__(name, value)

Intercept class attribute assignment for observables.

StoreSnapshot

StoreSnapshot(store_class, observable_attrs)

Immutable snapshot of store observable values at a specific point in time.

__getattr__

__getattr__(name)

Access snapshot values or fall back to class attributes.

observable

observable() -> Observable[None]
observable(initial_value: T) -> Observable[T]
observable(initial_value=None)

Create an observable with an initial value, used as a descriptor in Store classes.