comparison libervia/backend/memory/persistent.py @ 4071:4b842c1fb686

refactoring: renamed `sat` package to `libervia.backend`
author Goffi <goffi@goffi.org>
date Fri, 02 Jun 2023 11:49:51 +0200
parents sat/memory/persistent.py@524856bd7b19
children
comparison
equal deleted inserted replaced
4070:d10748475025 4071:4b842c1fb686
1 #!/usr/bin/env python3
2
3
4 # SAT: a jabber client
5 # Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org)
6
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
16
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 from twisted.internet import defer
21 from twisted.python import failure
22 from libervia.backend.core.i18n import _
23 from libervia.backend.core.log import getLogger
24
25
26 log = getLogger(__name__)
27
28
29 class MemoryNotInitializedError(Exception):
30 pass
31
32
33 class PersistentDict:
34 r"""A dictionary which save persistently each value assigned
35
36 /!\ be careful, each assignment means a database write
37 /!\ Memory must be initialised before loading/setting value with instances of this class"""
38 storage = None
39 binary = False
40
41 def __init__(self, namespace, profile=None):
42 """
43
44 @param namespace: unique namespace for this dictionary
45 @param profile(unicode, None): profile which *MUST* exists, or None for general values
46 """
47 if not self.storage:
48 log.error(_("PersistentDict can't be used before memory initialisation"))
49 raise MemoryNotInitializedError
50 self._cache = {}
51 self.namespace = namespace
52 self.profile = profile
53
54 def _set_cache(self, data):
55 self._cache = data
56
57 def load(self):
58 """Load persistent data from storage.
59
60 need to be called before any other operation
61 @return: defers the PersistentDict instance itself
62 """
63 d = defer.ensureDeferred(self.storage.get_privates(
64 self.namespace, binary=self.binary, profile=self.profile
65 ))
66 d.addCallback(self._set_cache)
67 d.addCallback(lambda __: self)
68 return d
69
70 def iteritems(self):
71 return iter(self._cache.items())
72
73 def items(self):
74 return self._cache.items()
75
76 def __repr__(self):
77 return self._cache.__repr__()
78
79 def __str__(self):
80 return self._cache.__str__()
81
82 def __lt__(self, other):
83 return self._cache.__lt__(other)
84
85 def __le__(self, other):
86 return self._cache.__le__(other)
87
88 def __eq__(self, other):
89 return self._cache.__eq__(other)
90
91 def __ne__(self, other):
92 return self._cache.__ne__(other)
93
94 def __gt__(self, other):
95 return self._cache.__gt__(other)
96
97 def __ge__(self, other):
98 return self._cache.__ge__(other)
99
100 def __cmp__(self, other):
101 return self._cache.__cmp__(other)
102
103 def __hash__(self):
104 return self._cache.__hash__()
105
106 def __bool__(self):
107 return self._cache.__len__() != 0
108
109 def __contains__(self, key):
110 return self._cache.__contains__(key)
111
112 def __iter__(self):
113 return self._cache.__iter__()
114
115 def __getitem__(self, key):
116 return self._cache.__getitem__(key)
117
118 def __setitem__(self, key, value):
119 defer.ensureDeferred(
120 self.storage.set_private_value(
121 self.namespace, key, value, self.binary, self.profile
122 )
123 )
124 return self._cache.__setitem__(key, value)
125
126 def __delitem__(self, key):
127 self.storage.del_private_value(self.namespace, key, self.binary, self.profile)
128 return self._cache.__delitem__(key)
129
130 def clear(self):
131 """Delete all values from this namespace"""
132 self._cache.clear()
133 return self.storage.del_private_namespace(self.namespace, self.binary, self.profile)
134
135 def get(self, key, default=None):
136 return self._cache.get(key, default)
137
138 def aset(self, key, value):
139 """Async set, return a Deferred fired when value is actually stored"""
140 self._cache.__setitem__(key, value)
141 return defer.ensureDeferred(
142 self.storage.set_private_value(
143 self.namespace, key, value, self.binary, self.profile
144 )
145 )
146
147 def adel(self, key):
148 """Async del, return a Deferred fired when value is actually deleted"""
149 self._cache.__delitem__(key)
150 return self.storage.del_private_value(
151 self.namespace, key, self.binary, self.profile)
152
153 def setdefault(self, key, default):
154 try:
155 return self._cache[key]
156 except:
157 self.__setitem__(key, default)
158 return default
159
160 def force(self, name):
161 """Force saving of an attribute to storage
162
163 @return: deferred fired when data is actually saved
164 """
165 return defer.ensureDeferred(
166 self.storage.set_private_value(
167 self.namespace, name, self._cache[name], self.binary, self.profile
168 )
169 )
170
171
172 class PersistentBinaryDict(PersistentDict):
173 """Persistent dict where value can be any python data (instead of string only)"""
174 binary = True
175
176
177 class LazyPersistentBinaryDict(PersistentBinaryDict):
178 r"""PersistentBinaryDict which get key/value when needed
179
180 This Persistent need more database access, it is suitable for largest data,
181 to save memory.
182 /!\ most of methods return a Deferred
183 """
184 # TODO: missing methods should be implemented using database access
185 # TODO: a cache would be useful (which is deleted after a timeout)
186
187 def load(self):
188 # we show a warning as calling load on LazyPersistentBinaryDict sounds like a code mistake
189 log.warning(_("Calling load on LazyPersistentBinaryDict while it's not needed"))
190
191 def iteritems(self):
192 raise NotImplementedError
193
194 def items(self):
195 d = defer.ensureDeferred(self.storage.get_privates(
196 self.namespace, binary=self.binary, profile=self.profile
197 ))
198 d.addCallback(lambda data_dict: data_dict.items())
199 return d
200
201 def all(self):
202 return defer.ensureDeferred(self.storage.get_privates(
203 self.namespace, binary=self.binary, profile=self.profile
204 ))
205
206 def __repr__(self):
207 return self.__str__()
208
209 def __str__(self):
210 return "LazyPersistentBinaryDict (namespace: {})".format(self.namespace)
211
212 def __lt__(self, other):
213 raise NotImplementedError
214
215 def __le__(self, other):
216 raise NotImplementedError
217
218 def __eq__(self, other):
219 raise NotImplementedError
220
221 def __ne__(self, other):
222 raise NotImplementedError
223
224 def __gt__(self, other):
225 raise NotImplementedError
226
227 def __ge__(self, other):
228 raise NotImplementedError
229
230 def __cmp__(self, other):
231 raise NotImplementedError
232
233 def __hash__(self):
234 return hash(str(self.__class__) + self.namespace + (self.profile or ''))
235
236 def __bool__(self):
237 raise NotImplementedError
238
239 def __contains__(self, key):
240 raise NotImplementedError
241
242 def __iter__(self):
243 raise NotImplementedError
244
245 def _data2value(self, data, key):
246 try:
247 return data[key]
248 except KeyError as e:
249 # we return a Failure here to avoid the jump
250 # into debugger in debug mode.
251 raise failure.Failure(e)
252
253 def __getitem__(self, key):
254 """get the value as a Deferred"""
255 d = defer.ensureDeferred(self.storage.get_privates(
256 self.namespace, keys=[key], binary=self.binary, profile=self.profile
257 ))
258 d.addCallback(self._data2value, key)
259 return d
260
261 def __setitem__(self, key, value):
262 defer.ensureDeferred(
263 self.storage.set_private_value(
264 self.namespace, key, value, self.binary, self.profile
265 )
266 )
267
268 def __delitem__(self, key):
269 self.storage.del_private_value(self.namespace, key, self.binary, self.profile)
270
271 def _default_or_exception(self, failure_, default):
272 failure_.trap(KeyError)
273 return default
274
275 def get(self, key, default=None):
276 d = self.__getitem__(key)
277 d.addErrback(self._default_or_exception, default=default)
278 return d
279
280 def aset(self, key, value):
281 """Async set, return a Deferred fired when value is actually stored"""
282 # FIXME: redundant with force, force must be removed
283 # XXX: similar as PersistentDict.aset, but doesn't use cache
284 return defer.ensureDeferred(
285 self.storage.set_private_value(
286 self.namespace, key, value, self.binary, self.profile
287 )
288 )
289
290 def adel(self, key):
291 """Async del, return a Deferred fired when value is actually deleted"""
292 # XXX: similar as PersistentDict.adel, but doesn't use cache
293 return self.storage.del_private_value(
294 self.namespace, key, self.binary, self.profile)
295
296 def setdefault(self, key, default):
297 raise NotImplementedError
298
299 def force(self, name, value):
300 """Force saving of an attribute to storage
301
302 @param value(object): value is needed for LazyPersistentBinaryDict
303 @return: deferred fired when data is actually saved
304 """
305 return defer.ensureDeferred(
306 self.storage.set_private_value(
307 self.namespace, name, value, self.binary, self.profile
308 )
309 )
310
311 def remove(self, key):
312 """Delete a key from sotrage, and return a deferred called when it's done
313
314 @param key(unicode): key to delete
315 @return (D): A deferred fired when delete is done
316 """
317 return self.storage.del_private_value(self.namespace, key, self.binary, self.profile)