Coverage for src/gentrie/trie/storage.py: 100%

34 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-08-17 11:24 -0700

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

2"""Entry storage operations for the trie.""" 

3 

4# pylint does not understand the Protocol declaration of the mixing class 

5# pylint: disable=no-member 

6 

7from typing import Any, cast, Optional 

8 

9from ..exceptions import DuplicateKeyError, InvalidGeneralizedKeyError, ErrorTag 

10from ..nodes import Node 

11from ..protocols import GeneralizedKey 

12from ..types import TrieEntry, TrieId 

13from ..validation import is_generalizedkey 

14 

15from .trie_mixins import TrieMixinsInterface 

16 

17# Disabled because pyright does not understand mixins 

18# use of private attributes from the mixing class as declared 

19# in the TrieMixinsInterface protocol. 

20# pyright: reportPrivateUsage=false 

21 

22 

23class TrieStorageMixin: 

24 """Mixin providing entry storage operations.""" 

25 

26 def add(self: TrieMixinsInterface, key: GeneralizedKey, value: Optional[Any] = None) -> TrieId: 

27 """Adds the key to the trie. 

28 

29 .. warning:: **Keys Must Be Immutable** 

30 

31 Once a key is added to the trie, neither the key sequence itself nor any of its 

32 constituent tokens should be mutated. Modifying a key after it has been added 

33 can corrupt the internal state of the trie, leading to unpredictable behavior 

34 and making entries unreachable. The trie does not create a deep copy of keys 

35 for performance reasons. 

36 

37 If you need to modify a key, you should remove the old key and add a new one 

38 with the modified value. 

39 

40 Args: 

41 key (GeneralizedKey): Must be an object that can be iterated and that when iterated 

42 returns elements conforming to the :class:`TrieKeyToken` protocol. 

43 value (Optional[Any], default=None): Optional value to associate with the key. 

44 

45 Raises: 

46 InvalidGeneralizedKeyError: 

47 If key is not a valid :class:`GeneralizedKey`. 

48 DuplicateKeyError: 

49 If the key is already in the trie. 

50 

51 Returns: 

52 :class:`TrieId`: Id of the inserted key. If the key was not in the trie, 

53 it returns the id of the new entry. If the key was already in the trie, 

54 it raises a :class:`DuplicateKeyError`. 

55 """ 

56 return self._store_entry(key=key, value=value, allow_value_update=False) 

57 

58 def update(self: TrieMixinsInterface, key: GeneralizedKey, value: Optional[Any] = None) -> TrieId: 

59 """Updates the key/value pair in the trie. 

60 

61 .. warning:: **Keys Must Be Immutable** 

62 

63 Once a key is added to the trie, neither the key sequence itself nor any of its 

64 constituent tokens should be mutated. Modifying a key after it has been added 

65 can corrupt the internal state of the trie, leading to unpredictable behavior 

66 and making entries unreachable. The trie does not create a deep copy of keys 

67 for performance reasons. 

68 

69 If you need to modify a key, you should remove the old key and add a new one 

70 with the modified value. 

71 

72 Args: 

73 key (GeneralizedKey): Must be an object that can be iterated and that when iterated 

74 returns elements conforming to the :class:`TrieKeyToken` protocol. 

75 value (Optional[Any], default=None): Optional value to associate with the key. 

76 

77 Raises: 

78 InvalidGeneralizedKeyError: 

79 If key is not a valid :class:`GeneralizedKey`. 

80 

81 Returns: 

82 :class:`TrieId`: Id of the inserted key. If the key was already in the trie with the same value 

83 it returns the id for the already existing entry. If the key was not already in the trie, 

84 it returns the id for a new entry. 

85 """ 

86 return self._store_entry(key=key, value=value, allow_value_update=True) 

87 

88 def _store_entry(self: TrieMixinsInterface, 

89 key: GeneralizedKey, 

90 value: Any, 

91 allow_value_update: bool) -> TrieId: 

92 """Stores a key/value pair entry in the trie. 

93 

94 Args: 

95 key (GeneralizedKey): Must be an object that can be iterated and that when iterated 

96 returns elements conforming to the :class:`TrieKeyToken` protocol. 

97 value (Optional[Any], default=None): Optional value to associate with the key. 

98 allow_value_update (bool): 

99 Whether to allow overwriting the value if the key already exists. 

100 Raises: 

101 InvalidGeneralizedKeyError: If key is not a valid :class:`GeneralizedKey`. 

102 DuplicateKeyError: If the key is already in the trie and `allow_value_update` is False. 

103 

104 Returns: 

105 :class:`TrieId`: Id of the key's entry. If the key was not in the trie, 

106 it returns the id of the new entry. If the key was already in the trie and 

107 `allow_value_update` is True, it updates the value and returns the existing id. 

108 If the key was already in the trie and `allow_value_update` is False, 

109 it raises a :class:`DuplicateKeyError`. 

110 """ 

111 if self.runtime_validation and not is_generalizedkey(key): 

112 raise InvalidGeneralizedKeyError( 

113 msg="key is not a valid `GeneralizedKey`", 

114 tag=ErrorTag.STORE_ENTRY_INVALID_GENERALIZED_KEY) 

115 

116 # Traverse the trie to find the insertion point for the key, 

117 # creating nodes as necessary. 

118 current_node = self 

119 for token in key: 

120 if token not in current_node.children: 

121 child_node = Node(token=token, parent=current_node) # type: ignore[reportArgumentType] 

122 current_node.children[token] = child_node 

123 current_node = current_node.children[token] 

124 

125 # This key is already in the trie (it has a trie id) 

126 if current_node.ident: 

127 # If we allow updating, update the value and return the existing id 

128 if allow_value_update: 

129 current_node.value = value 

130 self._trie_entries[current_node.ident] = TrieEntry( 

131 current_node.ident, key, value) 

132 return current_node.ident 

133 

134 # The key is already in the trie but we are not allowing updating values - so raise an error 

135 raise DuplicateKeyError( 

136 msg=("Attempted to store a key with a value that is already in the trie " 

137 " - use `update()` to change the value of an existing key."), 

138 tag=ErrorTag.STORE_ENTRY_DUPLICATE_KEY) 

139 

140 # Assign a new trie id for the node and set the value 

141 self._ident_counter += 1 

142 new_ident = TrieId(self._ident_counter) 

143 current_node.ident = new_ident 

144 current_node.value = value 

145 self._trie_index[new_ident] = cast(Node, current_node) 

146 self._trie_entries[new_ident] = TrieEntry(new_ident, key, value) 

147 

148 return new_ident