Skip to main content

Hierarchical data structures with builder pattern support for Genro Kyō

Project description

Genro-TreeStore

PyPI version Tests codecov Documentation Python 3.10+ License

A lightweight hierarchical data structure with builder pattern support for the Genro ecosystem (Genro Kyo).

TreeStore provides a powerful tree-based container with O(1) path lookup, reactive subscriptions, lazy value resolution, and schema-driven builders for generating validated hierarchical structures like HTML, XML, or custom DSLs.

Architecture Overview

graph TB
    subgraph "TreeStore Core"
        TS[TreeStore<br/>Container]
        TSN[TreeStoreNode<br/>Node wrapper]
        TS -->|contains| TSN
        TSN -->|value is| TS2[TreeStore<br/>Branch]
        TSN -->|or| VAL[Leaf Value]
        TSN -->|parent ref| TS
        TS2 -->|parent ref| TSN
    end

    subgraph "Features"
        RES[Resolvers<br/>Lazy values]
        SUB[Subscriptions<br/>Reactive events]
        BLD[Builders<br/>Typed APIs]
        VAL2[Validation<br/>Structure rules]
    end

    TS --> RES
    TS --> SUB
    TS --> BLD
    BLD --> VAL2

Installation

pip install genro-treestore

Key Features

Feature Description
O(1) Lookup Direct path-based access via internal index
Builder Pattern Fluent APIs with auto-labeling and validation
Reactive Subscriptions Event propagation for insert/update/delete
Lazy Resolvers Dynamic value computation with TTL caching
Schema Builders Generate builders from RNC or XSD schemas
Type-Safe Serialization TYTX format preserves Decimal, date, datetime

Quick Start

Basic TreeStore Usage

from genro_treestore import TreeStore

store = TreeStore()

# Create nested structure (auto-creates intermediate nodes)
store.set_item('config.database.host', 'localhost')
store.set_item('config.database.port', 5432)
store.set_item('config.debug', True)

# Access values
print(store['config.database.host'])  # 'localhost'
print(store['config.debug'])          # True

# Iterate over children
for key, value in store['config.database'].items():
    print(f"{key}: {value}")
# host: localhost
# port: 5432

Path Syntax

TreeStore supports a rich path syntax for navigation:

graph LR
    subgraph "Path Syntax"
        A["store['a.b.c']"] -->|Dotted path| B[Nested access]
        C["store['#0']"] -->|Positional| D[First child]
        E["store['#-1']"] -->|Negative| F[Last child]
        G["store['path?attr']"] -->|Attribute| H[Get attribute]
    end
Syntax Description Example
'a.b.c' Dotted path store['config.database.host']
'#N' Position index store['users.#0'] (first child)
'#-N' Negative index store['users.#-1'] (last child)
'path?attr' Attribute access store['div?class']

Builder Pattern with HTML

from genro_treestore import TreeStore
from genro_treestore.builders import HtmlBuilder

store = TreeStore(builder=HtmlBuilder())

# Fluent API with auto-labeling
body = store.body()
div = body.div(id='main', class_='container')
div.h1(value='Welcome')
div.p(value='Hello, World!')

ul = div.ul()
ul.li(value='Item 1')
ul.li(value='Item 2')
ul.li(value='Item 3')

# Access by auto-generated labels
print(store['body_0.div_0.h1_0'])  # 'Welcome'
print(store['body_0.div_0?id'])    # 'main'

Dynamic Builders from Schemas

From RNC (RELAX NG Compact)

from genro_treestore import TreeStore
from genro_treestore.builders import RncBuilder

builder = RncBuilder.from_rnc('''
    start = document
    document = element doc { section+ }
    section = element section { title, para* }
    title = element title { text }
    para = element para { text }
''')

store = TreeStore(builder=builder)
doc = store.doc()
sec = doc.section()
sec.title(value='Introduction')
sec.para(value='Welcome to TreeStore.')

From XSD (XML Schema)

from genro_treestore import TreeStore
from genro_treestore.builders import XsdBuilder

# Load XSD schema
xsd_content = open('invoice.xsd').read()
schema = TreeStore.from_xml(xsd_content)
builder = XsdBuilder(schema)

# Build validated structure
store = TreeStore(builder=builder)
invoice = store.Invoice()
invoice.Header().Date(value='2025-01-01')
invoice.Header().Number(value='001')

Resolvers: Lazy Value Computation

sequenceDiagram
    participant C as Client
    participant S as TreeStore
    participant N as Node
    participant R as Resolver

    C->>S: store['config.computed']
    S->>N: get value
    N->>R: load()
    R-->>R: compute value
    R->>N: return + cache
    N-->>S: value
    S-->>C: value

    Note over R: Cached until TTL expires
from genro_treestore import TreeStore, CallbackResolver

store = TreeStore()
store.set_item('config.base_url', 'https://api.example.com')
store.set_item('config.version', 'v2')

# Dynamic computed value
def get_full_url(node):
    parent = node.parent
    base = parent.get_item('base_url')
    version = parent.get_item('version')
    return f"{base}/{version}"

store.set_item('config.full_url')
store.set_resolver('config.full_url', CallbackResolver(get_full_url))

print(store['config.full_url'])  # 'https://api.example.com/v2'

# With caching (60 seconds TTL)
store.set_resolver('config.full_url',
                   CallbackResolver(get_full_url, cache_time=60))

Built-in Resolvers

Resolver Description
CallbackResolver Compute value via callback function
DirectoryResolver Lazy-load directory contents
TxtDocResolver Load text file content

Reactive Subscriptions

graph BT
    subgraph "Event Propagation"
        LEAF[Leaf Node<br/>value changed]
        PARENT[Parent Store]
        ROOT[Root Store<br/>subscriber]

        LEAF -->|"upd_value"| PARENT
        PARENT -->|propagate| ROOT
    end

    ROOT -->|callback| CB[Handler Function]
from genro_treestore import TreeStore

store = TreeStore()

def on_change(node, path, evt, **kw):
    print(f"{evt}: {path} = {node.value}")

# Subscribe to all events
store.subscribe('logger', any=on_change)

store.set_item('users.alice', 'Alice')
# Output: ins: users.alice = Alice

store.set_item('users.alice', 'Alicia')
# Output: upd_value: users.alice = Alicia

store.del_item('users.alice')
# Output: del: users.alice = Alicia

store.unsubscribe('logger', any=True)

Event Types

Event Description
ins Node inserted
del Node deleted
upd_value Node value changed
upd_attr Node attribute changed

Validation with Builders

graph TD
    subgraph "Validation Flow"
        ADD[Add child node]
        CHK{Valid child?}
        CARD{Cardinality OK?}
        OK[Node added]
        ERR[InvalidChildError]
        CERR[TooManyChildrenError]

        ADD --> CHK
        CHK -->|Yes| CARD
        CHK -->|No| ERR
        CARD -->|Yes| OK
        CARD -->|No| CERR
    end
from genro_treestore.builders import BuilderBase, element, valid_children

class DocBuilder(BuilderBase):
    @element
    @valid_children('section[1:]')  # At least one section required
    def document(self, store, parent, **attrs):
        pass

    @element
    @valid_children(
        'title[1]',      # Exactly one title required
        'para[0:]',      # Zero or more paragraphs
    )
    def section(self, store, parent, **attrs):
        pass

    @element
    def title(self, store, parent, value=None, **attrs):
        pass

    @element
    def para(self, store, parent, value=None, **attrs):
        pass

Cardinality Syntax

Syntax Meaning
tag or tag[:] Zero or more (unlimited)
tag[1] Exactly one required
tag[0:1] Zero or one (optional)
tag[1:] One or more required
tag[2:5] Between 2 and 5

Serialization (TYTX)

TreeStore supports type-preserving serialization:

from decimal import Decimal
from datetime import date, datetime
from genro_treestore import TreeStore

store = TreeStore()
store.set_item('invoice.amount', Decimal('1234.56'))
store.set_item('invoice.date', date(2025, 1, 15))
store.set_item('invoice.timestamp', datetime.now())
store.set_item('invoice.paid', False)

# Serialize to JSON (types preserved as metadata)
json_data = store.to_tytx()

# Deserialize - types restored exactly
restored = TreeStore.from_tytx(json_data)
assert isinstance(restored['invoice.amount'], Decimal)
assert isinstance(restored['invoice.date'], date)

# Binary format (MessagePack - more compact)
binary_data = store.to_tytx(transport='msgpack')
restored = TreeStore.from_tytx(binary_data, transport='msgpack')

XML Support

from genro_treestore import TreeStore

# Parse XML
xml = '''<html>
    <head><title>Hello</title></head>
    <body><div id="main">Content</div></body>
</html>'''

store = TreeStore.from_xml(xml)
print(store['html_0.body_0.div_0'])      # 'Content'
print(store['html_0.body_0.div_0?id'])   # 'main'

# Generate XML
output = store.to_xml()

Module Structure

graph TB
    subgraph "genro_treestore"
        INIT[__init__.py<br/>Public API]

        subgraph "store/"
            CORE[core.py<br/>TreeStore]
            NODE[node.py<br/>TreeStoreNode]
            SUB[subscription.py<br/>Events]
            SER[serialization.py<br/>TYTX]
        end

        subgraph "builders/"
            BASE[base.py<br/>BuilderBase]
            HTML[html.py<br/>HtmlBuilder]
            RNC[rnc/<br/>RncBuilder]
            XSD[xsd/<br/>XsdBuilder]
        end

        subgraph "resolvers/"
            RBASE[base.py<br/>TreeStoreResolver]
            DIR[directory.py<br/>DirectoryResolver]
        end

        EXC[exceptions.py]
        VAL[validation.py]
    end

    INIT --> CORE
    INIT --> BASE
    INIT --> RBASE
    CORE --> NODE
    CORE --> SUB
    CORE --> SER
    BASE --> HTML
    BASE --> RNC
    BASE --> XSD

Development

# Clone repository
git clone https://github.com/genropy/genro-treestore.git
cd genro-treestore

# Install dev dependencies
pip install -e ".[test,dev,docs]"

# Run tests
pytest tests/

# Run tests with coverage
pytest tests/ --cov=src/genro_treestore --cov-report=term-missing

# Lint
ruff check src/

# Format
black src/

# Build docs
cd docs && make html

Documentation

Full documentation is available at genro-treestore.readthedocs.io.

License

Apache License 2.0 - See LICENSE for details.

Copyright 2025 Softwell S.r.l. - Genropy Team

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

genro_treestore-0.2.0.tar.gz (134.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

genro_treestore-0.2.0-py3-none-any.whl (83.0 kB view details)

Uploaded Python 3

File details

Details for the file genro_treestore-0.2.0.tar.gz.

File metadata

  • Download URL: genro_treestore-0.2.0.tar.gz
  • Upload date:
  • Size: 134.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for genro_treestore-0.2.0.tar.gz
Algorithm Hash digest
SHA256 7dc3775abf93852b8a68bd8dc628ed6c7509d729dad4056069b5f0c502e1c7d0
MD5 c3ecddb33cc72b33db096c6be1d18a15
BLAKE2b-256 c2176d35f167d390bd217b9b487ec4f970420868bfd5324f4ebeaf4d6e25cdd8

See more details on using hashes here.

Provenance

The following attestation bundles were made for genro_treestore-0.2.0.tar.gz:

Publisher: publish.yml on genropy/genro-treestore

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file genro_treestore-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for genro_treestore-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d6092655209d5baf7c6c0951a4019fa7c81f94121a9faec633843d164f9a9563
MD5 472344dbc4e0fcddd541706eb95cf1c0
BLAKE2b-256 f47fc29b8c11bdf6e1aa0feb75fed463b308025dc0262c4b9c378d3c23b49cee

See more details on using hashes here.

Provenance

The following attestation bundles were made for genro_treestore-0.2.0-py3-none-any.whl:

Publisher: publish.yml on genropy/genro-treestore

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page