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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ 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]
### Changed:
- Misc: Import module instead of functions, following Google Python Style Guide.

## [0.21.0] - 2024-08-26
### Added:
Expand Down
10 changes: 5 additions & 5 deletions bigtree/binarytree/construct.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
from typing import List, Type, TypeVar

from bigtree.node.binarynode import BinaryNode
from bigtree.utils.assertions import assert_length_not_empty
from bigtree.node import binarynode
from bigtree.utils import assertions

__all__ = ["list_to_binarytree"]

T = TypeVar("T", bound=BinaryNode)
T = TypeVar("T", bound=binarynode.BinaryNode)


def list_to_binarytree(
heapq_list: List[int],
node_type: Type[T] = BinaryNode, # type: ignore[assignment]
node_type: Type[T] = binarynode.BinaryNode, # type: ignore[assignment]
) -> T:
"""Construct tree from a list of numbers (int or float) in heapq format.

Expand Down Expand Up @@ -41,7 +41,7 @@ def list_to_binarytree(
Returns:
(BinaryNode)
"""
assert_length_not_empty(heapq_list, "Input list", "heapq_list")
assertions.assert_length_not_empty(heapq_list, "Input list", "heapq_list")

root_node = node_type(heapq_list[0])
node_list = [root_node]
Expand Down
46 changes: 21 additions & 25 deletions bigtree/dag/construct.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,8 @@

from typing import Any, Dict, List, Tuple, Type, TypeVar

from bigtree.node.dagnode import DAGNode
from bigtree.utils.assertions import (
assert_dataframe_no_duplicate_attribute,
assert_dataframe_not_empty,
assert_length_not_empty,
assert_not_reserved_keywords,
filter_attributes,
isnull,
)
from bigtree.utils.exceptions import optional_dependencies_pandas
from bigtree.node import dagnode
from bigtree.utils import assertions, exceptions

try:
import pandas as pd
Expand All @@ -22,12 +14,12 @@

__all__ = ["list_to_dag", "dict_to_dag", "dataframe_to_dag"]

T = TypeVar("T", bound=DAGNode)
T = TypeVar("T", bound=dagnode.DAGNode)


def list_to_dag(
relations: List[Tuple[str, str]],
node_type: Type[T] = DAGNode, # type: ignore[assignment]
node_type: Type[T] = dagnode.DAGNode, # type: ignore[assignment]
) -> T:
"""Construct DAG from list of tuples containing parent-child names.
Note that node names must be unique.
Expand All @@ -46,10 +38,10 @@ def list_to_dag(
Returns:
(DAGNode)
"""
assert_length_not_empty(relations, "Input list", "relations")
assertions.assert_length_not_empty(relations, "Input list", "relations")

node_dict: Dict[str, T] = dict()
parent_node: T = DAGNode() # type: ignore[assignment]
parent_node: T = dagnode.DAGNode() # type: ignore[assignment]

for parent_name, child_name in relations:
if parent_name not in node_dict:
Expand All @@ -71,7 +63,7 @@ def list_to_dag(
def dict_to_dag(
relation_attrs: Dict[str, Any],
parent_key: str = "parents",
node_type: Type[T] = DAGNode, # type: ignore[assignment]
node_type: Type[T] = dagnode.DAGNode, # type: ignore[assignment]
) -> T:
"""Construct DAG from nested dictionary, ``key``: child name, ``value``: dictionary of parent names, attribute
name, and attribute value.
Expand Down Expand Up @@ -99,7 +91,7 @@ def dict_to_dag(
Returns:
(DAGNode)
"""
assert_length_not_empty(relation_attrs, "Dictionary", "relation_attrs")
assertions.assert_length_not_empty(relation_attrs, "Dictionary", "relation_attrs")

node_dict: Dict[str, T] = dict()
parent_node: T | None = None
Expand All @@ -109,7 +101,9 @@ def dict_to_dag(
parent_names: List[str] = []
if parent_key in node_attrs:
parent_names = node_attrs.pop(parent_key)
assert_not_reserved_keywords(node_attrs, ["parent", "parents", "children"])
assertions.assert_not_reserved_keywords(
node_attrs, ["parent", "parents", "children"]
)

if child_name in node_dict:
child_node = node_dict[child_name]
Expand All @@ -131,13 +125,13 @@ def dict_to_dag(
return parent_node


@optional_dependencies_pandas
@exceptions.optional_dependencies_pandas
def dataframe_to_dag(
data: pd.DataFrame,
child_col: str = "",
parent_col: str = "",
attribute_cols: List[str] = [],
node_type: Type[T] = DAGNode, # type: ignore[assignment]
node_type: Type[T] = dagnode.DAGNode, # type: ignore[assignment]
) -> T:
"""Construct DAG from pandas DataFrame.
Note that node names must be unique.
Expand Down Expand Up @@ -180,7 +174,7 @@ def dataframe_to_dag(
Returns:
(DAGNode)
"""
assert_dataframe_not_empty(data)
assertions.assert_dataframe_not_empty(data)

if not child_col:
child_col = data.columns[0]
Expand All @@ -194,30 +188,32 @@ def dataframe_to_dag(
attribute_cols = list(data.columns)
attribute_cols.remove(child_col)
attribute_cols.remove(parent_col)
assert_not_reserved_keywords(attribute_cols, ["parent", "parents", "children"])
assertions.assert_not_reserved_keywords(
attribute_cols, ["parent", "parents", "children"]
)

data = data[[child_col, parent_col] + attribute_cols].copy()

assert_dataframe_no_duplicate_attribute(
assertions.assert_dataframe_no_duplicate_attribute(
data, "child name", child_col, attribute_cols
)
if sum(data[child_col].isnull()):
raise ValueError(f"Child name cannot be empty, check column: {child_col}")

node_dict: Dict[str, T] = dict()
parent_node: T = DAGNode() # type: ignore[assignment]
parent_node: T = dagnode.DAGNode() # type: ignore[assignment]

for row in data.reset_index(drop=True).to_dict(orient="index").values():
child_name = row[child_col]
parent_name = row[parent_col]
node_attrs = filter_attributes(
node_attrs = assertions.filter_attributes(
row, omit_keys=["name", child_col, parent_col], omit_null_values=True
)
child_node = node_dict.get(child_name, node_type(child_name, **node_attrs))
child_node.set_attrs(node_attrs)
node_dict[child_name] = child_node

if not isnull(parent_name):
if not assertions.isnull(parent_name):
parent_node = node_dict.get(parent_name, node_type(parent_name))
node_dict[parent_name] = parent_node
child_node.parents = [parent_node]
Expand Down
25 changes: 10 additions & 15 deletions bigtree/dag/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,8 @@

from typing import Any, Dict, List, Tuple, TypeVar, Union

from bigtree.node.dagnode import DAGNode
from bigtree.utils.assertions import assert_tree_type
from bigtree.utils.exceptions import (
optional_dependencies_image,
optional_dependencies_pandas,
)
from bigtree.utils.iterators import dag_iterator
from bigtree.node import dagnode
from bigtree.utils import assertions, exceptions, iterators

try:
import pandas as pd
Expand All @@ -27,7 +22,7 @@
__all__ = ["dag_to_list", "dag_to_dict", "dag_to_dataframe", "dag_to_dot"]


T = TypeVar("T", bound=DAGNode)
T = TypeVar("T", bound=dagnode.DAGNode)


def dag_to_list(
Expand All @@ -52,7 +47,7 @@ def dag_to_list(
(List[Tuple[str, str]])
"""
relations = []
for parent_node, child_node in dag_iterator(dag):
for parent_node, child_node in iterators.dag_iterator(dag):
relations.append((parent_node.node_name, child_node.node_name))
return relations

Expand Down Expand Up @@ -90,7 +85,7 @@ def dag_to_dict(
dag = dag.copy()
data_dict = {}

for parent_node, child_node in dag_iterator(dag):
for parent_node, child_node in iterators.dag_iterator(dag):
if parent_node.is_root:
data_parent: Dict[str, Any] = {}
if all_attrs:
Expand Down Expand Up @@ -119,7 +114,7 @@ def dag_to_dict(
return data_dict


@optional_dependencies_pandas
@exceptions.optional_dependencies_pandas
def dag_to_dataframe(
dag: T,
name_col: str = "name",
Expand Down Expand Up @@ -160,7 +155,7 @@ def dag_to_dataframe(
dag = dag.copy()
data_list: List[Dict[str, Any]] = []

for parent_node, child_node in dag_iterator(dag):
for parent_node, child_node in iterators.dag_iterator(dag):
if parent_node.is_root:
data_parent = {name_col: parent_node.node_name, parent_col: None}
if all_attrs:
Expand All @@ -186,7 +181,7 @@ def dag_to_dataframe(
return pd.DataFrame(data_list).drop_duplicates().reset_index(drop=True)


@optional_dependencies_image("pydot")
@exceptions.optional_dependencies_image("pydot")
def dag_to_dot(
dag: Union[T, List[T]],
rankdir: str = "TB",
Expand Down Expand Up @@ -257,10 +252,10 @@ def dag_to_dot(
dag = [dag]

for _dag in dag:
assert_tree_type(_dag, DAGNode, "DAGNode")
assertions.assert_tree_type(_dag, dagnode.DAGNode, "DAGNode")
_dag = _dag.copy()

for parent_node, child_node in dag_iterator(_dag):
for parent_node, child_node in iterators.dag_iterator(_dag):
_node_style = node_style.copy()
_edge_style = edge_style.copy()

Expand Down
33 changes: 20 additions & 13 deletions bigtree/node/basenode.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
from typing import Any, Dict, Generator, Iterable, List, Optional, Set, Tuple, TypeVar

from bigtree.globals import ASSERTIONS
from bigtree.utils.exceptions import CorruptedTreeError, LoopError, TreeError
from bigtree.utils.iterators import preorder_iter
from bigtree.utils import exceptions, iterators

try:
import matplotlib.pyplot as plt
Expand Down Expand Up @@ -164,13 +163,15 @@ def __check_parent_loop(self, new_parent: T) -> None:
"""
if new_parent is not None:
if new_parent is self:
raise LoopError("Error setting parent: Node cannot be parent of itself")
raise exceptions.LoopError(
"Error setting parent: Node cannot be parent of itself"
)
if any(
ancestor is self
for ancestor in new_parent.ancestors
if new_parent.ancestors
):
raise LoopError(
raise exceptions.LoopError(
"Error setting parent: Node cannot be ancestor of itself"
)

Expand Down Expand Up @@ -205,7 +206,7 @@ def parent(self: T, new_parent: T) -> None:
if not any(
child is self for child in current_parent.children
): # pragma: no cover
raise CorruptedTreeError(
raise exceptions.CorruptedTreeError(
"Error setting parent: Node does not exist as children of its parent"
)
current_child_idx = current_parent.__children.index(self)
Expand All @@ -227,7 +228,7 @@ def parent(self: T, new_parent: T) -> None:
self.__parent = current_parent
if current_child_idx is not None:
current_parent.__children.insert(current_child_idx, self)
raise TreeError(exc_info)
raise exceptions.TreeError(exc_info)

def __pre_assign_parent(self, new_parent: T) -> None:
"""Custom method to check before attaching parent
Expand Down Expand Up @@ -305,15 +306,17 @@ def __check_children_loop(self: T, new_children: Iterable[T]) -> None:

# Check for loop and tree structure
if new_child is self:
raise LoopError("Error setting child: Node cannot be child of itself")
raise exceptions.LoopError(
"Error setting child: Node cannot be child of itself"
)
if any(child is new_child for child in self.ancestors):
raise LoopError(
raise exceptions.LoopError(
"Error setting child: Node cannot be ancestor of itself"
)

# Check for duplicate children
if id(new_child) in seen_children:
raise TreeError(
raise exceptions.TreeError(
"Error setting child: Node cannot be added multiple times as a child"
)
else:
Expand Down Expand Up @@ -376,7 +379,7 @@ def children(self: T, new_children: List[T] | Tuple[T] | Set[T]) -> None:
self.__children = current_children
for child in current_children:
child.__parent = self
raise TreeError(exc_info)
raise exceptions.TreeError(exc_info)

@children.deleter
def children(self) -> None:
Expand Down Expand Up @@ -422,7 +425,9 @@ def descendants(self: T) -> Iterable[T]:
Returns:
(Iterable[Self])
"""
yield from preorder_iter(self, filter_condition=lambda _node: _node != self)
yield from iterators.preorder_iter(
self, filter_condition=lambda _node: _node != self
)

@property
def leaves(self: T) -> Iterable[T]:
Expand All @@ -431,7 +436,9 @@ def leaves(self: T) -> Iterable[T]:
Returns:
(Iterable[Self])
"""
yield from preorder_iter(self, filter_condition=lambda _node: _node.is_leaf)
yield from iterators.preorder_iter(
self, filter_condition=lambda _node: _node.is_leaf
)

@property
def siblings(self: T) -> Iterable[T]:
Expand Down Expand Up @@ -687,7 +694,7 @@ def go_to(self: T, node: T) -> Iterable[T]:
f"Expect node to be BaseNode type, received input type {type(node)}"
)
if self.root != node.root:
raise TreeError(
raise exceptions.TreeError(
f"Nodes are not from the same tree. Check {self} and {node}"
)
if self == node:
Expand Down
Loading