diff --git a/customerio/__init__.py b/customerio/__init__.py index 252383d..eb16ce1 100644 --- a/customerio/__init__.py +++ b/customerio/__init__.py @@ -3,6 +3,7 @@ import math import time import warnings +import json from requests import Session from requests.adapters import HTTPAdapter @@ -19,6 +20,27 @@ class CustomerIOException(Exception): pass +class CustomJSONEncoder(json.JSONEncoder): + def _datetime_to_timestamp(self, dt): + if USE_PY3_TIMESTAMPS: + return int(dt.replace(tzinfo=timezone.utc).timestamp()) + else: + return int(time.mktime(dt.timetuple())) + + def default(self, obj): + ''' Add special handling for data types not supported by the standard json library. + + :param obj: Value being serialized to JSON + :return: The serialized value as a native python type. + ''' + if isinstance(obj, datetime): + return self._datetime_to_timestamp(obj) + if isinstance(obj, float) and math.isnan(obj): + return None + + return json.JSONEncoder.default(self, obj) + + class CustomerIO(object): def __init__(self, site_id=None, api_key=None, host=None, port=None, url_prefix=None, json_encoder=None, retries=3, timeout=10, backoff_factor=0.02): @@ -74,7 +96,12 @@ def send_request(self, method, url, data): '''Dispatches the request and returns a response''' try: - response = self.http.request(method, url=url, json=self._sanitize(data), timeout=self.timeout) + response = self.http.request( + method, + url=url, + data=json.dumps(data, cls=CustomJSONEncoder), + timeout=self.timeout, + ) except Exception as e: # Raise exception alerting user that the system might be # experiencing an outage and refer them to system status page. @@ -117,14 +144,6 @@ def backfill(self, customer_id, name, timestamp, **data): '''Backfill an event (track with timestamp) for a given customer_id''' url = self.get_event_query_string(customer_id) - if isinstance(timestamp, datetime): - timestamp = self._datetime_to_timestamp(timestamp) - elif not isinstance(timestamp, int): - try: - timestamp = int(timestamp) - except Exception as e: - raise CustomerIOException("{t} is not a valid timestamp ({err})".format(t=timestamp, err=e)) - post_data = { 'name': name, 'data': data, @@ -205,20 +224,6 @@ def remove_from_segment(self, segment_id, customer_ids): payload = {'ids': self._stringify_list(customer_ids)} self.send_request('POST', url, payload) - def _sanitize(self, data): - for k, v in data.items(): - if isinstance(v, datetime): - data[k] = self._datetime_to_timestamp(v) - if isinstance(v, float) and math.isnan(v): - data[k] = None - return data - - def _datetime_to_timestamp(self, dt): - if USE_PY3_TIMESTAMPS: - return int(dt.replace(tzinfo=timezone.utc).timestamp()) - else: - return int(time.mktime(dt.timetuple())) - def _stringify_list(self, customer_ids): customer_string_ids = [] for v in customer_ids: