Coverage for pyTooling/Tree/__init__.py: 93%

419 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-10-25 12:33 +0000

1# ==================================================================================================================== # 

2# _____ _ _ _____ # 

3# _ __ _ |_ _|__ ___ | (_)_ __ __ _|_ _| __ ___ ___ # 

4# | '_ \| | | || |/ _ \ / _ \| | | '_ \ / _` | | || '__/ _ \/ _ \ # 

5# | |_) | |_| || | (_) | (_) | | | | | | (_| |_| || | | __/ __/ # 

6# | .__/ \__, ||_|\___/ \___/|_|_|_| |_|\__, (_)_||_| \___|\___| # 

7# |_| |___/ |___/ # 

8# ==================================================================================================================== # 

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

13# ==================================================================================================================== # 

14# Copyright 2017-2026 Patrick Lehmann - Bötzingen, Germany # 

15# # 

16# Licensed under the Apache License, Version 2.0 (the "License"); # 

17# you may not use this file except in compliance with the License. # 

18# You may obtain a copy of the License at # 

19# # 

20# http://www.apache.org/licenses/LICENSE-2.0 # 

21# # 

22# Unless required by applicable law or agreed to in writing, software # 

23# distributed under the License is distributed on an "AS IS" BASIS, # 

24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

25# See the License for the specific language governing permissions and # 

26# limitations under the License. # 

27# # 

28# SPDX-License-Identifier: Apache-2.0 # 

29# ==================================================================================================================== # 

30# 

31""" 

32A powerful tree data structure for Python. 

33 

34.. seealso:: 

35 

36 :mod:`pyTooling.Graph` 

37 |rarr| A graph, of which a tree is the acyclic single-rooted case. 

38 :mod:`pyTooling.Graph.GraphML` 

39 |rarr| Writing a tree as a GraphML document. 

40 :mod:`pyTooling.LinkedList` 

41 |rarr| An object-oriented doubly linked-list data structure. 

42""" 

43from __future__ import annotations 

44 

45from collections import deque 

46from typing import TypeVar, Generic, Deque, Union, Optional as Nullable, Any, Callable, Generator, \ 

47 Iterable, Mapping, Hashable 

48 

49from pyTooling.Decorators import export, readonly 

50from pyTooling.MetaClasses import ExtendedType 

51from pyTooling.Exceptions import AlreadyInHierarchyError, NotInSameHierarchyError, ToolingException, \ 

52 NotAChildError as CommonNotAChildError 

53from pyTooling.Common import getFullyQualifiedName 

54from pyTooling.DataModel import KeyValuePairsMixin, Predicate 

55 

56 

57IDType = TypeVar("IDType", bound=Hashable) 

58"""A type variable for a tree's ID.""" 

59 

60ValueType = TypeVar("ValueType") 

61"""A type variable for a tree's value.""" 

62 

63 

64@export 

65class TreeError(ToolingException): 

66 """Base exception of all exceptions raised by :mod:`pyTooling.Tree`.""" 

67 

68 

69@export 

70class InternalError(TreeError): 

71 """ 

72 The exception is raised when a data structure corruption is detected. 

73 

74 .. danger:: 

75 

76 This exception should never be raised. 

77 

78 If so, please create an issue at GitHub so the data structure corruption can be investigated and fixed. |br| 

79 :gh:`⇒ Bug Tracker at GitHub <pyTooling/pyTooling/issues>` 

80 """ 

81 

82 

83@export 

84class NoSiblingsError(TreeError): 

85 """ 

86 The exception is raised when a node has no parent and thus has no siblings. 

87 

88 .. hint:: 

89 

90 A node with no parent is the root node of the tree. 

91 """ 

92 

93 

94@export 

95class AlreadyInTreeError(TreeError, AlreadyInHierarchyError): 

96 """ 

97 The exception is raised when the current node and the other node are already in the same tree. 

98 

99 .. hint:: 

100 

101 A tree a an acyclic graph without cross-edges. Thus backward edges and cross edges are permitted. 

102 """ 

103 

104 _otherNode: Nullable[Node] #: Other node, which is in the same tree already. 

105 

106 def __init__(self, message: str, /, *, otherNode: Nullable[Node] = None) -> None: 

107 """ 

108 Initializes the exception with the other node, which is in the same tree already. 

109 

110 :param message: The exception's message. 

111 :param otherNode: Optional, the other node, which is in the same tree already. 

112 """ 

113 super().__init__(message) 

114 self._otherNode = otherNode 

115 

116 @readonly 

117 def OtherNode(self) -> Nullable[Node]: 

118 """ 

119 Read-only property to access the other node, which is in the same tree already (:attr:`_otherNode`). 

120 

121 :returns: The other node, or ``None`` if it wasn't recorded. 

122 """ 

123 return self._otherNode 

124 

125 

126@export 

127class NotInSameTreeError(TreeError, NotInSameHierarchyError): 

128 """The exception is raised when the current node and the other node are not in the same tree.""" 

129 

130 _otherNode: Nullable[Node] #: Other node, which is in another tree. 

131 

132 def __init__(self, message: str, /, *, otherNode: Nullable[Node] = None) -> None: 

133 """ 

134 Initializes the exception with the other node, which is in another tree. 

135 

136 :param message: The exception's message. 

137 :param otherNode: Optional, the other node, which is in another tree. 

138 """ 

139 super().__init__(message) 

140 self._otherNode = otherNode 

141 

142 @readonly 

143 def OtherNode(self) -> Nullable[Node]: 

144 """ 

145 Read-only property to access the other node, which is in another tree (:attr:`_otherNode`). 

146 

147 :returns: The other node, or ``None`` if it wasn't recorded. 

148 """ 

149 return self._otherNode 

150 

151 

152@export 

153class NotAChildError(TreeError, CommonNotAChildError): 

154 """ 

155 The exception is raised when a node is not a direct child of the current node. 

156 

157 .. hint:: 

158 

159 A grandchild is in the same tree, but it's a child of another node. 

160 """ 

161 

162 _child: Nullable[Node] #: Node that isn't a direct child of the current node. 

163 

164 def __init__(self, message: str, /, *, child: Nullable[Node] = None) -> None: 

165 """ 

166 Initializes the exception with the node that isn't a direct child of the current node. 

167 

168 :param message: The exception's message. 

169 :param child: Optional, the node that isn't a direct child of the current node. 

170 """ 

171 super().__init__(message) 

172 self._child = child 

173 

174 @readonly 

175 def Child(self) -> Nullable[Node]: 

176 """ 

177 Read-only property to access the node that isn't a direct child of the current node (:attr:`_child`). 

178 

179 :returns: The node, or ``None`` if it wasn't recorded. 

180 """ 

181 return self._child 

182 

183 

184@export 

185class Node(KeyValuePairsMixin, Generic[IDType, ValueType], metaclass=ExtendedType, slots=True): 

186 """ 

187 A **tree** data structure can be constructed of ``Node`` instances. 

188 

189 Therefore, nodes can be connected to parent nodes or a parent node can add child nodes. This allows to construct a 

190 tree top-down or bottom-up. 

191 

192 .. hint:: 

193 

194 The top-down construction should be preferred, because it's slightly faster. 

195 

196 Each tree uses the **root** node (a.k.a. tree-representative) to store some per-tree data structures. E.g. a list of 

197 all IDs in a tree. For easy and quick access to such data structures, each sibling node contains a reference to the 

198 root node (:attr:`_root`). In case of adding a tree to an existing tree, such data structures get merged and all added 

199 nodes get assigned with new root references. Use the read-only property :attr:`Root` to access the root reference. 

200 

201 The reference to the parent node (:attr:`_parent`) can be access via property :attr:`Parent`. If the property's setter 

202 is used, a node and all its siblings are added to another tree or to a new position in the same tree. 

203 

204 The references to all node's children is stored in a list (:attr:`_children`). Children, siblings, ancestors, can be 

205 accessed via various generators: 

206 

207 * :meth:`GetAncestors` |rarr| iterate all ancestors bottom-up. 

208 * :meth:`GetChildren` |rarr| iterate all direct children. 

209 * :meth:`GetDescendants` |rarr| iterate all descendants. 

210 * :meth:`IterateLevelOrder` |rarr| IterateLevelOrder. 

211 * :meth:`IteratePreOrder` |rarr| iterate siblings in pre-order. 

212 * :meth:`IteratePostOrder` |rarr| iterate siblings in post-order. 

213 

214 Each node can have a **unique ID** or no ID at all (``nodeID=None``). The root node is used to store all IDs in a 

215 dictionary (:attr:`_nodesWithID`). In case no ID is given, all such ID-less nodes are collected in a single bin and store as a 

216 list of nodes. An ID can be modified after the Node was created. Use the read-only property :attr:`ID` to access 

217 the ID. 

218 

219 Each node can have a **value** (:attr:`_value`), which can be given at node creation time, or it can be assigned and/or 

220 modified later. Use the property :attr:`Value` to get or set the value. 

221 

222 Moreover, each node can store arbitrary key-value-pairs (:class:`~pyTooling.DataModel.KeyValuePairsMixin`). Use the 

223 dictionary syntax to get and set key-value-pairs: ``in``, :pycode:`len(node)` and iterating a node address the 

224 key-value-pairs, not the children. 

225 """ 

226 

227 _id: Nullable[IDType] #: Unique identifier of a node. ``None`` if not used. 

228 _nodesWithID: Nullable[dict[IDType, Node]] #: Dictionary of all IDs in the tree. ``None`` if it's not the root node. 

229 _nodesWithoutID: Nullable[list[Node]] #: List of all nodes without an ID in the tree. ``None`` if it's not the root node. 

230 _root: Nullable[Node] #: Reference to the root of a tree. ``self`` if it's the root node, ``None`` once deleted. 

231 _parent: Nullable[Node] #: Reference to the parent node. ``None`` if it's the root node. 

232 _children: list[Node] #: List of all children 

233# _links: list['Node'] 

234 

235 _level: int #: Level of the node (distance to the root). 

236 _value: Nullable[ValueType] #: Field to store the node's value. 

237 

238 _format: Nullable[Callable[[Node], str]] #: A node formatting function returning a one-line representation for tree-rendering. 

239 

240 def __init__( 

241 self, 

242 nodeID: Nullable[IDType] = None, 

243 value: Nullable[ValueType] = None, 

244 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None, 

245 parent: Nullable[Node] = None, 

246 children: Nullable[Iterable[Node]] = None, 

247 format: Nullable[Callable[[Node], str]] = None 

248 ) -> None: 

249 """ 

250 .. todo:: TREE::Node::init Needs documentation. 

251 

252 :param nodeID: Optional, unique ID of a node within the whole tree data structure. 

253 :param value: Optional, value of the node. 

254 :param keyValuePairs: Optional, mapping (dictionary) of key-value-pairs. 

255 :param parent: Optional, parent node in the tree. 

256 :param children: Optional, list of child nodes. 

257 :param format: Optional, node formatting function returning a one-line representation for 

258 tree-rendering. 

259 

260 :raises TypeError: If parameter 'keyValuePairs' is not a mapping. 

261 :raises TypeError: If parameter parent is not an instance of Node. 

262 :raises ValueError: If nodeID already exists in the tree. 

263 :raises TypeError: If parameter children is not iterable. 

264 :raises ValueError: If an element of children is not an instance of Node. 

265 """ 

266 super().__init__(keyValuePairs) 

267 

268 self._id = nodeID 

269 self._value = value 

270 

271 self._format = format 

272 

273 if parent is not None and not isinstance(parent, Node): 

274 ex = TypeError("Parameter 'parent' is not of type 'Node'.") 

275 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.") 

276 raise ex 

277 

278 if parent is None: 

279 self._root = self 

280 self._parent = None 

281 self._level = 0 

282 

283 self._nodesWithID = {} 

284 self._nodesWithoutID = [] 

285 if nodeID is None: 

286 self._nodesWithoutID.append(self) 

287 else: 

288 self._nodesWithID[nodeID] = self 

289 else: 

290 self._root = parent._root 

291 self._parent = parent 

292 self._level = parent._level + 1 

293 self._nodesWithID = None 

294 self._nodesWithoutID = None 

295 

296 if nodeID is None: 

297 self._root._nodesWithoutID.append(self) 

298 elif nodeID in self._root._nodesWithID: 

299 raise ValueError(f"ID '{nodeID}' already exists in this tree.") 

300 else: 

301 self._root._nodesWithID[nodeID] = self 

302 

303 parent._children.append(self) 

304 

305 self._children = [] 

306 if children is not None: 

307 if not isinstance(children, Iterable): 

308 ex = TypeError("Parameter 'children' is not iterable.") 

309 ex.add_note(f"Got type '{getFullyQualifiedName(children)}'.") 

310 raise ex 

311 

312 for child in children: 

313 if not isinstance(child, Node): 

314 ex = TypeError(f"Item '{child}' in parameter 'children' is not of type 'Node'.") 

315 ex.add_note(f"Got type '{getFullyQualifiedName(child)}'.") 

316 raise ex 

317 

318 child.Parent = self 

319 

320 @readonly 

321 def ID(self) -> Nullable[IDType]: 

322 """ 

323 Read-only property to access the unique ID of a node (:attr:`_id`). 

324 

325 If no ID was given at node construction time, ID return None. 

326 

327 :returns: Unique ID of a node, if ID was given at node creation time, else None. 

328 """ 

329 return self._id 

330 

331 @property 

332 def Value(self) -> Nullable[ValueType]: 

333 """ 

334 Property to get and set the value (:attr:`_value`) of a node. 

335 

336 :returns: The value of a node. 

337 """ 

338 return self._value 

339 

340 @Value.setter 

341 def Value(self, value: Nullable[ValueType]) -> None: 

342 self._value = value 

343 

344 @readonly 

345 def Root(self) -> Node: 

346 """ 

347 Read-only property to access the tree's root node (:attr:`_root`). 

348 

349 :returns: The root node (representative node) of a tree. 

350 """ 

351 return self._root 

352 

353 @property 

354 def Parent(self) -> Nullable[Node]: 

355 """ 

356 Property to access the parent (:attr:`_parent`) of a node. 

357 

358 Assigning ``None`` detaches the node from its tree, which makes it the root node of the subtree it carries. 

359 Assigning a node appends this node - and everything below it - to that node's tree. 

360 

361 .. note:: 

362 

363 As the current node might be a tree itself, appending this node to a tree can lead to a merge of trees and 

364 especially to a merge of IDs. As IDs are unique, it might raise an :exc:`Exception`. 

365 

366 :returns: The parent of a node, or ``None`` if the node is a root node. 

367 :raises TypeError: If a node that is not a :class:`Node` is assigned. 

368 :raises AlreadyInTreeError: If the assigned parent is already a child node in this tree. 

369 """ 

370 return self._parent 

371 

372 @Parent.setter 

373 def Parent(self, parent: Nullable[Node]) -> None: 

374 # TODO: is moved inside the same tree, don't move nodes in _nodesWithID and don't change _root 

375 

376 if parent is None: 

377 self._nodesWithID = {} 

378 self._nodesWithoutID = [] 

379 self._level = 0 

380 

381 if self._id is None: 

382 self._nodesWithoutID.append(self) 

383 self._root._nodesWithoutID.remove(self) 

384 else: 

385 self._nodesWithID[self._id] = self 

386 del self._root._nodesWithID[self._id] 

387 

388 for sibling in self.GetDescendants(): 

389 sibling._root = self 

390 sibling._level = sibling._parent._level + 1 

391 if sibling._id is None: 

392 self._nodesWithoutID.append(sibling) 

393 self._root._nodesWithoutID.remove(sibling) 

394 else: 

395 self._nodesWithID[sibling._id] = sibling 

396 del self._root._nodesWithID[sibling._id] 

397 

398 self._parent._children.remove(self) 

399 

400 self._root = self 

401 self._parent = None 

402 elif not isinstance(parent, Node): 

403 ex = TypeError("Parameter 'parent' is not of type 'Node'.") 

404 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.") 

405 raise ex 

406 else: 

407 if parent._root is self._root: 

408 raise AlreadyInTreeError(f"Parent '{parent}' is already a child node in this tree.", otherNode=parent) 

409 

410 self._root = parent._root 

411 self._parent = parent 

412 self._level = parent._level + 1 

413 for node in self.GetDescendants(): 

414 node._level = node._parent._level + 1 

415 self._SetNewRoot(self._nodesWithID, self._nodesWithoutID) 

416 self._nodesWithID = self._nodesWithoutID = None 

417 parent._children.append(self) 

418 

419 @readonly 

420 def Siblings(self) -> tuple[Node, ...]: 

421 """ 

422 A read-only property to return a tuple of all siblings from the current node. 

423 

424 If the current node is the only child, the tuple is empty. 

425 

426 Siblings are child nodes of the current node's parent node, without the current node itself. 

427 

428 :returns: A tuple of all siblings of the current node. 

429 :raises NoSiblingsError: If the current node has no parent node and thus no siblings. 

430 """ 

431 if self._parent is None: 

432 raise NoSiblingsError("Root node has no siblings.") 

433 

434 return tuple([node for node in self._parent._children if node is not self]) 

435 

436 @readonly 

437 def LeftSiblings(self) -> tuple[Node, ...]: 

438 """ 

439 A read-only property to return a tuple of all siblings left from the current node. 

440 

441 If the current node is the only child, the tuple is empty. 

442 

443 Siblings are child nodes of the current node's parent node, without the current node itself. 

444 

445 :returns: A tuple of all siblings left of the current node. 

446 :raises NoSiblingsError: If the current node has no parent node and thus no siblings. 

447 :raises InternalError: If the tree's data structure is corrupted, because this node is not one of its parent's 

448 children. 

449 """ 

450 if self._parent is None: 

451 raise NoSiblingsError("Root node has no siblings.") 

452 

453 result = [] 

454 for node in self._parent._children: 

455 if node is not self: 

456 result.append(node) 

457 else: 

458 break 

459 else: 

460 raise InternalError("Data structure corruption: Self is not part of parent's children.") # pragma: no cover 

461 

462 return tuple(result) 

463 

464 @readonly 

465 def RightSiblings(self) -> tuple[Node, ...]: 

466 """ 

467 A read-only property to return a tuple of all siblings right from the current node. 

468 

469 If the current node is the only child, the tuple is empty. 

470 

471 Siblings are child nodes of the current node's parent node, without the current node itself. 

472 

473 :returns: A tuple of all siblings right of the current node. 

474 :raises NoSiblingsError: If the current node has no parent node and thus no siblings. 

475 :raises InternalError: If the tree's data structure is corrupted, because this node is not one of its parent's 

476 children. 

477 """ 

478 if self._parent is None: 

479 raise NoSiblingsError("Root node has no siblings.") 

480 

481 result = [] 

482 iterator = iter(self._parent._children) 

483 for node in iterator: 

484 if node is self: 

485 break 

486 else: 

487 raise InternalError("Data structure corruption: Self is not part of parent's children.") # pragma: no cover 

488 

489 for node in iterator: 

490 result.append(node) 

491 

492 return tuple(result) 

493 

494 def _GetPathAsLinkedList(self) -> Deque[Node]: 

495 """ 

496 Compute the path from current node to root node by using a linked list (:class:`deque`). 

497 

498 :meta private: 

499 :returns: Path from node to root node as double-ended queue (deque). 

500 """ 

501 path: Deque[Node] = deque() 

502 

503 node = self 

504 while node is not None: 

505 path.appendleft(node) 

506 node = node._parent 

507 

508 return path 

509 

510 @readonly 

511 def Path(self) -> tuple[Node]: 

512 """ 

513 Read-only property to return the path from root node to the node as a tuple of nodes. 

514 

515 :returns: A tuple of nodes describing the path from root node to the node. 

516 """ 

517 return tuple(self._GetPathAsLinkedList()) 

518 

519 @readonly 

520 def Level(self) -> int: 

521 """ 

522 Read-only property to access a node's level in the tree. 

523 

524 The level is the distance to the root node. 

525 

526 :returns: The node's level. 

527 """ 

528 return self._level 

529 

530 @readonly 

531 def Size(self) -> int: 

532 """ 

533 Read-only property to return the size of the tree. 

534 

535 :returns: Count of all nodes in the tree structure. 

536 """ 

537 return len(self._root._nodesWithID) + len(self._root._nodesWithoutID) 

538 

539 @readonly 

540 def IsRoot(self) -> bool: 

541 """ 

542 Returns true, if the node is the root node (representative node of the tree). 

543 

544 :returns: ``True``, if node is the root node. 

545 """ 

546 return self._parent is None 

547 

548 @readonly 

549 def IsLeaf(self) -> bool: 

550 """ 

551 Returns true, if the node is a leaf node (has no children). 

552 

553 :returns: ``True``, if node has no children. 

554 """ 

555 return len(self._children) == 0 

556 

557 @readonly 

558 def Children(self) -> tuple[Node, ...]: 

559 """ 

560 Read-only property to return a tuple of all direct children (:attr:`_children`). 

561 

562 :returns: A tuple of all children, in the order they were added. 

563 """ 

564 return tuple(self._children) 

565 

566 @readonly 

567 def ChildCount(self) -> int: 

568 """ 

569 Read-only property to return the number of direct children, but not including grand-children. 

570 

571 :returns: Number of child nodes. 

572 """ 

573 return len(self._children) 

574 

575 @readonly 

576 def HasChildren(self) -> bool: 

577 """ 

578 Returns true, if the node has child nodes. 

579 

580 :returns: ``True``, if node has children. 

581 """ 

582 return len(self._children) > 0 

583 

584 def _SetNewRoot(self, nodesWithIDs: dict[Node, Node], nodesWithoutIDs: list[Node]) -> None: 

585 """ 

586 Move the given nodes into this node's tree. 

587 

588 :param nodesWithIDs: Nodes with an ID, which have to stay unique within the tree. 

589 :param nodesWithoutIDs: Nodes without an ID. 

590 :raises ValueError: If one of the IDs already exists in this tree. 

591 """ 

592 for nodeID, node in nodesWithIDs.items(): 

593 if nodeID in self._root._nodesWithID: 

594 raise ValueError(f"ID '{nodeID}' already exists in this tree.") 

595 else: 

596 self._root._nodesWithID[nodeID] = node 

597 node._root = self._root 

598 

599 for node in nodesWithoutIDs: 

600 self._root._nodesWithoutID.append(node) 

601 node._root = self._root 

602 

603 def AddChild(self, child: Node) -> None: 

604 """ 

605 Add a child node to the current node of the tree. 

606 

607 If ``child`` is a subtree, both trees get merged. So all nodes in ``child`` get a new :attr:`_root` assigned and 

608 all IDs are merged into the node's root's ID lists (:attr:`_nodesWithID`). 

609 

610 :param child: The child node to be added to the tree. 

611 :raises TypeError: If parameter ``child`` is not a :class:`Node`. 

612 :raises AlreadyInTreeError: If parameter ``child`` is already a node in the tree. 

613 

614 .. seealso:: 

615 

616 :attr:`Parent` 

617 |rarr| Set the parent of a node. 

618 :meth:`AddChildren` 

619 |rarr| Add multiple children at once. 

620 """ 

621 if not isinstance(child, Node): 

622 ex = TypeError("Parameter 'child' is not of type 'Node'.") 

623 ex.add_note(f"Got type '{getFullyQualifiedName(child)}'.") 

624 raise ex 

625 

626 if child._root is self._root: 

627 raise AlreadyInTreeError(f"Child '{child}' is already a node in this tree.", otherNode=child) 

628 

629 child._root = self._root 

630 child._parent = self 

631 child._level = self._level + 1 

632 for node in child.GetDescendants(): 

633 node._level = node._parent._level + 1 

634 self._SetNewRoot(child._nodesWithID, child._nodesWithoutID) 

635 child._nodesWithID = child._nodesWithoutID = None 

636 self._children.append(child) 

637 

638 def AddChildren(self, children: Iterable[Node]) -> None: 

639 """ 

640 Add multiple children nodes to the current node of the tree. 

641 

642 :param children: The list of children nodes to be added to the tree. 

643 :raises TypeError: If parameter ``children`` contains an item, which is not a :class:`Node`. 

644 :raises AlreadyInTreeError: If parameter ``children`` contains an item, which is already a node in the tree. 

645 

646 .. seealso:: 

647 

648 :attr:`Parent` 

649 |rarr| Set the parent of a node. 

650 :meth:`AddChild` 

651 |rarr| Add a child node to the tree. 

652 """ 

653 for child in children: 

654 if not isinstance(child, Node): 

655 ex = TypeError(f"Item '{child}' in parameter 'children' is not of type 'Node'.") 

656 ex.add_note(f"Got type '{getFullyQualifiedName(child)}'.") 

657 raise ex 

658 

659 if child._root is self._root: 

660 # TODO: create a more specific exception 

661 raise AlreadyInTreeError(f"Child '{child}' is already a node in this tree.", otherNode=child) 

662 

663 child._root = self._root 

664 child._parent = self 

665 child._level = self._level + 1 

666 for node in child.GetDescendants(): 

667 node._level = node._parent._level + 1 

668 self._SetNewRoot(child._nodesWithID, child._nodesWithoutID) 

669 child._nodesWithID = child._nodesWithoutID = None 

670 self._children.append(child) 

671 

672 def RemoveChild(self, child: Node) -> None: 

673 """ 

674 Remove a child node from the current node of the tree. 

675 

676 The child is detached together with its subtree: it becomes the root node of its own tree, like assigning 

677 ``None`` to its :attr:`Parent`. All nodes of that subtree get the child as new :attr:`_root` assigned and their 

678 IDs are moved from the node's root's ID lists (:attr:`_nodesWithID`) into the child's. 

679 

680 :param child: The child node to be removed from the tree. 

681 :raises ValueError: If parameter 'child' is ``None``. 

682 :raises TypeError: If parameter 'child' is not of type :class:`Node`. 

683 :raises NotAChildError: If parameter 'child' is not a direct child of the current node. 

684 

685 .. seealso:: 

686 

687 :attr:`Parent` 

688 |rarr| Set the parent of a node. 

689 :meth:`RemoveChildren` 

690 |rarr| Remove multiple children at once. 

691 :meth:`AddChild` 

692 |rarr| Add a child node to the tree. 

693 """ 

694 if child is None: 

695 raise ValueError("Parameter 'child' is None.") 

696 elif not isinstance(child, Node): 

697 ex = TypeError("Parameter 'child' is not of type 'Node'.") 

698 ex.add_note(f"Got type '{getFullyQualifiedName(child)}'.") 

699 raise ex 

700 elif child._parent is not self: 

701 raise NotAChildError(f"Node '{child}' is not a child of this node.", child=child) 

702 

703 child.Parent = None 

704 

705 def RemoveChildren(self, children: Iterable[Node]) -> None: 

706 """ 

707 Remove multiple children nodes from the current node of the tree. 

708 

709 Each child is detached together with its subtree, like :meth:`RemoveChild` does. The children are collected 

710 before the first one is removed, so 'children' may iterate the current node's children, e.g. 

711 :meth:`GetChildren`. 

712 

713 :param children: The list of children nodes to be removed from the tree. 

714 :raises ValueError: If parameter 'children' is ``None``. 

715 :raises TypeError: If parameter 'children' is not iterable. 

716 :raises ValueError: If parameter 'children' contains ``None``. 

717 :raises TypeError: If parameter 'children' contains an item, which is not of type :class:`Node`. 

718 :raises NotAChildError: If parameter 'children' contains an item, which is not a direct child of the current 

719 node. 

720 

721 .. seealso:: 

722 

723 :attr:`Parent` 

724 |rarr| Set the parent of a node. 

725 :meth:`RemoveChild` 

726 |rarr| Remove a child node from the tree. 

727 :meth:`AddChildren` 

728 |rarr| Add multiple children at once. 

729 """ 

730 if children is None: 

731 raise ValueError("Parameter 'children' is None.") 

732 elif not isinstance(children, Iterable): 

733 ex = TypeError("Parameter 'children' is not iterable.") 

734 ex.add_note(f"Got type '{getFullyQualifiedName(children)}'.") 

735 raise ex 

736 

737 for child in tuple(children): 

738 if child is None: 

739 raise ValueError("Item in parameter 'children' is None.") 

740 elif not isinstance(child, Node): 

741 ex = TypeError(f"Item '{child}' in parameter 'children' is not of type 'Node'.") 

742 ex.add_note(f"Got type '{getFullyQualifiedName(child)}'.") 

743 raise ex 

744 elif child._parent is not self: 

745 raise NotAChildError(f"Item '{child}' in parameter 'children' is not a child of this node.", child=child) 

746 

747 child.Parent = None 

748 

749 def Delete(self) -> None: 

750 """ 

751 Delete this node and its subtree. 

752 

753 A node with a parent is removed from it first, like :meth:`RemoveChild` does. Then every node of the subtree is 

754 disconnected: it has no root, no parent and no children, and its key-value-pairs are removed. A deleted node 

755 belongs to no tree and can't be used any more. Its value is kept. 

756 

757 .. seealso:: 

758 

759 :ref:`Deleting a tree <STRUCT/Tree/Delete>` 

760 |rarr| Reference cycles in a tree and Python's garbage collector. 

761 :meth:`RemoveChild` 

762 |rarr| Remove a child node, which keeps its subtree as a tree of its own. 

763 """ 

764 if self._parent is not None: 

765 self.Parent = None 

766 

767 for node in (*self._nodesWithoutID, *self._nodesWithID.values()): 

768 node._root = None 

769 node._parent = None 

770 node._children = [] 

771 node._dict.clear() 

772 

773 self._nodesWithID = None 

774 self._nodesWithoutID = None 

775 

776 def GetPath(self) -> Generator[Node, None, None]: 

777 """ 

778 Compute the path from the root node to this node. 

779 

780 :returns: A generator yielding the nodes from the root down to this node. 

781 """ 

782 for node in self._GetPathAsLinkedList(): 

783 yield node 

784 

785 def GetAncestors(self) -> Generator[Node, None, None]: 

786 """ 

787 Iterate the ancestors of this node. 

788 

789 :returns: A generator yielding the parent, its parent, and so on up to the root node. 

790 """ 

791 node = self._parent 

792 while node is not None: 

793 yield node 

794 node = node._parent 

795 

796 def GetCommonAncestors(self, others: Union[Node, Iterable[Node]]) -> Generator[Node, None, None]: 

797 """ 

798 Compute the common ancestors of this node and one or more other nodes. 

799 

800 The nodes' paths from the root are walked in parallel and yielded as long as they are identical, so the last 

801 yielded node is the nearest common ancestor. 

802 

803 :param others: Another node, or an iterable of nodes, to compute the common ancestors with. 

804 :returns: A generator yielding the common ancestors, starting at the root node. 

805 :raises NotInSameTreeError: If one of the given nodes is not in the same tree. 

806 :raises NotImplementedError: If more than one other node is given; the common ancestors of a set of nodes are 

807 not computed yet. 

808 """ 

809 if isinstance(others, Node): 809 ↛ 826line 809 didn't jump to line 826 because the condition on line 809 was always true

810 # Check for trivial case 

811 if others is self: 811 ↛ 812line 811 didn't jump to line 812 because the condition on line 811 was never true

812 for node in self._GetPathAsLinkedList(): 

813 yield node 

814 return 

815 

816 # Check if both are in the same tree. 

817 if self._root is not others._root: 817 ↛ 821line 817 didn't jump to line 821 because the condition on line 817 was always true

818 raise NotInSameTreeError("Node 'others' is not in the same tree.", otherNode=others) 

819 

820 # Compute paths top-down and walk both paths until they deviate 

821 for left, right in zip(self.Path, others.Path): 

822 if left is right: 

823 yield left 

824 else: 

825 return 

826 elif isinstance(others, Iterable): 

827 raise NotImplementedError("Generator 'GetCommonAncestors' does not yet support an iterable of siblings to compute the common ancestors.") 

828 

829 def GetChildren(self) -> Generator[Node, None, None]: 

830 """ 

831 A generator to iterate all direct children of the current node. 

832 

833 :returns: A generator to iterate all children. 

834 

835 .. seealso:: 

836 

837 :meth:`GetDescendants` 

838 |rarr| Iterate all descendants. 

839 :meth:`IterateLevelOrder` 

840 |rarr| Iterate items level-by-level, which includes the node itself as a first returned node. 

841 :meth:`IteratePreOrder` 

842 |rarr| Iterate items in pre-order, which includes the node itself as a first returned node. 

843 :meth:`IteratePostOrder` 

844 |rarr| Iterate items in post-order, which includes the node itself as a last returned node. 

845 """ 

846 for child in self._children: 

847 yield child 

848 

849 def GetSiblings(self) -> Generator[Node, None, None]: 

850 """ 

851 A generator to iterate all siblings. 

852 

853 Siblings are child nodes of the current node's parent node, without the current node itself. 

854 

855 :returns: A generator to iterate all siblings of the current node. 

856 :raises NoSiblingsError: If the current node has no parent node and thus no siblings. 

857 """ 

858 if self._parent is None: 

859 raise NoSiblingsError("Root node has no siblings.") 

860 

861 for node in self._parent._children: 

862 if node is self: 

863 continue 

864 

865 yield node 

866 

867 def GetLeftSiblings(self) -> Generator[Node, None, None]: 

868 """ 

869 A generator to iterate all siblings left from the current node. 

870 

871 Siblings are child nodes of the current node's parent node, without the current node itself. 

872 

873 :returns: A generator to iterate all siblings left of the current node. 

874 :raises NoSiblingsError: If the current node has no parent node and thus no siblings. 

875 :raises InternalError: If the tree's data structure is corrupted, because this node is not one of its 

876 parent's children. 

877 """ 

878 if self._parent is None: 

879 raise NoSiblingsError("Root node has no siblings.") 

880 

881 for node in self._parent._children: 

882 if node is self: 

883 break 

884 

885 yield node 

886 else: 

887 raise InternalError("Data structure corruption: Self is not part of parent's children.") # pragma: no cover 

888 

889 def GetRightSiblings(self) -> Generator[Node, None, None]: 

890 """ 

891 A generator to iterate all siblings right from the current node. 

892 

893 Siblings are child nodes of the current node's parent node, without the current node itself. 

894 

895 :returns: A generator to iterate all siblings right of the current node. 

896 :raises NoSiblingsError: If the current node has no parent node and thus no siblings. 

897 :raises InternalError: If the tree's data structure is corrupted, because this node is not one of its 

898 parent's children. 

899 """ 

900 if self._parent is None: 

901 raise NoSiblingsError("Root node has no siblings.") 

902 

903 iterator = iter(self._parent._children) 

904 for node in iterator: 

905 if node is self: 

906 break 

907 else: 

908 raise InternalError("Data structure corruption: Self is not part of parent's children.") # pragma: no cover 

909 

910 for node in iterator: 

911 yield node 

912 

913 def GetDescendants(self) -> Generator[Node, None, None]: 

914 """ 

915 A generator to iterate all descendants of the current node. In contrast to `IteratePreOrder` and `IteratePostOrder` 

916 it doesn't include the node itself. 

917 

918 :returns: A generator to iterate all descendants. 

919 

920 .. seealso:: 

921 

922 :meth:`GetChildren` 

923 |rarr| Iterate all children, but no grand-children. 

924 :meth:`IterateLevelOrder` 

925 |rarr| Iterate items level-by-level, which includes the node itself as a first returned node. 

926 :meth:`IteratePreOrder` 

927 |rarr| Iterate items in pre-order, which includes the node itself as a first returned node. 

928 :meth:`IteratePostOrder` 

929 |rarr| Iterate items in post-order, which includes the node itself as a last returned node. 

930 """ 

931 for child in self._children: 

932 yield child 

933 yield from child.GetDescendants() 

934 

935 def GetRelatives(self) -> Generator[Node, None, None]: 

936 """ 

937 A generator to iterate all relatives (all siblings and all their descendants) of the current node. 

938 

939 :returns: A generator to iterate all relatives. 

940 """ 

941 for node in self.GetSiblings(): 

942 yield node 

943 yield from node.GetDescendants() 

944 

945 def GetLeftRelatives(self) -> Generator[Node, None, None]: 

946 """ 

947 A generator to iterate all left relatives (left siblings and all their descendants) of the current node. 

948 

949 :returns: A generator to iterate all left relatives. 

950 """ 

951 for node in self.GetLeftSiblings(): 

952 yield node 

953 yield from node.GetDescendants() 

954 

955 def GetRightRelatives(self) -> Generator[Node, None, None]: 

956 """ 

957 A generator to iterate all right relatives (right siblings and all their descendants) of the current node. 

958 

959 :returns: A generator to iterate all right relatives. 

960 """ 

961 for node in self.GetRightSiblings(): 

962 yield node 

963 yield from node.GetDescendants() 

964 

965 def IterateLeafs(self) -> Generator[Node, None, None]: 

966 """ 

967 A generator to iterate all leaf-nodes in a subtree, which subtree root is the current node. 

968 

969 :returns: A generator to iterate leaf-nodes reachable from current node. 

970 """ 

971 for child in self._children: 

972 if child.IsLeaf: 

973 yield child 

974 else: 

975 yield from child.IterateLeafs() 

976 

977 def IterateLevelOrder(self) -> Generator[Node, None, None]: 

978 """ 

979 A generator to iterate all siblings of the current node level-by-level top-down. In contrast to `GetDescendants`, 

980 this includes also the node itself as the first returned node. 

981 

982 :returns: A generator to iterate all siblings level-by-level. 

983 

984 .. seealso:: 

985 

986 :meth:`GetChildren` 

987 |rarr| Iterate all children, but no grand-children. 

988 :meth:`GetDescendants` 

989 |rarr| Iterate all descendants. 

990 :meth:`IteratePreOrder` 

991 |rarr| Iterate items in pre-order, which includes the node itself as a first returned node. 

992 :meth:`IteratePostOrder` 

993 |rarr| Iterate items in post-order, which includes the node itself as a last returned node. 

994 """ 

995 queue = deque([self]) 

996 while queue: 

997 currentNode = queue.pop() 

998 yield currentNode 

999 for node in currentNode._children: 

1000 queue.appendleft(node) 

1001 

1002 def IteratePreOrder(self) -> Generator[Node, None, None]: 

1003 """ 

1004 A generator to iterate all siblings of the current node in pre-order. In contrast to `GetDescendants`, this includes 

1005 also the node itself as the first returned node. 

1006 

1007 :returns: A generator to iterate all siblings in pre-order. 

1008 

1009 .. seealso:: 

1010 

1011 :meth:`GetChildren` 

1012 |rarr| Iterate all children, but no grand-children. 

1013 :meth:`GetDescendants` 

1014 |rarr| Iterate all descendants. 

1015 :meth:`IterateLevelOrder` 

1016 |rarr| Iterate items level-by-level, which includes the node itself as a first returned node. 

1017 :meth:`IteratePostOrder` 

1018 |rarr| Iterate items in post-order, which includes the node itself as a last returned node. 

1019 """ 

1020 yield self 

1021 for child in self._children: 

1022 yield from child.IteratePreOrder() 

1023 

1024 def IteratePostOrder(self) -> Generator[Node, None, None]: 

1025 """ 

1026 A generator to iterate all siblings of the current node in post-order. In contrast to `GetDescendants`, this 

1027 includes also the node itself as the last returned node. 

1028 

1029 :returns: A generator to iterate all siblings in post-order. 

1030 

1031 .. seealso:: 

1032 

1033 :meth:`GetChildren` 

1034 |rarr| Iterate all children, but no grand-children. 

1035 :meth:`GetDescendants` 

1036 |rarr| Iterate all descendants. 

1037 :meth:`IterateLevelOrder` 

1038 |rarr| Iterate items level-by-level, which includes the node itself as a first returned node. 

1039 :meth:`IteratePreOrder` 

1040 |rarr| Iterate items in pre-order, which includes the node itself as a first returned node. 

1041 """ 

1042 for child in self._children: 

1043 yield from child.IteratePostOrder() 

1044 yield self 

1045 

1046 def WalkTo(self, other: Node) -> Generator[Node, None, None]: 

1047 """ 

1048 Returns a generator to iterate the path from node to another node. 

1049 

1050 :param other: Node to walk to. 

1051 :returns: Generator to iterate the path from node to other node. 

1052 :raises NotInSameTreeError: If parameter ``other`` is not part of the same tree. 

1053 """ 

1054 # Check for trivial case 

1055 if other is self: 1055 ↛ 1056line 1055 didn't jump to line 1056 because the condition on line 1055 was never true

1056 yield from () 

1057 

1058 # Check if both are in the same tree. 

1059 if self._root is not other._root: 1059 ↛ 1065line 1059 didn't jump to line 1065 because the condition on line 1059 was always true

1060 raise NotInSameTreeError("Node 'other' is not in the same tree.", otherNode=other) 

1061 

1062 # Compute both paths to the root. 

1063 # 1. Walk from self to root, until a first common ancestor is found. 

1064 # 2. Walk from there to other (reverse paths) 

1065 otherPath = other.Path # TODO: Path generates a list and a tuple. Provide a generator for such a walk. 

1066 index = len(otherPath) 

1067 for node in self.GetAncestors(): 

1068 try: 

1069 index = otherPath.index(node) 

1070 break 

1071 except ValueError: 

1072 yield node 

1073 

1074 for i in range(index, len(otherPath)): 

1075 yield otherPath[i] 

1076 

1077 def GetNodeByID(self, nodeID: IDType) -> Node: 

1078 """ 

1079 Lookup a node by its unique ID. 

1080 

1081 :param nodeID: ID of a node to lookup in the tree. 

1082 :returns: Node for the given ID. 

1083 :raises ValueError: If parameter ``nodeID`` is None. 

1084 :raises KeyError: If parameter ``nodeID`` is not found in the tree. 

1085 """ 

1086 if nodeID is None: 

1087 raise ValueError("'None' is not supported as an ID value.") 

1088 

1089 return self._root._nodesWithID[nodeID] 

1090 

1091 def Find(self, predicate: Predicate[Node]) -> Generator[Node, None, None]: 

1092 """ 

1093 Search the tree for nodes matching a predicate. 

1094 

1095 :param predicate: Filter function accepting a node and returning a boolean. 

1096 :returns: A generator yielding the matching nodes. 

1097 :raises NotImplementedError: Searching a tree is not implemented yet. 

1098 """ 

1099 raise NotImplementedError("Method 'Find' is not yet implemented.") 

1100 

1101 def __repr__(self) -> str: 

1102 """ 

1103 Returns a detailed string representation of the node. 

1104 

1105 :returns: The detailed string representation of the node. 

1106 """ 

1107 nodeID = parent = value = "" 

1108 if self._id is not None: 

1109 nodeID = f"; nodeID='{self._id}'" 

1110 

1111 if (self._parent is not None) and (self._parent._id is not None): 

1112 parent = f"; parent='{self._parent._id}'" 

1113 

1114 if self._value is not None: 

1115 value = f"; value='{self._value}'" 

1116 

1117 return f"<node{nodeID}{parent}{value}>" 

1118 

1119 def __str__(self) -> str: 

1120 """ 

1121 Return a string representation of the node. 

1122 

1123 Order of resolution: 

1124 

1125 1. If :attr:`_value` is not None, return the string representation of :attr:`_value`. 

1126 2. If :attr:`_id` is not None, return the string representation of :attr:`_id`. 

1127 3. Else, return :meth:`__repr__`. 

1128 

1129 :returns: The resolved string representation of the node. 

1130 """ 

1131 if self._value is not None: 

1132 return str(self._value) 

1133 elif self._id is not None: 

1134 return str(self._id) 

1135 else: 

1136 return self.__repr__() 

1137 

1138 def Render( 

1139 self, 

1140 prefix: str = "", 

1141 lineend: str = "\n", 

1142 nodeMarker: str = "├─", 

1143 lastNodeMarker: str = "└─", 

1144 bypassMarker: str = "│ " 

1145 ) -> str: 

1146 """ 

1147 Render the tree as ASCII art. 

1148 

1149 :param prefix: Optional, a string printed in front of every line, e.g. for indentation. Default: ``""``. 

1150 :param lineend: Optional, a string printed at the end of every line. Default: ``"\\n"``. 

1151 :param nodeMarker: Optional, a string printed before every non-last tree node. Default: ``"├─"``. 

1152 :param lastNodeMarker: Optional, a string printed before every last tree node. Default: ``"└─"``. 

1153 :param bypassMarker: Optional, a string printed when there are further nodes in the parent level. Default: ``"│ 

1154 "``. 

1155 :returns: A rendered tree as multiline string. 

1156 """ 

1157 emptyMarker = " " * len(bypassMarker) 

1158 

1159 def _render(node: Node, markers: str): 

1160 """ 

1161 Nested function for recursion. 

1162 

1163 :param node: The node whose children are rendered. 

1164 :param markers: The prefix of the current level, assembled from the join and bypass markers. 

1165 :returns: The rendered lines of that subtree. 

1166 """ 

1167 result = [] 

1168 

1169 if node.HasChildren: 

1170 for child in node._children[:-1]: 

1171 nodeRepresentation = child._format(child) if child._format else str(child) 

1172 result.append(f"{prefix}{markers}{nodeMarker}{nodeRepresentation}{lineend}") 

1173 result.extend(_render(child, markers + bypassMarker)) 

1174 

1175 # last child node 

1176 child = node._children[-1] 

1177 nodeRepresentation = child._format(child) if child._format else str(child) 

1178 result.append(f"{prefix}{markers}{lastNodeMarker}{nodeRepresentation}{lineend}") 

1179 result.extend(_render(child, markers + emptyMarker)) 

1180 

1181 return result 

1182 

1183 # Root element 

1184 nodeRepresentation = self._format(self) if self._format else str(self) 

1185 result = [f"{prefix}{nodeRepresentation}{lineend}"] 

1186 result.extend(_render(self, "")) 

1187 

1188 return "".join(result)