diff --git a/CHANGELOG.md b/CHANGELOG.md index a7b0bd45..345244dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added: -- Misc: Documentation to rely on jupyter notebook. +- (WIP) Misc: Documentation to rely on jupyter notebook. +### Changed: +- Tree Export: Exporting to dot allow node_attr and edge_attr to modify node before retrieving node name. +- Misc: All code reference to node_name (immutable) instead of name (mutable). ## [0.22.0] - 2024-11-03 ### Added: diff --git a/bigtree/dag/export.py b/bigtree/dag/export.py index e05a067c..fe1af14e 100644 --- a/bigtree/dag/export.py +++ b/bigtree/dag/export.py @@ -259,7 +259,7 @@ def dag_to_dot( _node_style = node_style.copy() _edge_style = edge_style.copy() - child_name = child_node.name + child_name = child_node.node_name if node_attr and child_node.get_attr(node_attr): _node_style.update(child_node.get_attr(node_attr)) if edge_attr and child_node.get_attr(edge_attr): @@ -267,7 +267,7 @@ def dag_to_dot( pydot_child = pydot.Node(name=child_name, label=child_name, **_node_style) _graph.add_node(pydot_child) - parent_name = parent_node.name + parent_name = parent_node.node_name parent_node_style = node_style.copy() if node_attr and parent_node.get_attr(node_attr): parent_node_style.update(parent_node.get_attr(node_attr)) diff --git a/bigtree/node/basenode.py b/bigtree/node/basenode.py index 9568bc2b..8836a39c 100644 --- a/bigtree/node/basenode.py +++ b/bigtree/node/basenode.py @@ -739,7 +739,7 @@ def copy(self: T) -> T: return copy.deepcopy(self) def sort(self: T, **kwargs: Any) -> None: - """Sort children, possible keyword arguments include ``key=lambda node: node.name``, ``reverse=True`` + """Sort children, possible keyword arguments include ``key=lambda node: node.node_name``, ``reverse=True`` Accepts kwargs for sort() function. Examples: @@ -751,7 +751,7 @@ def sort(self: T, **kwargs: Any) -> None: a ├── c └── b - >>> a.sort(key=lambda node: node.name) + >>> a.sort(key=lambda node: node.node_name) >>> print_tree(a) a ├── b diff --git a/bigtree/node/node.py b/bigtree/node/node.py index 34a2d1ae..785fac00 100644 --- a/bigtree/node/node.py +++ b/bigtree/node/node.py @@ -122,7 +122,7 @@ def path_name(self) -> str: """ ancestors = [self] + list(self.ancestors) sep = ancestors[-1].sep - return sep + sep.join([str(node.name) for node in reversed(ancestors)]) + return sep + sep.join([str(node.node_name) for node in reversed(ancestors)]) def __pre_assign_children(self: T, new_children: List[T]) -> None: """Custom method to check before attaching children diff --git a/bigtree/tree/export.py b/bigtree/tree/export.py index 5e597edf..b1fd24b3 100644 --- a/bigtree/tree/export.py +++ b/bigtree/tree/export.py @@ -1303,12 +1303,6 @@ def _recursive_append(parent_name: Optional[str], child_node: T) -> None: _node_style = node_style.copy() _edge_style = edge_style.copy() - child_label = child_node.node_name - if child_node.path_name not in name_dict[child_label]: # pragma: no cover - name_dict[child_label].append(child_node.path_name) - child_name = child_label + str( - name_dict[child_label].index(child_node.path_name) - ) if node_attr: if isinstance(node_attr, str) and child_node.get_attr(node_attr): _node_style.update(child_node.get_attr(node_attr)) @@ -1319,6 +1313,13 @@ def _recursive_append(parent_name: Optional[str], child_node: T) -> None: _edge_style.update(child_node.get_attr(edge_attr)) elif isinstance(edge_attr, Callable): # type: ignore _edge_style.update(edge_attr(child_node)) # type: ignore + + child_label = child_node.node_name + if child_node.path_name not in name_dict[child_label]: # pragma: no cover + name_dict[child_label].append(child_node.path_name) + child_name = child_label + str( + name_dict[child_label].index(child_node.path_name) + ) node = pydot.Node(name=child_name, label=child_label, **_node_style) _graph.add_node(node) if parent_name is not None: @@ -1683,7 +1684,7 @@ def _get_attr( # Get custom style for root (node_shape_attr, node_attr) _parent_node_name = node_shapes[ _get_attr(_node.parent, node_shape_attr, node_shape) - ].format(label=_node.parent.name) + ].format(label=_node.parent.node_name) if _get_attr(_node.parent, node_attr, "") and len(styles) < 2: _from_style = _get_attr(_node.parent, node_attr, "") @@ -1698,7 +1699,7 @@ def _get_attr( _from_style = f":::{_from_style_class}" _node_name = node_shapes[ _get_attr(_node, node_shape_attr, node_shape) - ].format(label=_node.name) + ].format(label=_node.node_name) # Get custom style (edge_arrow_attr, edge_label) _arrow = edge_arrows[_get_attr(_node, edge_arrow_attr, edge_arrow)] diff --git a/docs/gettingstarted/demo/binarytree.md b/docs/gettingstarted/demo/binarytree.md index 4ba09c50..72bbd262 100644 --- a/docs/gettingstarted/demo/binarytree.md +++ b/docs/gettingstarted/demo/binarytree.md @@ -81,24 +81,24 @@ root.show() # ├── 6 # └── 7 -[node.name for node in inorder_iter(root)] +[node.node_name for node in inorder_iter(root)] # ['8', '4', '2', '5', '1', '6', '3', '7'] -[node.name for node in preorder_iter(root)] +[node.node_name for node in preorder_iter(root)] # ['1', '2', '4', '8', '5', '3', '6', '7'] -[node.name for node in postorder_iter(root)] +[node.node_name for node in postorder_iter(root)] # ['8', '4', '5', '2', '6', '7', '3', '1'] -[node.name for node in levelorder_iter(root)] +[node.node_name for node in levelorder_iter(root)] # ['1', '2', '3', '4', '5', '6', '7', '8'] -[[node.name for node in node_group] for node_group in levelordergroup_iter(root)] +[[node.node_name for node in node_group] for node_group in levelordergroup_iter(root)] # [['1'], ['2', '3'], ['4', '5', '6', '7'], ['8']] -[node.name for node in zigzag_iter(root)] +[node.node_name for node in zigzag_iter(root)] # ['1', '3', '2', '4', '5', '6', '7', '8'] -[[node.name for node in node_group] for node_group in zigzaggroup_iter(root)] +[[node.node_name for node in node_group] for node_group in zigzaggroup_iter(root)] # [['1'], ['3', '2'], ['4', '5', '6', '7'], ['8']] ``` diff --git a/docs/gettingstarted/demo/tree.md b/docs/gettingstarted/demo/tree.md index da1c919e..1ed8755e 100644 --- a/docs/gettingstarted/demo/tree.md +++ b/docs/gettingstarted/demo/tree.md @@ -585,19 +585,19 @@ a [node.node_name for node in preorder_iter(root)] # ['a', 'b', 'd', 'e', 'c'] -[node.name for node in postorder_iter(root)] +[node.node_name for node in postorder_iter(root)] # ['d', 'e', 'b', 'c', 'a'] -[node.name for node in levelorder_iter(root)] +[node.node_name for node in levelorder_iter(root)] # ['a', 'b', 'c', 'd', 'e'] -[[node.name for node in node_group] for node_group in levelordergroup_iter(root)] +[[node.node_name for node in node_group] for node_group in levelordergroup_iter(root)] # [['a'], ['b', 'c'], ['d', 'e']] -[node.name for node in zigzag_iter(root)] +[node.node_name for node in zigzag_iter(root)] # ['a', 'c', 'b', 'd', 'e'] -[[node.name for node in node_group] for node_group in zigzaggroup_iter(root)] +[[node.node_name for node in node_group] for node_group in zigzaggroup_iter(root)] # [['a'], ['c', 'b'], ['d', 'e']] ``` @@ -827,7 +827,7 @@ it does not require traversing the whole tree to find the node(s). find_children(root, lambda node: node.age >= 60) # (Node(/a/b, age=65), Node(/a/c, age=60)) - find_child(root, lambda node: node.name == "c") + find_child(root, lambda node: node.node_name == "c") # Node(/a/c, age=60) find_child_by_name(root, "c") diff --git a/docs_sphinx/source/demo/binarytree.md b/docs_sphinx/source/demo/binarytree.md index 028877fb..506f45ce 100644 --- a/docs_sphinx/source/demo/binarytree.md +++ b/docs_sphinx/source/demo/binarytree.md @@ -80,24 +80,24 @@ root.show() # ├── 6 # └── 7 -[node.name for node in inorder_iter(root)] +[node.node_name for node in inorder_iter(root)] # ['8', '4', '2', '5', '1', '6', '3', '7'] -[node.name for node in preorder_iter(root)] +[node.node_name for node in preorder_iter(root)] # ['1', '2', '4', '8', '5', '3', '6', '7'] -[node.name for node in postorder_iter(root)] +[node.node_name for node in postorder_iter(root)] # ['8', '4', '5', '2', '6', '7', '3', '1'] -[node.name for node in levelorder_iter(root)] +[node.node_name for node in levelorder_iter(root)] # ['1', '2', '3', '4', '5', '6', '7', '8'] -[[node.name for node in node_group] for node_group in levelordergroup_iter(root)] +[[node.node_name for node in node_group] for node_group in levelordergroup_iter(root)] # [['1'], ['2', '3'], ['4', '5', '6', '7'], ['8']] -[node.name for node in zigzag_iter(root)] +[node.node_name for node in zigzag_iter(root)] # ['1', '3', '2', '4', '5', '6', '7', '8'] -[[node.name for node in node_group] for node_group in zigzaggroup_iter(root)] +[[node.node_name for node in node_group] for node_group in zigzaggroup_iter(root)] # [['1'], ['3', '2'], ['4', '5', '6', '7'], ['8']] ``` diff --git a/docs_sphinx/source/demo/tree.md b/docs_sphinx/source/demo/tree.md index aac7a54e..f2cb071c 100644 --- a/docs_sphinx/source/demo/tree.md +++ b/docs_sphinx/source/demo/tree.md @@ -452,22 +452,22 @@ root.show() # │ └── e # └── c -[node.name for node in preorder_iter(root)] +[node.node_name for node in preorder_iter(root)] # ['a', 'b', 'd', 'e', 'c'] -[node.name for node in postorder_iter(root)] +[node.node_name for node in postorder_iter(root)] # ['d', 'e', 'b', 'c', 'a'] -[node.name for node in levelorder_iter(root)] +[node.node_name for node in levelorder_iter(root)] # ['a', 'b', 'c', 'd', 'e'] -[[node.name for node in node_group] for node_group in levelordergroup_iter(root)] +[[node.node_name for node in node_group] for node_group in levelordergroup_iter(root)] # [['a'], ['b', 'c'], ['d', 'e']] -[node.name for node in zigzag_iter(root)] +[node.node_name for node in zigzag_iter(root)] # ['a', 'c', 'b', 'd', 'e'] -[[node.name for node in node_group] for node_group in zigzaggroup_iter(root)] +[[node.node_name for node in node_group] for node_group in zigzaggroup_iter(root)] # [['a'], ['c', 'b'], ['d', 'e']] ``` @@ -673,7 +673,7 @@ root.show(attr_list=["age"]) find_children(root, lambda node: node.age >= 60) # (Node(/a/b, age=65), Node(/a/c, age=60)) -find_child(root, lambda node: node.name == "c") +find_child(root, lambda node: node.node_name == "c") # Node(/a/c, age=60) find_child_by_name(root, "c") diff --git a/mkdocs.yml b/mkdocs.yml index 2a3a966c..a901cc5e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -23,7 +23,7 @@ nav: - gettingstarted/demo/tree.md - gettingstarted/demo/binarytree.md - gettingstarted/demo/dag.md - - gettingstarted/demo/workflow.ipynb + - gettingstarted/demo/workflow.md - Resources: - gettingstarted/resources/articles.md - gettingstarted/resources/glossary.md diff --git a/pyproject.toml b/pyproject.toml index a08e43de..a48504ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -105,7 +105,7 @@ dependencies = [ "mkdocs-jupyter", "mkdocs-material[imaging]==9.5.17", "mdx_truly_sane_lists==1.3", - "mkdocstrings[python]==0.24.0", + "mkdocstrings[python]>=0.25.0", "requests", "termynal==0.11.1", ] diff --git a/tests/dag/test_construct.py b/tests/dag/test_construct.py index 949b0730..2749367d 100644 --- a/tests/dag/test_construct.py +++ b/tests/dag/test_construct.py @@ -203,16 +203,16 @@ def test_dataframe_to_dag_zero_attribute(): dag = construct.dataframe_to_dag(data) assert_dag_structure_root(dag) for parent, _ in dag_iterator(dag): - if parent.name == "a": + if parent.node_name == "a": assert hasattr( parent, "value" ), "Check a attribute, expected value attribute" assert parent.value == 0, "Check a value, expected 0" - elif parent.name == "b": + elif parent.node_name == "b": assert not hasattr( parent, "value" ), "Check b attribute, expected no value attribute" - elif parent.name == "c": + elif parent.node_name == "c": assert parent.value == -1, "Check c value, expected -1" def test_dataframe_to_dag_empty_row_error(self): diff --git a/tests/node/test_dagnode.py b/tests/node/test_dagnode.py index eee47c7e..27380875 100644 --- a/tests/node/test_dagnode.py +++ b/tests/node/test_dagnode.py @@ -674,7 +674,7 @@ def test_go_to(self): ) else: actual_path = [ - [_node.name for _node in _path] + [_node.node_name for _node in _path] for _path in node_pair[0].go_to(node_pair[1]) ] assert ( @@ -693,9 +693,9 @@ def test_go_to_same_node(self): for _node in self.nodes: actual_path = [ - [_node2.name for _node2 in _node1] for _node1 in _node.go_to(_node) + [_node2.node_name for _node2 in _node1] for _node1 in _node.go_to(_node) ] - expected_path = [[_node.name]] + expected_path = [[_node.node_name]] assert ( actual_path == expected_path ), f"Wrong path for {_node}, expected {expected_path}, received {actual_path}" @@ -1003,7 +1003,7 @@ def assert_dag_structure_self(self): def assert_dag_child_attr(dag, parent_name, child_name, child_attr, child_value): """Test tree attributes""" for parent, child in iterators.dag_iterator(dag): - if parent.name == parent_name and child.name == child_name: + if parent.node_name == parent_name and child.node_name == child_name: expected = child_value actual = child.get_attr(child_attr) assert actual == expected, f"Expected {expected}, received {actual}" diff --git a/tests/node/test_node.py b/tests/node/test_node.py index 22f99434..5fad4f36 100644 --- a/tests/node/test_node.py +++ b/tests/node/test_node.py @@ -326,15 +326,17 @@ def test_go_to(self): for node_pair, expected_path in zip( combinations(list(iterators.preorder_iter(self.a)), 2), expected_paths ): - actual_path = [_node.name for _node in node_pair[0].go_to(node_pair[1])] + actual_path = [ + _node.node_name for _node in node_pair[0].go_to(node_pair[1]) + ] assert ( actual_path == expected_path ), f"Wrong path for {node_pair}, expected {expected_path}, received {actual_path}" def test_go_to_same_node(self): for _node in iterators.preorder_iter(self.a): - actual_path = [_node1.name for _node1 in _node.go_to(_node)] - expected_path = [_node.name] + actual_path = [_node1.node_name for _node1 in _node.go_to(_node)] + expected_path = [_node.node_name] assert ( actual_path == expected_path ), f"Wrong path for {_node}, expected {expected_path}, received {actual_path}" diff --git a/tests/tree/test_construct.py b/tests/tree/test_construct.py index 0c9556db..1e22cdac 100644 --- a/tests/tree/test_construct.py +++ b/tests/tree/test_construct.py @@ -93,7 +93,7 @@ def test_add_path_to_tree_sep_trailing(self): assert_tree_structure_node_root(self.root) def test_add_path_to_tree_sep_error(self): - root1 = self.root.name + root1 = self.root.node_name root2 = "a\\b\\d" path_list = ["a\\b\\d", "a\\b\\e", "a\\b\\e\\g", "a\\b\\e\\h", "a\\c\\f"] @@ -169,7 +169,7 @@ def test_add_path_to_tree_node_type(self): assert_tree_structure_node_root(self.root) def test_add_path_to_tree_different_root_error(self): - root1 = self.root.name + root1 = self.root.node_name root2 = "a/b" path_list = [ "a", @@ -264,7 +264,7 @@ def test_add_dict_to_tree_by_path_sep_trailing(self): assert_tree_structure_node_root(self.root) def test_add_dict_to_tree_by_path_sep_error(self): - root1 = self.root.name + root1 = self.root.node_name root2 = "a-b" paths = { "a": {"age": 90}, @@ -384,7 +384,7 @@ def test_add_dict_to_tree_by_path_custom_node_type(self): assert_tree_structure_node_root(root) def test_add_dict_to_tree_by_path_different_root_error(self): - root1 = self.root.name + root1 = self.root.node_name root2 = "b" paths = { "a": {"age": 90}, @@ -784,7 +784,7 @@ def test_add_dataframe_to_tree_by_path_sep_trailing(self): assert_tree_structure_node_root(self.root) def test_add_dataframe_to_tree_by_path_sep_error(self): - root1 = self.root.name + root1 = self.root.node_name root2 = "a\\b" data = pd.DataFrame( [ @@ -971,7 +971,7 @@ def test_add_dataframe_to_tree_by_path_custom_node_type(self): assert_tree_structure_node_root(root) def test_add_dataframe_to_tree_by_path_different_root_error(self): - root1 = self.root.name + root1 = self.root.node_name root2 = "b" data = pd.DataFrame( [ @@ -1530,7 +1530,7 @@ def test_add_polars_to_tree_by_path_sep_trailing(self): assert_tree_structure_node_root(self.root) def test_add_polars_to_tree_by_path_sep_error(self): - root1 = self.root.name + root1 = self.root.node_name root2 = "a\\b" data = pl.DataFrame( [ @@ -1717,7 +1717,7 @@ def test_add_polars_to_tree_by_path_custom_node_type(self): assert_tree_structure_node_root(root) def test_add_polars_to_tree_by_path_different_root_error(self): - root1 = self.root.name + root1 = self.root.node_name root2 = "b" data = pl.DataFrame( [ diff --git a/tests/tree/test_modify.py b/tests/tree/test_modify.py index 2cf577f1..c3bd74f1 100644 --- a/tests/tree/test_modify.py +++ b/tests/tree/test_modify.py @@ -454,7 +454,7 @@ def test_copy_nodes_same_node_merge_leaves(self): to_paths = ["a/aa"] modify.copy_nodes(a, from_paths, to_paths, merge_leaves=True) - assert [child.name for child in a.children] == [ + assert [child.node_name for child in a.children] == [ "aa", "d", "g", @@ -1023,7 +1023,7 @@ def test_shift_nodes_same_node_merge_leaves(self): from_paths = ["a/aa"] to_paths = ["a/aa"] modify.shift_nodes(a, from_paths, to_paths, merge_leaves=True) - assert [child.name for child in a.children] == [ + assert [child.node_name for child in a.children] == [ "aa", "d", "g", @@ -1882,7 +1882,8 @@ def test_copy_nodes_from_tree_to_tree_merge_leaves(self): self.root_other.max_depth == 3 ), f"Depth is wrong, expected 3, received {self.root_other.depth}" assert [ - _node.name for _node in search.find_path(self.root_other, "a/b").children + _node.node_name + for _node in search.find_path(self.root_other, "a/b").children ] == [ "b", "c", diff --git a/tests/utils/test_iterators.py b/tests/utils/test_iterators.py index 4999d9e8..ef49c486 100644 --- a/tests/utils/test_iterators.py +++ b/tests/utils/test_iterators.py @@ -198,7 +198,7 @@ def test_levelordergroup_iter_filter_condition(tree_node): [node.node_name for node in group] for group in iterators.levelordergroup_iter( tree_node, - filter_condition=lambda x: x.name in ["a", "d", "e", "f", "g"], + filter_condition=lambda x: x.node_name in ["a", "d", "e", "f", "g"], ) ] assert actual == expected, f"Expected\n{expected}\nReceived\n{actual}" @@ -210,7 +210,7 @@ def test_levelordergroup_iter_filter_condition_skip_level(tree_node): [node.node_name for node in group] for group in iterators.levelordergroup_iter( tree_node, - filter_condition=lambda x: x.name in ["g", "h"], + filter_condition=lambda x: x.node_name in ["g", "h"], ) ] assert actual == expected, f"Expected\n{expected}\nReceived\n{actual}" @@ -221,7 +221,7 @@ def test_levelordergroup_iter_stop_condition(tree_node): actual = [ [node.node_name for node in group] for group in iterators.levelordergroup_iter( - tree_node, stop_condition=lambda x: x.name == "e" + tree_node, stop_condition=lambda x: x.node_name == "e" ) ] assert actual == expected, f"Expected\n{expected}\nReceived\n{actual}" @@ -319,7 +319,7 @@ def test_zigzaggroup_iter_filter_condition(tree_node): [node.node_name for node in group] for group in iterators.zigzaggroup_iter( tree_node, - filter_condition=lambda x: x.name in ["a", "d", "e", "f", "g"], + filter_condition=lambda x: x.node_name in ["a", "d", "e", "f", "g"], ) ] assert actual == expected, f"Expected\n{expected}\nReceived\n{actual}" @@ -331,7 +331,7 @@ def test_zigzaggroup_iter_filter_condition_skip_level(tree_node): [node.node_name for node in group] for group in iterators.zigzaggroup_iter( tree_node, - filter_condition=lambda x: x.name in ["g", "h"], + filter_condition=lambda x: x.node_name in ["g", "h"], ) ] assert actual == expected, f"Expected\n{expected}\nReceived\n{actual}" @@ -342,7 +342,7 @@ def test_zigzaggroup_iter_stop_condition(tree_node): actual = [ [node.node_name for node in group] for group in iterators.zigzaggroup_iter( - tree_node, stop_condition=lambda x: x.name == "e" + tree_node, stop_condition=lambda x: x.node_name == "e" ) ] assert actual == expected, f"Expected\n{expected}\nReceived\n{actual}" diff --git a/tests/workflows/test_todo.py b/tests/workflows/test_todo.py index ece77b5c..4b3060f2 100644 --- a/tests/workflows/test_todo.py +++ b/tests/workflows/test_todo.py @@ -18,7 +18,7 @@ def tearDown(self): @staticmethod def test_creation(): todoapp = app_todo.AppToDo("To Do Items") - assert todoapp._root.name == "To Do Items" + assert todoapp._root.node_name == "To Do Items" def test_add_list(self): self.todoapp.add_list("List 1")