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

64 statements  

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

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

2from typing import Any, cast 

3 

4 

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

6 def __init__( 

7 self, 

8 mapping: Mapping[KT, VT], 

9 *, 

10 values_in_list: bool = False, 

11 ) -> None: 

12 self.mapping = mapping 

13 

14 self.__values_in_list = values_in_list 

15 

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

17 return self.mapping[key] 

18 

19 def list( 

20 self, 

21 filter_func: Callable[[KT], bool] | None = None, 

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

23 if filter_func is None: 

24 if self.__values_in_list: 

25 yield from self.mapping.items() 

26 else: 

27 for key in self.mapping: 

28 yield key, None 

29 else: 

30 for key in filter(filter_func, self.mapping): 

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

32 

33 

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

35 def __init__( 

36 self, 

37 mapping: MutableMapping[KT, VT], 

38 *, 

39 values_in_list: bool = False, 

40 ) -> None: 

41 super().__init__( 

42 mapping, 

43 values_in_list=values_in_list, 

44 ) 

45 

46 self.mapping = mapping 

47 

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

49 if key in self.mapping: 

50 raise KeyError 

51 

52 self.mapping[key] = value 

53 return value 

54 

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

56 if key not in self.mapping: 

57 raise KeyError 

58 

59 self.mapping[key] = value 

60 return value 

61 

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

63 default = object() 

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

65 if value == default: 

66 raise KeyError 

67 

68 return cast("VT", value) 

69 

70 

71class RLDict[KT: Any, VT: Any](Mapping[KT, VT]): 

72 def __init__( 

73 self, 

74 *, 

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

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

77 ) -> None: 

78 self.__read_func = read_func 

79 self.__list_func = list_func 

80 

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

82 return self.__read_func(key) 

83 

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

85 for key, _ in self.__list_func(None): 

86 yield key 

87 

88 def __len__(self) -> int: 

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

90 count = 0 

91 for _ in self: 

92 count += 1 

93 return count 

94 

95 

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

97 def __init__( 

98 self, 

99 *, 

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

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

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

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

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

105 ) -> None: 

106 RLDict.__init__( 

107 self, 

108 read_func=read_func, 

109 list_func=list_func, 

110 ) 

111 

112 self.__create_func = create_func 

113 self.__read_func = read_func 

114 self.__update_func = update_func 

115 self.__delete_func = delete_func 

116 self.__list_func = list_func 

117 

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

119 self.__delete_func(key) 

120 

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

122 if key is None or key not in self: 

123 self.__create_func(key, value) 

124 else: 

125 self.__update_func(key, value)