Typed Builders

BuilderBase provides an abstract base class for creating domain-specific builders with a fluent API.

Why Use Builders?

Instead of using generic set_item() calls, builders let you:

  • Create type-safe methods for specific node types

  • Add validation for child relationships

  • Provide IDE autocompletion and type hints

  • Build domain-specific languages (DSLs)

Basic Builder

from genro_treestore import TreeStore
from genro_treestore.builders import BuilderBase, element

class HtmlBuilder(BuilderBase):
    @element()
    def div(self, target, tag, **attr):
        """Create a div element."""
        return self.child(target, tag, **attr)

    @element()
    def span(self, target, tag, value=None, **attr):
        """Create a span element with optional text."""
        return self.child(target, tag, value=value, **attr)

    @element()
    def ul(self, target, tag, **attr):
        """Create an unordered list."""
        return self.child(target, tag, **attr)

    @element()
    def li(self, target, tag, value=None, **attr):
        """Create a list item."""
        return self.child(target, tag, value=value, **attr)

Using the Builder

store = TreeStore(builder=HtmlBuilder())

# Build structure
main = store.div(id='main')
header = main.div(class_='header')
header.span(value='Welcome!')

nav = main.ul(class_='nav')
nav.li(value='Home')
nav.li(value='About')
nav.li(value='Contact')

# Access values
store['div_0.div_0.span_0']  # 'Welcome!'
store['div_0?id']  # 'main'

Method Chaining

Builder methods return new TreeStore/TreeStoreNode instances, enabling fluent chaining:

store = TreeStore(builder=HtmlBuilder())

(store
    .div(id='container')
    .div(class_='content')
    .span(value='Nested content'))

Custom Constructors

Add specialized methods for common patterns:

class HtmlBuilder(BuilderBase):
    @element()
    def div(self, target, tag, **attr):
        return self.child(target, tag, **attr)

    @element()
    def a(self, target, tag, href='#', value=None, **attr):
        """Create an anchor element."""
        return self.child(target, tag, href=href, value=value, **attr)

    @element()
    def img(self, target, tag, src='', alt='', **attr):
        """Create an image element."""
        return self.child(target, tag, src=src, alt=alt, value='', **attr)

Child Validation

Use the children parameter to specify which child tags are allowed:

class HtmlBuilder(BuilderBase):
    @element(children='li')  # Only 'li' children allowed
    def ul(self, target, tag, **attr):
        return self.child(target, tag, **attr)

    @element()  # Leaf element (no children)
    def li(self, target, tag, value=None, **attr):
        return self.child(target, tag, value=value, **attr)

    @element()
    def span(self, target, tag, value=None, **attr):
        return self.child(target, tag, value=value, **attr)

# Usage
store = TreeStore(builder=HtmlBuilder())
ul = store.ul()
ul.li(value='Item 1')  # OK
ul.li(value='Item 2')  # OK
ul.span(value='Bad!')  # Raises InvalidChildError

Inheritance

Extend builders for specialized domains:

class BaseBuilder(BuilderBase):
    @element()
    def container(self, target, tag, **attr):
        return self.child(target, tag, **attr)

class FormBuilder(BaseBuilder):
    @element()
    def input(self, target, tag, name='', type_='text', **attr):
        return self.child(target, tag, name=name, type=type_, value='', **attr)

    @element()
    def button(self, target, tag, value=None, **attr):
        return self.child(target, tag, value=value, **attr)

    @element(children='input, button')
    def form(self, target, tag, action='', method='POST', **attr):
        return self.child(target, tag, action=action, method=method, **attr)

Type Hints

Add type hints for better IDE support:

from typing import Self

class HtmlBuilder(BuilderBase):
    @element()
    def div(self, target, tag, **attr) -> TreeStore:
        """Create a div element."""
        return self.child(target, tag, **attr)

    @element()
    def span(self, target, tag, value: str | None = None, **attr) -> TreeStoreNode:
        """Create a span element."""
        return self.child(target, tag, value=value, **attr)

Complete Example

from genro_treestore import TreeStore
from genro_treestore.builders import BuilderBase, element

class MenuBuilder(BuilderBase):
    """Builder for navigation menus."""

    @element(children='item, submenu')
    def menu(self, target, tag, title: str = '', **attr):
        """Create a menu container."""
        return self.child(target, tag, title=title, **attr)

    @element(children='item, submenu')
    def submenu(self, target, tag, title: str = '', **attr):
        """Create a submenu."""
        return self.child(target, tag, title=title, **attr)

    @element()  # Leaf - no children
    def item(self, target, tag, label: str = '', href: str = '#', **attr):
        """Create a menu item."""
        return self.child(target, tag, label=label, href=href, value='', **attr)


# Usage
store = TreeStore(builder=MenuBuilder())
nav = store.menu(title='Main Navigation')
nav.item(label='Home', href='/')
nav.item(label='Products', href='/products')

more = nav.submenu(title='More')
more.item(label='About', href='/about')
more.item(label='Contact', href='/contact')

Next Steps

  • Learn about Validation with cardinality constraints