|
13 | 13 | import six |
14 | 14 | from devicecloud.apibase import APIBase |
15 | 15 | from devicecloud import DeviceCloudException, DeviceCloudHttpException |
16 | | -from devicecloud.util import conditional_write, to_none_or_dt, validate_type, isoformat |
| 16 | +from devicecloud.util import conditional_write, to_none_or_dt, validate_type, isoformat, \ |
| 17 | + dc_utc_timestamp_to_dt |
17 | 18 | from six import StringIO |
18 | 19 |
|
19 | 20 |
|
@@ -69,6 +70,10 @@ class NoSuchStreamException(StreamException): |
69 | 70 | """Failure to find a stream based on a given id""" |
70 | 71 |
|
71 | 72 |
|
| 73 | +class InvalidRollupDatatype(StreamException): |
| 74 | + """Roll-up's are only valid on numerical data types""" |
| 75 | + |
| 76 | + |
72 | 77 | class StreamsAPI(APIBase): |
73 | 78 | """Provide interface for interacting with device cloud streams API |
74 | 79 |
|
@@ -278,6 +283,29 @@ def from_json(cls, stream, json_data): |
278 | 283 | dp_id=json_data.get("id"), |
279 | 284 | ) |
280 | 285 |
|
| 286 | + @classmethod |
| 287 | + def from_rollup_json(cls, stream, json_data): |
| 288 | + """Rollup json data from the server looks slightly different |
| 289 | +
|
| 290 | + :param DataStream stream: The :class:`~DataStream` out of which this data is coming |
| 291 | + :param dict json_data: Deserialized JSON data from the device cloud about this device |
| 292 | + :raises ValueError: if the data is malformed |
| 293 | + :return: (:class:`~DataPoint`) newly created :class:`~DataPoint` |
| 294 | + """ |
| 295 | + dp = cls.from_json(stream, json_data) |
| 296 | + |
| 297 | + # Special handling for timestamp |
| 298 | + timestamp = isoformat(dc_utc_timestamp_to_dt(int(json_data.get("timestamp")))) |
| 299 | + |
| 300 | + # Special handling for data, all rollup data is float type |
| 301 | + type_converter = DSTREAM_TYPE_MAP[dp.get_data_type()] |
| 302 | + data = type_converter[0](float(json_data.get("data"))) |
| 303 | + |
| 304 | + # Update the special fields |
| 305 | + dp.set_timestamp(timestamp) |
| 306 | + dp.set_data(data) |
| 307 | + return dp |
| 308 | + |
281 | 309 | def __init__(self, data, stream_id=None, description=None, timestamp=None, |
282 | 310 | quality=None, location=None, data_type=None, units=None, dp_id=None, |
283 | 311 | customer_id=None, server_timestamp=None): |
@@ -831,7 +859,7 @@ def read(self, start_time=None, end_time=None, use_client_timeline=True, newest_ |
831 | 859 | the result set. |
832 | 860 |
|
833 | 861 | :param start_time: The start time for the window of data points to read. None means |
834 | | - that we should start with the old data available. |
| 862 | + that we should start with the oldest data available. |
835 | 863 | :type start_time: :class:`datetime.datetime` or None |
836 | 864 | :param end_time: The end time for the window of data points to read. None means |
837 | 865 | that we should include all points received until this point in time. |
@@ -868,6 +896,23 @@ def read(self, start_time=None, end_time=None, use_client_timeline=True, newest_ |
868 | 896 | :returns: A generator object which one can iterate over the DataPoints read. |
869 | 897 |
|
870 | 898 | """ |
| 899 | + |
| 900 | + is_rollup = False |
| 901 | + if (rollup_interval is not None) or (rollup_method is not None): |
| 902 | + is_rollup = True |
| 903 | + numeric_types = [ |
| 904 | + STREAM_TYPE_INTEGER, |
| 905 | + STREAM_TYPE_LONG, |
| 906 | + STREAM_TYPE_FLOAT, |
| 907 | + STREAM_TYPE_DOUBLE, |
| 908 | + STREAM_TYPE_STRING, |
| 909 | + STREAM_TYPE_BINARY, |
| 910 | + STREAM_TYPE_UNKNOWN, |
| 911 | + ] |
| 912 | + |
| 913 | + if self.get_data_type(use_cached=True) not in numeric_types: |
| 914 | + raise InvalidRollupDatatype('Rollups only support numerical DataPoints') |
| 915 | + |
871 | 916 | # Validate function inputs |
872 | 917 | start_time = to_none_or_dt(validate_type(start_time, datetime.datetime, type(None))) |
873 | 918 | end_time = to_none_or_dt(validate_type(end_time, datetime.datetime, type(None))) |
@@ -929,5 +974,8 @@ def read(self, start_time=None, end_time=None, use_client_timeline=True, newest_ |
929 | 974 | result_size = int(result["resultSize"]) # how many are actually included here? |
930 | 975 | query_parameters["pageCursor"] = result.get("pageCursor") # will not be present if result set is empty |
931 | 976 | for item_info in result.get("items", []): |
932 | | - data_point = DataPoint.from_json(self, item_info) |
| 977 | + if is_rollup: |
| 978 | + data_point = DataPoint.from_rollup_json(self, item_info) |
| 979 | + else: |
| 980 | + data_point = DataPoint.from_json(self, item_info) |
933 | 981 | yield data_point |
0 commit comments