Files
common/python37/packages/bigtree/node/node.py

205 lines
5.9 KiB
Python

from collections import Counter
from typing import List
from bigtree.node.basenode import BaseNode
from bigtree.utils.exceptions import TreeError
class Node(BaseNode):
"""
Node is an extension of BaseNode, and is able to extend to any Python class.
Nodes can have attributes if they are initialized from `Node`, *dictionary*, or *pandas DataFrame*.
Nodes can be linked to each other with `parent` and `children` setter methods.
>>> from bigtree import Node
>>> a = Node("a")
>>> b = Node("b")
>>> c = Node("c")
>>> d = Node("d")
>>> b.parent = a
>>> b.children = [c, d]
Directly passing `parent` argument.
>>> from bigtree import Node
>>> a = Node("a")
>>> b = Node("b", parent=a)
>>> c = Node("c", parent=b)
>>> d = Node("d", parent=b)
Directly passing `children` argument.
>>> from bigtree import Node
>>> d = Node("d")
>>> c = Node("c")
>>> b = Node("b", children=[c, d])
>>> a = Node("a", children=[b])
**Node Creation**
Node can be created by instantiating a `Node` class or by using a *dictionary*.
If node is created with dictionary, all keys of dictionary will be stored as class attributes.
>>> from bigtree import Node
>>> a = Node.from_dict({"name": "a", "age": 90})
**Node Attributes**
These are node attributes that have getter and/or setter methods.
Get and set `Node` configuration
1. ``sep``: Get/set separator for path name
Get `Node` configuration
1. ``node_name``: Get node name, without accessing `name` directly
2. ``path_name``: Get path name from root, separated by `sep`
----
"""
def __init__(self, name: str = "", **kwargs):
self.name = name
self._sep: str = "/"
super().__init__(**kwargs)
if not self.node_name:
raise TreeError("Node must have a `name` attribute")
@property
def node_name(self) -> str:
"""Get node name
Returns:
(str)
"""
return self.name
@property
def sep(self) -> str:
"""Get separator, gets from root node
Returns:
(str)
"""
if self.is_root:
return self._sep
return self.parent.sep
@sep.setter
def sep(self, value: str):
"""Set separator, affects root node
Args:
value (str): separator to replace default separator
"""
self.root._sep = value
@property
def path_name(self) -> str:
"""Get path name, separated by self.sep
Returns:
(str)
"""
if self.is_root:
return f"{self.sep}{self.name}"
return f"{self.parent.path_name}{self.sep}{self.name}"
def __pre_assign_children(self, new_children: List):
"""Custom method to check before attaching children
Can be overriden with `_Node__pre_assign_children()`
Args:
new_children (List[Self]): new children to be added
"""
pass
def __post_assign_children(self, new_children: List):
"""Custom method to check after attaching children
Can be overriden with `_Node__post_assign_children()`
Args:
new_children (List[Self]): new children to be added
"""
pass
def __pre_assign_parent(self, new_parent):
"""Custom method to check before attaching parent
Can be overriden with `_Node__pre_assign_parent()`
Args:
new_parent (Self): new parent to be added
"""
pass
def __post_assign_parent(self, new_parent):
"""Custom method to check after attaching parent
Can be overriden with `_Node__post_assign_parent()`
Args:
new_parent (Self): new parent to be added
"""
pass
def _BaseNode__pre_assign_parent(self, new_parent):
"""Do not allow duplicate nodes of same path
Args:
new_parent (Self): new parent to be added
"""
self.__pre_assign_parent(new_parent)
if new_parent is not None:
if any(
child.node_name == self.node_name and child is not self
for child in new_parent.children
):
raise TreeError(
f"Error: Duplicate node with same path\n"
f"There exist a node with same path {new_parent.path_name}{self.sep}{self.node_name}"
)
def _BaseNode__post_assign_parent(self, new_parent):
"""No rules
Args:
new_parent (Self): new parent to be added
"""
self.__post_assign_parent(new_parent)
def _BaseNode__pre_assign_children(self, new_children: List):
"""Do not allow duplicate nodes of same path
Args:
new_children (List[Self]): new children to be added
"""
self.__pre_assign_children(new_children)
children_names = [node.node_name for node in new_children]
duplicated_names = [
item[0] for item in Counter(children_names).items() if item[1] > 1
]
if len(duplicated_names):
duplicated_names = " and ".join(
[f"{self.path_name}{self.sep}{name}" for name in duplicated_names]
)
raise TreeError(
f"Error: Duplicate node with same path\n"
f"Attempting to add nodes same path {duplicated_names}"
)
def _BaseNode__post_assign_children(self, new_children: List):
"""No rules
Args:
new_children (List[Self]): new children to be added
"""
self.__post_assign_children(new_children)
def __repr__(self):
class_name = self.__class__.__name__
node_dict = self.describe(exclude_prefix="_", exclude_attributes=["name"])
node_description = ", ".join([f"{k}={v}" for k, v in node_dict])
return f"{class_name}({self.path_name}, {node_description})"