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
« 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
4from .typing import SearchableMapping
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
16 self.__values_in_list = values_in_list
18 def read(self, key: KT) -> VT:
19 return self.mapping[key]
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
45 keys = (
46 iter(self.mapping) if filter_body[0] is None
47 else cast("SearchableMapping", self.mapping).search(filter_body[0])
48 )
50 if filter_body[1] is not None:
51 keys = filter(filter_body[1], keys)
52 else:
53 raise ValueError
55 for key in keys:
56 yield key, self.mapping[key] if self.__values_in_list else None
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 )
71 self.mapping = mapping
73 def create(self, key: KT, value: VT) -> VT:
74 if key in self.mapping:
75 raise KeyError
77 self.mapping[key] = value
78 return value
80 def update(self, key: KT, value: VT) -> VT:
81 if key not in self.mapping:
82 raise KeyError
84 self.mapping[key] = value
85 return value
87 def delete(self, key: KT) -> VT:
88 default = object()
89 value = self.mapping.pop(key, default)
90 if value == default:
91 raise KeyError
93 return cast("VT", value)
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
106 def __getitem__(self, key: KT) -> VT:
107 return self.__read_func(key)
109 def __iter__(self) -> Iterator[KT]:
110 return self.search()
112 def search(self, filter_body: Any = None) -> Iterator[KT]:
113 for key, _ in self.__list_func(filter_body):
114 yield key
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
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 )
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
146 def __delitem__(self, key: KT) -> None:
147 self.__delete_func(key)
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)