Observable¶
The core observable class that provides reactive values.
A reactive value that automatically notifies dependents when it changes.
Observable wraps any Python value and makes it reactive. When you modify the value, all computations and functions that depend on it recalculate automatically. Wrap any value in an Observable, and functions that read it during reactive execution will re-run when the value changes.
The mechanism works through dependency tracking. When a reactive function executes, it runs within a ReactiveContext. That context watches which observables get accessed via their .value property. Each access registers the observable as a dependency and adds the context's re-run function as an observer. Later, when you call .set() on the observable, it notifies all observers, causing dependent functions to re-execute with fresh values.
Observable implements magic methods to behave like its underlying value. You can use it in boolean contexts (if observable:), string formatting (f"{observable}"), and equality comparisons (observable == 5) without accessing .value explicitly. That transparency makes observables easy to integrate into existing code—they look and feel like regular values.
The notification system uses batched processing with topological sorting. When multiple observables change, the system collects pending notifications and processes them in dependency order—source observables first, then computed observables, then conditional observables. That ordering ensures that when a conditional observable checks its condition values, those values have already been updated.
Circular dependency detection prevents infinite loops. If a computation tries to modify one of its own dependencies (directly or indirectly), the system raises a RuntimeError. The detection works by checking whether the current reactive context depends on the observable being modified.
Attributes:
| Name | Type | Description |
|---|---|---|
key |
str
|
Unique identifier for debugging and serialization |
_value |
The current wrapped value |
|
_observers |
Set[Callable]
|
Set of observer functions to notify on change |
Class Attributes
_current_context: Current reactive execution context (None when not in reactive execution) _context_stack: Stack of nested reactive contexts for proper dependency tracking _pending_notifications: Set of observables waiting to notify observers _notification_scheduled: Whether notification processing is scheduled _currently_notifying: Set of observables currently notifying (prevents re-entrant notifications)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
Optional[str]
|
A unique identifier for this observable (used for debugging and serialization).
If None, will be set to " |
None
|
initial_value
|
Optional[T]
|
The initial value to store. Can be any type compatible with the generic type parameter. |
None
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If setting this value would create a circular dependency (e.g., a computed value trying to modify its own input). |
Example
from fynx.observable import Observable
# Create an observable
counter = Observable("counter", 0)
# Direct access (transparent behavior)
print(counter.value) # 0
print(counter == 0) # True
print(str(counter)) # "0"
# Subscribe to changes
def on_change(new_value):
print(f"Counter changed to: {new_value}")
counter.subscribe(on_change)
counter.set(5) # Prints: "Counter changed to: 5"
Note
While you can create Observable instances directly, it's often more convenient to use the observable() descriptor in Store classes for better organization and automatic serialization support.
See Also
Store: For organizing observables into reactive state containers computed: For creating derived values from observables reactive: For creating reactive functions that respond to changes
Initialize an observable value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
Optional[str]
|
A unique identifier for this observable (used for serialization).
If None, will be set to " |
None
|
initial_value
|
Optional[T]
|
The initial value to store |
None
|
value ¶
Get the current value of this observable.
Accessing this property reads the wrapped value. If called within a reactive context (during execution of a reactive function or computation), it also registers this observable as a dependency. That registration happens automatically—the current ReactiveContext (if any) adds this observable to its dependency set and registers itself as an observer.
The dependency tracking enables automatic re-execution. When you later call .set() on this observable, all registered observers (including reactive contexts that depend on it) get notified and re-run their functions with fresh values.
Returns:
| Type | Description |
|---|---|
Optional[T]
|
The current value stored in this observable, or None if not set. |
Note
This property is tracked by the reactive system. Use it instead of accessing _value directly to ensure proper dependency tracking. Outside reactive contexts, reading .value behaves like a regular property access.
__bool__ ¶
Boolean conversion returns whether the value is truthy.
This allows observables to be used directly in boolean contexts (if statements, boolean operations) just like regular values.
Returns:
| Type | Description |
|---|---|
bool
|
True if the wrapped value is truthy, False otherwise. |
__eq__ ¶
Equality comparison with another value or observable.
Compares the wrapped values for equality. If comparing with another Observable, compares their wrapped values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other
|
object
|
Value or Observable to compare with |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the values are equal, False otherwise. |
__hash__ ¶
Hash based on object identity, not value.
Since values may be unhashable (like dicts, lists), observables hash based on their object identity rather than their value.
Returns:
| Type | Description |
|---|---|
int
|
Hash of the observable's object identity. |
Note
This means observables with the same value will not be considered equal for hashing purposes, only identical objects.
__repr__ ¶
__set_name__ ¶
Called when this Observable is assigned to a class attribute.
This method implements the descriptor protocol to enable automatic conversion of Observable instances to appropriate descriptors based on the owning class type.
For Store classes, the conversion is handled by StoreMeta metaclass. For other classes, converts to SubscriptableDescriptor for class-level observable behavior.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
owner
|
Type
|
The class that owns this attribute |
required |
name
|
str
|
The name of the attribute being assigned |
required |
Note
This method is called automatically by Python when an Observable instance is assigned to a class attribute. It modifies the class to use the appropriate descriptor for reactive behavior.
__str__ ¶
String representation of the wrapped value.
Returns the string representation of the current value, enabling observables to be used seamlessly in string contexts.
Returns:
| Type | Description |
|---|---|
str
|
String representation of the wrapped value. |
add_observer ¶
Add an observer function that will be called when this observable changes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
observer
|
Callable
|
A callable that takes no arguments |
required |
remove_observer ¶
Remove an observer function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
observer
|
Callable
|
The observer function to remove |
required |
set ¶
Set the value and notify all observers if the value changed.
This method updates the observable's wrapped value and triggers change notifications to all registered observers. The update only occurs if the new value differs from the current value (using != comparison). If the value hasn't changed, observers are not notified—that avoids unnecessary re-computations.
Before updating, the method checks for circular dependencies. If the current reactive context depends on this observable (directly or indirectly), setting the value would create a cycle. The system raises a RuntimeError in that case, preventing infinite loops.
When the value does change, the observable adds itself to a pending notifications set. The notification system processes these in topological order—source observables first, then computed observables, then conditional observables. That ordering ensures that when a conditional observable checks its condition values, those values have already been updated.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
Optional[T]
|
The new value to set. Can be any type compatible with the observable's generic type parameter. |
required |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If setting this value would create a circular dependency (e.g., a computed value trying to modify its own input). |
Example
Note
Equality is checked using the != operator, so custom objects should implement proper equality comparison if needed.
subscribe ¶
Subscribe a function to react to changes in this observable.
The subscribed function will be called whenever the observable's value changes. The function receives the new value as its single argument. This creates a ReactiveContext that wraps the function and registers it as an observer on this observable.
When you call .set() with a new value, the observable notifies all observers. The subscription system ensures that your function runs with the updated value. The function is not called immediately upon subscription—only when the value actually changes.
The subscription creates a reactive context internally. That context tracks dependencies if your function accesses other observables during execution, enabling automatic re-execution when those dependencies change as well.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
func
|
Callable
|
A callable that accepts one argument (the new value). The function will be called whenever the observable's value changes. |
required |
Returns:
| Type | Description |
|---|---|
Observable[T]
|
This observable instance for method chaining. |
Example
Note
The function is called only when the observable's value changes. It is not called immediately upon subscription.
See Also
unsubscribe: Remove a subscription reactive: Decorator-based subscription with automatic dependency tracking
unsubscribe ¶
Unsubscribe a function from this observable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
func
|
Callable
|
The function to unsubscribe from this observable |
required |