Subscriptions
TreeStore provides a reactive subscription system for monitoring changes to the tree structure. Subscribers receive notifications when nodes are inserted, updated, or deleted.
Event Propagation
graph BT
subgraph "Event Propagation"
LEAF[Leaf Node<br/>value changed]
PARENT[Parent Store]
ROOT[Root Store<br/>subscriber]
LEAF -->|"upd_value"| PARENT
PARENT -->|propagate| ROOT
end
ROOT -->|callback| CB[Handler Function]
Events bubble up from the changed node to the root, allowing subscribers at any level to receive notifications.
Event Types
Event |
Description |
Triggered By |
|---|---|---|
|
Node inserted |
|
|
Node deleted |
|
|
Node value changed |
|
|
Node attribute changed |
|
Basic Subscription
from genro_treestore import TreeStore
store = TreeStore()
def on_change(node, path, evt, **kw):
"""Handle any tree change."""
print(f"{evt}: {path} = {node.value}")
# Subscribe to all events
store.subscribe('logger', any=on_change)
store.set_item('users.alice', 'Alice')
# Output: ins: users.alice = Alice
store.set_item('users.alice', 'Alicia')
# Output: upd_value: users.alice = Alicia
store.del_item('users.alice')
# Output: del: users.alice = Alicia
Subscription Flow
sequenceDiagram
participant C as Client
participant S as TreeStore
participant N as Node
participant SUB as Subscriber
C->>S: set_item('path', value)
S->>N: create/update node
N->>S: trigger event
S->>SUB: notify(node, path, evt)
SUB-->>SUB: execute callback
SUB-->>S: done
S-->>C: return
Selective Subscriptions
Subscribe to specific event types:
def on_insert(node, path, evt, **kw):
print(f"New node: {path}")
def on_delete(node, path, evt, **kw):
print(f"Deleted: {path}")
def on_update(node, path, evt, **kw):
print(f"Updated: {path} = {node.value}")
# Subscribe to specific events
store.subscribe('insert_logger', ins=on_insert)
store.subscribe('delete_logger', delete=on_delete)
store.subscribe('update_logger', upd_value=on_update)
Callback Signature
def callback(
node: TreeStoreNode, # The affected node
path: str, # Full path to the node
evt: str, # Event type: 'ins', 'del', 'upd_value', 'upd_attr'
**kw # Additional keyword arguments
) -> None:
pass
Unsubscribing
# Unsubscribe from all events
store.unsubscribe('logger', any=True)
# Unsubscribe from specific events
store.unsubscribe('insert_logger', ins=True)
store.unsubscribe('delete_logger', delete=True)
Multiple Subscribers
store = TreeStore()
# Audit logging
def audit_log(node, path, evt, **kw):
with open('audit.log', 'a') as f:
f.write(f"{evt}: {path}\n")
# Real-time sync
def sync_to_db(node, path, evt, **kw):
if evt == 'ins':
db.insert(path, node.value)
elif evt == 'upd_value':
db.update(path, node.value)
elif evt == 'del':
db.delete(path)
# UI updates
def update_ui(node, path, evt, **kw):
ui.refresh(path)
store.subscribe('audit', any=audit_log)
store.subscribe('db_sync', any=sync_to_db)
store.subscribe('ui', any=update_ui)
Subscription Architecture
graph TB
subgraph "TreeStore"
STORE[TreeStore]
SUBS[Subscribers Dict]
STORE -->|manages| SUBS
end
subgraph "Subscribers"
S1[audit<br/>any=audit_log]
S2[db_sync<br/>any=sync_to_db]
S3[insert_only<br/>ins=on_insert]
SUBS --> S1
SUBS --> S2
SUBS --> S3
end
subgraph "Events"
INS[ins]
DEL[del]
UPD[upd_value]
INS --> S1
INS --> S2
INS --> S3
DEL --> S1
DEL --> S2
UPD --> S1
UPD --> S2
end
Use Cases
Change Tracking
changes = []
def track_changes(node, path, evt, **kw):
changes.append({
'event': evt,
'path': path,
'value': node.value,
'timestamp': datetime.now()
})
store.subscribe('tracker', any=track_changes)
Validation on Change
def validate_on_change(node, path, evt, **kw):
if evt in ('ins', 'upd_value'):
if path.startswith('config.'):
validate_config_value(path, node.value)
store.subscribe('validator', any=validate_on_change)
Computed Properties
def update_computed(node, path, evt, **kw):
if path in ('data.price', 'data.quantity'):
price = store.get_item('data.price', 0)
quantity = store.get_item('data.quantity', 0)
store.set_item('data.total', price * quantity)
store.subscribe('computed', any=update_computed)
Best Practices
Use descriptive subscriber names: Makes debugging easier
Keep callbacks fast: Long-running operations should be queued
Handle exceptions: Callbacks should not raise exceptions
Avoid infinite loops: Be careful with callbacks that modify the store
Unsubscribe when done: Clean up subscribers to prevent memory leaks
See Also
SubscriberCallback- Callback type definitionValidation - Using ValidationSubscriber for reactive validation