Resolvers
Resolvers enable lazy evaluation of node values in TreeStore. Instead of storing a static value, a node can have a resolver that computes the value on demand.
How Resolvers Work
sequenceDiagram
participant C as Client
participant S as TreeStore
participant N as Node
participant R as Resolver
C->>S: store['config.computed']
S->>N: get value
N->>N: check if resolver exists
N->>R: load()
R-->>R: compute value
R->>N: return + cache
N-->>S: value
S-->>C: value
Note over R: Cached until TTL expires
C->>S: store['config.computed'] (again)
S->>N: get value
N->>N: check cache
N-->>S: cached value
S-->>C: value (from cache)
Resolver Types
graph TB
subgraph "Resolver Hierarchy"
BASE[TreeStoreResolver<br/>Abstract base class]
CB[CallbackResolver<br/>Function-based]
DIR[DirectoryResolver<br/>Filesystem]
TXT[TxtDocResolver<br/>Text files]
BASE --> CB
BASE --> DIR
BASE --> TXT
end
Resolver |
Purpose |
Caching |
|---|---|---|
|
Compute value via callback function |
Optional TTL |
|
Lazy-load directory contents |
On demand |
|
Load text file content |
On demand |
CallbackResolver
The most flexible resolver - computes values using a callback function.
Basic Usage
from genro_treestore import TreeStore, CallbackResolver
store = TreeStore()
store.set_item('config.base_url', 'https://api.example.com')
store.set_item('config.version', 'v2')
# Define callback that computes value
def get_full_url(node):
parent = node.parent # TreeStore containing this node
base = parent.get_item('base_url')
version = parent.get_item('version')
return f"{base}/{version}"
# Create node and attach resolver
store.set_item('config.full_url')
store.set_resolver('config.full_url', CallbackResolver(get_full_url))
# Value is computed on access
print(store['config.full_url']) # 'https://api.example.com/v2'
With Caching
import time
def expensive_computation(node):
time.sleep(1) # Simulate expensive operation
return "computed_value"
# Cache for 60 seconds
resolver = CallbackResolver(expensive_computation, cache_time=60)
store.set_resolver('expensive.value', resolver)
# First access: computes value (slow)
value1 = store['expensive.value']
# Second access within 60s: returns cached value (fast)
value2 = store['expensive.value']
# After 60s: recomputes
Callback Signature
The callback receives the TreeStoreNode as its argument:
def my_callback(node: TreeStoreNode) -> Any:
# node.label - the node's label
# node.attr - the node's attributes dict
# node.parent - the parent TreeStore
# node.store - the root TreeStore
return computed_value
DirectoryResolver
Lazily loads directory contents into the tree structure.
from genro_treestore import TreeStore, DirectoryResolver
store = TreeStore()
# Attach resolver to a node
store.set_item('filesystem')
store.set_resolver('filesystem', DirectoryResolver('/path/to/directory'))
# Directory contents are loaded on first access
for name, node in store['filesystem'].items():
print(f"{name}: {node}")
TxtDocResolver
Loads text file content on demand.
from genro_treestore import TreeStore, TxtDocResolver
store = TreeStore()
# Attach resolver
store.set_item('readme')
store.set_resolver('readme', TxtDocResolver('/path/to/README.md'))
# Content is loaded on access
print(store['readme']) # File contents
Creating Custom Resolvers
Extend TreeStoreResolver to create custom resolvers:
from genro_treestore import TreeStoreResolver
class DatabaseResolver(TreeStoreResolver):
"""Load value from database on demand."""
def __init__(self, query: str, connection):
self.query = query
self.connection = connection
def load(self, node):
"""Called when node value is accessed."""
cursor = self.connection.execute(self.query)
return cursor.fetchone()
# Usage
store.set_item('user.current')
store.set_resolver('user.current', DatabaseResolver(
"SELECT * FROM users WHERE id = 1",
db_connection
))
Resolver Lifecycle
stateDiagram-v2
[*] --> Attached: set_resolver()
Attached --> Loading: value accessed
Loading --> Cached: load() returns
Cached --> Loading: cache expired
Cached --> [*]: node deleted
note right of Cached
Value stored in node
until TTL expires
end note
Best Practices
Use caching wisely: Set appropriate
cache_timefor expensive computationsKeep callbacks simple: Complex logic should be in separate functions
Handle errors: Callbacks should handle exceptions gracefully
Avoid circular dependencies: Don’t create resolvers that depend on each other cyclically
See Also
TreeStoreResolver- Base resolver classCallbackResolver- Function-based resolverDirectoryResolver- Filesystem resolverTxtDocResolver- Text file resolver