Repository: google/mobly

Issue:
Create an `event` type on the client side
So instead of dealing with the dict and key access:
```
{u'callbackId': u'2-2', u'data': {u'isSuccess': True}, u'name': u'status', u'time': 1487989424790}
```
We could have some sort of structure:
```
event.data -> {'isSuccess': True}
event.name -> 'status'
event.creation_time -> 1487989424790
```


Discussion:

Resolving patch:
diff --git a/mobly/controllers/android_device_lib/callback_handler.py b/mobly/controllers/android_device_lib/callback_handler.py
--- a/mobly/controllers/android_device_lib/callback_handler.py
+++ b/mobly/controllers/android_device_lib/callback_handler.py
@@ -17,6 +17,8 @@
 import logging
 import time
 
+from mobly.controllers.android_device_lib import snippet_event
+
 
 class Error(Exception):
     pass
@@ -30,16 +32,23 @@ class CallbackHandler(object):
     """The class used to handle a specific group of callback events.
 
     All the events handled by a CallbackHandler are originally triggered by one
-    async Rpc call. All the events are tagged with a callback_id specific to a call
-    to an AsyncRpc method defined on the server side.
+    async Rpc call. All the events are tagged with a callback_id specific to a
+    call to an AsyncRpc method defined on the server side.
 
-    The callback events are dictionaries that follow this schema:
+    The raw message representing an event looks like:
     {
         'callbackId': <string, callbackId>,
         'name': <string, name of the event>,
-        'time': <long, epoch time of when the event was created on the server side>,
+        'time': <long, epoch time of when the event was created on the server
+                 side>,
         'data': <dict, extra data from the callback on the server side>
     }
+
+    Each message is then used to create a SnippetEvent object on the client
+    side.
+
+    Attributes:
+        ret_value: The direct return value of the async Rpc call.
     """
 
     def __init__(self, callback_id, event_client, ret_value, method_name):
@@ -57,7 +66,7 @@ def waitAndGet(self, event_name, timeout=None):
             timeout: float, the number of seconds to wait before giving up.
 
         Returns:
-            The oldest entry of the specified event.
+            SnippetEvent, the oldest entry of the specified event.
 
         Raises:
             TimeoutError: The expected event does not occur within time limit.
@@ -65,25 +74,27 @@ def waitAndGet(self, event_name, timeout=None):
         if timeout:
             timeout *= 1000  # convert to milliseconds for java side
         try:
-            event = self._event_client.eventWaitAndGet(self._id, event_name,
-                                                       timeout)
+            raw_event = self._event_client.eventWaitAndGet(self._id,
+                                                           event_name, timeout)
         except Exception as e:
             if "EventSnippetException: timeout." in str(e):
                 raise TimeoutError(
                     'Timeout waiting for event "%s" triggered by %s (%s).'
                     % (event_name, self._method_name, self._id))
             raise
-        return event
+        return snippet_event.from_dict(raw_event)
 
     def getAll(self, event_name):
-        """Gets all the events of a certain name that have been received so far.
-        This is a non-blocking call.
+        """Gets all the events of a certain name that have been received so
+        far. This is a non-blocking call.
 
         Args:
             callback_id: The id of the callback.
             event_name: string, the name of the event to get.
 
         Returns:
-            A list of dicts, each dict representing an event from the Java side.
+            A list of SnippetEvent, each representing an event from the Java
+            side.
         """
-        return self._event_client.eventGetAll(self._id, event_name)
+        raw_events = self._event_client.eventGetAll(self._id, event_name)
+        return [snippet_event.from_dict(msg) for msg in raw_events]
diff --git a/mobly/controllers/android_device_lib/snippet_event.py b/mobly/controllers/android_device_lib/snippet_event.py
new file mode 100644
--- /dev/null
+++ b/mobly/controllers/android_device_lib/snippet_event.py
@@ -0,0 +1,50 @@
+# Copyright 2017 Google Inc.
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+#     http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import time
+
+
+def from_dict(event_dict):
+    """Create a SnippetEvent object from a dictionary.
+
+    Args:
+        event_dict: a dictionary representing an event.
+
+    Returns:
+        A SnippetEvent object.
+    """
+    return SnippetEvent(
+        callback_id=event_dict['callbackId'],
+        name=event_dict['name'],
+        creation_time=event_dict['time'],
+        data=event_dict['data'])
+
+
+class SnippetEvent(object):
+    """The class that represents callback events for mobly snippet library.
+
+    Attributes:
+        callback_id: string, the callback ID associated with the event.
+        name: string, the name of the event.
+        creation_time: int, the epoch time when the event is created on the
+                       Rpc server side.
+        data: dictionary, the data held by the event. Can be None.
+    """
+
+    def __init__(self, callback_id, name, creation_time, data):
+        self.callback_id = callback_id
+        self.name = name
+        self.creation_time = creation_time
+        self.data = data


Test patch:
diff --git a/tests/mobly/controllers/android_device_lib/callback_handler_test.py b/tests/mobly/controllers/android_device_lib/callback_handler_test.py
--- a/tests/mobly/controllers/android_device_lib/callback_handler_test.py
+++ b/tests/mobly/controllers/android_device_lib/callback_handler_test.py
@@ -21,19 +21,47 @@
 from mobly.controllers.android_device_lib import jsonrpc_client_base
 
 MOCK_CALLBACK_ID = "1-0"
+MOCK_RAW_EVENT = {
+    'callbackId': '2-1',
+    'name': 'AsyncTaskResult',
+    'time': 20460228696,
+    'data': {
+        'exampleData': "Here's a simple event.",
+        'successful': True,
+        'secretNumber': 12
+    }
+}
 
 
 class CallbackHandlerTest(unittest.TestCase):
     """Unit tests for mobly.controllers.android_device_lib.callback_handler.
     """
 
+    def test_event_dict_to_snippet_event(self):
+        mock_event_client = mock.Mock()
+        mock_event_client.eventWaitAndGet = mock.Mock(
+            return_value=MOCK_RAW_EVENT)
+        handler = callback_handler.CallbackHandler(
+            callback_id=MOCK_CALLBACK_ID,
+            event_client=mock_event_client,
+            ret_value=None,
+            method_name=None)
+        event = handler.waitAndGet('ha')
+        self.assertEqual(event.name, MOCK_RAW_EVENT['name'])
+        self.assertEqual(event.creation_time, MOCK_RAW_EVENT['time'])
+        self.assertEqual(event.data, MOCK_RAW_EVENT['data'])
+        self.assertEqual(event.callback_id, MOCK_RAW_EVENT['callbackId'])
+
     def test_wait_and_get_timeout(self):
         mock_event_client = mock.Mock()
         java_timeout_msg = 'com.google.android.mobly.snippet.event.EventSnippet$EventSnippetException: timeout.'
         mock_event_client.eventWaitAndGet = mock.Mock(
             side_effect=jsonrpc_client_base.ApiError(java_timeout_msg))
-        handler = callback_handler.CallbackHandler(MOCK_CALLBACK_ID,
-                                                   mock_event_client, None, None)
+        handler = callback_handler.CallbackHandler(
+            callback_id=MOCK_CALLBACK_ID,
+            event_client=mock_event_client,
+            ret_value=None,
+            method_name=None)
         expected_msg = 'Timeout waiting for event "ha" .*'
         with self.assertRaisesRegexp(callback_handler.TimeoutError,
                                      expected_msg):