Skip to content

Commit

Permalink
unique testing and serialization support for datetimes
Browse files Browse the repository at this point in the history
  • Loading branch information
tschellenbach committed Jul 29, 2014
1 parent 7b2b40b commit 6487f0e
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 5 deletions.
2 changes: 1 addition & 1 deletion stream/__init__.py
Expand Up @@ -4,7 +4,7 @@
__copyright__ = 'Copyright 2012, Thierry Schellenbach'
__credits__ = ['Thierry Schellenbach, mellowmorning.com, @tschellenbach']
__license__ = 'BSD'
__version__ = '0.2.12'
__version__ = '0.3.0'
__maintainer__ = 'Thierry Schellenbach'
__email__ = 'thierryschellenbach@gmail.com'
__status__ = 'Production'
Expand Down
14 changes: 10 additions & 4 deletions stream/client.py
@@ -1,13 +1,18 @@
import requests
from stream import exceptions
from stream import exceptions, serializer
import logging
from stream.signing import sign
from stream.utils import validate_feed
import os
import json
import datetime

logger = logging.getLogger(__name__)

import datetime




class StreamClient(object):
base_url = 'https://getstream.io/api/'
Expand Down Expand Up @@ -66,7 +71,7 @@ def get_default_params(self):
'''
params = dict(api_key=self.api_key)
return params

def _make_request(self, method, relative_url, authorization, params=None, data=None):
params = params or {}
data = data or {}
Expand All @@ -79,11 +84,12 @@ def _make_request(self, method, relative_url, authorization, params=None, data=N

url = self.base_url + relative_url

response = method(url, data=json.dumps(data), headers=headers,
serialized = serializer.dumps(data)
response = method(url, data=serialized, headers=headers,
params=default_params)
logger.debug('stream api call %s, headers %s data %s',
response.url, headers, data)
result = response.json()
result = serializer.loads(response.content)
if result.get('exception'):
self.raise_exception(result, status_code=response.status_code)
return result
Expand Down
54 changes: 54 additions & 0 deletions stream/serializer.py
@@ -0,0 +1,54 @@
import datetime
import json

'''
Adds the ability to send date and datetime objects to the API
The date and datetime formats from the API are automatically supported and parsed
'''

def _datetime_encoder(obj):
if isinstance(obj, (datetime.datetime, datetime.date)):
return obj.isoformat()


def _datetime_decoder(dict_):
for key, value in dict_.iteritems():
# The built-in `json` library will `unicode` strings, except for empty
# strings which are of type `str`. `jsondate` patches this for
# consistency so that `unicode` is always returned.
if value == '':
dict_[key] = u''
continue

if value is not None and isinstance(value, basestring):
try:
# The api always returns times like this 2014-07-25T09:12:24.735
datetime_obj = datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f" )
dict_[key] = datetime_obj
except (ValueError, TypeError):
try:
# The api always returns times like this 2014-07-25T09:12:24.735
datetime_obj = datetime.datetime.strptime(value, "%Y-%m-%d" )
dict_[key] = datetime_obj.date()
except (ValueError, TypeError):
continue
return dict_


def dumps(*args, **kwargs):
kwargs['default'] = _datetime_encoder
return json.dumps(*args, **kwargs)


def dump(*args, **kwargs):
kwargs['default'] = _datetime_encoder
return json.dump(*args, **kwargs)


def loads(*args, **kwargs):
kwargs['object_hook'] = _datetime_decoder
return json.loads(*args, **kwargs)

def load(*args, **kwargs):
kwargs['object_hook'] = _datetime_decoder
return json.load(*args, **kwargs)
53 changes: 53 additions & 0 deletions stream/tests.py
Expand Up @@ -6,6 +6,8 @@
from unittest.case import TestCase

import os
import datetime
from stream import serializer

def connect_debug():
return stream.connect(
Expand Down Expand Up @@ -156,6 +158,46 @@ def test_complex_field(self):
self.assertEqual(activities[0]['id'], activity_id)
self.assertEqual(activities[0]['participants'], ['Tommaso', 'Thierry'])

def assertDatetimeAlmostEqual(self, a, b):
difference = abs(a-b)
if difference > datetime.timedelta(milliseconds=1):
self.assertEqual(a, b)

def assertClearlyNotEqual(self, a, b):
difference = abs(a-b)
if difference < datetime.timedelta(milliseconds=1):
raise ValueError('the dates are too close')

def test_uniqueness(self):
'''
In order for things to be considere unique they need:
a.) The same time and activity data
b.) The same time and foreign id
'''
now = datetime.datetime.now()
activity_data = {'actor': 1, 'verb': 'tweet', 'object': 1, 'time': now}
response = self.user1.add_activity(activity_data)
response = self.user1.add_activity(activity_data)
activities = self.user1.get(limit=2)['results']
for activity in activities:
print activity['id'], activity['time']
self.assertDatetimeAlmostEqual(activities[0]['time'], now)
self.assertClearlyNotEqual(activities[1]['time'], now)

def test_uniqueness_foreign_id(self):
today = datetime.datetime.today()
activity_data = {'actor': 1, 'verb': 'tweet', 'object': 1, 'foreign_id': 'tweet:11', 'mydate': today}
response = self.user1.add_activity(activity_data)
activity_data = {'actor': 2, 'verb': 'tweet', 'object': 3, 'foreign_id': 'tweet:11', 'mydate': today}
response = self.user1.add_activity(activity_data)
activities = self.user1.get(limit=2)['results']
for activity in activities:
print activity['id'], activity['object'], activity['time']
print activities[0]
self.assertEqual(activities[0]['mydate'], today)
self.assertEqual(activities[0]['object'], 1)
self.assertEqual(activities[0]['foreign_id'], 'tweet:10')

def test_missing_actor(self):
activity_data = {'verb': 'tweet', 'object':
1, 'debug_example_undefined': 'test'}
Expand All @@ -172,3 +214,14 @@ def test_wrong_feed_spec(self):
'tfq2sdqpj9g446sbv653x3aqmgn33hsn8uzdc9jpskaw8mj6vsnhzswuwptuj9su'
)
self.assertRaises(ValueError, lambda: self.c.feed('user1'))

def test_serialization(self):
today = datetime.date.today()
now = datetime.datetime.now()
data = dict(string='string', float=0.1, int=1, date=today, datetime=now)
serialized = serializer.dumps(data)
loaded = serializer.loads(serialized)
self.assertEqual(data, loaded)



0 comments on commit 6487f0e

Please sign in to comment.