Advanced Patterns
This guide covers advanced builder patterns for complex use cases.
Builder Inheritance
Extending Existing Builders
SchemaBuilder: Programmatic Schema Creation
SchemaBuilder allows you to define schemas programmatically instead of using decorators. This is useful for:
Dynamic schema generation
Schemas loaded from external sources
Reusable schema definitions shared across builders
Basic Usage
The item() Method
schema.item(
name: str, # Element name (or '@name' for abstract)
sub_tags: str = '', # Valid child tags with cardinality
inherits_from: str = None, # Abstract element to inherit from
)
Defining Abstract Elements
Use @ prefix for abstract elements:
Compiling to File
Save the schema for reuse:
# Save to MessagePack (binary, compact)
schema.builder._compile('my_schema.msgpack')
Using Compiled Schema
Load the schema in a custom builder:
from genro_builders import BuilderBag
from genro_builders.builders import BagBuilderBase
# Method 1: Class attribute
class MyBuilder(BagBuilderBase):
_schema_path = 'my_schema.msgpack'
# Method 2: Constructor parameter
bag = BuilderBag(builder=BagBuilderBase, builder_schema_path='my_schema.msgpack')
Complete Example: Config Schema
When to Use SchemaBuilder vs @element
Approach |
Use When |
|---|---|
|
Schema is static, defined in code |
|
Schema is dynamic, generated at runtime |
|
Schema is shared across multiple builders |
XSD → SchemaBuilder |
Schema comes from external XSD file |
Loading Schema from File
Builders can load schema from a pre-compiled MessagePack file using _schema_path:
from genro_builders import BuilderBag
from genro_builders.builders import BagBuilderBase
class MyBuilder(BagBuilderBase):
_schema_path = 'path/to/schema.msgpack' # Load at class definition
# Or pass at instantiation
bag = BuilderBag(builder=MyBuilder, builder_schema_path='custom_schema.msgpack')
Custom Validation
For custom validation logic, see Validation.
Performance Tips
1. Use Batch Operations
# Instead of validating each step:
for data in large_dataset:
parent.item(data)
# Validate once at the end:
errors = builder.validate()
Real-World Example: Config Builder
Reactive Formulas
Data formulas let you define computed values that re-execute automatically
when their ^pointer dependencies change. See Reactive Data
for the full guide.
Formula Dependency Chains
When formulas depend on each other, they execute in topological order:
from genro_builders.contrib.html import HtmlBuilder
builder = HtmlBuilder()
s = builder.source
s.data_setter('base_price', value=100)
s.data_setter('discount', value=0.1)
s.data_setter('tax_rate', value=0.22)
# Executes first: depends on base_price and discount
s.data_formula('net_price',
func=lambda base_price, discount: base_price * (1 - discount),
base_price='^base_price',
discount='^discount',
)
# Executes second: depends on net_price (computed above)
s.data_formula('total',
func=lambda net_price, tax_rate: net_price * (1 + tax_rate),
net_price='^net_price',
tax_rate='^tax_rate',
)
s.body().p(value='^total')
builder.build()
builder.subscribe()
print(builder.output)
# Change base_price -> net_price recalculates -> total recalculates -> re-render
builder.data['base_price'] = 200
print(builder.output)
Computed Attributes
Callable attributes are resolved via 2-pass evaluation on the node.
In pass 1 all ^pointer attributes are resolved to values. In pass 2,
callables are called with matching resolved attributes as kwargs:
# Callable that uses other resolved attributes
s.body().div(
price="^item.price",
qty=3,
total=lambda price, qty: price * qty,
)
# Callable with ^pointer defaults (resolved from data store)
s.body().div(
style=lambda bg='^theme.bg', fg='^theme.fg': f'background:{bg};color:{fg}',
)
Output Suspension
When making multiple data changes, use suspend_output() / resume_output()
to avoid redundant re-renders:
builder.build()
builder.subscribe()
builder.suspend_output()
builder.data['a'] = 1
builder.data['b'] = 2
builder.data['c'] = 3 # formulas re-execute, but no render
builder.resume_output() # single render with all changes applied
This is useful for initialization patterns where many data values are set at once, or for batch updates from external sources.
Debounce and Periodic Execution
Debounce with _delay
Prevent rapid re-execution with a debounce window:
s.data_formula('search_results',
func=lambda query: api_search(query),
query='^search.query',
_delay=0.5, # wait 500ms after last change
)
Periodic with _interval
Re-execute at regular intervals after subscribe():
import time
s.data_formula('clock',
func=lambda: time.strftime('%H:%M:%S'),
_interval=1.0, # every second
)
Interval timers start on subscribe() and stop on rebuild or clear.