Skip to content

Commit

Permalink
Merge pull request #186 from kayjan/optional-assertions
Browse files Browse the repository at this point in the history
Optional assertions
  • Loading branch information
kayjan authored Feb 6, 2024
2 parents 5d87c3f + 9dbaaa3 commit 07ad11a
Show file tree
Hide file tree
Showing 11 changed files with 351 additions and 19 deletions.
16 changes: 10 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@ 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]

## [0.16.2] - 2024-02-06
### Added
- Misc: Documentation plugin Termynal for code animation.
- Misc: Usage of `docstr-coverage`.
- Misc: Docstrings for nested functions to pass `docstr-coverage`.
### Changed
- [#185] BaseNode: Make assertion checks optional.
- Misc: Documentation CSS for h1 display for windows compatibility, modify the related links on main page.

## [0.16.1] - 2023-01-29
## [0.16.1] - 2024-01-29
### Fixed
- Misc: Compatibility of mkdocs with readthedocs.

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

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

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

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

[Unreleased]: https://github.com/kayjan/bigtree/compare/0.16.1...HEAD
[Unreleased]: https://github.com/kayjan/bigtree/compare/0.16.2...HEAD
[0.16.2]: https://github.com/kayjan/bigtree/compare/0.16.1...0.16.2
[0.16.1]: https://github.com/kayjan/bigtree/compare/0.16.0...0.16.1
[0.16.0]: https://github.com/kayjan/bigtree/compare/0.15.7...0.16.0
[0.15.7]: https://github.com/kayjan/bigtree/compare/0.15.6...0.15.7
Expand Down
4 changes: 2 additions & 2 deletions bigtree/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "0.16.1"
__version__ = "0.16.2"

from bigtree.binarytree.construct import list_to_binarytree
from bigtree.dag.construct import dataframe_to_dag, dict_to_dag, list_to_dag
Expand Down Expand Up @@ -75,4 +75,4 @@
from bigtree.workflows.app_calendar import Calendar
from bigtree.workflows.app_todo import AppToDo

sphinx_versions = ["latest", "0.16.1", "0.15.7", "0.14.8"]
sphinx_versions = ["latest", "0.16.2", "0.15.7", "0.14.8"]
3 changes: 3 additions & 0 deletions bigtree/globals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import os

ASSERTIONS: bool = bool(os.environ.get("BIGTREE_CONF_ASSERTIONS", True))
11 changes: 7 additions & 4 deletions bigtree/node/basenode.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import copy
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

Expand Down Expand Up @@ -181,8 +182,9 @@ def parent(self: T, new_parent: T) -> None:
Args:
new_parent (Self): parent node
"""
self.__check_parent_type(new_parent)
self.__check_parent_loop(new_parent)
if ASSERTIONS:
self.__check_parent_type(new_parent)
self.__check_parent_loop(new_parent)

current_parent = self.parent
current_child_idx = None
Expand Down Expand Up @@ -325,8 +327,9 @@ def children(self: T, new_children: List[T] | Tuple[T] | Set[T]) -> None:
Args:
new_children (List[Self]): child node
"""
self.__check_children_type(new_children)
self.__check_children_loop(new_children)
if ASSERTIONS:
self.__check_children_type(new_children)
self.__check_children_loop(new_children)
new_children = list(new_children)

current_new_children = {
Expand Down
9 changes: 6 additions & 3 deletions bigtree/node/binarynode.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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

from bigtree.globals import ASSERTIONS
from bigtree.node.node import Node
from bigtree.utils.exceptions import CorruptedTreeError, LoopError, TreeError

Expand Down Expand Up @@ -165,8 +166,9 @@ def parent(self: T, new_parent: Optional[T]) -> None:
Args:
new_parent (Optional[Self]): parent node
"""
self.__check_parent_type(new_parent)
self._BaseNode__check_parent_loop(new_parent) # type: ignore
if ASSERTIONS:
self.__check_parent_type(new_parent)
self._BaseNode__check_parent_loop(new_parent) # type: ignore

current_parent = self.parent
current_child_idx = None
Expand Down Expand Up @@ -294,7 +296,8 @@ def children(self: T, _new_children: List[Optional[T]]) -> None:
"""
self._BaseNode__check_children_type(_new_children) # type: ignore
new_children = self.__check_children_type(_new_children)
self.__check_children_loop(new_children)
if ASSERTIONS:
self.__check_children_loop(new_children)

current_new_children = {
new_child: (
Expand Down
11 changes: 7 additions & 4 deletions bigtree/node/dagnode.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import copy
from typing import Any, Dict, Generator, Iterable, List, Optional, Tuple, TypeVar

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

Expand Down Expand Up @@ -208,8 +209,9 @@ def parents(self: T, new_parents: List[T]) -> None:
Args:
new_parents (List[Self]): parent nodes
"""
self.__check_parent_type(new_parents)
self.__check_parent_loop(new_parents)
if ASSERTIONS:
self.__check_parent_type(new_parents)
self.__check_parent_loop(new_parents)

current_parents = self.__parents.copy()

Expand Down Expand Up @@ -306,8 +308,9 @@ def children(self: T, new_children: Iterable[T]) -> None:
Args:
new_children (Iterable[Self]): child node
"""
self.__check_children_type(new_children)
self.__check_children_loop(new_children)
if ASSERTIONS:
self.__check_children_type(new_children)
self.__check_children_loop(new_children)

current_children = list(self.children)

Expand Down
20 changes: 20 additions & 0 deletions docs/others/remove_checks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Remove Tree Checks

!!! note

Available from version 0.16.2 onwards

When constructing trees, there are a few checks done that slow down performance.
This slowness will be more apparent with very large trees. The checks are to

- Check parent/children data type
- Check for loops (expensive for trees that are deep as it checks the ancestors of node)

These checks are enabled by default. To turn off these checks, you can set environment variable before importing `bigtree`.

```python
import os
os.environ["BIGTREE_CONF_ASSERTIONS"] = ""

import bigtree
```
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ nav:
- Others:
- Tips and Tricks:
- others/index.md
- others/remove_checks.md
- others/list_dir.md
- others/nodes.md
- others/merging_trees.md
Expand Down
85 changes: 85 additions & 0 deletions tests/node/test_basenode_no_assertions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import unittest
from unittest.mock import patch

import pytest

from bigtree.node.basenode import BaseNode


@patch("bigtree.node.basenode.ASSERTIONS", "")
class TestBaseNodeNoAssertions(unittest.TestCase):
def setUp(self):
"""
Tree should have structure
a (age=90)
|-- b (age=65)
| |-- d (age=40)
| +-- e (age=35)
| |-- g (age=10)
| +-- h (age=6)
+-- c (age=60)
+-- f (age=38)
"""
self.a = BaseNode(name="a", age=90)
self.b = BaseNode(name="b", age=65)
self.c = BaseNode(name="c", age=60)
self.d = BaseNode(name="d", age=40)
self.e = BaseNode(name="e", age=35)
self.f = BaseNode(name="f", age=38)
self.g = BaseNode(name="g", age=10)
self.h = BaseNode(name="h", age=6)

def tearDown(self):
self.a = None
self.b = None
self.c = None
self.d = None
self.e = None
self.f = None
self.g = None
self.h = None

def test_set_children_none_parent_error(self):
# TypeError: 'NoneType' object is not iterable
children = None
with pytest.raises(TypeError):
self.h.children = children

def test_set_parent_type_error(self):
# AttributeError: 'int' object has no attribute '_BaseNode__children'
parent = 1
with pytest.raises(AttributeError):
self.a.parent = parent

def test_set_parent_loop_error(self):
# No error without assertion
self.a.parent = self.a

# No error without assertion
self.b.parent = self.a
self.c.parent = self.b
self.a.parent = self.c

def test_set_children_type_error(self):
# AttributeError: 'int' object has no attribute '_BaseNode__children'
children = 1
with pytest.raises(AttributeError):
self.a.children = [self.b, children]

# AttributeError: 'NoneType' object has no attribute 'parent'
children = None
with pytest.raises(AttributeError):
self.a.children = [self.b, children]

def test_set_children_loop_error(self):
# No error without assertion
self.a.children = [self.b, self.a]

# No error without assertion
self.a.children = [self.b, self.c]
self.c.children = [self.d, self.e, self.f]
self.f.children = [self.a]

def test_set_duplicate_children_error(self):
# No error without assertion
self.a.children = [self.b, self.b]
85 changes: 85 additions & 0 deletions tests/node/test_binarynode_no_assertions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import unittest
from unittest.mock import patch

import pytest

from bigtree.node.basenode import BaseNode
from bigtree.node.binarynode import BinaryNode
from bigtree.node.node import Node
from bigtree.utils.exceptions import TreeError


@patch("bigtree.node.binarynode.ASSERTIONS", "")
class TestBinaryNodeNoAssertions(unittest.TestCase):
def setUp(self):
self.a = BinaryNode(1)
self.b = BinaryNode(2)
self.c = BinaryNode(3)
self.d = BinaryNode(4)
self.e = BinaryNode(5)
self.f = BinaryNode(6)
self.g = BinaryNode(7)
self.h = BinaryNode(8)

def tearDown(self):
self.a = None
self.b = None
self.c = None
self.d = None
self.e = None
self.f = None
self.g = None
self.h = None

def test_set_parent_type_error(self):
# AttributeError: 'int' object has no attribute '_BinaryNode__children'
parent = 1
with pytest.raises(AttributeError):
self.a.parent = parent

# AttributeError: 'BaseNode' object has no attribute '_BinaryNode__children'
parent = BaseNode()
with pytest.raises(AttributeError):
self.a.parent = parent

# AttributeError: 'Node' object has no attribute '_BinaryNode__children'
parent = Node("a")
with pytest.raises(AttributeError):
self.a.parent = parent

def test_set_parent_loop_error(self):
# No error without assertion
self.a.parent = self.a

# No error without assertion
self.b.parent = self.a
self.c.parent = self.b
self.a.parent = self.c

def test_set_children_type_error(self):
# AttributeError: 'int' object has no attribute 'parent'
children = 1
with pytest.raises(AttributeError):
self.a.children = [self.b, children]

# No error without assertion
children = BaseNode()
self.a.children = [children, None]

# bigtree.utils.exceptions.TreeError: 'NoneType' object has no attribute '_BinaryNode__children'
children = Node("a")
with pytest.raises(TreeError):
self.a.children = [children, None]

def test_set_children_loop_error(self):
# No error without assertion
self.a.children = [self.b, self.a]

# No error without assertion
self.a.children = [self.b, self.c]
self.c.children = [self.d, self.e]
self.e.children = [self.a, self.f]

def test_set_duplicate_children_error(self):
# No error without assertion
self.a.children = [self.b, self.b]
Loading

0 comments on commit 07ad11a

Please sign in to comment.