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 |
|---|---|---|
|
Zero or more (default) |
|
|
Exactly one |
|
|
One or more |
|
|
Zero or one |
|
|
Zero to three |
|
|
Two to five |
|
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:
Invalid child - Immediately when
child()is calledToo many children - Immediately when exceeding max count
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