Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ 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]
### Added
- Misc: Documentation plugin Termynal for code animation.
- Misc: Usage of `docstr-coverage`.
- Misc: Docstrings for nested functions to pass `docstr-coverage`.
### Changed
- Misc: Documentation CSS for h1 display for windows compatibility, modify the related links on main page.

## [0.16.1] - 2023-01-29
### Fixed
Expand Down
29 changes: 23 additions & 6 deletions bigtree/node/dagnode.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,12 +363,20 @@ def ancestors(self: T) -> Iterable[T]:
if not len(list(self.parents)):
return ()

def recursive_parent(node: T) -> Iterable[T]:
def _recursive_parent(node: T) -> Iterable[T]:
"""Recursively yield parent of current node, returns earliest to latest ancestor

Args:
node (DAGNode): current node

Returns:
(Iterable[DAGNode])
"""
for _node in node.parents:
yield from recursive_parent(_node)
yield from _recursive_parent(_node)
yield _node

ancestors = list(recursive_parent(self))
ancestors = list(_recursive_parent(self))
return list(dict.fromkeys(ancestors))

@property
Expand Down Expand Up @@ -531,18 +539,27 @@ def go_to(self: T, node: T) -> List[List[T]]:

self.__path: List[List[T]] = []

def recursive_path(_node: T, _path: List[T]) -> Optional[List[T]]:
def _recursive_path(_node: T, _path: List[T]) -> Optional[List[T]]:
"""Get path to specified node

Args:
_node (DAGNode): current node
_path (List[DAGNode]): current path, from start node to current node, excluding current node

Returns:
(List[DAGNode])
"""
if _node: # pragma: no cover
_path.append(_node)
if _node == node:
return _path
for _child in _node.children:
ans = recursive_path(_child, _path.copy())
ans = _recursive_path(_child, _path.copy())
if ans:
self.__path.append(ans)
return None

recursive_path(self, [])
_recursive_path(self, [])
return self.__path

def copy(self: T) -> T:
Expand Down
57 changes: 48 additions & 9 deletions bigtree/tree/construct.py
Original file line number Diff line number Diff line change
Expand Up @@ -740,9 +740,18 @@ def nested_dict_to_tree(
if not node_attrs:
raise ValueError("Dictionary does not contain any data, check `node_attrs`")

def recursive_add_child(
def _recursive_add_child(
child_dict: Dict[str, Any], parent_node: Optional[Node] = None
) -> Node:
"""Recursively add child to tree, given child attributes and parent node.

Args:
child_dict (Dict[str, Any]): child to be added to tree, from dictionary
parent_node (Node): parent node to be assigned to child node, defaults to None

Returns:
(Node)
"""
child_dict = child_dict.copy()
node_name = child_dict.pop(name_key)
node_children = child_dict.pop(child_key, [])
Expand All @@ -752,10 +761,10 @@ def recursive_add_child(
)
node = node_type(node_name, parent=parent_node, **child_dict)
for _child in node_children:
recursive_add_child(_child, parent_node=node)
_recursive_add_child(_child, parent_node=node)
return node

root_node = recursive_add_child(node_attrs)
root_node = _recursive_add_child(node_attrs)
return root_node


Expand Down Expand Up @@ -988,27 +997,40 @@ def dataframe_to_tree_by_relation(
else:
root_node = node_type(root_name)

def retrieve_attr(_row: Dict[str, Any]) -> Dict[str, Any]:
def _retrieve_attr(_row: Dict[str, Any]) -> Dict[str, Any]:
"""Retrieve node attributes from dictionary, remove parent and child column from dictionary.

Args:
_row (Dict[str, Any]): node attributes

Returns:
(Dict[str, Any])
"""
node_attrs = _row.copy()
node_attrs["name"] = node_attrs[child_col]
del node_attrs[child_col]
del node_attrs[parent_col]
_node_attrs = {k: v for k, v in node_attrs.items() if v is not None}
return _node_attrs

def recursive_create_child(parent_node: Node) -> None:
def _recursive_add_child(parent_node: Node) -> None:
"""Recursive add child to tree, given current node.

Args:
parent_node (Node): parent node
"""
child_rows = data[data[parent_col] == parent_node.node_name]

for row in child_rows.to_dict(orient="index").values():
child_node = node_type(**retrieve_attr(row))
child_node = node_type(**_retrieve_attr(row))
child_node.parent = parent_node
recursive_create_child(child_node)
_recursive_add_child(child_node)

# Create root node attributes
if len(root_row):
row = list(root_row.to_dict(orient="index").values())[0]
root_node.set_attrs(retrieve_attr(row))
recursive_create_child(root_node)
root_node.set_attrs(_retrieve_attr(row))
_recursive_add_child(root_node)
return root_node


Expand Down Expand Up @@ -1093,6 +1115,18 @@ def _create_node(
_depth_nodes: Dict[int, List[Node]],
_current_depth: int,
) -> Tuple[Node, int]:
"""Create node at checkpoint.

Args:
_new_node (Optional[Node]): existing node (to add length attribute), or nothing (to create a node)
_cumulative_string (str): cumulative string, contains either node name or length attribute
_unlabelled_node_counter (int): number of unlabelled nodes, updates and returns counter
_depth_nodes (Dict[int, List[Node]]): list of nodes at each depth
_current_depth (int): depth of current node or node to be created

Returns:
(Tuple[Node, int])
"""
if not _new_node:
if not _cumulative_string:
_cumulative_string = f"node{_unlabelled_node_counter}"
Expand All @@ -1116,6 +1150,11 @@ def _create_node(
return _new_node, _unlabelled_node_counter

def _raise_value_error(tree_idx: int) -> None:
"""Raise value error.

Raises:
ValueError
"""
raise ValueError(
f"String not properly closed, check `tree_string` at index {tree_idx}"
)
Expand Down
73 changes: 53 additions & 20 deletions bigtree/tree/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -789,7 +789,7 @@ def tree_to_newick(
attr_sep = attr_sep.value

def _serialize(item: Any) -> Any:
"""Serialize item if it contains special Newick characters
"""Serialize item if it contains special Newick characters.

Args:
item (Any): item to serialize
Expand Down Expand Up @@ -887,7 +887,12 @@ def tree_to_dict(
tree = tree.copy()
data_dict = {}

def recursive_append(node: T) -> None:
def _recursive_append(node: T) -> None:
"""Recursively iterate through node and its children to export to dictionary.

Args:
node (Node): current node
"""
if node:
if (
(not max_depth or node.depth <= max_depth)
Expand Down Expand Up @@ -915,9 +920,9 @@ def recursive_append(node: T) -> None:
data_child[v] = node.get_attr(k)
data_dict[node.path_name] = data_child
for _node in node.children:
recursive_append(_node)
_recursive_append(_node)

recursive_append(tree)
_recursive_append(tree)
return data_dict


Expand Down Expand Up @@ -960,7 +965,13 @@ def tree_to_nested_dict(
tree = tree.copy()
data_dict: Dict[str, List[Dict[str, Any]]] = {}

def recursive_append(node: T, parent_dict: Dict[str, Any]) -> None:
def _recursive_append(node: T, parent_dict: Dict[str, Any]) -> None:
"""Recursively iterate through node and its children to export to nested dictionary.

Args:
node (Node): current node
parent_dict (Dict[str, Any]): parent dictionary
"""
if node:
if not max_depth or node.depth <= max_depth:
data_child = {name_key: node.node_name}
Expand All @@ -981,9 +992,9 @@ def recursive_append(node: T, parent_dict: Dict[str, Any]) -> None:
parent_dict[child_key] = [data_child]

for _node in node.children:
recursive_append(_node, data_child)
_recursive_append(_node, data_child)

recursive_append(tree, data_dict)
_recursive_append(tree, data_dict)
return data_dict[child_key][0]


Expand Down Expand Up @@ -1044,7 +1055,12 @@ def tree_to_dataframe(
tree = tree.copy()
data_list = []

def recursive_append(node: T) -> None:
def _recursive_append(node: T) -> None:
"""Recursively iterate through node and its children to export to dataframe.

Args:
node (Node): current node
"""
if node:
if (
(not max_depth or node.depth <= max_depth)
Expand All @@ -1071,9 +1087,9 @@ def recursive_append(node: T) -> None:
data_child[v] = node.get_attr(k)
data_list.append(data_child)
for _node in node.children:
recursive_append(_node)
_recursive_append(_node)

recursive_append(tree)
_recursive_append(tree)
return pd.DataFrame(data_list)


Expand Down Expand Up @@ -1212,9 +1228,13 @@ def tree_to_dot(

name_dict: Dict[str, List[str]] = collections.defaultdict(list)

def recursive_create_node_and_edges(
parent_name: Optional[str], child_node: T
) -> None:
def _recursive_append(parent_name: Optional[str], child_node: T) -> None:
"""Recursively iterate through node and its children to export to dot by creating node and edges.

Args:
parent_name (Optional[str]): parent name
child_node (Node): current node
"""
_node_style = node_style.copy()
_edge_style = edge_style.copy()

Expand All @@ -1241,9 +1261,9 @@ def recursive_create_node_and_edges(
_graph.add_edge(edge)
for child in child_node.children:
if child:
recursive_create_node_and_edges(child_name, child)
_recursive_append(child_name, child)

recursive_create_node_and_edges(None, _tree.root)
_recursive_append(None, _tree.root)
return _graph


Expand Down Expand Up @@ -1522,6 +1542,17 @@ def _construct_style(
_node_border_colour: str,
_node_border_width: float,
) -> str:
"""Construct style for Mermaid.

Args:
_style_name (str): style name
_node_colour (str): node colour
_node_border_colour (str): node border colour
_node_border_width (float): node border width

Returns:
(str)
"""
style = []
if _node_colour:
style.append(f"fill:{_node_colour}")
Expand All @@ -1539,6 +1570,8 @@ def _construct_style(
styles.append(default_style)

class MermaidNode(Node):
"""Mermaid Node, adds property `mermaid_name`"""

@property
def mermaid_name(self) -> str:
"""Reference name for MermaidNode, must be unique for each node.
Expand All @@ -1550,7 +1583,7 @@ def mermaid_name(self) -> str:
return "0"
return f"{self.parent.mermaid_name}-{self.parent.children.index(self)}"

def get_attr(
def _get_attr(
_node: MermaidNode,
attr_parameter: str | Callable[[MermaidNode], str],
default_parameter: str,
Expand Down Expand Up @@ -1579,24 +1612,24 @@ def get_attr(
# Get custom style (node_shape_attr)
_parent_node_name = ""
if node.parent.is_root:
_parent_node_shape_choice = get_attr(
_parent_node_shape_choice = _get_attr(
node.parent, node_shape_attr, node_shape # type: ignore
)
_parent_node_shape = node_shapes[_parent_node_shape_choice]
_parent_node_name = _parent_node_shape.format(label=node.parent.name)
_node_shape_choice = get_attr(node, node_shape_attr, node_shape) # type: ignore
_node_shape_choice = _get_attr(node, node_shape_attr, node_shape) # type: ignore
_node_shape = node_shapes[_node_shape_choice]
_node_name = _node_shape.format(label=node.name)

# Get custom style (edge_arrow_attr, edge_label)
_arrow_choice = get_attr(node, edge_arrow_attr, edge_arrow) # type: ignore
_arrow_choice = _get_attr(node, edge_arrow_attr, edge_arrow) # type: ignore
_arrow = edge_arrows[_arrow_choice]
_arrow_label = (
f"|{node.get_attr(edge_label)}|" if node.get_attr(edge_label) else ""
)

# Get custom style (node_attr)
_flow_style = get_attr(node, node_attr, "") # type: ignore
_flow_style = _get_attr(node, node_attr, "") # type: ignore
if _flow_style:
_flow_style_class = f"""class{node.get_attr("mermaid_name")}"""
styles.append(
Expand Down
12 changes: 9 additions & 3 deletions bigtree/tree/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,23 @@ def clone_tree(tree: BaseNode, node_type: Type[BaseNodeT]) -> BaseNodeT:
root_info = dict(tree.root.describe(exclude_prefix="_"))
root_node = node_type(**root_info)

def recursive_add_child(
def _recursive_add_child(
_new_parent_node: BaseNodeT, _parent_node: BaseNode
) -> None:
"""Recursively clone current node

Args:
_new_parent_node (BaseNode): cloned parent node
_parent_node (BaseNode): parent node to be cloned
"""
for _child in _parent_node.children:
if _child:
child_info = dict(_child.describe(exclude_prefix="_"))
child_node = node_type(**child_info)
child_node.parent = _new_parent_node
recursive_add_child(child_node, _child)
_recursive_add_child(child_node, _child)

recursive_add_child(root_node, tree.root)
_recursive_add_child(root_node, tree.root)
return root_node


Expand Down
Loading