1
0
Fork 0
treelib/treelib/node.py
Daniel Baumann 49565454d8
Merging upstream version 1.7.1.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-03-05 09:29:32 +01:00

300 lines
10 KiB
Python

#!/usr/bin/env python
# Copyright (C) 2011
# Brett Alistair Kromkamp - brettkromkamp@gmail.com
# Copyright (C) 2012-2017
# Xiaming Chen - chenxm35@gmail.com
# and other contributors.
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Node structure in treelib.
A :class:`Node` object contains basic properties such as node identifier,
node tag, parent node, children nodes etc., and some operations for a node.
"""
from __future__ import unicode_literals
import copy
import uuid
import sys
from collections import defaultdict
from warnings import warn
from typing import cast, List, Any, Optional, Union
from .exceptions import NodePropertyError
from .misc import deprecated
if sys.version_info >= (3, 9):
StrList = list[str]
else:
StrList = List[str] # Python 3.8 and earlier
class Node(object):
"""
Nodes are elementary objects that are stored in the `_nodes` dictionary of a Tree.
Use `data` attribute to store node-specific data.
"""
#: Mode constants for routine `update_fpointer()`.
(ADD, DELETE, INSERT, REPLACE) = list(range(4))
def __init__(
self,
tag: Optional[str] = None,
identifier: Optional[str] = None,
expanded: bool = True,
data: Any = None,
) -> None:
"""Create a new Node object to be placed inside a Tree object"""
#: if given as a parameter, must be unique
self._identifier: Optional[str] = None
self._set_identifier(identifier)
#: None or something else
#: if None, self._identifier will be set to the identifier's value.
if tag is None:
self._tag = self._identifier
else:
self._tag = tag
#: boolean
self.expanded = expanded
#: identifier of the parent's node :
self._predecessor: dict = {}
#: identifier(s) of the soons' node(s) :
self._successors: dict = defaultdict(list)
#: User payload associated with this node.
self.data = data
# for retro-compatibility on bpointer/fpointer
self._initial_tree_id: Optional[str] = None
def __lt__(self, other) -> bool:
return self.tag < other.tag
def set_initial_tree_id(self, tree_id: str) -> None:
if self._initial_tree_id is None:
self._initial_tree_id = tree_id
def _set_identifier(self, nid: Optional[str]) -> None:
"""Initialize self._set_identifier"""
if nid is None:
self._identifier = str(uuid.uuid1())
else:
self._identifier = nid
@property
@deprecated(alias="node.predecessor")
def bpointer(self):
"""Use predecessor method, this property is deprecated and only kept for retro-compatilibity. Parents of
a node are dependant on a given tree. This implementation keeps the previous behavior by keeping returning
bpointer of first declared tree.
"""
if self._initial_tree_id not in self._predecessor.keys():
return None
return self._predecessor[self._initial_tree_id]
@bpointer.setter
@deprecated(alias="node.set_predecessor")
def bpointer(self, value) -> None:
self.set_predecessor(value, self._initial_tree_id)
@deprecated(alias="node.set_predecessor")
def update_bpointer(self, nid) -> None:
self.set_predecessor(nid, self._initial_tree_id)
@property
@deprecated(alias="node.successors")
def fpointer(self):
"""Use successors method, this property is deprecated and only kept for retro-compatilibity. Children of
a node are dependant on a given tree. This implementation keeps the previous behavior by keeping returning
fpointer of first declared tree.
"""
if self._initial_tree_id not in self._successors:
return []
return self._successors[self._initial_tree_id]
@fpointer.setter
@deprecated(alias="node.update_successors")
def fpointer(self, value: Union[None, list, dict, set]) -> None:
self.set_successors(value, tree_id=self._initial_tree_id)
@deprecated(alias="node.update_successors")
def update_fpointer(self, nid, mode=ADD, replace=None):
"""Deprecated"""
self.update_successors(nid, mode, replace, self._initial_tree_id)
def predecessor(self, tree_id):
"""
The parent ID of a node in a given tree.
"""
return self._predecessor[tree_id]
def set_predecessor(self, nid: Optional[str], tree_id: Optional[str]) -> None:
"""Set the value of `_predecessor`."""
self._predecessor[tree_id] = nid
def successors(self, tree_id: Optional[str]) -> StrList:
"""
With a getting operator, a list of IDs of node's children is obtained. With
a setting operator, the value can be list, set, or dict. For list or set,
it is converted to a list type by the package; for dict, the keys are
treated as the node IDs.
"""
return self._successors[tree_id]
def set_successors(
self, value: Union[None, list, dict, set], tree_id: Optional[str] = None
) -> None:
"""Set the value of `_successors`."""
setter_lookup = {
"NoneType": lambda x: list(),
"list": lambda x: x,
"dict": lambda x: list(x.keys()),
"set": lambda x: list(x),
}
t = value.__class__.__name__
if t in setter_lookup:
f_setter = setter_lookup[t]
self._successors[tree_id] = f_setter(value)
else:
raise NotImplementedError("Unsupported value type %s" % t)
def update_successors(
self,
nid: Optional[str],
mode: int = ADD,
replace: Optional[str] = None,
tree_id: Optional[str] = None,
) -> None:
"""
Update the children list with different modes: addition (Node.ADD or
Node.INSERT) and deletion (Node.DELETE).
"""
if nid is None:
return
def _manipulator_append() -> None:
self.successors(tree_id).append(nid)
def _manipulator_delete() -> None:
if nid in self.successors(tree_id):
self.successors(tree_id).remove(nid)
else:
warn("Nid %s wasn't present in fpointer" % nid)
def _manipulator_insert() -> None:
warn("WARNING: INSERT is deprecated to ADD mode")
self.update_successors(nid, tree_id=tree_id)
def _manipulator_replace() -> None:
if replace is None:
raise NodePropertyError(
'Argument "replace" should be provided when mode is {}'.format(mode)
)
ind = self.successors(tree_id).index(nid)
self.successors(tree_id)[ind] = replace
manipulator_lookup = {
self.ADD: "_manipulator_append",
self.DELETE: "_manipulator_delete",
self.INSERT: "_manipulator_insert",
self.REPLACE: "_manipulator_replace",
}
if mode not in manipulator_lookup:
raise NotImplementedError("Unsupported node updating mode %s" % str(mode))
f_name = cast(str, manipulator_lookup.get(mode))
f = locals()[f_name]
return f()
@property
def identifier(self) -> str:
"""
The unique ID of a node within the scope of a tree. This attribute can be
accessed and modified with ``.`` and ``=`` operator respectively.
"""
return cast(str, self._identifier)
def clone_pointers(self, former_tree_id: str, new_tree_id: str) -> None:
former_bpointer = self.predecessor(former_tree_id)
self.set_predecessor(former_bpointer, new_tree_id)
former_fpointer = self.successors(former_tree_id)
# fpointer is a list and would be copied by reference without deepcopy
self.set_successors(copy.deepcopy(former_fpointer), tree_id=new_tree_id)
def reset_pointers(self, tree_id) -> None:
self.set_predecessor(None, tree_id)
self.set_successors([], tree_id=tree_id)
@identifier.setter # type: ignore
def identifier(self, value: str) -> None:
"""Set the value of `_identifier`."""
if value is None:
print("WARNING: node ID can not be None")
else:
self._set_identifier(value)
def is_leaf(self, tree_id: Optional[str] = None) -> bool:
"""Return true if current node has no children."""
if tree_id is None:
# for retro-compatilibity
if self._initial_tree_id not in self._successors.keys():
return True
else:
tree_id = self._initial_tree_id
if len(self.successors(tree_id)) == 0:
return True
else:
return False
def is_root(self, tree_id: Optional[str] = None) -> bool:
"""Return true if self has no parent, i.e. as root."""
if tree_id is None:
# for retro-compatilibity
if self._initial_tree_id not in self._predecessor.keys():
return True
else:
tree_id = self._initial_tree_id
return self.predecessor(tree_id) is None
@property
def tag(self) -> str:
"""
The readable node name for human. This attribute can be accessed and
modified with ``.`` and ``=`` operator respectively.
"""
return cast(str, self._tag)
@tag.setter
def tag(self, value: Optional[str]) -> None:
"""Set the value of `_tag`."""
self._tag = value if value is not None else None
def __repr__(self) -> str:
name = self.__class__.__name__
kwargs = [
"tag={0}".format(self.tag),
"identifier={0}".format(self.identifier),
"data={0}".format(self.data),
]
return "%s(%s)" % (name, ", ".join(kwargs))