Metadata-Version: 2.4
Name: getattrtrie
Version: 0.1.0a0
Summary: Navigate and build tries using Python attribute syntax.
Author-email: Jifeng Wu <jifengwu2k@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/jifengwu2k/tinytrie
Project-URL: Bug Tracker, https://github.com/jifengwu2k/tinytrie/issues
Keywords: trie,prefix tree,data structure,python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Python: >=2
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: tinytrie
Requires-Dist: typing; python_version < "3.5"
Dynamic: license-file

# `getattrtrie`

A trie based on [`tinytrie`](https://github.com/jifengwu2k/tinytrie) that lets you navigate and build tries using Python attribute syntax. Designed for simulating entire object hierarchies while retaining full trie introspection.

## Usage

```python
from getattrtrie import GetAttrTrieNode
from tinytrie import update, search, collect_sequences

root = GetAttrTrieNode()

# Auto-vivification: intermediate nodes are created on access
# No setup needed for deep paths
root.server.host.value = 'localhost'
root.server.host.is_end = True
root.server.port.value = 8080
root.server.port.is_end = True

assert root.server.host.value == 'localhost'
assert root.server.port.value == 8080

# Intermediates are NOT terminal
assert root.server.is_end is False

# Graft a subtree built separately
db = GetAttrTrieNode()
update(db, ['host'], 'db.local')
update(db, ['port'], 5432)

root.database = db
assert root.database.host.value == 'db.local'

# Works with all tinytrie functions
update(root, ['name'], 'my-app')
assert search(root, ['name']).value == 'my-app'

seqs = {tuple(s): n.value for s, n in collect_sequences(root)}
assert seqs[('server', 'host')] == 'localhost'
assert seqs[('database', 'port')] == 5432
assert seqs[('name',)] == 'my-app'

# Delete a subtrie
del root.server
assert search(root, ['server', 'host']) is None
```

## Use case: dynamic access-path recording

Pass a `GetAttrTrieNode` as a stand-in for any object, then run code against it. Every attribute path the code touches is silently recorded in the trie. After execution, inspect the trie to see the full access footprint:

```python
from getattrtrie import GetAttrTrieNode, collect_leaves

# Simulate passing a config object to code under analysis
config = GetAttrTrieNode()

# --- code under analysis ---
host = config.database.primary.host
port = config.database.primary.port
name = config.app.name
# --- end ---

# Inspect which paths were accessed (leaves = deepest points reached)
accessed = [tuple(s) for s in collect_leaves(config)]
assert ('database', 'primary', 'host') in accessed
assert ('database', 'primary', 'port') in accessed
assert ('app', 'name') in accessed
assert ('database', 'replica') not in accessed  # never touched
```

This enables dead-field detection, config coverage analysis, and understanding which parts of a complex input object a function actually depends on — without bytecode instrumentation or static analysis.

## Why not `unittest.mock.Mock`?

`Mock` auto-vivifies attributes, but assigning a value (`mock.x = 5`) replaces the child `Mock` with a plain value — further chaining (`mock.x.y`) breaks. You must choose between storing data and nesting deeper.

`GetAttrTrieNode` avoids this by separating the node from its value. Attribute access always returns a node, so you can assign values (`.value`) and continue nesting on the same path:

```python
root = GetAttrTrieNode()
root.db.primary.host.value = 'db.local'
root.db.primary.host.port.value = 5432  # still chainable after setting .value above
```

## How it works

`GetAttrTrieNode` maps attribute access to trie operations with string keys:

| Syntax | Effect |
| --- | --- |
| `node.foo` | Access or auto-create child node at key `'foo'` |
| `node.foo = other_node` | Graft `other_node` as the child at key `'foo'` |
| `del node.foo` | Remove the child subtrie at key `'foo'` |

Attribute access always returns a node (auto-vivifying if needed), and assignment always expects a node. To store or read values, use `.value` and `.is_end` on the node directly, or use `tinytrie.update()` / `tinytrie.search()`.

## Use with tinytrie functions

Since `GetAttrTrieNode` is a `TrieNode` subclass, all standard `tinytrie` functions (`update`, `search`, `delete`, `collect_sequences`, etc.) work with it and preserve the subclass type via the `N` TypeVar.

```python
from getattrtrie import GetAttrTrieNode
from tinytrie import update, search, delete

root = GetAttrTrieNode()
node = update(root, ['a', 'b', 'c'], 42)

assert isinstance(node, GetAttrTrieNode)
assert root.a.b.c.value == 42

delete(root, ['a', 'b', 'c'])
assert search(root, ['a', 'b', 'c']) is None
```

## Internal attributes

The internal slot attributes (`parent`, `children`, `is_end`, `value`) are handled normally and do not conflict with trie keys. You cannot use these names as trie keys through attribute syntax — use `update(root, ['value'], ...)` if needed.
