added 3rd party packages, elog, bigtree
This commit is contained in:
856
python37/packages/bigtree/tree/modify.py
Normal file
856
python37/packages/bigtree/tree/modify.py
Normal file
@@ -0,0 +1,856 @@
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
|
||||
from bigtree.node.node import Node
|
||||
from bigtree.tree.search import find_path
|
||||
from bigtree.utils.exceptions import NotFoundError, TreeError
|
||||
|
||||
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
||||
|
||||
|
||||
__all__ = [
|
||||
"shift_nodes",
|
||||
"copy_nodes",
|
||||
"copy_nodes_from_tree_to_tree",
|
||||
"copy_or_shift_logic",
|
||||
]
|
||||
|
||||
|
||||
def shift_nodes(
|
||||
tree: Node,
|
||||
from_paths: List[str],
|
||||
to_paths: List[str],
|
||||
sep: str = "/",
|
||||
skippable: bool = False,
|
||||
overriding: bool = False,
|
||||
merge_children: bool = False,
|
||||
merge_leaves: bool = False,
|
||||
delete_children: bool = False,
|
||||
):
|
||||
"""Shift nodes from `from_paths` to `to_paths` *in-place*.
|
||||
|
||||
- Creates intermediate nodes if to path is not present
|
||||
- Able to skip nodes if from path is not found, defaults to False (from-nodes must be found; not skippable).
|
||||
- Able to override existing node if it exists, defaults to False (to-nodes must not exist; not overridden).
|
||||
- Able to merge children and remove intermediate parent node, defaults to False (nodes are shifted; not merged).
|
||||
- Able to merge only leaf nodes and remove all intermediate nodes, defaults to False (nodes are shifted; not merged)
|
||||
- Able to shift node only and delete children, defaults to False (nodes are shifted together with children).
|
||||
|
||||
For paths in `from_paths` and `to_paths`,
|
||||
- Path name can be with or without leading tree path separator symbol.
|
||||
- Path name can be partial path (trailing part of path) or node name.
|
||||
- Path name must be unique to one node.
|
||||
|
||||
For paths in `to_paths`,
|
||||
- Can set to empty string or None to delete the path in `from_paths`, note that ``copy`` must be set to False.
|
||||
|
||||
If ``merge_children=True``,
|
||||
- If `to_path` is not present, it shifts children of `from_path`.
|
||||
- If `to_path` is present, and ``overriding=False``, original and new children are merged.
|
||||
- If `to_path` is present and ``overriding=True``, it behaves like overriding and only new children are retained.
|
||||
|
||||
If ``merge_leaves=True``,
|
||||
- If `to_path` is not present, it shifts leaves of `from_path`.
|
||||
- If `to_path` is present, and ``overriding=False``, original children and leaves are merged.
|
||||
- If `to_path` is present and ``overriding=True``, it behaves like overriding and only new leaves are retained,
|
||||
original node in `from_path` is retained.
|
||||
|
||||
>>> from bigtree import Node, shift_nodes, print_tree
|
||||
>>> root = Node("a")
|
||||
>>> b = Node("b", parent=root)
|
||||
>>> c = Node("c", parent=root)
|
||||
>>> d = Node("d", parent=root)
|
||||
>>> print_tree(root)
|
||||
a
|
||||
├── b
|
||||
├── c
|
||||
└── d
|
||||
|
||||
>>> shift_nodes(root, ["a/c", "a/d"], ["a/b/c", "a/dummy/d"])
|
||||
>>> print_tree(root)
|
||||
a
|
||||
├── b
|
||||
│ └── c
|
||||
└── dummy
|
||||
└── d
|
||||
|
||||
To delete node,
|
||||
|
||||
>>> root = Node("a")
|
||||
>>> b = Node("b", parent=root)
|
||||
>>> c = Node("c", parent=root)
|
||||
>>> print_tree(root)
|
||||
a
|
||||
├── b
|
||||
└── c
|
||||
|
||||
>>> shift_nodes(root, ["a/b"], [None])
|
||||
>>> print_tree(root)
|
||||
a
|
||||
└── c
|
||||
|
||||
In overriding case,
|
||||
|
||||
>>> root = Node("a")
|
||||
>>> b = Node("b", parent=root)
|
||||
>>> c = Node("c", parent=root)
|
||||
>>> d = Node("d", parent=c)
|
||||
>>> c2 = Node("c", parent=b)
|
||||
>>> e = Node("e", parent=c2)
|
||||
>>> print_tree(root)
|
||||
a
|
||||
├── b
|
||||
│ └── c
|
||||
│ └── e
|
||||
└── c
|
||||
└── d
|
||||
|
||||
>>> shift_nodes(root, ["a/b/c"], ["a/c"], overriding=True)
|
||||
>>> print_tree(root)
|
||||
a
|
||||
├── b
|
||||
└── c
|
||||
└── e
|
||||
|
||||
In ``merge_children`` case, child nodes are shifted instead of the parent node.
|
||||
- If the path already exists, child nodes are merged with existing children.
|
||||
- If same node is shifted, the child nodes of the node are merged with the node's parent.
|
||||
|
||||
>>> root = Node("a")
|
||||
>>> b = Node("b", parent=root)
|
||||
>>> c = Node("c", parent=root)
|
||||
>>> d = Node("d", parent=c)
|
||||
>>> c2 = Node("c", parent=b)
|
||||
>>> e = Node("e", parent=c2)
|
||||
>>> z = Node("z", parent=b)
|
||||
>>> y = Node("y", parent=z)
|
||||
>>> f = Node("f", parent=root)
|
||||
>>> g = Node("g", parent=f)
|
||||
>>> h = Node("h", parent=g)
|
||||
>>> print_tree(root)
|
||||
a
|
||||
├── b
|
||||
│ ├── c
|
||||
│ │ └── e
|
||||
│ └── z
|
||||
│ └── y
|
||||
├── c
|
||||
│ └── d
|
||||
└── f
|
||||
└── g
|
||||
└── h
|
||||
|
||||
>>> shift_nodes(root, ["a/b/c", "z", "a/f"], ["a/c", "a/z", "a/f"], merge_children=True)
|
||||
>>> print_tree(root)
|
||||
a
|
||||
├── b
|
||||
├── c
|
||||
│ ├── d
|
||||
│ └── e
|
||||
├── y
|
||||
└── g
|
||||
└── h
|
||||
|
||||
In ``merge_leaves`` case, leaf nodes are copied instead of the parent node.
|
||||
- If the path already exists, leaf nodes are merged with existing children.
|
||||
- If same node is copied, the leaf nodes of the node are merged with the node's parent.
|
||||
|
||||
>>> root = Node("a")
|
||||
>>> b = Node("b", parent=root)
|
||||
>>> c = Node("c", parent=root)
|
||||
>>> d = Node("d", parent=c)
|
||||
>>> c2 = Node("c", parent=b)
|
||||
>>> e = Node("e", parent=c2)
|
||||
>>> z = Node("z", parent=b)
|
||||
>>> y = Node("y", parent=z)
|
||||
>>> f = Node("f", parent=root)
|
||||
>>> g = Node("g", parent=f)
|
||||
>>> h = Node("h", parent=g)
|
||||
>>> print_tree(root)
|
||||
a
|
||||
├── b
|
||||
│ ├── c
|
||||
│ │ └── e
|
||||
│ └── z
|
||||
│ └── y
|
||||
├── c
|
||||
│ └── d
|
||||
└── f
|
||||
└── g
|
||||
└── h
|
||||
|
||||
>>> shift_nodes(root, ["a/b/c", "z", "a/f"], ["a/c", "a/z", "a/f"], merge_leaves=True)
|
||||
>>> print_tree(root)
|
||||
a
|
||||
├── b
|
||||
│ ├── c
|
||||
│ └── z
|
||||
├── c
|
||||
│ ├── d
|
||||
│ └── e
|
||||
├── f
|
||||
│ └── g
|
||||
├── y
|
||||
└── h
|
||||
|
||||
In ``delete_children`` case, only the node is shifted without its accompanying children/descendants.
|
||||
|
||||
>>> root = Node("a")
|
||||
>>> b = Node("b", parent=root)
|
||||
>>> c = Node("c", parent=root)
|
||||
>>> d = Node("d", parent=c)
|
||||
>>> c2 = Node("c", parent=b)
|
||||
>>> e = Node("e", parent=c2)
|
||||
>>> z = Node("z", parent=b)
|
||||
>>> y = Node("y", parent=z)
|
||||
>>> print_tree(root)
|
||||
a
|
||||
├── b
|
||||
│ ├── c
|
||||
│ │ └── e
|
||||
│ └── z
|
||||
│ └── y
|
||||
└── c
|
||||
└── d
|
||||
|
||||
>>> shift_nodes(root, ["a/b/z"], ["a/z"], delete_children=True)
|
||||
>>> print_tree(root)
|
||||
a
|
||||
├── b
|
||||
│ └── c
|
||||
│ └── e
|
||||
├── c
|
||||
│ └── d
|
||||
└── z
|
||||
|
||||
Args:
|
||||
tree (Node): tree to modify
|
||||
from_paths (list): original paths to shift nodes from
|
||||
to_paths (list): new paths to shift nodes to
|
||||
sep (str): path separator for input paths, applies to `from_path` and `to_path`
|
||||
skippable (bool): indicator to skip if from path is not found, defaults to False
|
||||
overriding (bool): indicator to override existing to path if there is clashes, defaults to False
|
||||
merge_children (bool): indicator to merge children and remove intermediate parent node, defaults to False
|
||||
merge_leaves (bool): indicator to merge leaf nodes and remove intermediate parent node(s), defaults to False
|
||||
delete_children (bool): indicator to shift node only without children, defaults to False
|
||||
"""
|
||||
return copy_or_shift_logic(
|
||||
tree=tree,
|
||||
from_paths=from_paths,
|
||||
to_paths=to_paths,
|
||||
sep=sep,
|
||||
copy=False,
|
||||
skippable=skippable,
|
||||
overriding=overriding,
|
||||
merge_children=merge_children,
|
||||
merge_leaves=merge_leaves,
|
||||
delete_children=delete_children,
|
||||
to_tree=None,
|
||||
) # pragma: no cover
|
||||
|
||||
|
||||
def copy_nodes(
|
||||
tree: Node,
|
||||
from_paths: List[str],
|
||||
to_paths: List[str],
|
||||
sep: str = "/",
|
||||
skippable: bool = False,
|
||||
overriding: bool = False,
|
||||
merge_children: bool = False,
|
||||
merge_leaves: bool = False,
|
||||
delete_children: bool = False,
|
||||
):
|
||||
"""Copy nodes from `from_paths` to `to_paths` *in-place*.
|
||||
|
||||
- Creates intermediate nodes if to path is not present
|
||||
- Able to skip nodes if from path is not found, defaults to False (from-nodes must be found; not skippable).
|
||||
- Able to override existing node if it exists, defaults to False (to-nodes must not exist; not overridden).
|
||||
- Able to merge children and remove intermediate parent node, defaults to False (nodes are shifted; not merged).
|
||||
- Able to merge only leaf nodes and remove all intermediate nodes, defaults to False (nodes are shifted; not merged)
|
||||
- Able to copy node only and delete children, defaults to False (nodes are copied together with children).
|
||||
|
||||
For paths in `from_paths` and `to_paths`,
|
||||
- Path name can be with or without leading tree path separator symbol.
|
||||
- Path name can be partial path (trailing part of path) or node name.
|
||||
- Path name must be unique to one node.
|
||||
|
||||
If ``merge_children=True``,
|
||||
- If `to_path` is not present, it copies children of `from_path`.
|
||||
- If `to_path` is present, and ``overriding=False``, original and new children are merged.
|
||||
- If `to_path` is present and ``overriding=True``, it behaves like overriding and only new children are retained.
|
||||
|
||||
If ``merge_leaves=True``,
|
||||
- If `to_path` is not present, it copies leaves of `from_path`.
|
||||
- If `to_path` is present, and ``overriding=False``, original children and leaves are merged.
|
||||
- If `to_path` is present and ``overriding=True``, it behaves like overriding and only new leaves are retained.
|
||||
|
||||
>>> from bigtree import Node, copy_nodes, print_tree
|
||||
>>> root = Node("a")
|
||||
>>> b = Node("b", parent=root)
|
||||
>>> c = Node("c", parent=root)
|
||||
>>> d = Node("d", parent=root)
|
||||
>>> print_tree(root)
|
||||
a
|
||||
├── b
|
||||
├── c
|
||||
└── d
|
||||
|
||||
>>> copy_nodes(root, ["a/c", "a/d"], ["a/b/c", "a/dummy/d"])
|
||||
>>> print_tree(root)
|
||||
a
|
||||
├── b
|
||||
│ └── c
|
||||
├── c
|
||||
├── d
|
||||
└── dummy
|
||||
└── d
|
||||
|
||||
In overriding case,
|
||||
|
||||
>>> root = Node("a")
|
||||
>>> b = Node("b", parent=root)
|
||||
>>> c = Node("c", parent=root)
|
||||
>>> d = Node("d", parent=c)
|
||||
>>> c2 = Node("c", parent=b)
|
||||
>>> e = Node("e", parent=c2)
|
||||
>>> print_tree(root)
|
||||
a
|
||||
├── b
|
||||
│ └── c
|
||||
│ └── e
|
||||
└── c
|
||||
└── d
|
||||
|
||||
>>> copy_nodes(root, ["a/b/c"], ["a/c"], overriding=True)
|
||||
>>> print_tree(root)
|
||||
a
|
||||
├── b
|
||||
│ └── c
|
||||
│ └── e
|
||||
└── c
|
||||
└── e
|
||||
|
||||
In ``merge_children`` case, child nodes are copied instead of the parent node.
|
||||
- If the path already exists, child nodes are merged with existing children.
|
||||
- If same node is copied, the child nodes of the node are merged with the node's parent.
|
||||
|
||||
>>> root = Node("a")
|
||||
>>> b = Node("b", parent=root)
|
||||
>>> c = Node("c", parent=root)
|
||||
>>> d = Node("d", parent=c)
|
||||
>>> c2 = Node("c", parent=b)
|
||||
>>> e = Node("e", parent=c2)
|
||||
>>> z = Node("z", parent=b)
|
||||
>>> y = Node("y", parent=z)
|
||||
>>> f = Node("f", parent=root)
|
||||
>>> g = Node("g", parent=f)
|
||||
>>> h = Node("h", parent=g)
|
||||
>>> print_tree(root)
|
||||
a
|
||||
├── b
|
||||
│ ├── c
|
||||
│ │ └── e
|
||||
│ └── z
|
||||
│ └── y
|
||||
├── c
|
||||
│ └── d
|
||||
└── f
|
||||
└── g
|
||||
└── h
|
||||
|
||||
>>> copy_nodes(root, ["a/b/c", "z", "a/f"], ["a/c", "a/z", "a/f"], merge_children=True)
|
||||
>>> print_tree(root)
|
||||
a
|
||||
├── b
|
||||
│ ├── c
|
||||
│ │ └── e
|
||||
│ └── z
|
||||
│ └── y
|
||||
├── c
|
||||
│ ├── d
|
||||
│ └── e
|
||||
├── y
|
||||
└── g
|
||||
└── h
|
||||
|
||||
In ``merge_leaves`` case, leaf nodes are copied instead of the parent node.
|
||||
- If the path already exists, leaf nodes are merged with existing children.
|
||||
- If same node is copied, the leaf nodes of the node are merged with the node's parent.
|
||||
|
||||
>>> root = Node("a")
|
||||
>>> b = Node("b", parent=root)
|
||||
>>> c = Node("c", parent=root)
|
||||
>>> d = Node("d", parent=c)
|
||||
>>> c2 = Node("c", parent=b)
|
||||
>>> e = Node("e", parent=c2)
|
||||
>>> z = Node("z", parent=b)
|
||||
>>> y = Node("y", parent=z)
|
||||
>>> f = Node("f", parent=root)
|
||||
>>> g = Node("g", parent=f)
|
||||
>>> h = Node("h", parent=g)
|
||||
>>> print_tree(root)
|
||||
a
|
||||
├── b
|
||||
│ ├── c
|
||||
│ │ └── e
|
||||
│ └── z
|
||||
│ └── y
|
||||
├── c
|
||||
│ └── d
|
||||
└── f
|
||||
└── g
|
||||
└── h
|
||||
|
||||
>>> copy_nodes(root, ["a/b/c", "z", "a/f"], ["a/c", "a/z", "a/f"], merge_leaves=True)
|
||||
>>> print_tree(root)
|
||||
a
|
||||
├── b
|
||||
│ ├── c
|
||||
│ │ └── e
|
||||
│ └── z
|
||||
│ └── y
|
||||
├── c
|
||||
│ ├── d
|
||||
│ └── e
|
||||
├── f
|
||||
│ └── g
|
||||
│ └── h
|
||||
├── y
|
||||
└── h
|
||||
|
||||
In ``delete_children`` case, only the node is copied without its accompanying children/descendants.
|
||||
|
||||
>>> root = Node("a")
|
||||
>>> b = Node("b", parent=root)
|
||||
>>> c = Node("c", parent=root)
|
||||
>>> d = Node("d", parent=c)
|
||||
>>> c2 = Node("c", parent=b)
|
||||
>>> e = Node("e", parent=c2)
|
||||
>>> z = Node("z", parent=b)
|
||||
>>> y = Node("y", parent=z)
|
||||
>>> print_tree(root)
|
||||
a
|
||||
├── b
|
||||
│ ├── c
|
||||
│ │ └── e
|
||||
│ └── z
|
||||
│ └── y
|
||||
└── c
|
||||
└── d
|
||||
|
||||
>>> copy_nodes(root, ["a/b/z"], ["a/z"], delete_children=True)
|
||||
>>> print_tree(root)
|
||||
a
|
||||
├── b
|
||||
│ ├── c
|
||||
│ │ └── e
|
||||
│ └── z
|
||||
│ └── y
|
||||
├── c
|
||||
│ └── d
|
||||
└── z
|
||||
|
||||
Args:
|
||||
tree (Node): tree to modify
|
||||
from_paths (list): original paths to shift nodes from
|
||||
to_paths (list): new paths to shift nodes to
|
||||
sep (str): path separator for input paths, applies to `from_path` and `to_path`
|
||||
skippable (bool): indicator to skip if from path is not found, defaults to False
|
||||
overriding (bool): indicator to override existing to path if there is clashes, defaults to False
|
||||
merge_children (bool): indicator to merge children and remove intermediate parent node, defaults to False
|
||||
merge_leaves (bool): indicator to merge leaf nodes and remove intermediate parent node(s), defaults to False
|
||||
delete_children (bool): indicator to copy node only without children, defaults to False
|
||||
"""
|
||||
return copy_or_shift_logic(
|
||||
tree=tree,
|
||||
from_paths=from_paths,
|
||||
to_paths=to_paths,
|
||||
sep=sep,
|
||||
copy=True,
|
||||
skippable=skippable,
|
||||
overriding=overriding,
|
||||
merge_children=merge_children,
|
||||
merge_leaves=merge_leaves,
|
||||
delete_children=delete_children,
|
||||
to_tree=None,
|
||||
) # pragma: no cover
|
||||
|
||||
|
||||
def copy_nodes_from_tree_to_tree(
|
||||
from_tree: Node,
|
||||
to_tree: Node,
|
||||
from_paths: List[str],
|
||||
to_paths: List[str],
|
||||
sep: str = "/",
|
||||
skippable: bool = False,
|
||||
overriding: bool = False,
|
||||
merge_children: bool = False,
|
||||
merge_leaves: bool = False,
|
||||
delete_children: bool = False,
|
||||
):
|
||||
"""Copy nodes from `from_paths` to `to_paths` *in-place*.
|
||||
|
||||
- Creates intermediate nodes if to path is not present
|
||||
- Able to skip nodes if from path is not found, defaults to False (from-nodes must be found; not skippable).
|
||||
- Able to override existing node if it exists, defaults to False (to-nodes must not exist; not overridden).
|
||||
- Able to merge children and remove intermediate parent node, defaults to False (nodes are shifted; not merged).
|
||||
- Able to merge only leaf nodes and remove all intermediate nodes, defaults to False (nodes are shifted; not merged)
|
||||
- Able to copy node only and delete children, defaults to False (nodes are copied together with children).
|
||||
|
||||
For paths in `from_paths` and `to_paths`,
|
||||
- Path name can be with or without leading tree path separator symbol.
|
||||
- Path name can be partial path (trailing part of path) or node name.
|
||||
- Path name must be unique to one node.
|
||||
|
||||
If ``merge_children=True``,
|
||||
- If `to_path` is not present, it copies children of `from_path`
|
||||
- If `to_path` is present, and ``overriding=False``, original and new children are merged
|
||||
- If `to_path` is present and ``overriding=True``, it behaves like overriding and only new leaves are retained.
|
||||
|
||||
If ``merge_leaves=True``,
|
||||
- If `to_path` is not present, it copies leaves of `from_path`.
|
||||
- If `to_path` is present, and ``overriding=False``, original children and leaves are merged.
|
||||
- If `to_path` is present and ``overriding=True``, it behaves like overriding and only new leaves are retained.
|
||||
|
||||
>>> from bigtree import Node, copy_nodes_from_tree_to_tree, print_tree
|
||||
>>> root = Node("a")
|
||||
>>> b = Node("b", parent=root)
|
||||
>>> c = Node("c", parent=root)
|
||||
>>> d = Node("d", parent=c)
|
||||
>>> e = Node("e", parent=root)
|
||||
>>> f = Node("f", parent=e)
|
||||
>>> g = Node("g", parent=f)
|
||||
>>> print_tree(root)
|
||||
a
|
||||
├── b
|
||||
├── c
|
||||
│ └── d
|
||||
└── e
|
||||
└── f
|
||||
└── g
|
||||
|
||||
>>> root_other = Node("aa")
|
||||
>>> copy_nodes_from_tree_to_tree(root, root_other, ["a/b", "a/c", "a/e"], ["aa/b", "aa/b/c", "aa/dummy/e"])
|
||||
>>> print_tree(root_other)
|
||||
aa
|
||||
├── b
|
||||
│ └── c
|
||||
│ └── d
|
||||
└── dummy
|
||||
└── e
|
||||
└── f
|
||||
└── g
|
||||
|
||||
In overriding case,
|
||||
|
||||
>>> root_other = Node("aa")
|
||||
>>> c = Node("c", parent=root_other)
|
||||
>>> e = Node("e", parent=c)
|
||||
>>> print_tree(root_other)
|
||||
aa
|
||||
└── c
|
||||
└── e
|
||||
|
||||
>>> copy_nodes_from_tree_to_tree(root, root_other, ["a/b", "a/c"], ["aa/b", "aa/c"], overriding=True)
|
||||
>>> print_tree(root_other)
|
||||
aa
|
||||
├── b
|
||||
└── c
|
||||
└── d
|
||||
|
||||
In ``merge_children`` case, child nodes are copied instead of the parent node.
|
||||
- If the path already exists, child nodes are merged with existing children.
|
||||
|
||||
>>> root_other = Node("aa")
|
||||
>>> c = Node("c", parent=root_other)
|
||||
>>> e = Node("e", parent=c)
|
||||
>>> print_tree(root_other)
|
||||
aa
|
||||
└── c
|
||||
└── e
|
||||
|
||||
>>> copy_nodes_from_tree_to_tree(root, root_other, ["a/c", "e"], ["a/c", "a/e"], merge_children=True)
|
||||
>>> print_tree(root_other)
|
||||
aa
|
||||
├── c
|
||||
│ ├── e
|
||||
│ └── d
|
||||
└── f
|
||||
└── g
|
||||
|
||||
In ``merge_leaves`` case, leaf nodes are copied instead of the parent node.
|
||||
- If the path already exists, leaf nodes are merged with existing children.
|
||||
|
||||
>>> root_other = Node("aa")
|
||||
>>> c = Node("c", parent=root_other)
|
||||
>>> e = Node("e", parent=c)
|
||||
>>> print_tree(root_other)
|
||||
aa
|
||||
└── c
|
||||
└── e
|
||||
|
||||
>>> copy_nodes_from_tree_to_tree(root, root_other, ["a/c", "e"], ["a/c", "a/e"], merge_leaves=True)
|
||||
>>> print_tree(root_other)
|
||||
aa
|
||||
├── c
|
||||
│ ├── e
|
||||
│ └── d
|
||||
└── g
|
||||
|
||||
In ``delete_children`` case, only the node is copied without its accompanying children/descendants.
|
||||
|
||||
>>> root_other = Node("aa")
|
||||
>>> print_tree(root_other)
|
||||
aa
|
||||
|
||||
>>> copy_nodes_from_tree_to_tree(root, root_other, ["a/c", "e"], ["a/c", "a/e"], delete_children=True)
|
||||
>>> print_tree(root_other)
|
||||
aa
|
||||
├── c
|
||||
└── e
|
||||
|
||||
Args:
|
||||
from_tree (Node): tree to copy nodes from
|
||||
to_tree (Node): tree to copy nodes to
|
||||
from_paths (list): original paths to shift nodes from
|
||||
to_paths (list): new paths to shift nodes to
|
||||
sep (str): path separator for input paths, applies to `from_path` and `to_path`
|
||||
skippable (bool): indicator to skip if from path is not found, defaults to False
|
||||
overriding (bool): indicator to override existing to path if there is clashes, defaults to False
|
||||
merge_children (bool): indicator to merge children and remove intermediate parent node, defaults to False
|
||||
merge_leaves (bool): indicator to merge leaf nodes and remove intermediate parent node(s), defaults to False
|
||||
delete_children (bool): indicator to copy node only without children, defaults to False
|
||||
"""
|
||||
return copy_or_shift_logic(
|
||||
tree=from_tree,
|
||||
from_paths=from_paths,
|
||||
to_paths=to_paths,
|
||||
sep=sep,
|
||||
copy=True,
|
||||
skippable=skippable,
|
||||
overriding=overriding,
|
||||
merge_children=merge_children,
|
||||
merge_leaves=merge_leaves,
|
||||
delete_children=delete_children,
|
||||
to_tree=to_tree,
|
||||
) # pragma: no cover
|
||||
|
||||
|
||||
def copy_or_shift_logic(
|
||||
tree: Node,
|
||||
from_paths: List[str],
|
||||
to_paths: List[str],
|
||||
sep: str = "/",
|
||||
copy: bool = False,
|
||||
skippable: bool = False,
|
||||
overriding: bool = False,
|
||||
merge_children: bool = False,
|
||||
merge_leaves: bool = False,
|
||||
delete_children: bool = False,
|
||||
to_tree: Optional[Node] = None,
|
||||
):
|
||||
"""Shift or copy nodes from `from_paths` to `to_paths` *in-place*.
|
||||
|
||||
- Creates intermediate nodes if to path is not present
|
||||
- Able to copy node, defaults to False (nodes are shifted; not copied).
|
||||
- Able to skip nodes if from path is not found, defaults to False (from-nodes must be found; not skippable)
|
||||
- Able to override existing node if it exists, defaults to False (to-nodes must not exist; not overridden)
|
||||
- Able to merge children and remove intermediate parent node, defaults to False (nodes are shifted; not merged)
|
||||
- Able to merge only leaf nodes and remove all intermediate nodes, defaults to False (nodes are shifted; not merged)
|
||||
- Able to shift/copy node only and delete children, defaults to False (nodes are shifted/copied together with children).
|
||||
- Able to shift/copy nodes from one tree to another tree, defaults to None (shifting/copying happens within same tree)
|
||||
|
||||
For paths in `from_paths` and `to_paths`,
|
||||
- Path name can be with or without leading tree path separator symbol.
|
||||
- Path name can be partial path (trailing part of path) or node name.
|
||||
- Path name must be unique to one node.
|
||||
|
||||
For paths in `to_paths`,
|
||||
- Can set to empty string or None to delete the path in `from_paths`, note that ``copy`` must be set to False.
|
||||
|
||||
If ``merge_children=True``,
|
||||
- If `to_path` is not present, it shifts/copies children of `from_path`.
|
||||
- If `to_path` is present, and ``overriding=False``, original and new children are merged.
|
||||
- If `to_path` is present and ``overriding=True``, it behaves like overriding and only new children are retained.
|
||||
|
||||
If ``merge_leaves=True``,
|
||||
- If `to_path` is not present, it shifts/copies leaves of `from_path`.
|
||||
- If `to_path` is present, and ``overriding=False``, original children and leaves are merged.
|
||||
- If `to_path` is present and ``overriding=True``, it behaves like overriding and only new leaves are retained,
|
||||
original non-leaf nodes in `from_path` are retained.
|
||||
|
||||
Args:
|
||||
tree (Node): tree to modify
|
||||
from_paths (list): original paths to shift nodes from
|
||||
to_paths (list): new paths to shift nodes to
|
||||
sep (str): path separator for input paths, applies to `from_path` and `to_path`
|
||||
copy (bool): indicator to copy node, defaults to False
|
||||
skippable (bool): indicator to skip if from path is not found, defaults to False
|
||||
overriding (bool): indicator to override existing to path if there is clashes, defaults to False
|
||||
merge_children (bool): indicator to merge children and remove intermediate parent node, defaults to False
|
||||
merge_leaves (bool): indicator to merge leaf nodes and remove intermediate parent node(s), defaults to False
|
||||
delete_children (bool): indicator to shift/copy node only without children, defaults to False
|
||||
to_tree (Node): tree to copy to, defaults to None
|
||||
"""
|
||||
if merge_children and merge_leaves:
|
||||
raise ValueError(
|
||||
"Invalid shifting, can only specify one type of merging, check `merge_children` and `merge_leaves`"
|
||||
)
|
||||
if not (isinstance(from_paths, list) and isinstance(to_paths, list)):
|
||||
raise ValueError(
|
||||
"Invalid type, `from_paths` and `to_paths` should be list type"
|
||||
)
|
||||
if len(from_paths) != len(to_paths):
|
||||
raise ValueError(
|
||||
f"Paths are different length, input `from_paths` have {len(from_paths)} entries, "
|
||||
f"while output `to_paths` have {len(to_paths)} entries"
|
||||
)
|
||||
for from_path, to_path in zip(from_paths, to_paths):
|
||||
if to_path:
|
||||
if from_path.split(sep)[-1] != to_path.split(sep)[-1]:
|
||||
raise ValueError(
|
||||
f"Unable to assign from_path {from_path} to to_path {to_path}\n"
|
||||
f"Verify that `sep` is defined correctly for path\n"
|
||||
f"Alternatively, check that `from_path` and `to_path` is reassigning the same node"
|
||||
)
|
||||
|
||||
transfer_indicator = False
|
||||
node_type = tree.__class__
|
||||
tree_sep = tree.sep
|
||||
if to_tree:
|
||||
transfer_indicator = True
|
||||
node_type = to_tree.__class__
|
||||
tree_sep = to_tree.sep
|
||||
for from_path, to_path in zip(from_paths, to_paths):
|
||||
from_path = from_path.replace(sep, tree.sep)
|
||||
from_node = find_path(tree, from_path)
|
||||
|
||||
# From node not found
|
||||
if not from_node:
|
||||
if not skippable:
|
||||
raise NotFoundError(
|
||||
f"Unable to find from_path {from_path}\n"
|
||||
f"Set `skippable` to True to skip shifting for nodes not found"
|
||||
)
|
||||
else:
|
||||
logging.info(f"Unable to find from_path {from_path}")
|
||||
|
||||
# From node found
|
||||
else:
|
||||
# Node to be deleted
|
||||
if not to_path:
|
||||
to_node = None
|
||||
# Node to be copied/shifted
|
||||
else:
|
||||
to_path = to_path.replace(sep, tree_sep)
|
||||
if transfer_indicator:
|
||||
to_node = find_path(to_tree, to_path)
|
||||
else:
|
||||
to_node = find_path(tree, to_path)
|
||||
|
||||
# To node found
|
||||
if to_node:
|
||||
if from_node == to_node:
|
||||
if merge_children:
|
||||
parent = to_node.parent
|
||||
to_node.parent = None
|
||||
to_node = parent
|
||||
elif merge_leaves:
|
||||
to_node = to_node.parent
|
||||
else:
|
||||
raise TreeError(
|
||||
f"Attempting to shift the same node {from_node.node_name} back to the same position\n"
|
||||
f"Check from path {from_path} and to path {to_path}\n"
|
||||
f"Alternatively, set `merge_children` or `merge_leaves` to True if intermediate node is to be removed"
|
||||
)
|
||||
elif merge_children:
|
||||
# Specify override to remove existing node, else children are merged
|
||||
if not overriding:
|
||||
logging.info(
|
||||
f"Path {to_path} already exists and children are merged"
|
||||
)
|
||||
else:
|
||||
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_children = False
|
||||
elif merge_leaves:
|
||||
# Specify override to remove existing node, else leaves are merged
|
||||
if not overriding:
|
||||
logging.info(
|
||||
f"Path {to_path} already exists and leaves are merged"
|
||||
)
|
||||
else:
|
||||
logging.info(
|
||||
f"Path {to_path} already exists and its leaves be overridden by the merge"
|
||||
)
|
||||
del to_node.children
|
||||
else:
|
||||
if not overriding:
|
||||
raise TreeError(
|
||||
f"Path {to_path} already exists and unable to override\n"
|
||||
f"Set `overriding` to True to perform overrides\n"
|
||||
f"Alternatively, set `merge_children` to True if nodes are to be merged"
|
||||
)
|
||||
logging.info(
|
||||
f"Path {to_path} already exists and will be overridden"
|
||||
)
|
||||
parent = to_node.parent
|
||||
to_node.parent = None
|
||||
to_node = parent
|
||||
|
||||
# To node not found
|
||||
else:
|
||||
# Find parent node
|
||||
to_path_list = to_path.split(tree_sep)
|
||||
idx = 1
|
||||
to_path_parent = tree_sep.join(to_path_list[:-idx])
|
||||
if transfer_indicator:
|
||||
to_node = find_path(to_tree, to_path_parent)
|
||||
else:
|
||||
to_node = find_path(tree, to_path_parent)
|
||||
|
||||
# Create intermediate parent node, if applicable
|
||||
while (not to_node) & (idx + 1 < len(to_path_list)):
|
||||
idx += 1
|
||||
to_path_parent = sep.join(to_path_list[:-idx])
|
||||
if transfer_indicator:
|
||||
to_node = find_path(to_tree, to_path_parent)
|
||||
else:
|
||||
to_node = find_path(tree, to_path_parent)
|
||||
if not to_node:
|
||||
raise NotFoundError(
|
||||
f"Unable to find to_path {to_path}\n"
|
||||
f"Please specify valid path to shift node to"
|
||||
)
|
||||
for depth in range(len(to_path_list) - idx, len(to_path_list) - 1):
|
||||
intermediate_child_node = node_type(to_path_list[depth])
|
||||
intermediate_child_node.parent = to_node
|
||||
to_node = intermediate_child_node
|
||||
|
||||
# Reassign from_node to new parent
|
||||
if copy:
|
||||
logging.debug(f"Copying {from_node.node_name}")
|
||||
from_node = from_node.copy()
|
||||
if merge_children:
|
||||
logging.debug(
|
||||
f"Reassigning children from {from_node.node_name} to {to_node.node_name}"
|
||||
)
|
||||
for children in from_node.children:
|
||||
if delete_children:
|
||||
del children.children
|
||||
children.parent = to_node
|
||||
from_node.parent = None
|
||||
elif merge_leaves:
|
||||
logging.debug(
|
||||
f"Reassigning leaf nodes from {from_node.node_name} to {to_node.node_name}"
|
||||
)
|
||||
for children in from_node.leaves:
|
||||
children.parent = to_node
|
||||
else:
|
||||
if delete_children:
|
||||
del from_node.children
|
||||
from_node.parent = to_node
|
||||
Reference in New Issue
Block a user