Change notification callback and handle_events types 90/6290/1
authorTimo Tietavainen <timo.tietavainen@nokia.com>
Thu, 10 Jun 2021 12:54:37 +0000 (15:54 +0300)
committerTimo Tietavainen <timo.tietavainen@nokia.com>
Thu, 10 Jun 2021 13:23:14 +0000 (16:23 +0300)
In earlier implementation (commit db775397) single event was passed to
to application notification callback as a string type and if there was
multiple notification then as a list of string. Similarly
'handle_events' function was either returning a single event as string
or a list of string. In a code it is unpleasant to handle variables what
can be either string or list type, because in application you need to
check always the variable type what comes from SDL for these functions
and based on the type either handle the value as a string or list of
string. That's why by this commit change event(s) to be passed as a list
to the notification callback what application has registered and also
now 'handle_events' function returns always received notifications a
list. Because of type change this code change is not backward
compatible.

Issue-ID: RIC-795

Signed-off-by: Timo Tietavainen <timo.tietavainen@nokia.com>
Change-Id: I49f2bd1012d93f184945c891df03ed83ed83d544

docs/release-notes.rst
ricsdl-package/examples/notify.py
ricsdl-package/ricsdl/__init__.py
ricsdl-package/ricsdl/backend/dbbackend_abc.py
ricsdl-package/ricsdl/backend/fake_dict_db.py
ricsdl-package/ricsdl/backend/redis.py
ricsdl-package/ricsdl/syncstorage.py
ricsdl-package/ricsdl/syncstorage_abc.py
ricsdl-package/tests/backend/test_redis.py

index 12beb5b..f5ad8c2 100644 (file)
@@ -33,6 +33,12 @@ This document provides the release notes of the ricsdl library.
 Version history
 ---------------
 
+[3.0.0] - 2021-06-10
+
+* Simplify v2.3.0 notification callback and handle_events API to return received
+  event(s) only as a list type. Earlier single event was returned as a string type.
+  Notification callback and handle_events API changes are not backward compatible.
+
 [2.3.0] - 2021-06-09
 
 * Pack all the events in a channel to one DB notification to be in line with SDL Golang.
index 7495bfe..006001f 100755 (executable)
@@ -115,9 +115,8 @@ last_cb_message = ""
 stop_thread = False
 
 
-def cb(channel: str, message: Union[str, List[str]]):
-    """An example of function that will be called when a single event or list of
-    events are received.
+def cb(channel: str, message: List[str]):
+    """An example of function that will be called when an event list is received.
 
     This function sets last_cb_channel and last_cb_message as channel and
     message respectively. This also unlocks the global lock variable.
@@ -160,7 +159,7 @@ else:
 # type must be bytes and multiple key values can be set in one set function call.
 _try_func_callback_return(
     lambda: mysdl.set_and_publish(MY_NS, {MY_CHANNEL: "SET PUBLISH"}, {'my_key': b'my_value'}))
-assert last_cb_channel == MY_CHANNEL and last_cb_message == "SET PUBLISH"
+assert last_cb_channel == MY_CHANNEL and last_cb_message[0] == "SET PUBLISH"
 
 # Sets a value 'my_value' for a key 'my_key' under given namespace. Note that value
 # type must be bytes and multiple key values can be set in one set function call.
@@ -174,7 +173,7 @@ assert last_cb_channel == MY_CHANNEL and last_cb_message == ["SET PUBLISH1", "SE
 was_set = _try_func_callback_return(lambda: mysdl.set_if_and_publish(
     MY_NS, {MY_CHANNEL: "SET IF PUBLISH"}, 'my_key', b'my_value', b'my_value2'))
 assert was_set is True
-assert last_cb_channel == MY_CHANNEL and last_cb_message == "SET IF PUBLISH"
+assert last_cb_channel == MY_CHANNEL and last_cb_message[0] == "SET IF PUBLISH"
 # Try again. This time value 'my_value2' won't be set, because the key has already 'my_value2'
 # value. Callback function will not be called here.
 was_set = _try_func_return(lambda: mysdl.set_if_and_publish(MY_NS, {MY_CHANNEL: "SET IF PUBLISH"},
@@ -186,7 +185,7 @@ assert was_set is False
 was_set = _try_func_callback_return(lambda: mysdl.set_if_not_exists_and_publish(
     MY_NS, {MY_CHANNEL: "SET IF NOT PUBLISH"}, 'my_key2', b'my_value'))
 assert was_set is True
-assert last_cb_channel == MY_CHANNEL and last_cb_message == "SET IF NOT PUBLISH"
+assert last_cb_channel == MY_CHANNEL and last_cb_message[0] == "SET IF NOT PUBLISH"
 # Try again. This time the key 'my_key2' already exists. Callback function will not be called here.
 was_set = _try_func_return(lambda: mysdl.set_if_not_exists_and_publish(
     MY_NS, {MY_CHANNEL: "SET IF NOT PUBLISH"}, 'my_key2', b'my_value'))
@@ -197,13 +196,13 @@ _try_func_callback_return(
     lambda: mysdl.remove_and_publish(MY_NS, {MY_CHANNEL: "REMOVE PUBLISH"}, 'my_key'))
 my_ret_dict = _try_func_return(lambda: mysdl.get(MY_NS, 'my_key'))
 assert my_ret_dict == {}
-assert last_cb_channel == MY_CHANNEL and last_cb_message == "REMOVE PUBLISH"
+assert last_cb_channel == MY_CHANNEL and last_cb_message[0] == "REMOVE PUBLISH"
 
 # Removes a key 'my_key' under given namespace only if the old value is 'my_value'.
 was_removed = _try_func_callback_return(lambda: mysdl.remove_if_and_publish(
     MY_NS, {MY_CHANNEL: "REMOVE IF PUBLISH"}, 'my_key2', b'my_value'))
 assert was_removed is True
-assert last_cb_channel == MY_CHANNEL and last_cb_message == "REMOVE IF PUBLISH"
+assert last_cb_channel == MY_CHANNEL and last_cb_message[0] == "REMOVE IF PUBLISH"
 # Try again to remove not anymore existing key 'my_key'. Callback function will not be called here.
 was_removed = _try_func_return(lambda: mysdl.remove_if_and_publish(
     MY_NS, {MY_CHANNEL: "REMOVE IF PUBLISH"}, 'my_key2', b'my_value'))
@@ -218,7 +217,7 @@ _try_func_callback_return(
     lambda: mysdl.remove_all_and_publish(MY_NS, {MY_CHANNEL: "REMOVE ALL PUBLISH"}))
 my_ret_dict = _try_func_return(lambda: mysdl.get(MY_NS, {'my_key'}))
 assert my_ret_dict == {}
-assert last_cb_channel == MY_CHANNEL and last_cb_message == "REMOVE ALL PUBLISH"
+assert last_cb_channel == MY_CHANNEL and last_cb_message[0] == "REMOVE ALL PUBLISH"
 
 stop_thread = True
 mysdl.close()
index 81f7e72..165d2f4 100644 (file)
@@ -31,7 +31,7 @@ from .exceptions import (
 )
 
 
-__version__ = '2.3.0'
+__version__ = '3.0.0'
 
 
 __all__ = [
index 49944b5..8805bdd 100755 (executable)
@@ -161,13 +161,12 @@ class DbBackendAbc(ABC):
         pass
 
     @abstractmethod
-    def subscribe_channel(self, ns: str,
-                          cb: Union[Callable[[str, str], None], Callable[[str, List[str]], None]],
+    def subscribe_channel(self, ns: str, cb: Callable[[str, List[str]], None],
                           channels: List[str]) -> None:
         """
         This takes a callback function and one or many channels to be subscribed.
         When an event is received for the given channel, the given callback function
-        shall be called with channel and notifications as parameter.
+        shall be called with channel and notification(s) as parameter.
         """
         pass
 
@@ -185,7 +184,7 @@ class DbBackendAbc(ABC):
         pass
 
     @abstractmethod
-    def handle_events(self) -> Optional[Union[Tuple[str, str], Tuple[str, List[str]]]]:
+    def handle_events(self) -> Optional[Tuple[str, List[str]]]:
         """
         handle_events is a non-blocking function that returns a tuple containing channel
         name and message(s) received from notification.
index 1a63ebd..94d0f32 100755 (executable)
@@ -188,8 +188,7 @@ class FakeDictBackend(DbBackendAbc):
         for channel, events in channels_and_events.items():
             self._queue.put((channel, events))
 
-    def subscribe_channel(self, ns: str,
-                          cb: Union[Callable[[str, str], None], Callable[[str, List[str]], None]],
+    def subscribe_channel(self, ns: str, cb: Callable[[str, List[str]], None],
                           channels: List[str]) -> None:
         for channel in channels:
             self._channel_cbs[channel] = cb
@@ -201,7 +200,7 @@ class FakeDictBackend(DbBackendAbc):
             message = self._queue.get()
             cb = self._channel_cbs.get(message[0], None)
             if cb:
-                cb(message[0], message[1][0] if (isinstance(message[1], list) and len(message[1]) == 1) else message[1])
+                cb(message[0], message[1])
             time.sleep(0.001)
 
     def unsubscribe_channel(self, ns: str, channels: List[str]) -> None:
@@ -215,7 +214,7 @@ class FakeDictBackend(DbBackendAbc):
             self._listen_thread.start()
         self._run_in_thread = True
 
-    def handle_events(self) -> Optional[Union[Tuple[str, str], Tuple[str, List[str]]]]:
+    def handle_events(self) -> Optional[Tuple[str, List[str]]]:
         if self._listen_thread.is_alive() or self._run_in_thread:
             raise Exception("Event loop already started")
         try:
@@ -223,10 +222,9 @@ class FakeDictBackend(DbBackendAbc):
         except queue.Empty:
             return None
         cb = self._channel_cbs.get(message[0], None)
-        notifications = message[1][0] if (isinstance(message[1], list) and len(message[1]) == 1) else message[1]
         if cb:
-            cb(message[0], notifications)
-        return (message[0], notifications)
+            cb(message[0], message[1])
+        return (message[0], message[1])
 
 
 class FakeDictBackendLock(DbBackendLockAbc):
index bc4b43b..1c549db 100755 (executable)
@@ -117,9 +117,8 @@ class PubSub(redis.client.PubSub):
                 message_channel = self._strip_ns_from_bin_key('', message['channel'])
                 message_data = message['data'].decode('utf-8')
                 messages = message_data.split(self.event_separator)
-                notification = messages[0] if len(messages) == 1 else messages
-                handler(message_channel, notification)
-                return message_channel, notification
+                handler(message_channel, messages)
+                return message_channel, messages
         elif message_type != 'pong':
             # this is a subscribe/unsubscribe message. ignore if we don't
             # want them
@@ -330,8 +329,7 @@ class RedisBackend(DbBackendAbc):
                 *channels_and_events_prepared,
             )
 
-    def subscribe_channel(self, ns: str,
-                          cb: Union[Callable[[str, str], None], Callable[[str, List[str]], None]],
+    def subscribe_channel(self, ns: str, cb: Callable[[str, List[str]], None],
                           channels: List[str]) -> None:
         channels = self.__add_keys_ns_prefix(ns, channels)
         for channel in channels:
@@ -357,7 +355,7 @@ class RedisBackend(DbBackendAbc):
                 redis_ctx.pubsub_thread = redis_ctx.redis_pubsub.run_in_thread(sleep_time=0.001, daemon=True)
             redis_ctx.run_in_thread = True
 
-    def handle_events(self) -> Optional[Union[Tuple[str, str], Tuple[str, List[str]]]]:
+    def handle_events(self) -> Optional[Tuple[str, List[str]]]:
         if self.next_client_event >= len(self.clients):
             self.next_client_event = 0
         redis_ctx = self.clients[self.next_client_event]
index 55063e4..b15365a 100755 (executable)
@@ -266,8 +266,7 @@ class SyncStorage(SyncStorageAbc):
         self.__dbbackend.remove_all_and_publish(ns, channels_and_events)
 
     @func_arg_checker(SdlTypeError, 1, ns=str, cb=Callable, channels=(str, builtins.set))
-    def subscribe_channel(self, ns: str,
-                          cb: Union[Callable[[str, str], None], Callable[[str, List[str]], None]],
+    def subscribe_channel(self, ns: str, cb: Callable[[str, List[str]], None],
                           channels: Union[str, Set[str]]) -> None:
         self._validate_callback(cb)
         channels = [channels] if isinstance(channels, str) else list(channels)
@@ -281,7 +280,7 @@ class SyncStorage(SyncStorageAbc):
     def start_event_listener(self) -> None:
         self.__dbbackend.start_event_listener()
 
-    def handle_events(self) -> Optional[Union[Tuple[str, str], Tuple[str, List[str]]]]:
+    def handle_events(self) -> Optional[Tuple[str, List[str]]]:
         return self.__dbbackend.handle_events()
 
     @func_arg_checker(SdlTypeError, 1, ns=str, resource=str, expiration=(int, float))
index 1151979..3bcbd9c 100755 (executable)
@@ -829,8 +829,7 @@ class SyncStorageAbc(ABC):
         pass
 
     @abstractmethod
-    def subscribe_channel(self, ns: str,
-                          cb: Union[Callable[[str, str], None], Callable[[str, List[str]], None]],
+    def subscribe_channel(self, ns: str, cb: Callable[[str, List[str]], None],
                           channels: Union[str, Set[str]]) -> None:
         """
         Subscribes the client to the specified channels.
@@ -842,8 +841,8 @@ class SyncStorageAbc(ABC):
 
         When subscribing for a channel, a callback function is given as a parameter.
         Whenever single notification or many notifications are received from a channel,
-        this callback is called with channel and notifications as parameter. A call to
-        subscribe_channel function returns immediately, callbacks will be called
+        this callback is called with channel and notification list as parameter. A call
+        to subscribe_channel function returns immediately, callbacks will be called
         synchronously from a dedicated thread.
 
         It is possible to subscribe to different channels using different callbacks. In
@@ -856,7 +855,7 @@ class SyncStorageAbc(ABC):
 
         Args:
             ns: Namespace under which this operation is targeted.
-            cb: A function that is called when events on channel are received.
+            cb: A function that is called when event(s) on channel is received.
             channels: One channel or multiple channels to be subscribed.
 
         Returns:
@@ -910,7 +909,7 @@ class SyncStorageAbc(ABC):
         pass
 
     @abstractmethod
-    def handle_events(self) -> Optional[Union[Tuple[str, str], Tuple[str, List[str]]]]:
+    def handle_events(self) -> Optional[Tuple[str, List[str]]]:
         """
         handle_events is a non-blocking function that returns a tuple containing channel
         name and message(s) received from an event. The registered callback function will
@@ -925,8 +924,7 @@ class SyncStorageAbc(ABC):
         events handling starts.
 
         Returns:
-            Tuple: (channel: str, message: str) or
-            Tuple: (channel: str, messages: list of str)
+            Tuple: (channel: str, message(s): list of str)
 
         Raises:
             SdlTypeError: If function's argument is of an inappropriate type.
index 7d2928b..100795e 100755 (executable)
@@ -868,5 +868,5 @@ class TestRedisClient:
         cls.pubsub.channels = {b'{some-ns},ch1': Mock()}
 
     def test_handle_pubsub_message(self):
-        assert self.pubsub.handle_message([b'message', b'{some-ns},ch1', b'cbn']) == ('ch1', 'cbn')
-        self.pubsub.channels.get(b'{some-ns},ch1').assert_called_once_with('ch1', 'cbn')
+        assert self.pubsub.handle_message([b'message', b'{some-ns},ch1', b'cbn']) == ('ch1', ['cbn'])
+        self.pubsub.channels.get(b'{some-ns},ch1').assert_called_once_with('ch1', ['cbn'])