Subscriptions (Reactivity)
React to changes in Bag data. When values are modified, your callbacks are automatically invoked.
When Do You Need Subscriptions?
You need subscriptions when:
Validation: Check constraints when data changes
Logging: Audit trail of modifications
Computed properties: Update dependent values automatically
UI synchronization: React to model changes
Side effects: Trigger actions on data changes
The Core Idea
Without subscriptions:
bag['count'] = 1
# ... somewhere else in code
# How do you know 'count' changed?
With subscriptions:
def on_change(**kw):
print(f"Changed: {kw['node'].label}")
bag.subscribe('watcher', any=on_change)
bag['count'] = 1 # Prints: "Changed: count"
Quick Example
>>> from genro_bag import Bag
>>> bag = Bag()
>>> events = []
>>> def on_change(**kw):
... node = kw['node']
... evt = kw['evt']
... events.append(f"{evt}: {node.label}")
>>> bag.subscribe('watcher', any=on_change)
>>> bag['name'] = 'Alice'
>>> bag['name'] = 'Bob'
>>> events
['ins: name', 'upd_value: name']
Event Types
Event |
Triggered When |
|---|---|
|
New node added |
|
Node value changed |
|
Node attributes changed |
|
Node value AND attributes changed in the same mutation |
|
Node removed |
|
Timer interval elapsed |
Subscribing
bag.subscribe(
'my_subscriber', # Unique ID
insert=callback, # Called on new nodes
update=callback, # Called on value changes
delete=callback, # Called on removal
any=callback, # Called on ins/upd/del (not timer)
timer=callback, # Called every `interval` seconds
interval=20 # Required when timer is set
)
Callback Signature
# For ins/upd/del events:
def callback(**kw):
node = kw['node'] # The affected BagNode
evt = kw['evt'] # Event type: 'ins', 'upd_value', 'del'
pathlist = kw['pathlist'] # Path from subscription root
ind = kw.get('ind') # Index position
# For timer events:
def timer_callback(**kw):
bag = kw['bag'] # The Bag where the timer is subscribed
evt = kw['evt'] # Always 'tmr'
subscriber_id = kw['subscriber_id'] # Subscription ID
Stop Propagation
Any callback can return False to stop event propagation to parent bags:
def handle_locally(**kw):
# Process the event...
return False # Don't propagate to parent
bag.subscribe('local', update=handle_locally)
Unsubscribing
# Remove specific subscription type
bag.unsubscribe('my_subscriber', update=True)
# Remove timer subscription
bag.unsubscribe('my_subscriber', timer=True)
# Remove all subscriptions for this ID
bag.unsubscribe('my_subscriber', any=True)
Key Features
Nested Paths
Subscriptions work with hierarchical changes:
>>> from genro_bag import Bag
>>> bag = Bag()
>>> events = []
>>> bag.subscribe('w', any=lambda **kw: events.append(kw['node'].label))
>>> bag['config.database.host'] = 'localhost'
>>> events
['config', 'database', 'host']
Multiple Subscribers
bag.subscribe('logger', any=log_changes)
bag.subscribe('validator', update=validate)
bag.subscribe('sync', any=sync_to_server)
Backref Mode
Subscriptions automatically enable parent references:
>>> from genro_bag import Bag
>>> bag = Bag()
>>> bag.subscribe('w', any=lambda **kw: None)
>>> bag.backref
True