Common patterns

Last Updated: 2026-06-10 Status: 🟢 APPROVATO — allineato al contratto v0.8.0.

Cross-grammar idioms. These work the same way on HTML, SVG, CSS, or any user-defined dialect built on BuilderBase.

._ chaining — climbing back to the parent

Every node exposes a _ attribute that returns the parent bag. This makes leaf elements (<img>, <rect>, <br>, cssvar) easy to chain without breaking the call stream.

Every element call returns the newly created node. For leaves the node has no children, so chaining a sibling would otherwise require a fresh variable. ._ solves this without changing the return contract — leaves still return themselves, and the user explicitly asks to step back up.

SVG

from genro_builders.contrib.svg import SvgBuilder

class Chart(SvgBuilder):
    def main(self, root):
        svg = root.svg(viewBox="0 0 100 100")
        svg.rect(x=10, y=10, width=80, height=80, fill="red")\
           ._.rect(x=20, y=20, width=60, height=60, fill="blue")\
           ._.circle(cx=50, cy=50, r=10, fill="white")

Output (wrapped here for readability; the default render is one line):

<svg viewBox="0 0 100 100"><rect x="10" y="10" width="80" height="80" fill="red" />
<rect x="20" y="20" width="60" height="60" fill="blue" />
<circle cx="50" cy="50" r="10" fill="white" /></svg>

When ._ is not needed

For non-leaf elements that accept children, chain children directly on them:

body = root.body()
body.h1("Title")
body.p("First paragraph")
body.p("Second paragraph")

Use ._ only when a leaf node interrupts the chain.

Render targets — where the output goes

By default page.render() returns the rendered string. You can also pass a target inline, or register one in advance.

Inline target

# Return a string (default)
text = page.render()

# Write to a file (this call only)
with open("out.html", "w") as f:
    page.render(target=f)

# Pipe through a callable (this call only)
page.render(target=lambda chunk: socket.send(chunk))

Registered targets, one per mode

For repeated rendering, register a target once and let render() find it. set_render_target(target, mode=None) registers under the dialect’s default mode unless mode says otherwise:

page.set_render_target("out.html")              # default mode (html)
page.set_render_target("snapshot.xml", "xml")   # xml view

page.render()              # default mode, writes out.html
page.render(mode="xml")    # writes snapshot.xml
page.render(target=False)  # default mode, returns the string
print(page.rendered_target)  # read back the file just written

Target semantics

Inside render(target=...):

  • False — forces return-as-string, ignoring any registered target for the chosen mode;

  • any other falsy value (None, "", 0) — falls back to the target registered under the mode;

  • truthy value — used directly as the target for this call only.

A render target may be:

  • None — the renderer accumulates a string and returns it.

  • A path string — opened for writing.

  • An object with a .write(text) method — file, stream, socket.

  • A callable — invoked with the rendered output.

The renderer rejects any other type with TypeError.

node_id and lookup

Assign a stable identifier to a node to retrieve it later:

from genro_builders.contrib.html import HtmlBuilder

class Page(HtmlBuilder):
    def main(self, root):
        root.body().h1("Title", node_id="header")

page = Page(); page.create()

source_h1 = page.node_by_id("header")

node_id is unique per builder. Collisions raise during create().

The node_by_id lookup walks the source bag and stops at the first match. It is used internally to resolve symbolic pointers (^#node_id.field); see roadmap/data-architecture.md §10.

Render modes

A builder declares the modes it supports by exposing renderer_<mode> properties (e.g. renderer_html on HtmlBuilder; renderer_xml and renderer_yaml on BuilderBase, so every dialect can serve those views). render dispatches via mode:

print(page.render())              # dialect's default mode
print(page.render(mode="xml"))    # XML view
print(page.render(pretty=True))   # mode-specific kwarg

Mode-specific kwargs are passed verbatim through the walk to the dialect’s rendered_item(node, item, runtime_attrs, **opts); unknown kwargs are tolerated and propagated to children (each dialect picks the ones it understands).