Skip to content

Commit 34b6cb9

Browse files
authored
Merge pull request #225 from kayjan/abstract-functions
Abstract functions
2 parents 33bb781 + c038692 commit 34b6cb9

File tree

11 files changed

+214
-64
lines changed

11 files changed

+214
-64
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
8+
### Changed:
9+
- DAG Constructor: `list_to_dag` and `dict_to_dag` does not rely on `dataframe_to_dag` as pandas dataframe operation
10+
is phased out.
11+
### Fixed:
12+
- DAG Constructor: Handle cases where reserved keywords are part of attribute upon creation and throw error accordingly.
813

914
## [0.17.1] - 2024-04-23
1015
### Fixed

bigtree/binarytree/construct.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,7 @@ def list_to_binarytree(
4545
node_list = [root_node]
4646
for idx, num in enumerate(heapq_list):
4747
if idx:
48-
if idx % 2:
49-
parent_idx = int((idx - 1) / 2)
50-
else:
51-
parent_idx = int((idx - 2) / 2)
48+
parent_idx = int((idx + 1) / 2) - 1
5249
node = node_type(num, parent=node_list[parent_idx]) # type: ignore
5350
node_list.append(node)
5451
return root_node

bigtree/dag/construct.py

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from bigtree.utils.assertions import (
77
assert_dataframe_no_duplicate_attribute,
88
assert_dataframe_not_empty,
9-
assert_dictionary_not_empty,
9+
assert_key_not_in_dict_or_df,
1010
assert_length_not_empty,
1111
filter_attributes,
1212
isnull,
@@ -21,7 +21,6 @@
2121
__all__ = ["list_to_dag", "dict_to_dag", "dataframe_to_dag"]
2222

2323

24-
@optional_dependencies_pandas
2524
def list_to_dag(
2625
relations: List[Tuple[str, str]],
2726
node_type: Type[DAGNode] = DAGNode,
@@ -45,13 +44,26 @@ def list_to_dag(
4544
"""
4645
assert_length_not_empty(relations, "Input list", "relations")
4746

48-
relation_data = pd.DataFrame(relations, columns=["parent", "child"])
49-
return dataframe_to_dag(
50-
relation_data, child_col="child", parent_col="parent", node_type=node_type
51-
)
47+
node_dict: Dict[str, DAGNode] = dict()
48+
parent_node = DAGNode()
49+
50+
for parent_name, child_name in relations:
51+
if parent_name not in node_dict:
52+
parent_node = node_type(parent_name)
53+
node_dict[parent_name] = parent_node
54+
else:
55+
parent_node = node_dict[parent_name]
56+
if child_name not in node_dict:
57+
child_node = node_type(child_name)
58+
node_dict[child_name] = child_node
59+
else:
60+
child_node = node_dict[child_name]
61+
62+
child_node.parents = [parent_node]
63+
64+
return parent_node
5265

5366

54-
@optional_dependencies_pandas
5567
def dict_to_dag(
5668
relation_attrs: Dict[str, Any],
5769
parent_key: str = "parents",
@@ -83,22 +95,36 @@ def dict_to_dag(
8395
Returns:
8496
(DAGNode)
8597
"""
86-
assert_dictionary_not_empty(relation_attrs, "relation_attrs")
98+
assert_length_not_empty(relation_attrs, "Dictionary", "relation_attrs")
99+
100+
node_dict: Dict[str, DAGNode] = dict()
101+
parent_node: DAGNode | None = None
102+
103+
for child_name, node_attrs in relation_attrs.items():
104+
node_attrs = node_attrs.copy()
105+
parent_names: List[str] = []
106+
if parent_key in node_attrs:
107+
parent_names = node_attrs.pop(parent_key)
108+
assert_key_not_in_dict_or_df(node_attrs, ["parent", "parents", "children"])
109+
110+
if child_name in node_dict:
111+
child_node = node_dict[child_name]
112+
child_node.set_attrs(node_attrs)
113+
else:
114+
child_node = node_type(child_name, **node_attrs)
115+
node_dict[child_name] = child_node
116+
117+
for parent_name in parent_names:
118+
parent_node = node_dict.get(parent_name, node_type(parent_name))
119+
node_dict[parent_name] = parent_node
120+
child_node.parents = [parent_node]
87121

88-
# Convert dictionary to dataframe
89-
data = pd.DataFrame(relation_attrs).T.rename_axis("_tmp_child").reset_index()
90-
if parent_key not in data:
122+
if parent_node is None:
91123
raise ValueError(
92124
f"Parent key {parent_key} not in dictionary, check `relation_attrs` and `parent_key`"
93125
)
94126

95-
data = data.explode(parent_key)
96-
return dataframe_to_dag(
97-
data,
98-
child_col="_tmp_child",
99-
parent_col=parent_key,
100-
node_type=node_type,
101-
)
127+
return parent_node
102128

103129

104130
@optional_dependencies_pandas
@@ -164,6 +190,7 @@ def dataframe_to_dag(
164190
attribute_cols = list(data.columns)
165191
attribute_cols.remove(child_col)
166192
attribute_cols.remove(parent_col)
193+
assert_key_not_in_dict_or_df(attribute_cols, ["parent", "parents", "children"])
167194

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

bigtree/dag/export.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from typing import Any, Dict, List, Tuple, TypeVar, Union
44

55
from bigtree.node.dagnode import DAGNode
6+
from bigtree.utils.assertions import assert_tree_type
67
from bigtree.utils.exceptions import (
78
optional_dependencies_image,
89
optional_dependencies_pandas,
@@ -265,10 +266,7 @@ def dag_to_dot(
265266
dag = [dag]
266267

267268
for _dag in dag:
268-
if not isinstance(_dag, DAGNode):
269-
raise TypeError(
270-
"Tree should be of type `DAGNode`, or inherit from `DAGNode`"
271-
)
269+
assert_tree_type(_dag, DAGNode, "DAGNode")
272270
_dag = _dag.copy()
273271

274272
for parent_node, child_node in dag_iterator(_dag):

bigtree/tree/construct.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
assert_dataframe_no_duplicate_attribute,
1111
assert_dataframe_no_duplicate_children,
1212
assert_dataframe_not_empty,
13-
assert_dictionary_not_empty,
1413
assert_length_not_empty,
1514
filter_attributes,
1615
isnull,
@@ -185,7 +184,7 @@ def add_dict_to_tree_by_path(
185184
Returns:
186185
(Node)
187186
"""
188-
assert_dictionary_not_empty(path_attrs, "path_attrs")
187+
assert_length_not_empty(path_attrs, "Dictionary", "path_attrs")
189188

190189
root_node = tree.root
191190

@@ -232,7 +231,7 @@ def add_dict_to_tree_by_name(tree: Node, name_attrs: Dict[str, Dict[str, Any]])
232231
"""
233232
from bigtree.tree.search import findall
234233

235-
assert_dictionary_not_empty(name_attrs, "name_attrs")
234+
assert_length_not_empty(name_attrs, "Dictionary", "name_attrs")
236235

237236
attr_dict_names = set(name_attrs.keys())
238237

@@ -642,7 +641,7 @@ def dict_to_tree(
642641
Returns:
643642
(Node)
644643
"""
645-
assert_dictionary_not_empty(path_attrs, "path_attrs")
644+
assert_length_not_empty(path_attrs, "Dictionary", "path_attrs")
646645

647646
# Initial tree
648647
root_name = list(path_attrs.keys())[0].lstrip(sep).rstrip(sep).split(sep)[0]
@@ -724,7 +723,7 @@ def nested_dict_to_tree(
724723
Returns:
725724
(Node)
726725
"""
727-
assert_dictionary_not_empty(node_attrs, "node_attrs")
726+
assert_length_not_empty(node_attrs, "Dictionary", "node_attrs")
728727

729728
def _recursive_add_child(
730729
child_dict: Dict[str, Any], parent_node: Optional[Node] = None

bigtree/tree/export.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
assert_key_in_dict,
99
assert_str_in_list,
1010
assert_style_in_dict,
11+
assert_tree_type,
1112
isnull,
1213
)
1314
from bigtree.utils.constants import ExportConstants, MermaidConstants, NewickCharacter
@@ -1223,8 +1224,7 @@ def tree_to_dot(
12231224
tree = [tree]
12241225

12251226
for _tree in tree:
1226-
if not isinstance(_tree, Node):
1227-
raise TypeError("Tree should be of type `Node`, or inherit from `Node`")
1227+
assert_tree_type(_tree, Node, "Node")
12281228

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

bigtree/tree/helper.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from bigtree.tree.construct import add_dict_to_tree_by_path, dataframe_to_tree
88
from bigtree.tree.export import tree_to_dataframe
99
from bigtree.tree.search import find_path
10+
from bigtree.utils.assertions import assert_tree_type
1011
from bigtree.utils.exceptions import NotFoundError
1112
from bigtree.utils.iterators import levelordergroup_iter
1213

@@ -34,8 +35,7 @@ def clone_tree(tree: BaseNode, node_type: Type[BaseNodeT]) -> BaseNodeT:
3435
Returns:
3536
(BaseNode)
3637
"""
37-
if not isinstance(tree, BaseNode):
38-
raise TypeError("Tree should be of type `BaseNode`, or inherit from `BaseNode`")
38+
assert_tree_type(tree, BaseNode, "BaseNode")
3939

4040
# Start from root
4141
root_info = dict(tree.root.describe(exclude_prefix="_"))

bigtree/utils/assertions.py

Lines changed: 60 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,27 @@
11
from __future__ import annotations
22

3-
import math
4-
from typing import Any, Dict, List, Union
3+
from typing import TYPE_CHECKING, Any, Dict, List, Sized, Type, Union
54

6-
try:
5+
if TYPE_CHECKING:
76
import pandas as pd
8-
except ImportError: # pragma: no cover
9-
pd = None
7+
8+
from bigtree.node.basenode import BaseNode
9+
from bigtree.node.dagnode import DAGNode
10+
from bigtree.node.node import Node
11+
12+
13+
__all__ = [
14+
"assert_style_in_dict",
15+
"assert_str_in_list",
16+
"assert_key_in_dict",
17+
"assert_length_not_empty",
18+
"assert_dataframe_not_empty",
19+
"assert_dataframe_no_duplicate_attribute",
20+
"assert_dataframe_no_duplicate_children",
21+
"assert_tree_type",
22+
"isnull",
23+
"filter_attributes",
24+
]
1025

1126

1227
def assert_style_in_dict(
@@ -43,6 +58,23 @@ def assert_str_in_list(
4358
)
4459

4560

61+
def assert_key_not_in_dict_or_df(
62+
parameter_dict: Union[Dict[str, Any], pd.DataFrame],
63+
not_accepted_parameters: List[str],
64+
) -> None:
65+
"""Raise ValueError is parameter is in key of dictionary
66+
67+
Args:
68+
parameter_dict (Dict[str, Any]/pd.DataFrame): argument input for parameter
69+
not_accepted_parameters (List[str]): list of not accepted parameters
70+
"""
71+
for parameter in parameter_dict:
72+
if parameter in not_accepted_parameters:
73+
raise ValueError(
74+
f"Invalid input, check `{parameter}` is not a valid key as it is a reserved keyword"
75+
)
76+
77+
4678
def assert_key_in_dict(
4779
parameter_name: str,
4880
parameter: Any,
@@ -61,13 +93,11 @@ def assert_key_in_dict(
6193
)
6294

6395

64-
def assert_length_not_empty(
65-
data: Union[str, List[Any]], argument_name: str, argument: str
66-
) -> None:
67-
"""Raise ValueError if data (str, list, or iterable) does not have length
96+
def assert_length_not_empty(data: Sized, argument_name: str, argument: str) -> None:
97+
"""Raise ValueError if data does not have length
6898
6999
Args:
70-
data (str/List[Any]): data to check
100+
data (Sized): data to check
71101
argument_name: argument name for data, for error message
72102
argument (str): argument for data, for error message
73103
"""
@@ -77,17 +107,6 @@ def assert_length_not_empty(
77107
)
78108

79109

80-
def assert_dictionary_not_empty(data_dict: Dict[Any, Any], argument: str) -> None:
81-
"""Raise ValueError is dictionary is empty
82-
83-
Args:
84-
data_dict (Dict[Any, Any]): dictionary to check
85-
argument (str): argument for dictionary, for error message
86-
"""
87-
if not len(data_dict):
88-
raise ValueError(f"Dictionary does not contain any data, check `{argument}`")
89-
90-
91110
def assert_dataframe_not_empty(data: pd.DataFrame) -> None:
92111
"""Raise ValueError is dataframe is empty
93112
@@ -158,6 +177,24 @@ def assert_dataframe_no_duplicate_children(
158177
)
159178

160179

180+
def assert_tree_type(
181+
tree: Union[BaseNode, Node, DAGNode],
182+
tree_type: Union[Type[BaseNode], Type[Node], Type[DAGNode]],
183+
tree_type_name: str,
184+
) -> None:
185+
"""Raise TypeError is tree is not of `tree_type`
186+
187+
Args:
188+
tree (Union["BaseNode", "Node", "DAGNode"]): tree to check
189+
tree_type: tree type to assert for
190+
tree_type_name (str): tree type name
191+
"""
192+
if not isinstance(tree, tree_type):
193+
raise TypeError(
194+
f"Tree should be of type `{tree_type_name}`, or inherit from `{tree_type_name}`"
195+
)
196+
197+
161198
def isnull(value: Any) -> bool:
162199
"""Check if value is null
163200
@@ -167,6 +204,8 @@ def isnull(value: Any) -> bool:
167204
Returns:
168205
(bool)
169206
"""
207+
import math
208+
170209
if not value or (isinstance(value, float) and math.isnan(value)):
171210
return True
172211
return False

mkdocs.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,9 @@ theme:
9898
plugins:
9999
- glightbox
100100
- search
101-
- social:
102-
cards_layout_options:
103-
logo: docs/_static/favicon.svg
101+
# - social:
102+
# cards_layout_options:
103+
# logo: docs/_static/favicon.svg
104104
- mkdocstrings:
105105
handlers:
106106
python:

0 commit comments

Comments
 (0)