Store Module
TreeStore package - Hierarchical data container with O(1) lookup.
This package provides the core TreeStore data structure, a hierarchical container optimized for path-based access with constant-time lookup.
- Architecture:
TreeStore uses a dual-reference pattern where:
TreeStore is a container of TreeStoreNode children
TreeStoreNode wraps a value (leaf) or another TreeStore (branch)
Each node maintains a reference to its parent TreeStore
Each TreeStore maintains a reference to its parent node (if any)
This enables bidirectional navigation and efficient path resolution.
- Modules:
core: TreeStore class with path traversal, CRUD operations, iteration, serialization (TYTX, XML), and builder integration
node: TreeStoreNode class representing individual tree nodes with attributes, values, resolvers, and subscription support
loading: Functions for populating TreeStore from various sources (dict, list, another TreeStore)
subscription: Reactive event system for change notifications with hierarchical propagation
serialization: TYTX format serialization for type-preserving round-trip storage
- Path Syntax:
TreeStore supports a rich path syntax for navigation:
store['config.database.host'] # Dotted path store['users.#0.name'] # Positional index store['users.#-1'] # Negative index (last) store['config?timeout'] # Attribute access
Example
Basic usage with nested data:
from genro_treestore import TreeStore
store = TreeStore()
store.set_item('app.name', 'MyApp')
store.set_item('app.version', '1.0.0')
store.set_item('app.settings.debug', True)
# Path access
print(store['app.name']) # 'MyApp'
print(store['app.settings.debug']) # True
# Iteration
for key, value in store['app'].items():
print(f"{key}: {value}")
With builder pattern:
from genro_treestore import TreeStore
from genro_treestore.builders import HtmlBuilder
store = TreeStore(builder=HtmlBuilder())
body = store.body()
body.div(id='main').p(value='Hello!')
See also
resolvers- Lazy value resolutionbuilders- Typed builder APIs
- class genro_treestore.store.TreeStore[source]
Bases:
SubscriptionMixinA hierarchical data container with O(1) lookup.
TreeStore provides: - set_item(path, value, **attr): Create/update nodes with autocreate - get_item(path) / store[path]: Get values - get_attr(path, attr) / set_attr(path, **attr): Attribute access - digest(what): Extract data with #k, #v, #a syntax
The internal storage uses dict for O(1) lookup performance.
- parent
The TreeStoreNode that contains this store as its value, or None if this is a root store.
Example
>>> store = TreeStore() >>> store.set_item('html.body.div', color='red') >>> store['html.body.div?color'] 'red'
- Parameters:
-
parent:
TreeStoreNode|None
- __getattr__(name)[source]
Delegate attribute access to builder if present.
If a builder is set and has a method matching the name, returns a callable that invokes the builder method with this store as the target.
- Parameters:
name (
str) – Attribute name (e.g., ‘div’, ‘meta’, ‘span’)- Return type:
- Returns:
Callable that creates a child via the builder.
- Raises:
AttributeError – If no builder or builder has no such method.
- __getitem__(path)[source]
Get value or attribute by path.
- Parameters:
path (
str) – Dotted path, with optional ?attr or positional #N syntax.- Return type:
- Returns:
Value at path, or attribute if ?attr used.
- Raises:
KeyError – If path not found.
Example
>>> store['html.body.div'] # value >>> store['html.body.div?color'] # attribute >>> store['#0.#1'] # positional access
- __init__(source=None, parent=None, builder=None, raise_on_error=True)[source]
Initialize a TreeStore.
- Parameters:
source (
dict|list|TreeStore|None(default:None)) –Optional initial data. Can be: - dict: Nested dict converted to nodes. Keys with ‘_’ prefix
are treated as attributes (e.g., {‘_color’: ‘red’, ‘child’: …})
TreeStore: Copy from another TreeStore
list: List of tuples (label, value) or (label, value, attr)
parent (
TreeStoreNode|None(default:None)) – The TreeStoreNode that contains this store as its value.builder (
Any|None(default:None)) – Optional builder object that provides domain-specific methods. When set, attribute access delegates to the builder, enabling fluent API like store.div(), store.meta(), etc.raise_on_error (
bool(default:True)) – If True (default), raises ValueError on hard errors (invalid attributes, invalid child tags, too many children). Soft errors (missing required children) are always collected in node._invalid_reasons without raising. If False, all errors are collected without raising.
- Return type:
None
Example
>>> TreeStore({'a': 1, 'b': {'c': 2}}) >>> TreeStore([('x', 1), ('y', 2, {'color': 'red'})]) >>> TreeStore(other_store) # copy >>> TreeStore(builder=HtmlBodyBuilder()) # with builder >>> TreeStore(builder=HtmlBuilder(), raise_on_error=False) # permissive mode
- __setitem__(path, value)[source]
Set value or attribute by path.
- Parameters:
- Return type:
Example
>>> store['html.body.div'] = 'text' # set value >>> store['html.body.div?color'] = 'red' # set attribute
- as_dict()[source]
Convert to plain dict (recursive).
Branch nodes become nested dicts with their attributes and children. Leaf nodes become their value directly (or dict with _value if has attrs).
- clear()[source]
Remove all nodes from this store.
Does not trigger deletion events for individual nodes.
- Return type:
- del_item(path)[source]
Delete and return node at path.
- digest(what='#k,#v')[source]
Extract data from nodes using digest syntax.
- Parameters:
what (
str(default:'#k,#v')) – Comma-separated specifiers: - #k: labels - #v: values - #a: all attributes (dict) - #a.attrname: specific attribute- Return type:
- Returns:
List of values, or list of tuples if multiple specifiers.
Example
>>> store.digest('#k') # ['label1', 'label2'] >>> store.digest('#v') # [value1, value2] >>> store.digest('#k,#v') # [('label1', val1), ('label2', val2)] >>> store.digest('#a.color') # ['red', 'blue']
- flattened(path_registry=None)[source]
Yield flat tuples representing the tree in depth-first order.
Converts the hierarchical tree structure into a flat sequence of tuples, suitable for serialization. Each tuple contains all information needed to reconstruct a node: its parent reference, label, tag, value, and attributes.
This method is the foundation for TYTX serialization (see to_tytx()).
- Output Modes:
- Normal mode (path_registry=None):
Parent references are full path strings. More readable and compresses very well with gzip due to repetitive patterns.
- Compact mode (path_registry=dict):
Parent references are sequential numeric codes (0, 1, 2…). The provided dict is populated with {code: path} mappings. Produces smaller output without compression (~32% smaller), but gzip actually compresses normal mode better.
- Parameters:
path_registry (
dict[int,str] |None(default:None)) –Optional dict to enable compact mode. - If None: parent is emitted as path string (normal mode) - If dict: parent is emitted as numeric code, and the dict
is populated with {code: full_path} mappings for branches
- Yields:
tuple –
- (parent, label, tag, value, attr) where:
- parent: Reference to parent node
Normal mode: path string (’’ for root-level nodes)
Compact mode: int code (None for root-level nodes)
label: Node’s unique key within its parent (e.g., ‘floor_0’)
tag: Node type from builder (e.g., ‘floor’) or None
- value: Node value
None for branch nodes (nodes with children)
Actual value for leaf nodes (scalar values)
attr: Dict of node attributes (always a copy)
- Return type:
Iterator[tuple[str|int|None,str,str|None,Any,dict[str,Any]]]
Note
Iteration order is depth-first (parent before children)
Branch nodes have value=None because their “value” is the child store
The path_registry dict is modified in place during iteration
Uses walk() internally for tree traversal
Example
Normal mode (path strings):
>>> store = TreeStore(builder=BuildingBuilder()) >>> b = store.building(name='Casa Mia') >>> b.floor(number=1).room(name='Kitchen') >>> >>> for row in store.flattened(): ... parent, label, tag, value, attr = row ... print(f"{parent!r:20} {label:15} {tag}") '' building_0 building 'building_0' floor_0 floor 'building_0.floor_0' room_0 room
Compact mode (numeric codes):
>>> paths = {} >>> for row in store.flattened(path_registry=paths): ... parent, label, tag, value, attr = row ... print(f"{parent!r:5} {label:15} {tag}") None building_0 building 0 floor_0 floor 1 room_0 room >>> >>> paths {0: 'building_0', 1: 'building_0.floor_0'}
See also
to_tytx(): Serializes using this method
walk(): The underlying tree traversal method
- classmethod from_tytx(data, transport=None, builder=None)[source]
Deserialize TreeStore from TYTX format with type preservation.
Reconstructs a TreeStore from TYTX-serialized data. Automatically detects normal vs compact format. Types (Decimal, date, datetime, time) are preserved exactly as they were before serialization.
- Parameters:
data (
str|bytes) – Serialized data from to_tytx(). - str: If transport is None or ‘json’ - bytes: If transport is ‘msgpack’transport (
Optional[Literal['json','msgpack']] (default:None)) – Input format (must match serialization): - None or ‘json’: Parse as JSON string (default) - ‘msgpack’: Parse as MessagePack bytesbuilder (
Any|None(default:None)) – Optional builder for the reconstructed store. Enables builder methods (e.g., store.div()) on the result.
- Returns:
- Fully reconstructed tree with:
Complete node hierarchy
All values with original types preserved
All node attributes restored
- Return type:
- Raises:
ImportError – If genro-tytx package is not installed.
Example
>>> # Basic round-trip >>> original = TreeStore() >>> original.set_item('config.price', Decimal('99.99')) >>> data = original.to_tytx() >>> >>> restored = TreeStore.from_tytx(data) >>> restored['config.price'] # Decimal('99.99'), not float >>> >>> # With MessagePack >>> data = original.to_tytx(transport='msgpack') >>> restored = TreeStore.from_tytx(data, transport='msgpack') >>> >>> # With builder >>> restored = TreeStore.from_tytx(data, builder=HtmlBuilder())
See also
to_tytx(): Serialize TreeStore to TYTX format
- classmethod from_xml(data, builder=None)[source]
Load TreeStore from XML string.
Each XML element becomes a node with:
label: tag name with counter suffix (element_0, element_1, etc.)
value: text content (leaf) or child TreeStore (branch)
attr: XML attributes, plus ‘_tag’ with namespace prefix if present
- Namespace Handling:
XML namespaces are processed as follows:
Namespace declarations (xmlns:prefix=”uri”) are extracted
Element tags with namespaces store the prefixed form in ‘_tag’
The label uses only the local name (without namespace)
Namespaced attributes are filtered out (not preserved)
Example with namespaces:
xml = '''<root xmlns:ns="http://example.com"> <ns:item>value</ns:item> </root>''' store = TreeStore.from_xml(xml) # Label: 'item_0', attr['_tag']: 'ns:item'
- Parameters:
- Return type:
- Returns:
TreeStore with XML structure. The root element becomes the first (and typically only) top-level node.
Example
Simple XML:
>>> xml = '<html><head><title>Hello</title></head></html>' >>> store = TreeStore.from_xml(xml) >>> store['html_0.head_0.title_0'] 'Hello'
Accessing tag and attributes:
>>> xml = '<div class="main"><span id="x">text</span></div>' >>> store = TreeStore.from_xml(xml) >>> node = store.get_node('div_0.span_0') >>> node.attr['id'] 'x' >>> node.value 'text'
See also
to_xml()- Convert TreeStore back to XML
- get(label, default=None)[source]
Get node by label at this level, with default.
Unlike get_node(), this only looks at direct children (no path traversal).
- Parameters:
- Return type:
- Returns:
TreeStoreNode if found, default otherwise.
- get_item(path, default=None)[source]
Get the value at the given path.
- Parameters:
- Return type:
- Returns:
The value at the path, attribute value, or default.
Example
>>> store.get_item('html.body.div') # returns value >>> store.get_item('html.body.div?color') # returns attribute
- get_node(path)[source]
Get node at the given path.
- Parameters:
path (
str) – Dotted path to the node.- Return type:
- Returns:
TreeStoreNode at the path, or None if not found.
- get_nodes(path='')[source]
Get nodes at path (or root if empty).
- Parameters:
path (
str(default:'')) – Optional path to get nodes from.- Return type:
- Returns:
List of TreeStoreNode at the specified level in insertion order.
- property is_valid: bool
True if all nodes in this store are valid.
Recursively checks all nodes in the tree for validation errors.
- Returns:
True if no node has validation errors, False otherwise.
Example
>>> store = TreeStore(builder=HtmlBuilder()) >>> thead = store.thead() >>> store.is_valid False # thead requires at least 1 tr >>> thead.tr() >>> store.is_valid True
- iter_digest(what='#k,#v')[source]
Yield data from nodes using digest syntax.
- Parameters:
what (
str(default:'#k,#v')) – Comma-separated specifiers: - #k: labels - #v: values - #a: all attributes (dict) - #a.attrname: specific attribute- Yields:
Values, or tuples if multiple specifiers.
- Return type:
Example
>>> for label in store.iter_digest('#k'): ... print(label)
- property parent_node: TreeStoreNode | None
Get the parent node (alias for self.parent).
- set_item(path, value=None, _attributes=None, _position=None, **kwargs)[source]
Set an item at the given path, creating intermediate nodes as needed.
- Parameters:
path (
str) – Dotted path to the item (e.g., ‘html.body.div’).value (
Any(default:None)) – The value to store. If None, creates a branch node._attributes (
dict[str,Any] |None(default:None)) – Dictionary of attributes._position (
str|None(default:None)) – Position specifier: - None or ‘>’: append to end (default) - ‘<’: insert at beginning - ‘<label’: insert before label - ‘>label’: insert after label - ‘<#N’: insert before position N - ‘>#N’: insert after position N - ‘#N’: insert at exact position N**kwargs (
Any) – Additional attributes as keyword arguments.
- Returns:
If branch created: returns the new branch’s TreeStore
If leaf created: returns the parent TreeStore
- Return type:
TreeStore for fluent chaining
Example
>>> store.set_item('html').set_item('body').set_item('div', color='red') >>> store.set_item('ul').set_item('li', 'Item 1').set_item('li', 'Item 2') >>> store.set_item('first', 'value', _position='<') # insert at beginning
- to_tytx(transport=None, compact=False)[source]
Serialize TreeStore to TYTX format with type preservation.
TYTX (Typed Transport) preserves Python types (Decimal, date, datetime, time) in the serialized format, eliminating manual type conversion when deserializing.
The tree is serialized as a flat list of row tuples in depth-first order. Each row contains: (parent, label, tag, value, attr).
- Parameters:
transport (
Optional[Literal['json','msgpack']] (default:None)) –Output format: - None or ‘json’: JSON string (default). Human-readable,
compresses very well with gzip.
’msgpack’: Binary MessagePack bytes. ~30% smaller than JSON before compression.
compact (
bool(default:False)) –Serialization mode: - False (default): Parent as path strings (‘a.b.c’).
Recommended with gzip compression.
True: Parent as numeric codes (0, 1, 2…). ~32% smaller without compression, but gzip prefers normal.
- Returns:
If transport is None or ‘json’ bytes: If transport is ‘msgpack’
- Return type:
- Raises:
ImportError – If genro-tytx package is not installed.
Example
>>> from decimal import Decimal >>> from datetime import date >>> >>> store = TreeStore() >>> store.set_item('invoice.amount', Decimal('1234.56')) >>> store.set_item('invoice.date', date(2025, 1, 15)) >>> >>> # Default JSON format >>> json_data = store.to_tytx() >>> >>> # Binary MessagePack (smaller) >>> msgpack_data = store.to_tytx(transport='msgpack') >>> >>> # Compact mode (for uncompressed transmission) >>> compact_data = store.to_tytx(compact=True)
See also
from_tytx(): Deserialize back to TreeStore
flattened(): The underlying flat representation
- to_xml(root_tag=None)[source]
Serialize TreeStore to XML string.
Converts the TreeStore hierarchy into an XML document. Each node becomes an XML element with:
tag: from node’s ‘_tag’ attribute, or label without suffix
attributes: node’s attr dict (excluding internal _* keys)
content: text value (if leaf) or child elements (if branch)
- Root Element Handling:
The root element is determined as follows:
If
root_tagis provided, it wraps all contentIf store has exactly one top-level node and no
root_tag, that node becomes the root element directlyIf store has multiple top-level nodes and no
root_tag, they are wrapped in a<root>element
- Tag Resolution:
For each node, the XML tag is resolved in order:
node.attr['_tag']- Explicit tag (may include namespace prefix)node.label.rsplit('_', 1)[0]- Label without counter suffix
This allows round-trip preservation when loading from XML.
- Parameters:
root_tag (
str|None(default:None)) – Optional root element tag. If None and store has exactly one root node, uses that node’s tag. If None and store has multiple nodes, defaults to ‘root’.- Return type:
- Returns:
XML string representation without XML declaration.
Example
Single root node:
>>> store = TreeStore() >>> store.set_item('html.head.title', 'Hello') >>> print(store.to_xml()) <html><head><title>Hello</title></head></html>
Multiple top-level nodes:
>>> store = TreeStore() >>> store.set_item('item', 'first') >>> store.set_item('item', 'second') >>> print(store.to_xml()) <root><item>first</item><item>second</item></root>
With explicit root tag:
>>> store = TreeStore() >>> store.set_item('item', 'value') >>> print(store.to_xml(root_tag='items')) <items><item>value</item></items>
See also
from_xml()- Load TreeStore from XML
- update(other, ignore_none=False)[source]
Update this TreeStore with data from another source.
For each item in other: - If label exists: updates attributes, and if both are branches,
recursively updates children; otherwise replaces value
If label doesn’t exist: adds the new node
- Parameters:
- Return type:
Example
>>> store = TreeStore({'config': {'a': 1, 'b': 2}}) >>> store.update({'config': {'b': 3, 'c': 4}}) >>> store['config.a'] # 1 (preserved) >>> store['config.b'] # 3 (updated) >>> store['config.c'] # 4 (added)
- validation_errors()[source]
Return all validation errors in the tree.
- Return type:
- Returns:
Dictionary mapping node paths to their error lists. Only includes nodes with errors.
Example
>>> store = TreeStore(builder=HtmlBuilder()) >>> thead = store.thead() >>> store.validation_errors() {'thead_0': ["requires at least 1 'tr', has 0"]}
- walk(callback=None, _prefix='')[source]
Walk the tree, optionally calling a callback on each node.
- Parameters:
callback (
Optional[Callable[[TreeStoreNode],Any]] (default:None)) – Optional function to call on each node. If provided, walk returns None._prefix (
str(default:'')) – Internal use for path building.
- Yields:
Tuples of (path, node) if no callback provided.
- Return type:
Example
>>> for path, node in store.walk(): ... print(path, node.value)
>>> store.walk(lambda n: print(n.label))
- class genro_treestore.store.TreeStoreNode[source]
Bases:
objectA node in a TreeStore hierarchy.
Each node has: - label: The node’s unique name/key within its parent - attr: Dictionary of attributes - value: Either a scalar value or a TreeStore (for children) - parent: Reference to the containing TreeStore - tag: Optional type/tag for the node (used by builders)
Example
>>> node = TreeStoreNode('user', {'id': 1}, 'Alice') >>> node.label 'user' >>> node.value 'Alice'
- Parameters:
- label
- attr
- parent
- tag
- __init__(label, attr=None, value=None, parent=None, tag=None, resolver=None)[source]
Initialize a TreeStoreNode.
- Parameters:
label (
str) – The node’s unique name/key.attr (
dict[str,Any] |None(default:None)) – Optional dictionary of attributes.value (
Any(default:None)) – The node’s value (scalar or TreeStore for children).parent (
TreeStore|None(default:None)) – The TreeStore containing this node.tag (
str|None(default:None)) – Optional type/tag for the node (used by builders).resolver (
TreeStoreResolver|None(default:None)) – Optional resolver for lazy/dynamic value computation.
- Return type:
None
- property resolver: TreeStoreResolver | None
Get the node’s resolver.
- set_attr(_attr=None, trigger=True, reason=None, **kwargs)[source]
Set attributes on the node.
- Parameters:
- Return type:
- set_value(value, trigger=True, reason=None)[source]
Set the node’s value, optionally triggering events.
- subscribe(subscriber_id, callback)[source]
Subscribe to changes on this specific node.
- Parameters:
- Return type:
- Callback signature:
callback(node, info, evt) - node: This TreeStoreNode - info: oldvalue (for ‘upd_value’) or list of changed attrs (for ‘upd_attr’) - evt: Event type (‘upd_value’ or ‘upd_attr’)
Example
>>> def on_change(node, info, evt): ... print(f"{evt}: {info}") >>> node.subscribe('watcher', on_change)
Classes
TreeStore
- class genro_treestore.TreeStore[source]
Bases:
SubscriptionMixinA hierarchical data container with O(1) lookup.
TreeStore provides: - set_item(path, value, **attr): Create/update nodes with autocreate - get_item(path) / store[path]: Get values - get_attr(path, attr) / set_attr(path, **attr): Attribute access - digest(what): Extract data with #k, #v, #a syntax
The internal storage uses dict for O(1) lookup performance.
- parent
The TreeStoreNode that contains this store as its value, or None if this is a root store.
Example
>>> store = TreeStore() >>> store.set_item('html.body.div', color='red') >>> store['html.body.div?color'] 'red'
- Parameters:
- __init__(source=None, parent=None, builder=None, raise_on_error=True)[source]
Initialize a TreeStore.
- Parameters:
source (
dict|list|TreeStore|None(default:None)) –Optional initial data. Can be: - dict: Nested dict converted to nodes. Keys with ‘_’ prefix
are treated as attributes (e.g., {‘_color’: ‘red’, ‘child’: …})
TreeStore: Copy from another TreeStore
list: List of tuples (label, value) or (label, value, attr)
parent (
TreeStoreNode|None(default:None)) – The TreeStoreNode that contains this store as its value.builder (
Any|None(default:None)) – Optional builder object that provides domain-specific methods. When set, attribute access delegates to the builder, enabling fluent API like store.div(), store.meta(), etc.raise_on_error (
bool(default:True)) – If True (default), raises ValueError on hard errors (invalid attributes, invalid child tags, too many children). Soft errors (missing required children) are always collected in node._invalid_reasons without raising. If False, all errors are collected without raising.
- Return type:
None
Example
>>> TreeStore({'a': 1, 'b': {'c': 2}}) >>> TreeStore([('x', 1), ('y', 2, {'color': 'red'})]) >>> TreeStore(other_store) # copy >>> TreeStore(builder=HtmlBodyBuilder()) # with builder >>> TreeStore(builder=HtmlBuilder(), raise_on_error=False) # permissive mode
-
parent:
TreeStoreNode|None
- __getattr__(name)[source]
Delegate attribute access to builder if present.
If a builder is set and has a method matching the name, returns a callable that invokes the builder method with this store as the target.
- Parameters:
name (
str) – Attribute name (e.g., ‘div’, ‘meta’, ‘span’)- Return type:
- Returns:
Callable that creates a child via the builder.
- Raises:
AttributeError – If no builder or builder has no such method.
- set_item(path, value=None, _attributes=None, _position=None, **kwargs)[source]
Set an item at the given path, creating intermediate nodes as needed.
- Parameters:
path (
str) – Dotted path to the item (e.g., ‘html.body.div’).value (
Any(default:None)) – The value to store. If None, creates a branch node._attributes (
dict[str,Any] |None(default:None)) – Dictionary of attributes._position (
str|None(default:None)) – Position specifier: - None or ‘>’: append to end (default) - ‘<’: insert at beginning - ‘<label’: insert before label - ‘>label’: insert after label - ‘<#N’: insert before position N - ‘>#N’: insert after position N - ‘#N’: insert at exact position N**kwargs (
Any) – Additional attributes as keyword arguments.
- Returns:
If branch created: returns the new branch’s TreeStore
If leaf created: returns the parent TreeStore
- Return type:
TreeStore for fluent chaining
Example
>>> store.set_item('html').set_item('body').set_item('div', color='red') >>> store.set_item('ul').set_item('li', 'Item 1').set_item('li', 'Item 2') >>> store.set_item('first', 'value', _position='<') # insert at beginning
- get_item(path, default=None)[source]
Get the value at the given path.
- Parameters:
- Return type:
- Returns:
The value at the path, attribute value, or default.
Example
>>> store.get_item('html.body.div') # returns value >>> store.get_item('html.body.div?color') # returns attribute
- __getitem__(path)[source]
Get value or attribute by path.
- Parameters:
path (
str) – Dotted path, with optional ?attr or positional #N syntax.- Return type:
- Returns:
Value at path, or attribute if ?attr used.
- Raises:
KeyError – If path not found.
Example
>>> store['html.body.div'] # value >>> store['html.body.div?color'] # attribute >>> store['#0.#1'] # positional access
- __setitem__(path, value)[source]
Set value or attribute by path.
- Parameters:
- Return type:
Example
>>> store['html.body.div'] = 'text' # set value >>> store['html.body.div?color'] = 'red' # set attribute
- get_node(path)[source]
Get node at the given path.
- Parameters:
path (
str) – Dotted path to the node.- Return type:
- Returns:
TreeStoreNode at the path, or None if not found.
- del_item(path)[source]
Delete and return node at path.
- get_nodes(path='')[source]
Get nodes at path (or root if empty).
- Parameters:
path (
str(default:'')) – Optional path to get nodes from.- Return type:
- Returns:
List of TreeStoreNode at the specified level in insertion order.
- iter_digest(what='#k,#v')[source]
Yield data from nodes using digest syntax.
- Parameters:
what (
str(default:'#k,#v')) – Comma-separated specifiers: - #k: labels - #v: values - #a: all attributes (dict) - #a.attrname: specific attribute- Yields:
Values, or tuples if multiple specifiers.
- Return type:
Example
>>> for label in store.iter_digest('#k'): ... print(label)
- digest(what='#k,#v')[source]
Extract data from nodes using digest syntax.
- Parameters:
what (
str(default:'#k,#v')) – Comma-separated specifiers: - #k: labels - #v: values - #a: all attributes (dict) - #a.attrname: specific attribute- Return type:
- Returns:
List of values, or list of tuples if multiple specifiers.
Example
>>> store.digest('#k') # ['label1', 'label2'] >>> store.digest('#v') # [value1, value2] >>> store.digest('#k,#v') # [('label1', val1), ('label2', val2)] >>> store.digest('#a.color') # ['red', 'blue']
- walk(callback=None, _prefix='')[source]
Walk the tree, optionally calling a callback on each node.
- Parameters:
callback (
Optional[Callable[[TreeStoreNode],Any]] (default:None)) – Optional function to call on each node. If provided, walk returns None._prefix (
str(default:'')) – Internal use for path building.
- Yields:
Tuples of (path, node) if no callback provided.
- Return type:
Example
>>> for path, node in store.walk(): ... print(path, node.value)
>>> store.walk(lambda n: print(n.label))
- flattened(path_registry=None)[source]
Yield flat tuples representing the tree in depth-first order.
Converts the hierarchical tree structure into a flat sequence of tuples, suitable for serialization. Each tuple contains all information needed to reconstruct a node: its parent reference, label, tag, value, and attributes.
This method is the foundation for TYTX serialization (see to_tytx()).
- Output Modes:
- Normal mode (path_registry=None):
Parent references are full path strings. More readable and compresses very well with gzip due to repetitive patterns.
- Compact mode (path_registry=dict):
Parent references are sequential numeric codes (0, 1, 2…). The provided dict is populated with {code: path} mappings. Produces smaller output without compression (~32% smaller), but gzip actually compresses normal mode better.
- Parameters:
path_registry (
dict[int,str] |None(default:None)) –Optional dict to enable compact mode. - If None: parent is emitted as path string (normal mode) - If dict: parent is emitted as numeric code, and the dict
is populated with {code: full_path} mappings for branches
- Yields:
tuple –
- (parent, label, tag, value, attr) where:
- parent: Reference to parent node
Normal mode: path string (’’ for root-level nodes)
Compact mode: int code (None for root-level nodes)
label: Node’s unique key within its parent (e.g., ‘floor_0’)
tag: Node type from builder (e.g., ‘floor’) or None
- value: Node value
None for branch nodes (nodes with children)
Actual value for leaf nodes (scalar values)
attr: Dict of node attributes (always a copy)
- Return type:
Iterator[tuple[str|int|None,str,str|None,Any,dict[str,Any]]]
Note
Iteration order is depth-first (parent before children)
Branch nodes have value=None because their “value” is the child store
The path_registry dict is modified in place during iteration
Uses walk() internally for tree traversal
Example
Normal mode (path strings):
>>> store = TreeStore(builder=BuildingBuilder()) >>> b = store.building(name='Casa Mia') >>> b.floor(number=1).room(name='Kitchen') >>> >>> for row in store.flattened(): ... parent, label, tag, value, attr = row ... print(f"{parent!r:20} {label:15} {tag}") '' building_0 building 'building_0' floor_0 floor 'building_0.floor_0' room_0 room
Compact mode (numeric codes):
>>> paths = {} >>> for row in store.flattened(path_registry=paths): ... parent, label, tag, value, attr = row ... print(f"{parent!r:5} {label:15} {tag}") None building_0 building 0 floor_0 floor 1 room_0 room >>> >>> paths {0: 'building_0', 1: 'building_0.floor_0'}
See also
to_tytx(): Serializes using this method
walk(): The underlying tree traversal method
- property parent_node: TreeStoreNode | None
Get the parent node (alias for self.parent).
- as_dict()[source]
Convert to plain dict (recursive).
Branch nodes become nested dicts with their attributes and children. Leaf nodes become their value directly (or dict with _value if has attrs).
- clear()[source]
Remove all nodes from this store.
Does not trigger deletion events for individual nodes.
- Return type:
- update(other, ignore_none=False)[source]
Update this TreeStore with data from another source.
For each item in other: - If label exists: updates attributes, and if both are branches,
recursively updates children; otherwise replaces value
If label doesn’t exist: adds the new node
- Parameters:
- Return type:
Example
>>> store = TreeStore({'config': {'a': 1, 'b': 2}}) >>> store.update({'config': {'b': 3, 'c': 4}}) >>> store['config.a'] # 1 (preserved) >>> store['config.b'] # 3 (updated) >>> store['config.c'] # 4 (added)
- get(label, default=None)[source]
Get node by label at this level, with default.
Unlike get_node(), this only looks at direct children (no path traversal).
- Parameters:
- Return type:
- Returns:
TreeStoreNode if found, default otherwise.
- property is_valid: bool
True if all nodes in this store are valid.
Recursively checks all nodes in the tree for validation errors.
- Returns:
True if no node has validation errors, False otherwise.
Example
>>> store = TreeStore(builder=HtmlBuilder()) >>> thead = store.thead() >>> store.is_valid False # thead requires at least 1 tr >>> thead.tr() >>> store.is_valid True
- validation_errors()[source]
Return all validation errors in the tree.
- Return type:
- Returns:
Dictionary mapping node paths to their error lists. Only includes nodes with errors.
Example
>>> store = TreeStore(builder=HtmlBuilder()) >>> thead = store.thead() >>> store.validation_errors() {'thead_0': ["requires at least 1 'tr', has 0"]}
- to_tytx(transport=None, compact=False)[source]
Serialize TreeStore to TYTX format with type preservation.
TYTX (Typed Transport) preserves Python types (Decimal, date, datetime, time) in the serialized format, eliminating manual type conversion when deserializing.
The tree is serialized as a flat list of row tuples in depth-first order. Each row contains: (parent, label, tag, value, attr).
- Parameters:
transport (
Optional[Literal['json','msgpack']] (default:None)) –Output format: - None or ‘json’: JSON string (default). Human-readable,
compresses very well with gzip.
’msgpack’: Binary MessagePack bytes. ~30% smaller than JSON before compression.
compact (
bool(default:False)) –Serialization mode: - False (default): Parent as path strings (‘a.b.c’).
Recommended with gzip compression.
True: Parent as numeric codes (0, 1, 2…). ~32% smaller without compression, but gzip prefers normal.
- Returns:
If transport is None or ‘json’ bytes: If transport is ‘msgpack’
- Return type:
- Raises:
ImportError – If genro-tytx package is not installed.
Example
>>> from decimal import Decimal >>> from datetime import date >>> >>> store = TreeStore() >>> store.set_item('invoice.amount', Decimal('1234.56')) >>> store.set_item('invoice.date', date(2025, 1, 15)) >>> >>> # Default JSON format >>> json_data = store.to_tytx() >>> >>> # Binary MessagePack (smaller) >>> msgpack_data = store.to_tytx(transport='msgpack') >>> >>> # Compact mode (for uncompressed transmission) >>> compact_data = store.to_tytx(compact=True)
See also
from_tytx(): Deserialize back to TreeStore
flattened(): The underlying flat representation
- classmethod from_tytx(data, transport=None, builder=None)[source]
Deserialize TreeStore from TYTX format with type preservation.
Reconstructs a TreeStore from TYTX-serialized data. Automatically detects normal vs compact format. Types (Decimal, date, datetime, time) are preserved exactly as they were before serialization.
- Parameters:
data (
str|bytes) – Serialized data from to_tytx(). - str: If transport is None or ‘json’ - bytes: If transport is ‘msgpack’transport (
Optional[Literal['json','msgpack']] (default:None)) – Input format (must match serialization): - None or ‘json’: Parse as JSON string (default) - ‘msgpack’: Parse as MessagePack bytesbuilder (
Any|None(default:None)) – Optional builder for the reconstructed store. Enables builder methods (e.g., store.div()) on the result.
- Returns:
- Fully reconstructed tree with:
Complete node hierarchy
All values with original types preserved
All node attributes restored
- Return type:
- Raises:
ImportError – If genro-tytx package is not installed.
Example
>>> # Basic round-trip >>> original = TreeStore() >>> original.set_item('config.price', Decimal('99.99')) >>> data = original.to_tytx() >>> >>> restored = TreeStore.from_tytx(data) >>> restored['config.price'] # Decimal('99.99'), not float >>> >>> # With MessagePack >>> data = original.to_tytx(transport='msgpack') >>> restored = TreeStore.from_tytx(data, transport='msgpack') >>> >>> # With builder >>> restored = TreeStore.from_tytx(data, builder=HtmlBuilder())
See also
to_tytx(): Serialize TreeStore to TYTX format
- classmethod from_xml(data, builder=None)[source]
Load TreeStore from XML string.
Each XML element becomes a node with:
label: tag name with counter suffix (element_0, element_1, etc.)
value: text content (leaf) or child TreeStore (branch)
attr: XML attributes, plus ‘_tag’ with namespace prefix if present
- Namespace Handling:
XML namespaces are processed as follows:
Namespace declarations (xmlns:prefix=”uri”) are extracted
Element tags with namespaces store the prefixed form in ‘_tag’
The label uses only the local name (without namespace)
Namespaced attributes are filtered out (not preserved)
Example with namespaces:
xml = '''<root xmlns:ns="http://example.com"> <ns:item>value</ns:item> </root>''' store = TreeStore.from_xml(xml) # Label: 'item_0', attr['_tag']: 'ns:item'
- Parameters:
- Return type:
- Returns:
TreeStore with XML structure. The root element becomes the first (and typically only) top-level node.
Example
Simple XML:
>>> xml = '<html><head><title>Hello</title></head></html>' >>> store = TreeStore.from_xml(xml) >>> store['html_0.head_0.title_0'] 'Hello'
Accessing tag and attributes:
>>> xml = '<div class="main"><span id="x">text</span></div>' >>> store = TreeStore.from_xml(xml) >>> node = store.get_node('div_0.span_0') >>> node.attr['id'] 'x' >>> node.value 'text'
See also
to_xml()- Convert TreeStore back to XML
- to_xml(root_tag=None)[source]
Serialize TreeStore to XML string.
Converts the TreeStore hierarchy into an XML document. Each node becomes an XML element with:
tag: from node’s ‘_tag’ attribute, or label without suffix
attributes: node’s attr dict (excluding internal _* keys)
content: text value (if leaf) or child elements (if branch)
- Root Element Handling:
The root element is determined as follows:
If
root_tagis provided, it wraps all contentIf store has exactly one top-level node and no
root_tag, that node becomes the root element directlyIf store has multiple top-level nodes and no
root_tag, they are wrapped in a<root>element
- Tag Resolution:
For each node, the XML tag is resolved in order:
node.attr['_tag']- Explicit tag (may include namespace prefix)node.label.rsplit('_', 1)[0]- Label without counter suffix
This allows round-trip preservation when loading from XML.
- Parameters:
root_tag (
str|None(default:None)) – Optional root element tag. If None and store has exactly one root node, uses that node’s tag. If None and store has multiple nodes, defaults to ‘root’.- Return type:
- Returns:
XML string representation without XML declaration.
Example
Single root node:
>>> store = TreeStore() >>> store.set_item('html.head.title', 'Hello') >>> print(store.to_xml()) <html><head><title>Hello</title></head></html>
Multiple top-level nodes:
>>> store = TreeStore() >>> store.set_item('item', 'first') >>> store.set_item('item', 'second') >>> print(store.to_xml()) <root><item>first</item><item>second</item></root>
With explicit root tag:
>>> store = TreeStore() >>> store.set_item('item', 'value') >>> print(store.to_xml(root_tag='items')) <items><item>value</item></items>
See also
from_xml()- Load TreeStore from XML
TreeStoreNode
- class genro_treestore.TreeStoreNode[source]
Bases:
objectA node in a TreeStore hierarchy.
Each node has: - label: The node’s unique name/key within its parent - attr: Dictionary of attributes - value: Either a scalar value or a TreeStore (for children) - parent: Reference to the containing TreeStore - tag: Optional type/tag for the node (used by builders)
Example
>>> node = TreeStoreNode('user', {'id': 1}, 'Alice') >>> node.label 'user' >>> node.value 'Alice'
- Parameters:
- __init__(label, attr=None, value=None, parent=None, tag=None, resolver=None)[source]
Initialize a TreeStoreNode.
- Parameters:
label (
str) – The node’s unique name/key.attr (
dict[str,Any] |None(default:None)) – Optional dictionary of attributes.value (
Any(default:None)) – The node’s value (scalar or TreeStore for children).parent (
TreeStore|None(default:None)) – The TreeStore containing this node.tag (
str|None(default:None)) – Optional type/tag for the node (used by builders).resolver (
TreeStoreResolver|None(default:None)) – Optional resolver for lazy/dynamic value computation.
- Return type:
None
- label
- attr
- parent
- tag
- property value: Any
Get the node’s value.
If a resolver is set, triggers resolution instead of returning the stored value.
- property resolver: TreeStoreResolver | None
Get the node’s resolver.
- set_value(value, trigger=True, reason=None)[source]
Set the node’s value, optionally triggering events.
- set_attr(_attr=None, trigger=True, reason=None, **kwargs)[source]
Set attributes on the node.
- Parameters:
- Return type:
- subscribe(subscriber_id, callback)[source]
Subscribe to changes on this specific node.
- Parameters:
- Return type:
- Callback signature:
callback(node, info, evt) - node: This TreeStoreNode - info: oldvalue (for ‘upd_value’) or list of changed attrs (for ‘upd_attr’) - evt: Event type (‘upd_value’ or ‘upd_attr’)
Example
>>> def on_change(node, info, evt): ... print(f"{evt}: {info}") >>> node.subscribe('watcher', on_change)
See Also
Builders Module - BuilderBase and typed builders
Resolvers Module - Lazy value resolution
Exceptions
InvalidChildError
- exception genro_treestore.InvalidChildError[source]
Bases:
TreeStoreErrorRaised when a child tag is not allowed under the current parent.
This exception is raised when attempting to add a child node with a tag that violates the builder’s structural rules. Common causes:
Adding a child to a void/leaf element (e.g., children under <br>)
Tag not in the parent’s allowed children list
Tag explicitly excluded from the parent
Example
>>> store = TreeStore(builder=HtmlBuilder(), raise_on_error=True) >>> ul = store.body().ul() >>> ul.div() # div is not a valid child of ul InvalidChildError: 'div' is not a valid child of 'ul'
See also
element()
MissingChildError
- exception genro_treestore.MissingChildError[source]
Bases:
TreeStoreErrorRaised when a required child tag is missing from a parent.
This exception is raised during validation when a parent node is missing one or more mandatory children as defined by the builder’s cardinality rules (minimum count > 0).
The error is typically raised when:
Calling
store.builder.check()explicitlyUsing ValidationSubscriber with hard error mode
Finalizing a structure that requires certain children
Example
>>> # Assuming a builder requires 'head' and 'body' in 'html' >>> store = TreeStore(builder=HtmlBuilder()) >>> html = store.html() >>> # Missing head and body children >>> store.builder.check(html) MissingChildError: 'html' requires child 'head' (min: 1, found: 0)
See also
check()ValidationSubscriber
TooManyChildrenError
- exception genro_treestore.TooManyChildrenError[source]
Bases:
TreeStoreErrorRaised when a child tag exceeds its maximum allowed count.
This exception is raised when adding a child would exceed the maximum cardinality defined by the builder’s rules. For example, an HTML document can only have one <head> element.
Cardinality is specified using slice notation in builder definitions:
tagortag[:]- unlimited (0 to infinity)tag[1]- exactly one requiredtag[0:1]- zero or one (optional, max 1)tag[1:3]- between 1 and 3 inclusive
Example
>>> # Assuming 'head' has max cardinality of 1 >>> store = TreeStore(builder=HtmlBuilder(), raise_on_error=True) >>> html = store.html() >>> html.head() # First head - OK >>> html.head() # Second head - Error TooManyChildrenError: 'html' already has maximum 'head' children (1)
See also
element()_parse_children_spec()