bigtree

πŸ’‘ Tips and TricksΒΆ

List DirectoryΒΆ

To list directories recursively using bigtree, we can use the glob built-in Python package to extract a list of paths.

import glob
from bigtree import list_to_tree, print_tree

# Get all directories recursively
path_list = []
for f in glob.glob("./**/*.py", recursive=True):
    path_list.append(f)

# Construct tree
root = list_to_tree(path_list)

# View tree
print_tree(root, max_depth=3)

Extending NodesΒΆ

Nodes can be extended from BaseNode or Node class to have extended functionalities or add pre-/post-assign checks.

  • For example, Node class extends BaseNode and added the name functionality with pre-assign checks to ensure no duplicate path names.

Population Node (add functionality/method/property)ΒΆ

from bigtree import Node, print_tree


class PopulationNode(Node):

    def __init__(self, name: str, population: int = 0, **kwargs):
        super().__init__(name, **kwargs)
        self._population = population

    @property
    def population(self):
        if self.is_leaf:
            return self._population
        return sum([child.population for child in self.children])

    @property
    def percentage(self):
        if self.is_root:
            return 1
        return round(self.population / self.root.population, 2)


root = PopulationNode("World")
b1 = PopulationNode("Country A", parent=root)
c1 = PopulationNode("State A1", 100, parent=b1)
c2 = PopulationNode("State A2", 50, parent=b1)
b2 = PopulationNode("Country B", 200, parent=root)
b3 = PopulationNode("Country C", 100, parent=root)

print_tree(root, attr_list=["population", "percentage"])
# World [population=450, percentage=1]
# β”œβ”€β”€ Country A [population=150, percentage=0.33]
# β”‚   β”œβ”€β”€ State A1 [population=100, percentage=0.22]
# β”‚   └── State A2 [population=50, percentage=0.11]
# β”œβ”€β”€ Country B [population=200, percentage=0.44]
# └── Country C [population=100, percentage=0.22]

Read-Only Node (add pre-/post-assign checks)ΒΆ

import pytest

from bigtree import Node
from typing import List


class ReadOnlyNode(Node):

    def __init__(self, *args, **kwargs):
        self.__readonly = False
        super().__init__(*args, **kwargs)
        self.__readonly = True

    def _Node__pre_assign_parent(self, new_parent: Node):
        if self.__readonly:
            raise RuntimeError("Nodes cannot be reassigned for ReadOnlyNode")

    def _Node__pre_assign_children(self, new_children: List[Node]):
        if self.__readonly:
            raise RuntimeError("Nodes cannot be reassigned for ReadOnlyNode")


a = ReadOnlyNode("a")
b = ReadOnlyNode("b", parent=a)
c = ReadOnlyNode("c", parent=a)

with pytest.raises(RuntimeError):
    c.parent = b

with pytest.raises(RuntimeError):
    a.children = [b, c]

Merging TreesΒΆ

To merge two separate trees into one, we can use the tree modify module.

In this example, we are merging two trees that have similar node b. Children of node b from both trees are retained as long as merge_children=True is set. If only children of other tree is desired, set overriding=True instead.

from bigtree import Node, print_tree, copy_nodes_from_tree_to_tree

# Construct trees
root1 = Node("a")
b1 = Node("b", parent=root1)
c1 = Node("c", parent=root1)
d1 = Node("d", parent=b1)

b2 = Node("b")
e2 = Node("e", parent=b2)

# Validate tree structure
print_tree(root1)
# a
# β”œβ”€β”€ b
# β”‚   └── d
# └── c

print_tree(b2)
# b
# └── e

# Merge trees
copy_nodes_from_tree_to_tree(
    from_tree=b2,
    to_tree=root1,
    from_paths=["b"],
    to_paths=["a/b"],
    merge_children=True,  # set overriding=True to override existing children
)

# Validate tree structure
print_tree(root1)
# a
# β”œβ”€β”€ b
# β”‚   β”œβ”€β”€ d
# β”‚   └── e
# └── c

Trees with Weighted EdgesΒΆ

Edge weights should be defined in the child node for the parent-child edge since each node can only have one parent.

We can simply add weight attribute to the Node class. However, if we want to visualize the weighted tree, we can create a WeightedNode class to generate the edge attribute dictionary.

from bigtree import Node, tree_to_dot

class WeightedNode(Node):
    def __init__(self, name, weight=0, **kwargs):
        super().__init__(name, **kwargs)
        self.weight = weight

    @property
    def edge_attr(self):
        return {"label": self.weight}

# Construct weighted tree
root = WeightedNode("a")
b = WeightedNode("b", parent=root, weight=1)
c = WeightedNode("c", parent=root, weight=2)
d = WeightedNode("d", parent=b, weight=3)

graph = tree_to_dot(root, node_colour="gold", edge_attr="edge_attr")
graph.write_png("assets/sphinx/weighted_tree.png")

Sample DAG Output