diff --git a/CHANGELOG.md b/CHANGELOG.md
index c6e21d88..f128ec35 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,13 +5,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
+
+## [0.23.0] - 2024-12-26
### Changed:
- Tree Modify: Update documentation and docstring with some rephrasing.
- Tree Modify: Clean up test cases.
### Added:
- Tree Modify: Add parameter `merge_attribute` to allow from-node and to-node attributes to be merged if there are clashes.
### Fixed:
-- Tree Modify: Fixed bug when `merge_children` is used with `overriding` as the `merge_children` value is changed in for-loop (bad move, literally).
+- Tree Modify: Fixed bug when `merge_children` is used with `overriding` as the `merge_children` value is changed in
+for-loop (bad move, literally). Modified the logic such that if there are clashes for `merge_children=True, overriding=True`,
+the origin node parent and destination node children are preserved. The origin node's children are overridden.
+**This might not be backwards-compatible!**
## [0.22.3] - 2024-11-14
### Added:
diff --git a/bigtree/__init__.py b/bigtree/__init__.py
index 0be8dbb2..239e4a3c 100644
--- a/bigtree/__init__.py
+++ b/bigtree/__init__.py
@@ -1,4 +1,4 @@
-__version__ = "0.22.3"
+__version__ = "0.23.0"
from bigtree.binarytree.construct import list_to_binarytree
from bigtree.dag.construct import dataframe_to_dag, dict_to_dag, list_to_dag
diff --git a/bigtree/tree/modify.py b/bigtree/tree/modify.py
index e9bad59d..212a51c8 100644
--- a/bigtree/tree/modify.py
+++ b/bigtree/tree/modify.py
@@ -1216,10 +1216,6 @@ def copy_or_shift_logic(
# Perform shifting/copying
for from_path, to_path in zip(from_paths, to_paths):
- # Reset parameters
- merge_children2 = merge_children
- merge_leaves2 = merge_leaves
-
if with_full_path:
from_node = search.find_full_path(tree, from_path)
else:
@@ -1264,10 +1260,7 @@ def copy_or_shift_logic(
logging.info(
f"Path {to_path} already exists and its children be overridden by the merge"
)
- parent = to_node.parent
- to_node.parent = None
- to_node = parent
- merge_children2 = False
+ del to_node.children
elif merge_attribute:
logging.info(
f"Path {to_path} already exists and their attributes will be merged"
@@ -1279,10 +1272,10 @@ def copy_or_shift_logic(
merge_children=merge_children,
merge_leaves=merge_leaves,
)
- parent = to_node.parent
- to_node.parent = None
- to_node = parent
- merge_children2 = False
+ to_node.set_attrs(
+ dict(from_node.describe(exclude_prefix="_"))
+ )
+ del to_node.children
else:
logging.info(
f"Path {to_path} already exists and children are merged"
@@ -1304,10 +1297,10 @@ def copy_or_shift_logic(
merge_children=merge_children,
merge_leaves=merge_leaves,
)
- parent = to_node.parent
- to_node.parent = None
- to_node = parent
- merge_leaves2 = False
+ to_node.set_attrs(
+ dict(from_node.describe(exclude_prefix="_"))
+ )
+ del to_node.children
else:
logging.info(
f"Path {to_path} already exists and leaves are merged"
@@ -1353,7 +1346,7 @@ def copy_or_shift_logic(
if copy:
logging.debug(f"Copying {from_node.node_name}")
from_node = from_node.copy()
- if merge_children2:
+ if merge_children:
# overriding / merge_attribute handled merge_children, set merge_children=False
logging.debug(
f"Reassigning children from {from_node.node_name} to {to_node.node_name}"
@@ -1363,7 +1356,7 @@ def copy_or_shift_logic(
del children.children
children.parent = to_node
from_node.parent = None
- elif merge_leaves2:
+ elif merge_leaves:
logging.debug(
f"Reassigning leaf nodes from {from_node.node_name} to {to_node.node_name}"
)
diff --git a/docs/bigtree/tree/modify.md b/docs/bigtree/tree/modify.md
index b158788b..c8a67d06 100644
--- a/docs/bigtree/tree/modify.md
+++ b/docs/bigtree/tree/modify.md
@@ -94,33 +94,37 @@ If you're still feeling lost over the parameters, here are some guiding question
- Do I want to retain the original node where they are?
- Yes: Set `copy=True`
- Default performs a shift instead of copy
-- Am I unsure of what nodes I am going to shift, they may or may not exist and this is perfectly fine?
+- Am I unsure of what nodes I am going to copy/shift, they may or may not exist and this is perfectly fine?
- Yes: Set `skippable=True`
- Default throws error if origin node is not found
- The origin node (and its descendants) may clash with the destination node(s), how do I want to handle it?
- Set `overriding=True` to overwrite origin node
- Set `merge_attribute=True` to combine both nodes' attributes
- Default throws error about the clash in node name
-- I want to shift everything under the node, but not the node itself
+- I want to copy/shift everything under the node, but not the node itself
- Set `merge_children=True` or `merge_leaves=True` to shift the children and leaf nodes respectively
- Default shifts the node itself, and everything under it
-- I want to shift the node and only the node, and not everything under it
+- I want to copy/shift the node and only the node, and not everything under it
- Yes: Set `delete_children=True`
- Default shifts the node itself, and everything under it
-- I want to shift things from one tree to another tree
+- I want to copy/shift things from one tree to another tree
- Specify `to_tree`
- Default shifts nodes within the same tree
What about the permutations between the parameters?
-- These parameters are standalone and does not produce any interaction effect
+- These parameters are standalone and do not produce any interaction effect
- `copy`, `skippable`, `delete_children`
- These parameters have some interaction:
- `overriding` and `merge_attribute` with `merge_children` and `merge_leaves`
- - `overriding` + `merge_children`: Behaves like `merge_children` when there is no clash in node name, otherwise behaves like `overriding`. Note that clashes will preserve destination nodes' children only.
- - `overriding` + `merge_leaves`: Behaves like `merge_leaves` when there is no clash in node name, otherwise behaves like `overriding`. Note that clashes will preserve destination nodes' leaves only.
- - `merge_attribute` + `merge_children`: Behaves like `merge_children` when there is no clash in node name, otherwise behaves like `merge_attribute`. Note that attributes will be merged for node and all descendants, and will preserve origin and destination nodes' children.
- - `merge_attribute` + `merge_leaves`: Behaves like `merge_leaves` when there is no clash in node name, otherwise behaves like `merge_attribute`. Note that attributes will be merged for node and all descendants, and will preserve origin nodes' children and destination nodes' leaves.
+ - `overriding` + `merge_children`: Behaves like `merge_children` when there is no clash in node name, otherwise behaves like `overriding`.
+ Note that clashes will preserve origin node parent and destination nodes' children.
+ - `overriding` + `merge_leaves`: Behaves like `merge_leaves` when there is no clash in node name, otherwise behaves like `overriding`.
+ Note that clashes will preserve origin node parent and destination nodes' leaves.
+ - `merge_attribute` + `merge_children`: Behaves like `merge_children` when there is no clash in node name, otherwise behaves like `merge_attribute`.
+ Note that attributes will be merged for node and all descendants, and will preserve origin and destination nodes' children.
+ - `merge_attribute` + `merge_leaves`: Behaves like `merge_leaves` when there is no clash in node name, otherwise behaves like `merge_attribute`.
+ Note that attributes will be merged for node and all descendants, and will preserve origin nodes' children and destination nodes' leaves.
-----
diff --git a/docs/gettingstarted/demo/tree.md b/docs/gettingstarted/demo/tree.md
index 38981500..93a84533 100644
--- a/docs/gettingstarted/demo/tree.md
+++ b/docs/gettingstarted/demo/tree.md
@@ -617,6 +617,9 @@ Nodes can be shifted (with or without replacement) or copied<
from one path to another, this changes the tree in-place.
Nodes can also be copied (with or without replacement) between two different trees.
+There are various other configurations for performing copying/shifting, refer to [code documentation](../../bigtree/tree/modify.md)
+for more examples.
+
=== "Shift nodes"
```python hl_lines="12-16 24-28"
from bigtree import list_to_tree, shift_nodes, shift_and_replace_nodes
diff --git a/tests/tree/test_modify.py b/tests/tree/test_modify.py
index 9216effd..0c95e167 100644
--- a/tests/tree/test_modify.py
+++ b/tests/tree/test_modify.py
@@ -2377,7 +2377,7 @@ def test_copy_nodes_from_tree_to_tree_merge_children_overriding(self):
overriding=True,
)
assert_tree_structure_basenode_root(self.root_other_full_wrong)
- assert_tree_structure_basenode_root_attr(self.root_other_full_wrong)
+ assert_tree_structure_basenode_root_attr(self.root_other_full_wrong, c=("c", 1))
assert_tree_structure_node_root(self.root_other_full_wrong)
# merge_children, merge_attribute
@@ -2454,6 +2454,7 @@ def test_copy_nodes_from_tree_to_tree_merge_leaves_merge_attribute(self):
merge_leaves=True,
merge_attribute=True,
)
+ self.root_other_full_wrong["b"].sort(key=lambda node: node.node_name)
assert_tree_structure_basenode_root(self.root_other_full_wrong)
assert_tree_structure_basenode_root_attr(self.root_other_full_wrong)
assert_tree_structure_node_root(self.root_other_full_wrong)