Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend the default JSON serializer to fully support datetime instances #47

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 28 additions & 23 deletions customerio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import math
import time
import warnings
import json

from requests import Session
from requests.adapters import HTTPAdapter
Expand All @@ -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):
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down