diff --git a/healthgraph/parser.py b/healthgraph/parser.py index 131d86a..926587d 100644 --- a/healthgraph/parser.py +++ b/healthgraph/parser.py @@ -1,6 +1,6 @@ -"""Python Client Library for Health Graph API (http://developer.runkeeper.com/healthgraph). +"""Python Client Library for Health Graph API (http://developer.runkeeper.com/healthgraph). -The API is used for accessing RunKeeper (http://runkeeper.com) for retrieving, +The API is used for accessing RunKeeper (http://runkeeper.com) for retrieving, updating, deleting and uploading Fitness Activity and Health Measurements Information. This module contains the classes and methods for parsing Health Graph API data. @@ -20,7 +20,7 @@ __email__ = "aouyar at gmail.com" __status__ = "Development" - + def parse_bool(val): if val is None: return None @@ -39,19 +39,19 @@ def parse_date(val): else: mobj = re.match('\w+,\s*(\d+)\s+(\w+)\s+(\d+)', val) if mobj is not None: - return date(int(mobj.group(3)), + return date(int(mobj.group(3)), settings.MONTH2NUM[mobj.group(2)], int(mobj.group(1))) else: exceptions.ParseValueError("Error parsing date value.") - + def parse_datetime(val): if val is None: return None else: mobj = re.match('\w+,\s*(\d+)\s+(\w+)\s+(\d+)\s+(\d+):(\d+):(\d+)', val) if mobj is not None: - return datetime(int(mobj.group(3)), + return datetime(int(mobj.group(3)), settings.MONTH2NUM[mobj.group(2)], int(mobj.group(1)), int(mobj.group(4)), @@ -59,7 +59,7 @@ def parse_datetime(val): int(mobj.group(6)),) else: exceptions.ParseValueError("Error parsing date-time value.") - + def parse_distance(val): if val is None: return None @@ -67,7 +67,7 @@ def parse_distance(val): return float(val) except: raise exceptions.ParseValueError("Error parsing distance value.") - + def parse_distance_km(val): if val is None: return None @@ -75,7 +75,7 @@ def parse_distance_km(val): return float(val) * 1000 except: raise exceptions.ParseValueError("Error parsing distance value.") - + def parse_resource_dict(prop_defs, data): prop_dict = dict([(k, None) for k in prop_defs]) for k,v in data.items(): @@ -94,4 +94,3 @@ def parse_date_param(val): return val.strftime('%y-%m-%d') else: return val - diff --git a/healthgraph/resources.py b/healthgraph/resources.py index 23acde4..ecf7335 100644 --- a/healthgraph/resources.py +++ b/healthgraph/resources.py @@ -1,9 +1,9 @@ -"""Python Client Library for Health Graph API (http://developer.runkeeper.com/healthgraph). +"""Python Client Library for Health Graph API (http://developer.runkeeper.com/healthgraph). -The API is used for accessing RunKeeper (http://runkeeper.com) for retrieving, +The API is used for accessing RunKeeper (http://runkeeper.com) for retrieving, updating, deleting and uploading Fitness Activity and Health Measurements Information. -This module contains the resource definitions for retrieving, updating, deleting +This module contains the resource definitions for retrieving, updating, deleting and uploading Fitness Activity and Health Measurements information. """ @@ -15,10 +15,10 @@ import settings import content_types import sessionmgr -from parser import (parse_resource_dict, - parse_bool, - parse_distance, parse_distance_km, - parse_date, parse_datetime, +from parser import (parse_resource_dict, + parse_bool, + parse_distance, parse_distance_km, + parse_date, parse_datetime, parse_date_param) @@ -30,10 +30,10 @@ __email__ = "aouyar at gmail.com" __status__ = "Development" - + class PersonalRecordType: - """Personal record types.""" - + """Personal record types.""" + THIS_WEEK = 'THIS_WEEK' THIS_MONTH = 'THIS_MONTH' LAST_WEEK = 'LAST_WEEK' @@ -49,28 +49,28 @@ class ResourceLink(namedtuple('ResourceLink', ('clsname', 'resource'))): class PropResourceLink(object): - + def __init__(self, clsname): self._clsname = clsname - + def __call__(self, resource=None): return ResourceLink(self._clsname, resource) - + class ContainerMixin(MutableMapping): - + def __getitem__(self, k): return self._prop_dict[k] - + def __setitem__(self, k, v): self._prop_dict[k] = v - + def __delitem__(self, k): del self._prop_dict[k] - + def __len__(self): return len(self._prop_dict) - + def __iter__(self): return iter(self._prop_dict) @@ -78,7 +78,7 @@ def __iter__(self): class APIobject(object): _prop_defs = None _prop_main = None - + def __init__(self, session=None): self._prop_dict = {} self._resource = None @@ -86,11 +86,11 @@ def __init__(self, session=None): self._session = session else: self._session = sessionmgr.get_session() - + def _get_resource_data(self, resource, content_type, params=None): resp = self._session.get(resource, content_type, params) return resp.json() # TODO - Error Checking - + def _get_linked_resource(self, link, cls_override=None, **kwargs): if link is not None: if cls_override is None: @@ -103,7 +103,7 @@ def _get_linked_resource(self, link, cls_override=None, **kwargs): pass else: return None - + def __str__(self): if self._resource is not None: prop_strs = ["resource=%s" % self._resource,] @@ -117,28 +117,28 @@ def __str__(self): class BaseResource(APIobject): - + _content_type = None - + def __init__(self, resource = None, session=None, params=None): super(BaseResource,self).__init__(session=session) self._resource = resource self.load(params) - + @property def resource(self): return self._resource - + @property def content_type(self): return self._content_type - + def load(self, params=None): if self._resource is not None: - data = self._get_resource_data(self._resource, self._content_type, + data = self._get_resource_data(self._resource, self._content_type, params) self._prop_dict = self._parse_data(data) - + def _parse_data(self, data): return parse_resource_dict(self._prop_defs, data) @@ -151,13 +151,13 @@ def __init__(self, data=None, session=None): self._prop_dict = parse_resource_dict(self._prop_defs, data) else: self._prop_dict = {} - + class ResourceArray(list): - + def __init__(self, data=None): super(ResourceArray, self).__init__(data or []) - + def __str__(self): cnt = len(self) if cnt > 0: @@ -166,7 +166,7 @@ def __str__(self): cont_str = '[]' return "%s(%s)" % (self.__class__.__name__, cont_str) - + __repr__ = __str__ @@ -199,24 +199,24 @@ class ArrayComments(ResourceArray): def __init__(self, data=None): super(ArrayComments, self).__init__(data) - + class Resource(BaseResource, ContainerMixin): - + def __init__(self, resource = None, params=None, session=None): super(Resource, self).__init__(resource, params=params, session=session) class ResourceFeedIter(BaseResource): - + _prop_defs = {'size': None, 'items': None, 'previous': PropResourceLink('ResourceFeedIter'), 'next': PropResourceLink('ResourceFeedIter')} _item_cls = None _prop_main = ('size',) - - def __init__(self, resource, - date_min=None, date_max=None, + + def __init__(self, resource, + date_min=None, date_max=None, mod_date_min=None, mod_date_max=None, descending=True, session=None): @@ -232,22 +232,24 @@ def __init__(self, resource, super(ResourceFeedIter, self).__init__(resource, params=params, session=session) self._descending = descending - if descending: - self._iter = iter(self._prop_dict['items']) + if self._prop_dict['items'] is None: + self._iter = iter([]) else: - self._last_page() - self._iter = reversed(self._prop_dict['items']) - - + if descending: + self._iter = iter(self._prop_dict['items']) + else: + self._last_page() + self._iter = reversed(self._prop_dict['items']) + def count(self): return self._prop_dict['size'] - + def __iter__(self): if self._item_cls is not None: return self else: pass - + def next(self): try: item = self._iter.next() @@ -261,7 +263,7 @@ def next(self): else: raise StopIteration return self._item_cls(item, self._session) - + def _prev_page(self): link = self._prop_dict.get('previous') if link is not None: @@ -270,7 +272,7 @@ def _prev_page(self): return True else: return False - + def _next_page(self): link = self._prop_dict.get('next') if link is not None: @@ -279,7 +281,7 @@ def _next_page(self): return True else: return False - + def _last_page(self): link = self._prop_dict.get('next') if link is not None: @@ -300,13 +302,13 @@ def _last_page(self): class FeedItem(ResourceItem): - + def __init__(self, data, session=None): super(FeedItem, self).__init__(data, session=session) class User(Resource): - + _content_type = content_types.USER _prop_defs = {'userID': None, 'profile': PropResourceLink('Profile'), @@ -320,68 +322,72 @@ class User(Resource): 'general_measurements': PropResourceLink('GeneralMeasurementIter'), 'diabetes': PropResourceLink('DiabetesMeasurementIter'), 'records': PropResourceLink('PersonalRecords'), - 'team': PropResourceLink('FriendIter'), + 'team': PropResourceLink('FriendIter'), } _prop_main = ('userID',) - + def __init__(self, session=None): super(User, self).__init__(settings.USER_RESOURCE, session=session) - + def get_profile(self): return self._get_linked_resource(self._prop_dict['profile']) - + def get_settings(self): return self._get_linked_resource(self._prop_dict['settings']) - + def get_records(self): return self._get_linked_resource(self._prop_dict['records']) - - def get_fitness_activity_iter(self, - date_min=None, date_max=None, + + def get_fitness_activity_iter(self, + date_min=None, date_max=None, mod_date_min=None, mod_date_max=None, descending=True): return self._get_linked_resource(self._prop_dict['fitness_activities'], - date_min=date_min, + date_min=date_min, date_max=date_max, mod_date_min=mod_date_min, mod_date_max=mod_date_max, descending=descending) - + def get_strength_activity_iter(self, - date_min=None, date_max=None, + date_min=None, date_max=None, mod_date_min=None, mod_date_max=None, descending=True): return self._get_linked_resource(self._prop_dict['strength_training_activities'], - date_min=date_min, + date_min=date_min, date_max=date_max, mod_date_min=mod_date_min, mod_date_max=mod_date_max, descending=descending) - + def get_weight_measurement_iter(self, - date_min=None, date_max=None, + date_min=None, date_max=None, mod_date_min=None, mod_date_max=None, descending=True): return self._get_linked_resource(self._prop_dict['weight'], - date_min=date_min, + date_min=date_min, date_max=date_max, mod_date_min=mod_date_min, mod_date_max=mod_date_max, descending=descending) - + def get_sleep_measurement_iter(self, - date_min=None, date_max=None, + date_min=None, date_max=None, mod_date_min=None, mod_date_max=None, descending=True): return self._get_linked_resource(self._prop_dict['sleep'], - date_min=date_min, + date_min=date_min, date_max=date_max, mod_date_min=mod_date_min, mod_date_max=mod_date_max, descending=descending) + def get_background_activities_iter(self): + return self._get_linked_resource(self._prop_dict['background_activities']) + + class Profile(Resource): - + _content_type = content_types.PROFILE _prop_defs = {'name': None, 'location': None, @@ -396,13 +402,13 @@ class Profile(Resource): 'large_picture': None, } _prop_main = ('name', 'gender', 'birthday',) - + def __init__(self, resource, session=None): super(Profile, self).__init__(resource, session=session) class Settings(Resource): - + _content_type = content_types.SETTINGS _prop_defs = {'facebook_connected': parse_bool, 'twitter_connected': parse_bool, @@ -435,13 +441,13 @@ class Settings(Resource): 'weight_units': None, 'first_day_of_week': None, } - + def __init__(self, resource, session=None): super(Settings, self).__init__(resource, session=session) - + class PersonalRecords(Resource): - + _content_type = content_types.PERSONAL_RECORDS def __init__(self, resource, session=None): @@ -472,22 +478,22 @@ def _parse_data(self, data): if len(bests) > 0: prop_dict['bests'][act_type] = bests return prop_dict - + def get_activity_types(self): return self._prop_dict.keys() - + def get_totals(self): return self._prop_dict['totals'] - + def get_bests(self): return self._prop_dict['bests'] - + def get_activity_totals(self, activity_type): try: return self._prop_dict['totals'][activity_type] except KeyError: return None - + def get_activity_bests(self, activity_type): try: return self._prop_dict['bests'][activity_type] @@ -496,7 +502,7 @@ def get_activity_bests(self, activity_type): class FitnessActivity(Resource): - + _content_type = content_types.FITNESS_ACTIVITY _prop_defs = {'uri': PropResourceLink('FitnessActivity'), 'userID': None, @@ -537,9 +543,9 @@ class FitnessActivity(Resource): 'nearest_diabetes': None, 'nearest_teammate_diabetes': None, } - + _prop_main = ('type', 'start_time',) - + def __init__(self, resource, session=None): super(FitnessActivity, self).__init__(resource, session=session) @@ -548,13 +554,13 @@ def get_comment_thread(self): def get_prev_activity(self): return self._get_linked_resource(self._prop_dict['previous']) - + def get_next_activity(self): return self._get_linked_resource(self._prop_dict['next']) - + class FitnessActivitySummary(Resource): - + _content_type = content_types.FITNESS_ACTIVITY_SUMMARY _prop_defs = {'uri': PropResourceLink('FitnessActivity'), 'userID': None, @@ -573,16 +579,16 @@ class FitnessActivitySummary(Resource): 'activity': None, } _prop_main = ('type', 'start_time',) - + def __init__(self, resource, session=None): super(FitnessActivitySummary, self).__init__(resource, session=session) - + def get_activity_detail(self): return self._get_linked_resource(self._prop_dict['uri']) - + class FitnessActivityFeedItem(FeedItem): - + _prop_defs = {'start_time': parse_datetime, 'type': None, 'duration': None, @@ -593,24 +599,24 @@ class FitnessActivityFeedItem(FeedItem): 'uri': PropResourceLink('FitnessActivity'), } _prop_main = ('type', 'start_time',) - + def __init__(self, data, session=None): super(FitnessActivityFeedItem, self).__init__(data, session=session) - + def get_activity_detail(self): return self._get_linked_resource(self._prop_dict['uri']) - + def get_activity_summary(self): return self._get_linked_resource(self._prop_dict['uri'], 'FitnessActivitySummary') class FitnessActivityIter(ResourceFeedIter): - + _content_type = content_types.FITNESS_ACTIVITY_FEED _item_cls = FitnessActivityFeedItem - - def __init__(self, resource, - date_min=None, date_max=None, + + def __init__(self, resource, + date_min=None, date_max=None, mod_date_min=None, mod_date_max=None, descending=True, session=None): @@ -624,31 +630,31 @@ def __init__(self, resource, class StrengthActivityFeedItem(FeedItem): - + _prop_defs = {'start_time': parse_datetime, 'uri': PropResourceLink('StrengthActivity')} _prop_main = ('start_time',) - + def __init__(self, data, session=None): super(StrengthActivityFeedItem, self).__init__(data, session=session) class StrengthActivityIter(ResourceFeedIter): - + _content_type = content_types.STRENGTH_ACTIVITY_FEED _item_cls = StrengthActivityFeedItem - - def __init__(self, resource, - date_min=None, date_max=None, + + def __init__(self, resource, + date_min=None, date_max=None, mod_date_min=None, mod_date_max=None, descending=True, session=None): - super(StrengthActivityIter, self).__init__(resource, + super(StrengthActivityIter, self).__init__(resource, date_min=date_min, date_max=date_max, mod_date_min=mod_date_min, mod_date_max=mod_date_max, - descending=descending, + descending=descending, session=session) @@ -673,7 +679,7 @@ class WeightMeasurementIter(ResourceFeedIter): _content_type = content_types.WEIGHT_MEASUREMENT_FEED _item_cls = WeightMeasurementFeedItem - def __init__(self, resource, + def __init__(self, resource, date_min=None, date_max=None, mod_date_min=None, mod_date_max=None, descending=True, @@ -709,32 +715,56 @@ def __init__(self, data, session=None): class SleepMeasurementIter(ResourceFeedIter): - + _content_type = content_types.SLEEP_MEASUREMENT_FEED _item_cls = SleepMeasurementFeedItem - - def __init__(self, resource, - date_min=None, date_max=None, + + def __init__(self, resource, + date_min=None, date_max=None, mod_date_min=None, mod_date_max=None, descending=True, session=None): - super(SleepMeasurementIter, self).__init__(resource, + super(SleepMeasurementIter, self).__init__(resource, date_min=date_min, date_max=date_max, mod_date_min=mod_date_min, mod_date_max=mod_date_max, - descending=descending, + descending=descending, session=session) +class BackgroundActivityFeedItem(FeedItem): + + _prop_defs = {'uri': PropResourceLink('BackgroundActivity'), + 'timestamp': parse_datetime, + 'steps': int, + 'source': None, + 'previous': None, + 'next': None, + } + _prop_main = ('timestamp',) + + def __init__(self, data, session=None): + super(BackgroundActivityFeedItem, self).__init__(data, session=session) + + +class BackgroundActivityIter(ResourceFeedIter): + + _content_type = content_types.BACKGROUND_ACTIVITY_FEED + _item_cls = BackgroundActivityFeedItem + + def __init__(self, resource, session=None): + super(BackgroundActivityIter, self).__init__(resource, session=session) + + class CommentThread(Resource): - + _content_type = content_types.COMMENT_THREAD _prop_defs = {'uri': None, 'userID': None, 'comments': ArrayComments, } _prop_main = ('uri',) - + def __init__(self, resource, session=None): super(CommentThread, self).__init__(resource, session=session)