Coverage for src/extratools_core/crudl.py: 100%

78 statements  

« prev     ^ index     » next       coverage.py v7.8.1, created at 2025-06-24 04:41 -0700

1from collections.abc import Callable, Iterable, Iterator, Mapping, MutableMapping 

2from typing import Any, cast 

3 

4from .typing import SearchableMapping 

5 

6 

7class RLWrapper[KT: Any, VT: Any]: 

8 def __init__( 

9 self, 

10 mapping: Mapping[KT, VT], 

11 *, 

12 values_in_list: bool = False, 

13 ) -> None: 

14 self.mapping = mapping 

15 

16 self.__values_in_list = values_in_list 

17 

18 def read(self, key: KT) -> VT: 

19 return self.mapping[key] 

20 

21 def list( 

22 self, 

23 filter_body: ( 

24 tuple[Any, Callable[[KT], bool] | None] 

25 | Callable[[KT], bool] 

26 | None 

27 ) = None, 

28 ) -> Iterable[tuple[KT, VT | None]]: 

29 if filter_body is None: 

30 if self.__values_in_list: 

31 yield from self.mapping.items() 

32 else: 

33 for key in self.mapping: 

34 yield key, None 

35 else: 

36 keys: Iterator[KT] 

37 if isinstance(filter_body, Callable): 

38 keys = filter(filter_body, self.mapping) 

39 elif isinstance(filter_body, tuple): 

40 if filter_body[0] is not None and not isinstance(self.mapping, SearchableMapping): 

41 raise ValueError 

42 if filter_body[1] is not None and not isinstance(filter_body[1], Callable): 

43 raise ValueError 

44 

45 keys = ( 

46 iter(self.mapping) if filter_body[0] is None 

47 else cast("SearchableMapping", self.mapping).search(filter_body[0]) 

48 ) 

49 

50 if filter_body[1] is not None: 

51 keys = filter(filter_body[1], keys) 

52 else: 

53 raise ValueError 

54 

55 for key in keys: 

56 yield key, self.mapping[key] if self.__values_in_list else None 

57 

58 

59class CRUDLWrapper[KT: Any, VT: Any](RLWrapper[KT, VT]): 

60 def __init__( 

61 self, 

62 mapping: MutableMapping[KT, VT], 

63 *, 

64 values_in_list: bool = False, 

65 ) -> None: 

66 super().__init__( 

67 mapping, 

68 values_in_list=values_in_list, 

69 ) 

70 

71 self.mapping = mapping 

72 

73 def create(self, key: KT, value: VT) -> VT: 

74 if key in self.mapping: 

75 raise KeyError 

76 

77 self.mapping[key] = value 

78 return value 

79 

80 def update(self, key: KT, value: VT) -> VT: 

81 if key not in self.mapping: 

82 raise KeyError 

83 

84 self.mapping[key] = value 

85 return value 

86 

87 def delete(self, key: KT) -> VT: 

88 default = object() 

89 value = self.mapping.pop(key, default) 

90 if value == default: 

91 raise KeyError 

92 

93 return cast("VT", value) 

94 

95 

96class RLDict[KT: Any, VT: Any](SearchableMapping[KT, VT]): 

97 def __init__( 

98 self, 

99 *, 

100 read_func: Callable[[KT], VT], 

101 list_func: Callable[[Any | None], Iterable[tuple[KT, VT | None]]], 

102 ) -> None: 

103 self.__read_func = read_func 

104 self.__list_func = list_func 

105 

106 def __getitem__(self, key: KT) -> VT: 

107 return self.__read_func(key) 

108 

109 def __iter__(self) -> Iterator[KT]: 

110 return self.search() 

111 

112 def search(self, filter_body: Any = None) -> Iterator[KT]: 

113 for key, _ in self.__list_func(filter_body): 

114 yield key 

115 

116 def __len__(self) -> int: 

117 # Cannot use `count` in `toolz` as itself depends on this function 

118 count = 0 

119 for _ in self: 

120 count += 1 

121 return count 

122 

123 

124class CRUDLDict[KT: Any, VT: Any](MutableMapping[KT, VT], RLDict[KT, VT]): 

125 def __init__( 

126 self, 

127 *, 

128 create_func: Callable[[KT | None, Any], VT | None], 

129 read_func: Callable[[KT], VT], 

130 update_func: Callable[[KT, Any], VT | None], 

131 delete_func: Callable[[KT], VT | None], 

132 list_func: Callable[[Any | None], Iterable[tuple[KT, VT | None]]], 

133 ) -> None: 

134 RLDict.__init__( 

135 self, 

136 read_func=read_func, 

137 list_func=list_func, 

138 ) 

139 

140 self.__create_func = create_func 

141 self.__read_func = read_func 

142 self.__update_func = update_func 

143 self.__delete_func = delete_func 

144 self.__list_func = list_func 

145 

146 def __delitem__(self, key: KT) -> None: 

147 self.__delete_func(key) 

148 

149 def __setitem__(self, key: KT | None, value: VT) -> None: 

150 if key is None or key not in self: 

151 self.__create_func(key, value) 

152 else: 

153 self.__update_func(key, value)