1 # ==================================================================================
2 # Copyright (c) 2020 Nokia
3 # Copyright (c) 2020 AT&T Intellectual Property.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 # ==================================================================================
23 from ricsdl.syncstorage import SyncStorage
28 Provides convenient wrapper methods for using the SDL Python interface.
29 Optionally uses msgpack for binary (de)serialization:
30 see https://msgpack.org/index.html
32 Published as a standalone module (and kept separate from the Xapp
33 framework classes) so these features can be used outside Xapps.
36 def __init__(self, use_fake_sdl=False):
42 use_fake_sdl: bool (optional, default False)
43 if this is True, then use SDL's in-memory backend,
44 which is very useful for testing since it allows use
45 of SDL without a running SDL or Redis instance.
46 This can be used while developing an xapp and also
47 for monkeypatching during unit testing; e.g., the xapp
48 framework unit tests do this.
51 self._sdl = SyncStorage(fake_db_backend="dict")
53 self._sdl = SyncStorage()
55 def set(self, ns, key, value, usemsgpack=True):
57 Stores a key-value pair,
58 optionally serializing the value to bytes using msgpack.
60 TODO: discuss whether usemsgpack should *default* to True or
61 False here. This seems like a usage statistic question (that we
62 don't have enough data for yet). Are more uses for an xapp to
63 write/read their own data, or will more xapps end up reading data
64 written by some other thing? I think it's too early to know.
73 Object or byte array to store. See the `usemsgpack` parameter.
74 usemsgpack: boolean (optional, default is True)
75 Determines whether the value is serialized using msgpack before storing.
76 If usemsgpack is True, the msgpack function `packb` is invoked
77 on the value to yield a byte array that is then sent to SDL.
78 Stated differently, if usemsgpack is True, the value can be anything
79 that is serializable by msgpack.
80 If usemsgpack is False, the value must be bytes.
83 value = msgpack.packb(value, use_bin_type=True)
84 self._sdl.set(ns, {key: value})
86 def set_if(self, ns, key, old_value, new_value, usemsgpack=True):
88 Conditionally modify the value of a key if the current value in data storage matches the
89 user's last known value.
98 Lask known object or byte array. See the `usemsgpack` parameter.
100 Object or byte array to be written. See the `usemsgpack` parameter.
101 usemsgpack: boolean (optional, default is True)
102 Determines whether the value is serialized using msgpack before storing.
103 If usemsgpack is True, the msgpack function `packb` is invoked
104 on the value to yield a byte array that is then sent to SDL.
105 Stated differently, if usemsgpack is True, the value can be anything
106 that is serializable by msgpack.
107 If usemsgpack is False, the value must be bytes.
112 True for successful modification, false if the user's last known data did not
113 match the current value in data storage.
116 old_value = msgpack.packb(old_value, use_bin_type=True)
117 new_value = msgpack.packb(new_value, use_bin_type=True)
118 return self._sdl.set_if(ns, key, old_value, new_value)
120 def set_if_not_exists(self, ns, key, value, usemsgpack=True):
122 Write data to SDL storage if key does not exist.
131 Object or byte array to store. See the `usemsgpack` parameter.
132 usemsgpack: boolean (optional, default is True)
133 Determines whether the value is serialized using msgpack before storing.
134 If usemsgpack is True, the msgpack function `packb` is invoked
135 on the value to yield a byte array that is then sent to SDL.
136 Stated differently, if usemsgpack is True, the value can be anything
137 that is serializable by msgpack.
138 If usemsgpack is False, the value must be bytes.
143 True for successful modification, false if the user's last known data did not
144 match the current value in data storage.
147 value = msgpack.packb(value, use_bin_type=True)
148 return self._sdl.set_if_not_exists(ns, key, value)
150 def get(self, ns, key, usemsgpack=True):
152 Gets the value for the specified namespace and key,
153 optionally deserializing stored bytes using msgpack.
161 usemsgpack: boolean (optional, default is True)
162 If usemsgpack is True, the byte array stored by SDL is deserialized
163 using msgpack to yield the original object that was stored.
164 If usemsgpack is False, the byte array stored by SDL is returned
165 without further processing.
170 See the usemsgpack parameter for an explanation of the returned value type.
171 Answers None if the key is not found.
174 ret_dict = self._sdl.get(ns, {key})
176 result = ret_dict[key]
178 result = msgpack.unpackb(result, raw=False)
181 def find_keys(self, ns, prefix):
183 Find all keys matching search pattern under the namespace.
195 A list of found keys.
197 return self._sdl.find_keys(ns, f"{prefix}*")
199 def find_and_get(self, ns, prefix, usemsgpack=True):
201 Gets all key-value pairs in the specified namespace
202 with keys that start with the specified prefix,
203 optionally deserializing stored bytes using msgpack.
211 usemsgpack: boolean (optional, default is True)
212 If usemsgpack is True, every byte array stored by SDL is deserialized
213 using msgpack to yield the original value that was stored.
214 If usemsgpack is False, every byte array stored by SDL is returned
215 without further processing.
219 Dictionary of key-value pairs
220 Each key has the specified prefix.
221 See the usemsgpack parameter for an explanation of the returned value types.
222 Answers an empty dictionary if no keys matched the prefix.
225 # note: SDL "*" usage is inconsistent with real python regex, where it would be ".*"
226 ret_dict = self._sdl.find_and_get(ns, f"{prefix}*")
228 ret_dict = {k: msgpack.unpackb(v, raw=False) for k, v in ret_dict.items()}
231 def delete(self, ns, key):
233 Deletes the key-value pair with the specified key in the specified namespace.
242 self._sdl.remove(ns, {key})
244 def delete_if(self, ns, key, value, usemsgpack=True):
246 Conditionally remove data from SDL storage if the current data value matches the user's
256 Object or byte array to store. See the `usemsgpack` parameter.
257 usemsgpack: boolean (optional, default is True)
258 Determines whether the value is serialized using msgpack before storing.
259 If usemsgpack is True, the msgpack function `packb` is invoked
260 on the value to yield a byte array that is then sent to SDL.
261 Stated differently, if usemsgpack is True, the value can be anything
262 that is serializable by msgpack.
263 If usemsgpack is False, the value must be bytes.
268 True if successful removal, false if the user's last known data did not match the
269 current value in data storage.
272 value = msgpack.packb(value, use_bin_type=True)
273 return self._sdl.remove_if(ns, key, value)
275 def add_member(self, ns, group, member, usemsgpack=True):
277 Add new members to a SDL group under the namespace.
287 usemsgpack: boolean (optional, default is True)
288 Determines whether the member is serialized using msgpack before storing.
289 If usemsgpack is True, the msgpack function `packb` is invoked
290 on the member to yield a byte array that is then sent to SDL.
291 Stated differently, if usemsgpack is True, the member can be anything
292 that is serializable by msgpack.
293 If usemsgpack is False, the member must be bytes.
296 member = msgpack.packb(member, use_bin_type=True)
297 self._sdl.add_member(ns, group, {member})
299 def remove_member(self, ns, group, member, usemsgpack=True):
301 Remove members from a SDL group.
311 usemsgpack: boolean (optional, default is True)
312 Determines whether the member is serialized using msgpack before storing.
313 If usemsgpack is True, the msgpack function `packb` is invoked
314 on the member to yield a byte array that is then sent to SDL.
315 Stated differently, if usemsgpack is True, the member can be anything
316 that is serializable by msgpack.
317 If usemsgpack is False, the member must be bytes.
320 member = msgpack.packb(member, use_bin_type=True)
321 self._sdl.remove_member(ns, group, {member})
323 def remove_group(self, ns, group):
325 Remove a SDL group along with its members.
333 usemsgpack: boolean (optional, default is True)
334 Determines whether the member is serialized using msgpack before storing.
335 If usemsgpack is True, the msgpack function `packb` is invoked
336 on the member to yield a byte array that is then sent to SDL.
337 Stated differently, if usemsgpack is True, the member can be anything
338 that is serializable by msgpack.
339 If usemsgpack is False, the member must be bytes.
341 self._sdl.remove_group(ns, group)
343 def get_members(self, ns, group, usemsgpack=True):
345 Get all the members of a SDL group.
352 group name to retrive
353 usemsgpack: boolean (optional, default is True)
354 Determines whether the member is serialized using msgpack before storing.
355 If usemsgpack is True, the msgpack function `packb` is invoked
356 on the member to yield a byte array that is then sent to SDL.
357 Stated differently, if usemsgpack is True, the member can be anything
358 that is serializable by msgpack.
359 If usemsgpack is False, the member must be bytes.
363 Set[str] or Set[bytes]
364 A set of the members of the group.
367 ret_set = self._sdl.get_members(ns, group)
369 ret_set = {msgpack.unpackb(m, raw=False) for m in ret_set}
372 def is_member(self, ns, group, member, usemsgpack=True):
374 Validate if a given member is in the SDL group.
384 usemsgpack: boolean (optional, default is True)
385 Determines whether the member is serialized using msgpack before storing.
386 If usemsgpack is True, the msgpack function `packb` is invoked
387 on the member to yield a byte array that is then sent to SDL.
388 Stated differently, if usemsgpack is True, the member can be anything
389 that is serializable by msgpack.
390 If usemsgpack is False, the member must be bytes.
395 True if member was in the group, false otherwise.
398 member = msgpack.packb(member, use_bin_type=True)
399 return self._sdl.is_member(ns, group, member)
401 def group_size(self, ns, group):
403 Return the number of members in a group.
404 If the group does not exist, value 0 is returned.
411 group name to retrive size
412 usemsgpack: boolean (optional, default is True)
413 Determines whether the member is serialized using msgpack before storing.
414 If usemsgpack is True, the msgpack function `packb` is invoked
415 on the member to yield a byte array that is then sent to SDL.
416 Stated differently, if usemsgpack is True, the member can be anything
417 that is serializable by msgpack.
418 If usemsgpack is False, the member must be bytes.
423 Number of members in a group.
425 return self._sdl.group_size(ns, group)
427 def set_and_publish(self, ns, channel, event, key, value, usemsgpack=True):
429 Publish event to channel after writing data.
436 channel to publish event
442 Object or byte array to store. See the `usemsgpack` parameter.
443 usemsgpack: boolean (optional, default is True)
444 Determines whether the value is serialized using msgpack before storing.
445 If usemsgpack is True, the msgpack function `packb` is invoked
446 on the value to yield a byte array that is then sent to SDL.
447 Stated differently, if usemsgpack is True, the value can be anything
448 that is serializable by msgpack.
449 If usemsgpack is False, the value must be bytes.
452 value = msgpack.packb(value, use_bin_type=True)
453 self._sdl.set_and_publish(ns, {channel: event}, {key: value})
455 def set_if_and_publish(self, ns, channel, event, key, old_value, new_value, usemsgpack=True):
457 Publish event to channel after conditionally modifying the value of a key if the
458 current value in data storage matches the user's last known value.
465 channel to publish event
471 Lask known object or byte array. See the `usemsgpack` parameter.
473 Object or byte array to be written. See the `usemsgpack` parameter.
474 usemsgpack: boolean (optional, default is True)
475 Determines whether the old_value & new_value is serialized using msgpack before storing.
476 If usemsgpack is True, the msgpack function `packb` is invoked
477 on the old_value & new_value to yield a byte array that is then sent to SDL.
478 Stated differently, if usemsgpack is True, the old_value & new_value can be anything
479 that is serializable by msgpack.
480 If usemsgpack is False, the old_value & new_value must be bytes.
485 True for successful modification, false if the user's last known data did not
486 match the current value in data storage.
489 old_value = msgpack.packb(old_value, use_bin_type=True)
490 new_value = msgpack.packb(new_value, use_bin_type=True)
491 return self._sdl.set_if_and_publish(ns, {channel: event}, key, old_value, new_value)
493 def set_if_not_exists_and_publish(self, ns, channel, event, key, value, usemsgpack=True):
495 Publish event to channel after writing data to SDL storage if key does not exist.
502 channel to publish event
508 Object or byte array to store. See the `usemsgpack` parameter.
509 usemsgpack: boolean (optional, default is True)
510 Determines whether the value is serialized using msgpack before storing.
511 If usemsgpack is True, the msgpack function `packb` is invoked
512 on the value to yield a byte array that is then sent to SDL.
513 Stated differently, if usemsgpack is True, the value can be anything
514 that is serializable by msgpack.
515 If usemsgpack is False, the value must be bytes.
520 True if key didn't exist yet and set operation was executed, false if key already
521 existed and thus its value was left untouched.
524 value = msgpack.packb(value, use_bin_type=True)
525 return self._sdl.set_if_not_exists_and_publish(ns, {channel: event}, key, value)
527 def remove_and_publish(self, ns, channel, event, key):
529 Publish event to channel after removing data.
536 channel to publish event
542 self._sdl.remove_and_publish(ns, {channel: event}, {key})
544 def remove_if_and_publish(self, ns, channel, event, key, value, usemsgpack=True):
546 Publish event to channel after removing key and its data from database if the
547 current data value is expected one.
554 channel to publish event
560 Object or byte array to store. See the `usemsgpack` parameter.
561 usemsgpack: boolean (optional, default is True)
562 Determines whether the value is serialized using msgpack before storing.
563 If usemsgpack is True, the msgpack function `packb` is invoked
564 on the value to yield a byte array that is then sent to SDL.
565 Stated differently, if usemsgpack is True, the value can be anything
566 that is serializable by msgpack.
567 If usemsgpack is False, the value must be bytes.
572 True if successful removal, false if the user's last known data did not match the
573 current value in data storage.
576 value = msgpack.packb(value, use_bin_type=True)
577 return self._sdl.remove_if_and_publish(ns, {channel: event}, key, value)
579 def remove_all_and_publish(self, ns, channel, event):
581 Publish event to channel after removing all keys under the namespace.
588 channel to publish event
592 self._sdl.remove_all_and_publish(ns, {channel: event})
594 def subscribe_channel(self, ns, cb, channel):
596 Subscribes the client to the specified channels.
603 A function that is called when an event on channel is received.
607 self._sdl.subscribe_channel(ns, cb, {channel})
609 def unsubscribe_channel(self, ns, channel):
611 unsubscribe_channel removes subscription from one or several channels.
618 channel to unsubscribe
620 self._sdl.unsubscribe_channel(ns, {channel})
622 def start_event_listener(self):
624 start_event_listener creates an event loop in a separate thread for handling
625 events from subscriptions. The registered callback function will be called
626 when an event is received.
628 self._sdl.start_event_listener()
630 def handle_events(self):
632 handle_events is a non-blocking function that returns a tuple containing channel
633 name and a list of message(s) received from an event. The registered callback
634 function will still be called when an event is received.
636 This function is called if SDL user decides to handle notifications in its own
637 event loop. Calling this function after start_event_listener raises an exception.
638 If there are no notifications, these returns None.
643 (channel: str, message: list of str)
645 return self._sdl.handle_events()
647 def healthcheck(self):
649 Checks if the sdl connection is healthy.
655 return self._sdl.is_active()