Builders

Domain-specific fluent APIs: HTML, Markdown, XML Schema, custom DSLs.

When Do You Need Builders?

  • Structure has rules: HTML, XML, configuration schemas

  • Validation at build time: Catch errors early, not at runtime

  • Domain vocabulary: Methods like div(), p() instead of set_item()

  • Output generation: Compile to HTML, Markdown, XML

Quick Start

from genro_builders import BuilderBag
from genro_builders.contrib.html import HtmlBuilder

html = BuilderBag(builder=HtmlBuilder)
div = html.div(id='main')
div.p(value='Hello World')

print(html.to_xml(pretty=True))
# <div id="main">
#   <p>Hello World</p>
# </div>

The Key Insight

Without a builder:

bag = Bag()
bag.set_item('div', Bag())
bag['div'].set_item('p', 'Hello')  # No validation, no structure

With a builder:

html = BuilderBag(builder=HtmlBuilder)
div = html.div()       # Returns Bag for children
div.p(value='Hello')   # Validated, returns BagNode
div.invalid()          # Error! 'invalid' not in HTML schema

Built-in Builders

Builder

Purpose

HtmlBuilder

HTML5 with 112 tags

MarkdownBuilder

Markdown generation

XsdBuilder

From XML Schema files

Return Types

  • Container elements (can have children) → return Bag

  • Leaf elements (no children) → return BagNode

div = html.div()     # Bag - can add children
meta = html.meta()   # BagNode - leaf element

Labels and Tags

Every node has both:

  • Label: Unique identifier (div_0, div_1) for path access

  • Tag: Semantic type (div, p) for validation

html.div()
html.div()
list(html.keys())  # ['div_0', 'div_1']

# Use node_label for explicit labels
html.div(node_label='header')
html['header']  # Access by label

Custom Builders

Three decorators for defining schema:

Decorator

Purpose

Body

@element

Simple elements

Required empty (...)

@abstract

Inheritance groups

Optional (...)

@component

Composite structures

Required

@element Example

from genro_builders.builders import BagBuilderBase, element

class MenuBuilder(BagBuilderBase):
    @element(sub_tags='item,separator')
    def menu(self): ...

    @element()
    def item(self, label='', action=''): ...

    @element()
    def separator(self): ...

menu = BuilderBag(builder=MenuBuilder)
m = menu.menu()
m.item(label='Open', action='open_file')
m.separator()
m.item(label='Exit', action='quit')

@component Example

Use @component for reusable composite structures:

from genro_builders.builders import BagBuilderBase, element, component

class PageBuilder(BagBuilderBase):
    @element()
    def input(self): ...

    @element()
    def button(self): ...

    @component(sub_tags='')  # Closed component
    def login_form(self, component: Bag, **kwargs):
        component.input(name='username')
        component.input(name='password')
        component.button('Login')
        return component

page = BuilderBag(builder=PageBuilder)
page.login_form()  # Creates complete form structure

Output Generation

HTML/XML

html.to_xml(pretty=True)

Markdown

doc = BuilderBag(builder=MarkdownBuilder)
doc.h1("Title")
doc.p("Content")
doc.builder.build()
doc.builder.render()  # Returns markdown string

Documentation