Builder system for genro-bag — grammar, validation, reactive data binding, and computed data infrastructure.
pip install genro-buildersfrom genro_builders.contrib.html import HtmlBuilder
builder = HtmlBuilder()
body = builder.source.body()
body.div(id='main').p('Hello, world!')
builder.build()
print(builder.render())A builder is a machine. To define what to build, use a BuilderManager:
from genro_builders.contrib.html import HtmlBuilder
from genro_builders.manager import BuilderManager
class HtmlManager(BuilderManager):
def __init__(self):
self.page = self.set_builder('page', HtmlBuilder)
def render(self):
return self.page.render()
class MyPage(HtmlManager):
def __init__(self):
super().__init__()
self.setup() # store -> main
self.build() # source -> built
def store(self, data):
data['title'] = 'Hello, world!'
def main(self, source):
body = source.body()
body.h1(value='^title')
self.footer(source)
def footer(self, source):
source.footer().p('(c) 2026')
page = MyPage()
print(page.render())- Domain-specific grammars — Define elements, validation rules, and components via decorators (
@element,@abstract,@component) - Data infrastructure —
@data_elementdecorator fordata_setter,data_formula, anddata_controller— write, compute, and act on the reactive data store directly from the source Bag - Reactive formulas —
data_formulawith^pointerdependencies re-executes automatically when sources change, in topological order (dependencies first, cycles detected) - Computed attributes — Callable attributes with
^pointerdefaults are resolved just-in-time during render/compile - Debounce and periodic —
_delayfor debounced formula re-execution,_intervalfor periodic polling - Output suspension —
suspend_output()/resume_output()to batch data changes and trigger a single render - Pointer formali — The built Bag retains
^pointerstrings; resolution happens just-in-time during render/compile, not during build - Named slots — Components can declare insertion points (
slots=['left', 'right']) for user content injection - Contributed builders — HTML5, Markdown, SVG, XSD in
genro_builders.contrib(optional, not loaded unless imported) - Multi-builder coordination —
BuilderManagercoordinates multiple builders with a shared data store - Renderers and compilers —
@rendererfor serialized output (HTML, Markdown),@compilerfor live objects (widgets, workbooks) - Node identification —
node_idattribute for O(1) lookup vianode_by_id() - Validation —
sub_tagscardinality,parent_tagsconstraints, typed attribute validation viaAnnotated
A builder is a machine that materializes a source Bag into a built Bag.
A BuilderManager mixin orchestrates population and lifecycle:
setup() -> build() -> subscribe() -> render() / compile()
store + main two-pass walk: activate output
(populate) 1. data elements reactivity
2. normal elements
setup()— on manager: callsstore(data)thenmain(source)build()— two-pass materialize:- Pass 1: process data elements (data_setter writes values, data_formula computes, data_controller executes side effects)
- Pass 2: materialize normal elements and components into the built Bag, register
^pointerbindings - After both passes: topological sort of formulas, fire
_onBuilthooks
subscribe()— optional: activate reactive bindings. Data changes trigger formula re-execution and automatic re-render. Starts_intervaltimers.render()— produce serialized output viaBagRendererBase(just-in-time^pointerresolution)compile()— produce live objects viaBagCompilerBase(just-in-time^pointerresolution)
Data elements let you define computed and reactive data directly in the source Bag:
from genro_builders.contrib.html import HtmlBuilder
builder = HtmlBuilder()
s = builder.source
# Static data
s.data_setter('greeting', value='Hello')
# Computed data (re-executes when ^greeting changes)
s.data_formula('message', func=lambda greeting: f'{greeting}, World!',
greeting='^greeting')
# Controller (side effect, no output path)
s.data_controller(func=lambda message: print(f'Message: {message}'),
message='^message')
# UI bound to computed data
s.body().h1(value='^message')
builder.build()
builder.subscribe()
print(builder.output)
# <body><h1>Hello, World!</h1></body>
# Change source data -> formula re-executes -> re-render
builder.data['greeting'] = 'Ciao'
print(builder.output)
# <body><h1>Ciao, World!</h1></body># Re-execute at most once every 500ms (debounce)
s.data_formula('search_results', func=do_search,
query='^search.query', _delay=0.5)
# Re-execute every 10 seconds (polling)
s.data_formula('clock', func=lambda: time.strftime('%H:%M:%S'),
_interval=10.0)builder.suspend_output() # pause rendering
builder.data['a'] = 1
builder.data['b'] = 2
builder.data['c'] = 3 # no render triggered yet
builder.resume_output() # single render with all 3 changesSee the docs/ directory for full documentation:
- Getting Started — Build your first page in 5 minutes
- Custom Builders — Define your own grammar
- Reactive Data — Data elements, formulas, reactivity
- Architecture — Build pipeline, pointer formali, formula registry
Apache License 2.0 — Copyright 2025 Softwell S.r.l.