diff --git a/datadog/api/metrics.py b/datadog/api/metrics.py index d22094a66..f0853f6ec 100644 --- a/datadog/api/metrics.py +++ b/datadog/api/metrics.py @@ -1,4 +1,5 @@ import time +from numbers import Number from datadog.api.base import SearchableAPIResource, SendableAPIResource from datadog.api.exceptions import ApiError @@ -14,17 +15,52 @@ class Metric(SearchableAPIResource, SendableAPIResource): _METRIC_QUERY_ENDPOINT = '/query' _METRIC_SUBMIT_ENDPOINT = '/series' - _SUPPORTED_DATA_TYPES = (int, float, long) - @classmethod def _process_points(cls, points): + """ + Format `points` parameter. + + Input: + a value or (timestamp, value) pair or a list of value or (timestamp, value) pairs + + Returns: + list of (timestamp, float value) pairs + + """ now = time.time() - if isinstance(points, cls._SUPPORTED_DATA_TYPES): - points = [(now, points)] - elif isinstance(points, tuple): - points = [points] + points_lst = points if isinstance(points, list) else [points] + + def rec_parse(points_lst): + """ + Recursively parse a list of values or a list of (timestamp, value) pairs to a list of + (timestamp, `float` value) pairs. + """ + try: + if not points_lst: + return [] + + point = points_lst.pop() + timestamp = now if isinstance(point, Number) else point[0] + value = float(point) if isinstance(point, Number) else float(point[1]) + + point = [(timestamp, value)] + + return point + rec_parse(points_lst) + + except TypeError as e: + raise TypeError( + u"{0}: " + "`points` parameter must use real numerical values.".format(e) + ) + + except IndexError as e: + raise IndexError( + u"{0}: " + u"`points` must be a list of values or " + u"a list of (timestamp, value) pairs".format(e) + ) - return points + return rec_parse(points_lst) @classmethod def send(cls, metrics=None, **single_metric): @@ -34,7 +70,7 @@ def send(cls, metrics=None, **single_metric): :param metric: the name of the time series :type metric: string - :param points: list of points to submit + :param points: a (timestamp, value) pair or list of (timestamp, value) pairs :type points: list :param host: host name that produced the metric diff --git a/tests/unit/api/test_api.py b/tests/unit/api/test_api.py index a84927c21..e94adafcf 100644 --- a/tests/unit/api/test_api.py +++ b/tests/unit/api/test_api.py @@ -281,7 +281,7 @@ def submit_and_assess_metric_payload(self, serie): # it consists of a [time, value] pair self.assertEquals(len(metric['points'][0]), 2) # its value == value we sent - self.assertEquals(metric['points'][0][1], serie[i]['points']) + self.assertEquals(metric['points'][0][1], float(serie[i]['points'])) # it's time not so far from current time assert now - 1 < metric['points'][0][0] < now + 1 @@ -289,9 +289,9 @@ def test_metric_submit_query_switch(self): """ Endpoints are different for submission and queries. """ - Metric.send(points="val") + Metric.send(points=(123, 456)) self.request_called_with('POST', "host/api/v1/series", - data={'series': [{'points': "val", 'host': api._host_name}]}) + data={'series': [{'points': [[123, 456.0]], 'host': api._host_name}]}) Metric.query(start="val1", end="val2") self.request_called_with('GET', "host/api/v1/query", @@ -312,9 +312,17 @@ def test_points_submission(self): def test_data_type_support(self): """ - `Metric` API supports built-in real numerical data types. + `Metric` API supports `real` numerical data types. """ - supported_data_types = [1, 1.0, 1L] + from decimal import Decimal + from fractions import Fraction + + m_long = int(1) # long in Python 3.x + + if not is_p3k(): + m_long = long(1) + + supported_data_types = [1, 1.0, m_long, Decimal(1), Fraction(1, 2)] for point in supported_data_types: serie = dict(metric='metric.numerical', points=point)