Validation

Builders support two types of validation:

  1. Structure Validation - Which children are allowed under which parents

  2. Attribute Validation - Type checking, enums, required fields

Structure Validation

Defining Valid Children

Use the sub_tags parameter in @element to specify allowed child tags:

Wildcard: Accept Any Children

Use sub_tags='*' to create container elements that accept any child without validation:

Key points:

  • sub_tags='*' disables child validation entirely

  • Useful for generic container elements

  • validate() will not report errors for unknown children

  • Different from sub_tags='' which means no children allowed (leaf element)

Syntax

Meaning

sub_tags='*'

Accept any children (no validation)

sub_tags=''

Leaf element (no children allowed)

sub_tags='a,b'

Only a and b allowed

Cardinality Constraints

Specify minimum and maximum occurrences with bracket syntax:

Syntax

Meaning

tag

Any number (0..N)

tag[1]

Exactly 1

tag[3]

Exactly 3

tag[0:]

0 or more (same as tag)

tag[1:]

At least 1

tag[:3]

0 to 3

tag[2:5]

Between 2 and 5

Note: tag[] (empty brackets) is not valid syntax and raises ValueError. Use tag for 0..N or tag[0:] explicitly.

The validate() Method

Use validate() to validate structure after building. It walks the bag and returns a list of dicts (with keys path, tag, reasons) for every invalid node:

Invalid Children Detection

Restricting Valid Parents (parent_tags)

Use parent_tags to specify where an element can be placed. This is the inverse of sub_tags:

  • sub_tags: “What children can I have?”

  • parent_tags: “What parents can I be inside?”

Key points:

  • parent_tags is a comma-separated list of allowed parent tags

  • Element is added but marked invalid if parent doesn’t match

  • Validation happens at build time, errors collected via validate()

  • Works with both @element and @component

Combining sub_tags and parent_tags

Use both parameters for complete bidirectional validation:

class StrictHtmlBuilder(BagBuilderBase):
    @element(sub_tags='tr')
    def tbody(self): ...

    @element(sub_tags='td', parent_tags='tbody,thead,tfoot')
    def tr(self): ...

    @element(parent_tags='tr')
    def td(self): ...

This ensures:

  • tbody can only contain tr

  • tr can only be inside tbody, thead, or tfoot

  • td can only be inside tr

Attribute Validation

Attribute validation is handled automatically by the builder. Pass attributes as keyword arguments when calling elements:

Combining Structure and Attribute Validation

A complete example with structure constraints:

Best Practices

1. Define Constraints Early

Document your schema constraints clearly:

class ConfigBuilder(BagBuilderBase):
    """Builder for application config.

    Structure:
        config
        ├── database     # Required, exactly one
        ├── cache[:1]    # Optional, at most one
        └── logging[:1]  # Optional, at most one
    """
    @element(sub_tags='database,cache[:1],logging[:1]')
    def config(self): ...

2. Validate After Building

Always validate complete structures before use:

bag = BuilderBag(builder=MyBuilder)
# ... build the structure ...

errors = bag.builder.validate()
if errors:
    for err in errors:
        print(f"ERROR at {err['path']}: {err['reasons']}")
    raise ValueError("Invalid structure")

3. Use sub_tags for Self-Documentation

The sub_tags parameter serves as documentation and enables validation:

@element(sub_tags='input,button,textarea')
def form(self):
    """Form element that accepts inputs, buttons, and textareas."""
    ...