1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """
23 Flumotion Twisted-like flavors
24
25 Inspired by L{twisted.spread.flavors}
26 """
27
28 from twisted.internet import defer
29 from twisted.spread import pb
30 from zope.interface import Interface
31 from flumotion.common import log
32
33 __version__ = "$Rev: 7740 $"
34
35
36
37
38
40 """
41 I am an interface for objects that want to listen to changes on
42 cached states.
43 """
44
46 """
47 @type object: L{StateRemoteCache}
48 @param object: the state object having changed
49 @type key: string
50 @param key: the key being set
51 @param value: the value the key is being set to
52
53 The given key on the given object has been set to the given value.
54 """
55
57 """
58 @type object: L{StateRemoteCache}
59 @param object: the state object having changed
60 @type key: string
61 @param key: the key being appended to
62 @param value: the value being appended to the list given by key
63
64 The given value has been added to the list given by the key.
65 """
66
68 """
69 @type object: L{StateRemoteCache}
70 @param object: the state object having changed
71 @type key: string
72 @param key: the key being removed from
73 @param value: the value being removed from the list given by key
74
75 The given value has been removed from the list given by the key.
76 """
77
78
80 """
81 I am a cacheable state object.
82
83 I cache key-value pairs, where values can be either single objects
84 or list of objects.
85 """
86
88 self._observers = []
89 self._dict = {}
90
91
92
93 - def addKey(self, key, value=None):
94 """
95 Add a key to the state cache so it can be used with set.
96 """
97 self._dict[key] = value
98
99
100
101
103 """
104 Add a key for a list of objects to the state cache.
105 """
106 if value is None:
107 value = []
108 self._dict[key] = value
109
110
111
112
114 """
115 Add a key for a dict value to the state cache.
116 """
117 if value is None:
118 value = {}
119 self._dict[key] = value
120
122 return key in self._dict.keys()
123
125 return self._dict.keys()
126
127 - def get(self, key, otherwise=None):
128 """
129 Get the state cache value for the given key.
130
131 Return otherwise in case where key is present but value None.
132 """
133 if not key in self._dict.keys():
134 raise KeyError('%s in %r' % (key, self))
135
136 v = self._dict[key]
137
138 if v == None:
139 return otherwise
140
141 return v
142
143 - def set(self, key, value):
144 """
145 Set a given state key to the given value.
146 Notifies observers of this Cacheable through observe_set.
147 """
148 if not key in self._dict.keys():
149 raise KeyError('%s in %r' % (key, self))
150
151 self._dict[key] = value
152 dList = [o.callRemote('set', key, value) for o in self._observers]
153 return defer.DeferredList(dList)
154
155 - def append(self, key, value):
156 """
157 Append the given object to the given list.
158 Notifies observers of this Cacheable through observe_append.
159 """
160 if not key in self._dict.keys():
161 raise KeyError('%s in %r' % (key, self))
162
163 self._dict[key].append(value)
164 dList = [o.callRemote('append', key, value) for o in self._observers]
165 return defer.DeferredList(dList)
166
167 - def remove(self, key, value):
168 """
169 Remove the given object from the given list.
170 Notifies observers of this Cacheable through observe_remove.
171 """
172 if not key in self._dict.keys():
173 raise KeyError('%s in %r' % (key, self))
174
175 try:
176 self._dict[key].remove(value)
177 except ValueError:
178 raise ValueError('value %r not in list %r for key %r' % (
179 value, self._dict[key], key))
180 dList = [o.callRemote('remove', key, value) for o in self._observers]
181 dl = defer.DeferredList(dList)
182 return dl
183
184 - def setitem(self, key, subkey, value):
185 """
186 Set a value in the given dict.
187 Notifies observers of this Cacheable through observe_setitem.
188 """
189 if not key in self._dict.keys():
190 raise KeyError('%s in %r' % (key, self))
191
192 self._dict[key][subkey] = value
193 dList = [o.callRemote('setitem', key, subkey, value)
194 for o in self._observers]
195 return defer.DeferredList(dList)
196
198 """
199 Removes an element from the given dict. Note that the key refers
200 to the dict; it is the subkey (and its value) that will be removed.
201 Notifies observers of this Cacheable through observe_delitem.
202 """
203 if not key in self._dict.keys():
204 raise KeyError('%s in %r' % (key, self))
205
206 try:
207 value = self._dict[key].pop(subkey)
208 except KeyError:
209 raise KeyError('key %r not in dict %r for key %r' % (
210 subkey, self._dict[key], key))
211 dList = [o.callRemote('delitem', key, subkey, value) for o in
212 self._observers]
213 dl = defer.DeferredList(dList)
214 return dl
215
216
217
219 self._observers.append(observer)
220 return self._dict
221
223 self._observers.remove(observer)
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
241 """
242 I am a remote cache of a state object.
243 """
244
247
248
249
250
251
253 return key in self._dict.keys()
254
256 return self._dict.keys()
257
258 - def get(self, key, otherwise=None):
259 """
260 Get the state cache value for the given key.
261
262 Return otherwise in case where key is present but value None.
263 """
264 if not key in self._dict.keys():
265 raise KeyError('%s in %r' % (key, self))
266
267 v = self._dict[key]
268
269 if v == None:
270 return otherwise
271
272 return v
273
275
276
277 if not hasattr(self, '_listeners'):
278
279
280
281 self._listeners = {}
282
283
284
285 - def addListener(self, listener, set=None, append=None, remove=None,
286 setitem=None, delitem=None, invalidate=None, set_=None):
287 """
288 Adds a listener to the remote cache.
289
290 The caller will be notified of state events via the functions
291 given as the 'set', 'append', and 'remove', 'setitem', and
292 'delitem' keyword arguments.
293
294 Always call this method using keyword arguments for the functions;
295 calling them with positional arguments is not supported.
296
297 Setting one of the event handlers to None will ignore that
298 event. It is an error for all event handlers to be None.
299
300 @param listener: new listener object that wants to receive
301 cache state change notifications.
302 @type listener: object implementing
303 L{flumotion.twisted.flavors.IStateListener}
304 @param set_: procedure to call when a value is set
305 @type set_: procedure(object, key, value) -> None
306 @param append: procedure to call when a value is appended to a list
307 @type append: procedure(object, key, value) -> None
308 @param remove: procedure to call when a value is removed from
309 a list
310 @type remove: procedure(object, key, value) -> None
311 @param setitem: procedure to call when a value is set in a dict
312 @type setitem: procedure(object, key, subkey, value) -> None
313 @param delitem: procedure to call when a value is removed
314 from a dict.
315 @type delitem: procedure(object, key, subkey, value) -> None
316 @param invalidate: procedure to call when this cache has been
317 invalidated.
318 @type invalidate: procedure(object) -> None
319 """
320
321 if set:
322 import warnings
323 warnings.warn('Please use the set_ kwarg instead',
324 DeprecationWarning, stacklevel=2)
325 set_ = set
326
327 if not (set_ or append or remove or setitem or delitem or invalidate):
328
329 import sys
330 log.safeprintf(sys.stderr,
331 "Warning: Use of deprecated %r.addListener(%r)"
332 " without explicit event handlers\n", self,
333 listener)
334 set_ = listener.stateSet
335 append = listener.stateAppend
336 remove = listener.stateRemove
337
338 self._ensureListeners()
339 if listener in self._listeners:
340 raise KeyError(
341 "%r is already a listener of %r" % (listener, self))
342 self._listeners[listener] = [set_, append, remove, setitem,
343 delitem, invalidate]
344 if invalidate and hasattr(self, '_cache_invalid'):
345 invalidate(self)
346
348 self._ensureListeners()
349 if listener not in self._listeners:
350 raise KeyError(listener)
351 del self._listeners[listener]
352
353
354
357
359
360
361 self._ensureListeners()
362 for proc in [tup[index] for tup in self._listeners.values()]:
363 if proc:
364 try:
365 proc(self, *args)
366 except Exception, e:
367
368 log.warning("stateremotecache",
369 'Exception in StateCache handler: %s',
370 log.getExceptionMessage(e))
371
379
388
390
391 if hasattr(self, 'remove'):
392 StateCacheable.remove(self, key, value)
393 else:
394 try:
395 self._dict[key].remove(value)
396 except ValueError:
397 raise ValueError("value %r not under key %r with values %r" %
398 (value, key, self._dict[key]))
399
400 self._notifyListeners(2, key, value)
401
403
404 if hasattr(self, 'setitem'):
405 StateCacheable.setitem(self, key, subkey, value)
406 else:
407 self._dict[key][subkey] = value
408
409 self._notifyListeners(3, key, subkey, value)
410
412
413 if hasattr(self, 'delitem'):
414 StateCacheable.delitem(self, key, subkey)
415 else:
416 try:
417 del self._dict[key][subkey]
418 except KeyError:
419 raise KeyError("key %r not in dict %r for state dict %r" %
420 (subkey, self._dict[key], self._dict))
421
422 self._notifyListeners(4, key, subkey, value)
423
425 """Invalidate this StateRemoteCache.
426
427 Calling this method will result in the invalidate callback being
428 called for all listeners that passed an invalidate handler to
429 addListener. This method is not called automatically; it is
430 provided as a convenience to applications.
431 """
432 assert not hasattr(self, '_cache_invalid'), \
433 'object has already been invalidated'
434
435
436
437
438 self._cache_invalid = True
439
440 self._notifyListeners(5)
441