Store Module

The store package contains the core TreeStore implementation.

Module Structure

        graph TB
    subgraph "genro_treestore.store"
        CORE[core.py<br/>TreeStore class]
        NODE[node.py<br/>TreeStoreNode class]
        SUB[subscription.py<br/>Event system]
        SER[serialization.py<br/>TYTX format]

        CORE --> NODE
        CORE --> SUB
        CORE --> SER
    end
    

TreeStore

class genro_treestore.TreeStore[source]

Bases: SubscriptionMixin

A 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
__repr__()[source]

Return string representation showing node labels.

Return type:

str

__len__()[source]

Return the number of direct children in this store.

Return type:

int

__iter__()[source]

Iterate over direct child nodes in insertion order.

Return type:

Iterator[TreeStoreNode]

__contains__(label)[source]

Check if a label exists at root level or as a path.

Parameters:

label (str) – Label or dotted path to check.

Return type:

bool

Returns:

True if the label/path exists, False otherwise.

__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:

Any

Returns:

Callable that creates a child via the builder.

Raises:

AttributeError – If no builder or builder has no such method.

property builder: Any

Access the builder instance.

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:
  • path (str) – Dotted path, optionally with ?attr suffix.

  • default (Any (default: None)) – Default value if path not found.

Return type:

Any

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:

Any

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:
  • path (str) – Dotted path. Use ?attr suffix to set attribute.

  • value (Any) – Value to set.

Return type:

None

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:

TreeStoreNode | None

Returns:

TreeStoreNode at the path, or None if not found.

get_attr(path, attr=None, default=None)[source]

Get attribute(s) from node at path.

Parameters:
  • path (str) – Path to the node.

  • attr (str | None (default: None)) – Attribute name. If None, returns all attributes.

  • default (Any (default: None)) – Default value if attribute not found.

Return type:

Any

Returns:

Attribute value, all attributes dict, or default.

set_attr(path, _attributes=None, **kwargs)[source]

Set attributes on node at path.

Parameters:
  • path (str) – Path to the node.

  • _attributes (dict[str, Any] | None (default: None)) – Dictionary of attributes.

  • **kwargs (Any) – Additional attributes as keyword arguments.

Return type:

None

set_resolver(path, resolver)[source]

Set a resolver on the node at the given path.

Parameters:
  • path (str) – Path to the node.

  • resolver (Any) – The resolver to set.

Return type:

None

get_resolver(path)[source]

Get the resolver from the node at the given path.

Parameters:

path (str) – Path to the node.

Return type:

Any

Returns:

The resolver, or None if no resolver is set.

del_item(path)[source]

Delete and return node at path.

Parameters:

path (str) – Path to the node.

Return type:

TreeStoreNode

Returns:

The removed TreeStoreNode.

Raises:

KeyError – If path not found.

pop(path, default=None)[source]

Remove and return value at path.

Parameters:
  • path (str) – Path to the node.

  • default (Any (default: None)) – Default value if path not found.

Return type:

Any

Returns:

The value of the removed node, or default.

iter_keys()[source]

Yield labels at this level in insertion order.

Return type:

Iterator[str]

iter_values()[source]

Yield values at this level in insertion order.

Return type:

Iterator[Any]

iter_items()[source]

Yield (label, value) pairs in insertion order.

Return type:

Iterator[tuple[str, Any]]

iter_nodes()[source]

Yield nodes at this level in insertion order.

Return type:

Iterator[TreeStoreNode]

keys()[source]

Return list of labels at this level in insertion order.

Return type:

list[str]

values()[source]

Return list of values at this level in insertion order.

Return type:

list[Any]

items()[source]

Return list of (label, value) pairs in insertion order.

Return type:

list[tuple[str, Any]]

nodes()[source]

Return list of nodes at this level in insertion order.

Return type:

list[TreeStoreNode]

get_nodes(path='')[source]

Get nodes at path (or root if empty).

Parameters:

path (str (default: '')) – Optional path to get nodes from.

Return type:

list[TreeStoreNode]

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:

Iterator[Any]

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:

list[Any]

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:

Optional[Iterator[tuple[str, TreeStoreNode]]]

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 root: TreeStore

Get the root TreeStore of this hierarchy.

property depth: int

Get the depth of this store in the hierarchy (root=0).

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).

Return type:

dict[str, Any]

Returns:

Nested dictionary representation of the tree.

clear()[source]

Remove all nodes from this store.

Does not trigger deletion events for individual nodes.

Return type:

None

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:
  • other (dict | list | TreeStore) – Source data (dict, list of tuples, or TreeStore)

  • ignore_none (bool (default: False)) – If True, don’t update values that are None

Return type:

None

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:
  • label (str) – Node label to find.

  • default (Any (default: None)) – Value to return if not found.

Return type:

TreeStoreNode | None

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:

dict[str, list[str]]

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:

str

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 bytes

  • builder (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:

TreeStore

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:
  • data (str) – XML string to parse.

  • builder (Any | None (default: None)) – Optional builder for the resulting store.

Return type:

TreeStore

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_tag is provided, it wraps all content

  • If store has exactly one top-level node and no root_tag, that node becomes the root element directly

  • If 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:

  1. node.attr['_tag'] - Explicit tag (may include namespace prefix)

  2. 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:

str

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

TreeStoreNode

class genro_treestore.TreeStoreNode[source]

Bases: object

A 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.

Parameters:
  • value (Any) – The new value.

  • trigger (bool (default: True)) – If True, notify subscribers of the change.

  • reason (str | None (default: None)) – Optional reason string for the trigger.

Return type:

None

property is_branch: bool

True if this node contains a TreeStore (has children).

property is_leaf: bool

True if this node contains a scalar value.

get_attr(attr=None, default=None)[source]

Get attribute value or all attributes.

Parameters:
  • attr (str | None (default: None)) – Attribute name. If None, returns all attributes.

  • default (Any (default: None)) – Default value if attribute not found.

Return type:

Any

Returns:

Attribute value, default, or dict of all attributes.

set_attr(_attr=None, trigger=True, reason=None, **kwargs)[source]

Set attributes on the node.

Parameters:
  • _attr (dict[str, Any] | None (default: None)) – Dictionary of attributes to set.

  • trigger (bool (default: True)) – If True, notify subscribers of the change.

  • reason (str | None (default: None)) – Optional reason string for the trigger.

  • **kwargs (Any) – Additional attributes as keyword arguments.

Return type:

None

subscribe(subscriber_id, callback)[source]

Subscribe to changes on this specific node.

Parameters:
  • subscriber_id (str) – Unique identifier for this subscription.

  • callback (Callable[..., None]) – Function to call on changes.

Return type:

None

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)
unsubscribe(subscriber_id)[source]

Unsubscribe from changes on this node.

Parameters:

subscriber_id (str) – The subscription identifier to remove.

Return type:

None

property is_valid: bool

True if this node has no validation errors.

Class Relationships

        classDiagram
    class TreeStore {
        +parent: TreeStoreNode | None
        +builder: BuilderBase | None
        +set_item(path, value, **attr)
        +get_item(path, default)
        +del_item(path)
        +get_node(path)
        +subscribe(name, **callbacks)
        +unsubscribe(name, **events)
    }

    class TreeStoreNode {
        +label: str
        +value: Any
        +attr: dict
        +parent: TreeStore
        +resolver: TreeStoreResolver | None
    }

    TreeStore "1" *-- "*" TreeStoreNode : contains
    TreeStoreNode "1" --> "0..1" TreeStore : value can be
    TreeStoreNode --> TreeStore : parent
    TreeStore --> TreeStoreNode : parent
    

Path Resolution

        flowchart LR
    subgraph "Path Types"
        DOT[a.b.c<br/>Dotted path]
        POS[#0, #-1<br/>Positional]
        ATTR[path?attr<br/>Attribute]
    end

    subgraph "Resolution"
        DOT --> SPLIT[Split by dot]
        POS --> INDEX[Array index]
        ATTR --> GETATTR[Get attribute]
    end
    

Subscription System

TreeStore subscription and event notification system.

This module provides the event subscription mechanism for TreeStore, enabling reactive programming patterns. Subscribers can listen to node changes (value/attribute updates), insertions, and deletions.

Events propagate up the hierarchy: a subscriber on the root TreeStore receives events from all descendants, with the path indicating where the change occurred.

Event Types:
  • ‘upd_value’: Node value changed

  • ‘upd_attr’: Node attributes changed

  • ‘ins’: Node inserted

  • ‘del’: Node deleted

Callback Signature:

def callback(node, path, evt, oldvalue=None, index=None, reason=None):
    '''
    Args:
        node: The affected TreeStoreNode
        path: Dot-separated path from the subscribed store to the node
        evt: Event type ('upd_value', 'upd_attr', 'ins', 'del')
        oldvalue: Previous value (for 'upd_value' events)
        index: Position index (for 'ins' and 'del' events)
        reason: Optional string identifying the change source
    '''

Example

>>> def on_change(node, path, evt, **kw):
...     print(f"{evt} at {path}: {node.value}")
>>> store.subscribe('logger', any=on_change)
>>> store.set_item('config.debug', True)
ins at config.debug: True
class genro_treestore.store.subscription.SubscriptionMixin[source]

Bases: object

Mixin class providing subscription functionality for TreeStore.

This mixin adds subscribe/unsubscribe methods and event notification to TreeStore. It requires the host class to have: - _upd_subscribers: dict[str, SubscriberCallback] - _ins_subscribers: dict[str, SubscriberCallback] - _del_subscribers: dict[str, SubscriberCallback] - parent: TreeStoreNode | None

parent: TreeStoreNode | None
subscribe(subscriber_id, update=None, insert=None, delete=None, any=None)[source]

Subscribe to change events on this store.

Events propagate up the hierarchy: a subscriber on the root receives events from all descendants, with the path indicating where the change occurred.

Parameters:
Return type:

None

Callback Signature:

def callback(node, path, evt, oldvalue=None, index=None, reason=None):
    '''
    Args:
        node: The affected TreeStoreNode
        path: Dot-separated path from this store to the node
        evt: Event type ('upd_value', 'upd_attr', 'ins', 'del')
        oldvalue: Previous value (for 'upd_value' events)
        index: Position index (for 'ins' and 'del' events)
        reason: Optional string identifying the change source
    '''

Example

>>> def on_change(node, path, evt, **kw):
...     print(f"{evt} at {path}")
>>> store.subscribe('renderer', any=on_change)
unsubscribe(subscriber_id, update=False, insert=False, delete=False, any=False)[source]

Unsubscribe from change events.

Parameters:
  • subscriber_id (str) – The subscription identifier to remove.

  • update (bool (default: False)) – Unsubscribe from update events.

  • insert (bool (default: False)) – Unsubscribe from insert events.

  • delete (bool (default: False)) – Unsubscribe from delete events.

  • any (bool (default: False)) – Unsubscribe from all events.

Return type:

None

Event Types

Event

Constant

Description

Insert

'ins'

Node created

Delete

'del'

Node removed

Update Value

'upd_value'

Value changed

Update Attribute

'upd_attr'

Attribute changed

Serialization

TreeStore serialization - TYTX format support.

This module provides functions to serialize and deserialize TreeStore hierarchies to/from TYTX format. TYTX preserves Python types (Decimal, date, datetime, time) across serialization, eliminating manual type conversion on both ends.

Wire Format:

The serialized format is a dict with: - ‘rows’: List of tuples, one per node in depth-first order - ‘paths’: (optional, compact mode only) Registry mapping codes to paths

Each row tuple contains: (parent, label, tag, value, attr) - parent: Parent path string (e.g., ‘a.b’) or numeric code if compact - label: Node’s unique key within its parent (e.g., ‘div_0’) - tag: Node type from builder (e.g., ‘div’) or None - value: Node value (None for branches, actual value for leaves) - attr: Dict of node attributes

Two Serialization Modes:
Normal mode (compact=False, default):

Parent references are full path strings. More readable, compresses well with gzip due to repetitive path patterns.

Compact mode (compact=True):

Parent references are numeric codes (0, 1, 2…). Smaller without compression, but gzip actually makes it larger than normal mode.

Recommendation:
  • Use normal mode (default) if you’ll compress the data

  • Use compact mode only for uncompressed transmission

Datetime Handling:

TYTX serializes all datetimes as UTC with millisecond precision. Naive datetimes are treated as UTC on serialization. On deserialization, datetimes are always returned as timezone-aware (UTC).

For roundtrip comparison, use genro_tytx.utils.tytx_equivalent() which handles naive vs aware UTC equivalence.

Requirements:

Requires the genro-tytx package for type-preserving encoding/decoding. Install with: pip install genro-tytx

Example

>>> from genro_treestore import TreeStore
>>> from decimal import Decimal
>>>
>>> store = TreeStore()
>>> store.set_item('invoice.amount', Decimal('1234.56'))
>>> store.set_item('invoice.paid', False)
>>>
>>> # Serialize
>>> data = store.to_tytx()
>>>
>>> # Deserialize - types are preserved
>>> restored = TreeStore.from_tytx(data)
>>> restored['invoice.amount']  # Decimal('1234.56'), not string
genro_treestore.store.serialization.to_tytx(store, transport=None, compact=False)[source]

Serialize a TreeStore to TYTX format.

Converts the entire tree hierarchy into a flat list of row tuples, then encodes it using TYTX which preserves Python types (Decimal, date, datetime, time) in the wire format.

Parameters:
  • store (TreeStore) – The TreeStore to serialize.

  • 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, good for bandwidth-constrained scenarios.

  • compact (bool (default: False)) –

    Serialization mode: - False (default): Parent paths as full strings (‘a.b.c’).

    Recommended when using gzip compression.

    • True: Parent paths as numeric codes (0, 1, 2…). ~30% smaller uncompressed, but larger after gzip.

Return type:

str | bytes

Returns:

str if transport is None or ‘json’, bytes if ‘msgpack’.

Raises:

ImportError – If genro-tytx package is not installed.

Output Format:

Normal mode:

{"rows": [
    ["", "root", "div", null, {"id": "main"}],
    ["root", "child_0", "span", "text", {}],
    ...
]}

Compact mode:

{"rows": [
    [null, "root", "div", null, {"id": "main"}],
    [0, "child_0", "span", "text", {}],
    ...
], "paths": {"0": "root", ...}}

Example

>>> store = TreeStore()
>>> store.set_item('config.timeout', 30)
>>> store.set_item('config.retry', True)
>>>
>>> # Default JSON
>>> json_data = to_tytx(store)
>>>
>>> # Binary MessagePack
>>> msgpack_data = to_tytx(store, transport='msgpack')
>>>
>>> # Compact mode (smaller without gzip)
>>> compact_data = to_tytx(store, compact=True)
genro_treestore.store.serialization.from_tytx(data, transport=None, builder=None)[source]

Deserialize TreeStore from TYTX format.

Reconstructs a complete TreeStore hierarchy from TYTX-encoded data. Automatically detects whether the data uses normal or compact format by checking for the presence of a ‘paths’ registry.

The reconstruction algorithm:
  1. Decode TYTX data to get rows and optional path registry

  2. For each row in depth-first order: - Resolve parent reference (path string or numeric code) - Create TreeStoreNode with label, tag, value, and attributes - If value is None, create child TreeStore (branch node) - Insert node into parent store

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 matching how data was serialized: - None or ‘json’: Parse as JSON string (default) - ‘msgpack’: Parse as MessagePack bytes

  • builder (Any | None (default: None)) – Optional builder instance for the reconstructed store. If provided, enables builder pattern methods on the result.

Returns:

Fully reconstructed tree with:
  • All nodes in correct hierarchy

  • Original values with types preserved (Decimal, date, etc.)

  • All node attributes restored

  • Parent-child relationships established

Return type:

TreeStore

Raises:
  • ImportError – If genro-tytx package is not installed.

  • ValueError – If data format is invalid or corrupted.

Format Detection:

The function automatically handles both serialization modes:

Normal mode (no ‘paths’ key):

{"rows": [["", "root", "div", null, {}], ...]}

Compact mode (has ‘paths’ key):

{"rows": [[null, "root", "div", null, {}], ...],
 "paths": {"0": "root", ...}}

Example

>>> # Basic deserialization
>>> store = from_tytx(json_data)
>>> store['config.timeout']
30
>>> # With MessagePack
>>> store = from_tytx(msgpack_data, transport='msgpack')
>>> # With builder for DOM-like API
>>> store = from_tytx(data, builder=HtmlBuilder())
>>> store.div()  # Builder methods available
>>> # Round-trip preserves types
>>> original = TreeStore()
>>> original.set_item('price', Decimal('99.99'))
>>> data = to_tytx(original)
>>> restored = from_tytx(data)
>>> restored['price']  # Decimal('99.99'), not float

See Also