Child Validation

The @element decorator with the children parameter enforces rules about which children a node can contain.

Basic Usage

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

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

    @element()
    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)

Valid Children

store = TreeStore(builder=HtmlBuilder())
ul = store.ul()
ul.li(value='Item 1')  # OK
ul.li(value='Item 2')  # OK

Invalid Children

store = TreeStore(builder=HtmlBuilder())
ul = store.ul()
ul.span(value='Invalid!')  # Raises InvalidChildError

Cardinality Constraints

Control how many children of each type are allowed:

Syntax

Meaning

Example

'tag'

Zero or more (default)

'li' - any number of li

'tag[1]'

Exactly one

'title[1]' - must have one title

'tag[1:]'

One or more

'item[1:]' - at least one item

'tag[0:1]'

Zero or one

'footer[0:1]' - optional, max one

'tag[0:3]'

Zero to three

'option[0:3]' - max three options

'tag[2:5]'

Two to five

'row[2:5]' - between 2 and 5 rows

Examples

Required Child

@element(children='title[1], item')
def section(self, target, tag, **attr):
    """Section must have exactly one title, any number of items."""
    return self.child(target, tag, **attr)

Optional Single Child

@element(children='header[0:1], content[1], footer[0:1]')
def page(self, target, tag, **attr):
    """Page has optional header/footer, required content."""
    return self.child(target, tag, **attr)

Minimum Required

@element(children='option[1:]')
def select(self, target, tag, **attr):
    """Select must have at least one option."""
    return self.child(target, tag, **attr)

Maximum Limit

@element(children='column[1:4]')
def row(self, target, tag, **attr):
    """Row must have 1-4 columns."""
    return self.child(target, tag, **attr)

Exceptions

InvalidChildError

Raised when adding a child with a non-allowed tag:

from genro_treestore import InvalidChildError

try:
    ul.span(value='text')  # ul only allows 'li'
except InvalidChildError as e:
    print(e)  # "Invalid child 'span' for parent 'ul'"

MissingChildError

Raised when validation detects missing required children:

from genro_treestore import MissingChildError

@element(children='title[1], content[1]')
def article(self, target, tag, **attr):
    return self.child(target, tag, **attr)

# If article node is finalized without required children
# MissingChildError: "Missing required child 'title' for 'article'"

TooManyChildrenError

Raised when exceeding maximum allowed children:

from genro_treestore import TooManyChildrenError

@element(children='item[0:3]')
def menu(self, target, tag, **attr):
    return self.child(target, tag, **attr)

store = TreeStore(builder=MyBuilder())
menu = store.menu()
menu.item(value='A')
menu.item(value='B')
menu.item(value='C')
menu.item(value='D')  # Raises TooManyChildrenError

Complete Example

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

class DocumentBuilder(BuilderBase):
    @element(children='head[1], body[1]')
    def html(self, target, tag, **attr):
        """HTML document requires exactly one head and one body."""
        return self.child(target, tag, **attr)

    @element(children='title[1], meta, link')
    def head(self, target, tag, **attr):
        """Head requires one title, allows meta and link tags."""
        return self.child(target, tag, **attr)

    @element(children='header[0:1], main[1], footer[0:1]')
    def body(self, target, tag, **attr):
        """Body has optional header/footer, required main."""
        return self.child(target, tag, **attr)

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

    @element()
    def meta(self, target, tag, **attr):
        return self.child(target, tag, **attr)

    @element()
    def link(self, target, tag, **attr):
        return self.child(target, tag, **attr)

    @element(children='article, section, div')
    def main(self, target, tag, **attr):
        return self.child(target, tag, **attr)

    @element()
    def header(self, target, tag, **attr):
        return self.child(target, tag, **attr)

    @element()
    def footer(self, target, tag, **attr):
        return self.child(target, tag, **attr)

    @element()
    def article(self, target, tag, **attr):
        return self.child(target, tag, **attr)

    @element()
    def section(self, target, tag, **attr):
        return self.child(target, tag, **attr)

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


# Usage
store = TreeStore(builder=DocumentBuilder())
html = store.html()

head = html.head()
head.title(value='My Page')
head.meta(charset='utf-8')

body = html.body()
body.header()
main = body.main()
main.article()
body.footer()

Validation Timing

Validation happens at different times:

  1. Invalid child - Immediately when child() is called

  2. Too many children - Immediately when exceeding max count

  3. Missing children - When explicitly validated (implementation-dependent)

Tips

  • Use validation for document structures, configuration schemas, UI hierarchies

  • Start without validation, add it incrementally

  • Use clear error messages to guide users

  • Consider making some constraints warnings instead of errors during development