Skip to content

Commit 07ad11a

Browse files
authored
Merge pull request #186 from kayjan/optional-assertions
Optional assertions
2 parents 5d87c3f + 9dbaaa3 commit 07ad11a

11 files changed

+351
-19
lines changed

CHANGELOG.md

+10-6
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,21 @@ 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+
9+
## [0.16.2] - 2024-02-06
810
### Added
911
- Misc: Documentation plugin Termynal for code animation.
1012
- Misc: Usage of `docstr-coverage`.
1113
- Misc: Docstrings for nested functions to pass `docstr-coverage`.
1214
### Changed
15+
- [#185] BaseNode: Make assertion checks optional.
1316
- Misc: Documentation CSS for h1 display for windows compatibility, modify the related links on main page.
1417

15-
## [0.16.1] - 2023-01-29
18+
## [0.16.1] - 2024-01-29
1619
### Fixed
1720
- Misc: Compatibility of mkdocs with readthedocs.
1821

19-
## [0.16.0] - 2023-01-28
22+
## [0.16.0] - 2024-01-28
2023
### Added
2124
- Misc: Documentation using mkdocs.
2225
### Changed
@@ -25,7 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2528
### Fixed
2629
- Misc: Docstring bullet point alignment, images compatibility with markdown.
2730

28-
## [0.15.7] - 2023-01-26
31+
## [0.15.7] - 2024-01-26
2932
### Added
3033
- Misc: Sphinx documentation to support mermaid markdown images, reflect CHANGELOG section, add more emojis.
3134
### Changed
@@ -35,7 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3538
### Fixed
3639
- Tree Exporter: `hprint_tree` and `hyield_tree` to be compatible with `BinaryNode` where child nodes can be None type.
3740

38-
## [0.15.6] - 2023-01-20
41+
## [0.15.6] - 2024-01-20
3942
### Added
4043
- DAGNode: Able to access and delete node children via name with square bracket accessor with `__getitem__` and `__delitem__` magic methods.
4144
- DAGNode: Able to delete all children for a node.
@@ -47,7 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4750
- Misc: Documentation enhancement to split README into multiple files.
4851
- Misc: New Sphinx documentation theme.
4952

50-
## [0.15.5] - 2023-01-17
53+
## [0.15.5] - 2024-01-17
5154
### Changed
5255
- Misc: Neater handling of strings for tests.
5356
- Misc: Better examples for merging trees and weighted trees in Sphinx documentation.
@@ -492,7 +495,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
492495
- Utility Iterator: Tree traversal methods.
493496
- Workflow To Do App: Tree use case with to-do list implementation.
494497

495-
[Unreleased]: https://github.com/kayjan/bigtree/compare/0.16.1...HEAD
498+
[Unreleased]: https://github.com/kayjan/bigtree/compare/0.16.2...HEAD
499+
[0.16.2]: https://github.com/kayjan/bigtree/compare/0.16.1...0.16.2
496500
[0.16.1]: https://github.com/kayjan/bigtree/compare/0.16.0...0.16.1
497501
[0.16.0]: https://github.com/kayjan/bigtree/compare/0.15.7...0.16.0
498502
[0.15.7]: https://github.com/kayjan/bigtree/compare/0.15.6...0.15.7

bigtree/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "0.16.1"
1+
__version__ = "0.16.2"
22

33
from bigtree.binarytree.construct import list_to_binarytree
44
from bigtree.dag.construct import dataframe_to_dag, dict_to_dag, list_to_dag
@@ -75,4 +75,4 @@
7575
from bigtree.workflows.app_calendar import Calendar
7676
from bigtree.workflows.app_todo import AppToDo
7777

78-
sphinx_versions = ["latest", "0.16.1", "0.15.7", "0.14.8"]
78+
sphinx_versions = ["latest", "0.16.2", "0.15.7", "0.14.8"]

bigtree/globals.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import os
2+
3+
ASSERTIONS: bool = bool(os.environ.get("BIGTREE_CONF_ASSERTIONS", True))

bigtree/node/basenode.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import copy
44
from typing import Any, Dict, Generator, Iterable, List, Optional, Set, Tuple, TypeVar
55

6+
from bigtree.globals import ASSERTIONS
67
from bigtree.utils.exceptions import CorruptedTreeError, LoopError, TreeError
78
from bigtree.utils.iterators import preorder_iter
89

@@ -181,8 +182,9 @@ def parent(self: T, new_parent: T) -> None:
181182
Args:
182183
new_parent (Self): parent node
183184
"""
184-
self.__check_parent_type(new_parent)
185-
self.__check_parent_loop(new_parent)
185+
if ASSERTIONS:
186+
self.__check_parent_type(new_parent)
187+
self.__check_parent_loop(new_parent)
186188

187189
current_parent = self.parent
188190
current_child_idx = None
@@ -325,8 +327,9 @@ def children(self: T, new_children: List[T] | Tuple[T] | Set[T]) -> None:
325327
Args:
326328
new_children (List[Self]): child node
327329
"""
328-
self.__check_children_type(new_children)
329-
self.__check_children_loop(new_children)
330+
if ASSERTIONS:
331+
self.__check_children_type(new_children)
332+
self.__check_children_loop(new_children)
330333
new_children = list(new_children)
331334

332335
current_new_children = {

bigtree/node/binarynode.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from typing import Any, List, Optional, Tuple, TypeVar, Union
44

5+
from bigtree.globals import ASSERTIONS
56
from bigtree.node.node import Node
67
from bigtree.utils.exceptions import CorruptedTreeError, LoopError, TreeError
78

@@ -165,8 +166,9 @@ def parent(self: T, new_parent: Optional[T]) -> None:
165166
Args:
166167
new_parent (Optional[Self]): parent node
167168
"""
168-
self.__check_parent_type(new_parent)
169-
self._BaseNode__check_parent_loop(new_parent) # type: ignore
169+
if ASSERTIONS:
170+
self.__check_parent_type(new_parent)
171+
self._BaseNode__check_parent_loop(new_parent) # type: ignore
170172

171173
current_parent = self.parent
172174
current_child_idx = None
@@ -294,7 +296,8 @@ def children(self: T, _new_children: List[Optional[T]]) -> None:
294296
"""
295297
self._BaseNode__check_children_type(_new_children) # type: ignore
296298
new_children = self.__check_children_type(_new_children)
297-
self.__check_children_loop(new_children)
299+
if ASSERTIONS:
300+
self.__check_children_loop(new_children)
298301

299302
current_new_children = {
300303
new_child: (

bigtree/node/dagnode.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import copy
44
from typing import Any, Dict, Generator, Iterable, List, Optional, Tuple, TypeVar
55

6+
from bigtree.globals import ASSERTIONS
67
from bigtree.utils.exceptions import LoopError, TreeError
78
from bigtree.utils.iterators import preorder_iter
89

@@ -208,8 +209,9 @@ def parents(self: T, new_parents: List[T]) -> None:
208209
Args:
209210
new_parents (List[Self]): parent nodes
210211
"""
211-
self.__check_parent_type(new_parents)
212-
self.__check_parent_loop(new_parents)
212+
if ASSERTIONS:
213+
self.__check_parent_type(new_parents)
214+
self.__check_parent_loop(new_parents)
213215

214216
current_parents = self.__parents.copy()
215217

@@ -306,8 +308,9 @@ def children(self: T, new_children: Iterable[T]) -> None:
306308
Args:
307309
new_children (Iterable[Self]): child node
308310
"""
309-
self.__check_children_type(new_children)
310-
self.__check_children_loop(new_children)
311+
if ASSERTIONS:
312+
self.__check_children_type(new_children)
313+
self.__check_children_loop(new_children)
311314

312315
current_children = list(self.children)
313316

docs/others/remove_checks.md

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Remove Tree Checks
2+
3+
!!! note
4+
5+
Available from version 0.16.2 onwards
6+
7+
When constructing trees, there are a few checks done that slow down performance.
8+
This slowness will be more apparent with very large trees. The checks are to
9+
10+
- Check parent/children data type
11+
- Check for loops (expensive for trees that are deep as it checks the ancestors of node)
12+
13+
These checks are enabled by default. To turn off these checks, you can set environment variable before importing `bigtree`.
14+
15+
```python
16+
import os
17+
os.environ["BIGTREE_CONF_ASSERTIONS"] = ""
18+
19+
import bigtree
20+
```

mkdocs.yml

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ nav:
5555
- Others:
5656
- Tips and Tricks:
5757
- others/index.md
58+
- others/remove_checks.md
5859
- others/list_dir.md
5960
- others/nodes.md
6061
- others/merging_trees.md
+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import unittest
2+
from unittest.mock import patch
3+
4+
import pytest
5+
6+
from bigtree.node.basenode import BaseNode
7+
8+
9+
@patch("bigtree.node.basenode.ASSERTIONS", "")
10+
class TestBaseNodeNoAssertions(unittest.TestCase):
11+
def setUp(self):
12+
"""
13+
Tree should have structure
14+
a (age=90)
15+
|-- b (age=65)
16+
| |-- d (age=40)
17+
| +-- e (age=35)
18+
| |-- g (age=10)
19+
| +-- h (age=6)
20+
+-- c (age=60)
21+
+-- f (age=38)
22+
"""
23+
self.a = BaseNode(name="a", age=90)
24+
self.b = BaseNode(name="b", age=65)
25+
self.c = BaseNode(name="c", age=60)
26+
self.d = BaseNode(name="d", age=40)
27+
self.e = BaseNode(name="e", age=35)
28+
self.f = BaseNode(name="f", age=38)
29+
self.g = BaseNode(name="g", age=10)
30+
self.h = BaseNode(name="h", age=6)
31+
32+
def tearDown(self):
33+
self.a = None
34+
self.b = None
35+
self.c = None
36+
self.d = None
37+
self.e = None
38+
self.f = None
39+
self.g = None
40+
self.h = None
41+
42+
def test_set_children_none_parent_error(self):
43+
# TypeError: 'NoneType' object is not iterable
44+
children = None
45+
with pytest.raises(TypeError):
46+
self.h.children = children
47+
48+
def test_set_parent_type_error(self):
49+
# AttributeError: 'int' object has no attribute '_BaseNode__children'
50+
parent = 1
51+
with pytest.raises(AttributeError):
52+
self.a.parent = parent
53+
54+
def test_set_parent_loop_error(self):
55+
# No error without assertion
56+
self.a.parent = self.a
57+
58+
# No error without assertion
59+
self.b.parent = self.a
60+
self.c.parent = self.b
61+
self.a.parent = self.c
62+
63+
def test_set_children_type_error(self):
64+
# AttributeError: 'int' object has no attribute '_BaseNode__children'
65+
children = 1
66+
with pytest.raises(AttributeError):
67+
self.a.children = [self.b, children]
68+
69+
# AttributeError: 'NoneType' object has no attribute 'parent'
70+
children = None
71+
with pytest.raises(AttributeError):
72+
self.a.children = [self.b, children]
73+
74+
def test_set_children_loop_error(self):
75+
# No error without assertion
76+
self.a.children = [self.b, self.a]
77+
78+
# No error without assertion
79+
self.a.children = [self.b, self.c]
80+
self.c.children = [self.d, self.e, self.f]
81+
self.f.children = [self.a]
82+
83+
def test_set_duplicate_children_error(self):
84+
# No error without assertion
85+
self.a.children = [self.b, self.b]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import unittest
2+
from unittest.mock import patch
3+
4+
import pytest
5+
6+
from bigtree.node.basenode import BaseNode
7+
from bigtree.node.binarynode import BinaryNode
8+
from bigtree.node.node import Node
9+
from bigtree.utils.exceptions import TreeError
10+
11+
12+
@patch("bigtree.node.binarynode.ASSERTIONS", "")
13+
class TestBinaryNodeNoAssertions(unittest.TestCase):
14+
def setUp(self):
15+
self.a = BinaryNode(1)
16+
self.b = BinaryNode(2)
17+
self.c = BinaryNode(3)
18+
self.d = BinaryNode(4)
19+
self.e = BinaryNode(5)
20+
self.f = BinaryNode(6)
21+
self.g = BinaryNode(7)
22+
self.h = BinaryNode(8)
23+
24+
def tearDown(self):
25+
self.a = None
26+
self.b = None
27+
self.c = None
28+
self.d = None
29+
self.e = None
30+
self.f = None
31+
self.g = None
32+
self.h = None
33+
34+
def test_set_parent_type_error(self):
35+
# AttributeError: 'int' object has no attribute '_BinaryNode__children'
36+
parent = 1
37+
with pytest.raises(AttributeError):
38+
self.a.parent = parent
39+
40+
# AttributeError: 'BaseNode' object has no attribute '_BinaryNode__children'
41+
parent = BaseNode()
42+
with pytest.raises(AttributeError):
43+
self.a.parent = parent
44+
45+
# AttributeError: 'Node' object has no attribute '_BinaryNode__children'
46+
parent = Node("a")
47+
with pytest.raises(AttributeError):
48+
self.a.parent = parent
49+
50+
def test_set_parent_loop_error(self):
51+
# No error without assertion
52+
self.a.parent = self.a
53+
54+
# No error without assertion
55+
self.b.parent = self.a
56+
self.c.parent = self.b
57+
self.a.parent = self.c
58+
59+
def test_set_children_type_error(self):
60+
# AttributeError: 'int' object has no attribute 'parent'
61+
children = 1
62+
with pytest.raises(AttributeError):
63+
self.a.children = [self.b, children]
64+
65+
# No error without assertion
66+
children = BaseNode()
67+
self.a.children = [children, None]
68+
69+
# bigtree.utils.exceptions.TreeError: 'NoneType' object has no attribute '_BinaryNode__children'
70+
children = Node("a")
71+
with pytest.raises(TreeError):
72+
self.a.children = [children, None]
73+
74+
def test_set_children_loop_error(self):
75+
# No error without assertion
76+
self.a.children = [self.b, self.a]
77+
78+
# No error without assertion
79+
self.a.children = [self.b, self.c]
80+
self.c.children = [self.d, self.e]
81+
self.e.children = [self.a, self.f]
82+
83+
def test_set_duplicate_children_error(self):
84+
# No error without assertion
85+
self.a.children = [self.b, self.b]

0 commit comments

Comments
 (0)