Coverage for src/gentrie/nodes.py: 95%

1# -*- coding: utf-8 -*-

2"""Internal node implementation for the gentrie package."""

3

4 from copy import deepcopy

5 from textwrap import indent

6 from typing import Any, Optional, TYPE_CHECKING

7

8 from .protocols import TrieKeyToken

9 from .types import TrieId

10

11 if TYPE_CHECKING:

12 from .trie import GeneralizedTrie

13

14

15 class Node: # pylint: disable=too-few-public-methods

16 """A node in the trie.

17

18 A node is a container for a key in the trie. It has a unique identifier

19 and a reference to the key.

20

21 Attributes:

22 ident (TrieId): Unique identifier for the key.

23 token (TrieKeyToken): Token for the key.

24 parent (Optional[GeneralizedTrie | Node): Reference to the parent node.

25 children (dict[TrieKeyToken, Node]): Dictionary of child nodes.

26 """

27 __slots__ = ('ident', 'token', 'value', 'parent', 'children')

28

29 def __init__(self, token: TrieKeyToken, parent: "GeneralizedTrie | Node", value: Optional[Any] = None) -> None:

30 self.ident: Optional[TrieId] = None

31 self.token: TrieKeyToken = token

32 self.value: Optional[Any] = value

33 self.parent: Optional["GeneralizedTrie | Node"] = parent

34 self.children: dict[TrieKeyToken, "Node"] = {}

35

36 def __str__(self) -> str:

37 """Generates a stringified version of the trie for visual examination.

38

39 The output IS NOT executable code but more in the nature of debug and testing support."""

40 output: list[str] = ["{"]

41 if self.parent is None: 41 ↛ 42line 41 didn't jump to line 42 because the condition on line 41 was never true

42 output.append(" parent = None")

43 else:

44 from .trie import GeneralizedTrie # pylint: disable=import-outside-toplevel

45 if isinstance(self.parent, GeneralizedTrie):

46 output.append(" parent = root node")

47 else:

48 output.append(f" parent = {repr(self.parent.token)}")

49 output.append(f" node token = {repr(self.token)}")

50 if self.ident:

51 output.append(f" trie id = {self.ident}")

52 if self.children:

53 output.append(" children = {")

54 for child_key, child_value in self.children.items():

55 output.append(f" {repr(child_key)} = " + indent(str(child_value), " ").lstrip())

56 output.append(" }")

57 output.append("}")

58 return "\n".join(output)

59

60 def _as_dict(self) -> dict[str, Any]:

61 """Converts the node to a dictionary representation.

62

63 This is useful for tests and debugging purposes and is not intended

64 for general purpose serialization of the trie. It's output is not

65 suitable for use with :func:`json.dumps()` or similar functions and

66 is subject to change without notice. This is NOT a public API - it is

67 intended for internal use by tests only.

68

69 Returns:

70 :class:`dict[str, Any]`: Dictionary representation of the node.

71 The dictionary contains the following keys:

72 - "ident": The unique identifier of the node.

73 - "token": The token of the node.

74 - "value": The value associated with the node.

75 - "parent": The token of the parent node, or None if there is no parent.

76 - "children": A dictionary of child nodes, where the keys are the tokens

77 of the child nodes and the values are dictionaries representing the child nodes.

78 """

79 # pylint: disable=protected-access

80 # Using deepcopy to ensure that the dictionary is a copy of the data in the trie,

81 # not a dictionary of live references to it

82 return deepcopy(

83 {

84 "ident": self.ident,

85 "token": self.token,

86 "value": self.value,

87 "parent": self.parent.token if self.parent else None,

88 "children": {str(k): v._as_dict() for k, v in self.children.items()},

89 }

90 )