Hierarchical knowledge artifact. Holds the full set of nodes in a flat ``nodes`` dict for O(1) lookup, plus the ``root_node_id`` pointer. Whether a backend loads all nodes eagerly or lazily is its concern; the schema always represents a complete tree.
| 45 | |
| 46 | |
| 47 | class StructureTree(BaseModel): |
| 48 | """Identity-free tree payload shared by document-specific bindings. |
| 49 | |
| 50 | Holds the full set of nodes in a flat ``nodes`` dict for O(1) lookup, |
| 51 | plus the ``root_node_id`` pointer. Subclasses add canonical or derived |
| 52 | artifact identity without nesting a second tree model. |
| 53 | """ |
| 54 | |
| 55 | model_config = ConfigDict(extra="forbid", frozen=True) |
| 56 | |
| 57 | root_node_id: UUID |
| 58 | nodes: dict[UUID, TreeNode] |
| 59 | |
| 60 | def root(self) -> TreeNode: |
| 61 | return self.nodes[self.root_node_id] |
| 62 | |
| 63 | def children_of(self, node_id: UUID) -> list[TreeNode]: |
| 64 | node = self.nodes[node_id] |
| 65 | return [self.nodes[c] for c in node.children_ids] |
| 66 | |
| 67 | def walk_dfs(self) -> Iterator[TreeNode]: |
| 68 | """Depth-first traversal starting at the root.""" |
| 69 | stack: list[UUID] = [self.root_node_id] |
| 70 | while stack: |
| 71 | node_id = stack.pop() |
| 72 | node = self.nodes[node_id] |
| 73 | yield node |
| 74 | # Reverse so children are visited in declared order. |
| 75 | stack.extend(reversed(node.children_ids)) |
| 76 | |
| 77 | def find_path(self, node_id: UUID) -> list[TreeNode]: |
| 78 | """Root-to-node path. Empty if `node_id` is not in the tree.""" |
| 79 | if node_id not in self.nodes: |
| 80 | return [] |
| 81 | path: list[TreeNode] = [] |
| 82 | seen: set[UUID] = set() |
| 83 | cursor: UUID | None = node_id |
| 84 | while cursor is not None: |
| 85 | if cursor in seen or cursor not in self.nodes: |
| 86 | return [] |
| 87 | seen.add(cursor) |
| 88 | node = self.nodes[cursor] |
| 89 | path.append(node) |
| 90 | cursor = node.parent_id |
| 91 | path.reverse() |
| 92 | return path if path[0].node_id == self.root_node_id else [] |
| 93 | |
| 94 | def validate( # pyright: ignore[reportIncompatibleMethodOverride] |
nothing calls this directly
no outgoing calls
no test coverage detected