diff --git a/.gitignore b/.gitignore index 3a1283a5..994b5f22 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ dist/ *.egg-info/ *.egg/ docs/_build/ +test.ini diff --git a/stravalib/client.py b/stravalib/client.py index da4f2960..8ae68ddc 100644 --- a/stravalib/client.py +++ b/stravalib/client.py @@ -586,7 +586,7 @@ def get_activity_laps(self, activity_id): '/activities/{id}/laps', id=activity_id) - return BatchedResultsIterator(entity=model.ActivityLaps, + return BatchedResultsIterator(entity=model.ActivityLap, bind_client=self, result_fetcher=result_fetcher) diff --git a/stravalib/model.py b/stravalib/model.py index 52304557..0798a522 100644 --- a/stravalib/model.py +++ b/stravalib/model.py @@ -350,9 +350,12 @@ class ActivityKudos(LoadableEntity): approve_followers = Attribute(bool, (SUMMARY,DETAILED)) #: Whether athlete has elected to approve followers -class ActivityLaps(LoadableEntity): +class ActivityLap(LoadableEntity): + name = Attribute(unicode, (SUMMARY,DETAILED)) #: Name of lap - + + athlete = EntityAttribute(Athlete, (SUMMARY,DETAILED)) + elapsed_time = TimeIntervalAttribute((SUMMARY, DETAILED)) #: :class:`datetime.timedelta` of elapsed time for lap moving_time = TimeIntervalAttribute((SUMMARY, DETAILED)) #: :class:`datetime.timedelta` of moving time for lap start_date = TimestampAttribute((SUMMARY,DETAILED)) #: :class:`datetime.datetime` when lap was started in GMT @@ -361,8 +364,8 @@ class ActivityLaps(LoadableEntity): start_index= Attribute(int, (SUMMARY,DETAILED)) #: end_index= Attribute(int, (SUMMARY,DETAILED)) #: total_elevation_gain = Attribute(float, (SUMMARY,DETAILED,), units=uh.meters) #: What is total elevation gain for lap - average_speed = Attribute(float, (SUMMARY,DETAILED,)) #: Average speed for lap - max_speed = Attribute(float, (SUMMARY,DETAILED,)) #: Max speed for lap + average_speed = Attribute(float, (SUMMARY,DETAILED,), units=uh.meters_per_second) #: Average speed for lap + max_speed = Attribute(float, (SUMMARY,DETAILED,), units=uh.meters_per_second) #: Max speed for lap average_cadence = Attribute(float, (SUMMARY,DETAILED,)) #: Average cadence for lap average_watts = Attribute(float, (SUMMARY,DETAILED,)) #: Average watts for lap average_heartrate = Attribute(float, (SUMMARY,DETAILED,)) #: Average heartrate for lap @@ -435,6 +438,7 @@ class Segment(LoadableEntity): climb_category = Attribute(int, (SUMMARY,DETAILED)) # 0-5, lower is harder city = Attribute(unicode, (SUMMARY,DETAILED)) #: The city this segment is in. state = Attribute(unicode, (SUMMARY,DETAILED)) #: The state this segment is in. + country = Attribute(unicode, (SUMMARY,DETAILED)) #: The country this segment is in. private = Attribute(bool, (SUMMARY,DETAILED)) #: Whether this is a private segment. starred = Attribute(bool, (SUMMARY,DETAILED)) #: Whether this segment is starred by authenticated athlete @@ -476,6 +480,7 @@ class BaseEffort(LoadableEntity): distance = Attribute(int, (SUMMARY,DETAILED), units=uh.meters) #: The distance for this effort. average_watts = Attribute(float, (SUMMARY,DETAILED)) #: Average power during effort average_heartrate = Attribute(float, (SUMMARY,DETAILED)) #: Average HR during effort + max_heartrate = Attribute(float, (SUMMARY,DETAILED)) #: Max HR during effort average_cadence = Attribute(float, (SUMMARY,DETAILED)) #: Average cadence during effort @@ -489,8 +494,10 @@ class SegmentEffort(BaseEffort): Class representing a best effort on a particular segment. """ start_index = Attribute(int, (SUMMARY,DETAILED)) # the activity stream index of the start of this effort - end_index = Attribute(int, (SUMMARY,DETAILED)) # the activity stream index of the end of this effort - + end_index = Attribute(int, (SUMMARY,DETAILED)) # the activity stream index of the end of this effort + hidden = Attribute(bool, (SUMMARY,DETAILED,)) # indicates a hidden/non-important effort when returned as part of an activity, value may change over time. + + class Activity(LoadableEntity): """ Represents an activity (ride, run, etc.). @@ -564,7 +571,7 @@ class Activity(LoadableEntity): average_speed = Attribute(float, (SUMMARY,DETAILED), units=uh.meters_per_second) #: Average speed for activity. max_speed = Attribute(float, (SUMMARY,DETAILED), units=uh.meters_per_second) #: Max speed for activity - calories = Attribute(float, (SUMMARY,DETAILED)) #: Calculation of how many calories burned on activity + truncated = Attribute(int, (SUMMARY,DETAILED)) #: Only present if activity is owned by authenticated athlete, set to 0 if not truncated by privacy zones has_kudoed = Attribute(bool, (SUMMARY,DETAILED)) #: If authenticated user has kudoed this activity @@ -582,6 +589,7 @@ class Activity(LoadableEntity): average_temp = Attribute(int, (SUMMARY,DETAILED)) #: (undocumented) Average temperature (when available from device) during activity. + calories = Attribute(float, (DETAILED,)) #: Calculation of how many calories burned on activity description = Attribute(unicode, (DETAILED,)) #: (undocumented) Description of activity. workout_type = Attribute(unicode, (DETAILED,)) #: (undocumented) @@ -599,6 +607,20 @@ def comments(self): self._comments = [] return self._comments + @property + def laps(self): + """ + Iterator of :class:`stravalib.model.ActivityLaps` objects for this activity. + """ + if self._friends is None: + self.assert_bind_client() + if self.friend_count > 0: + self._friends = self.bind_client.get_athlete_friends(self.id) + else: + # Shortcut if we know there aren't any + self._friends = [] + return self._friends + @property def zones(self): """ diff --git a/stravalib/tests/functional/__init__.py b/stravalib/tests/functional/__init__.py index 05149297..3951599e 100644 --- a/stravalib/tests/functional/__init__.py +++ b/stravalib/tests/functional/__init__.py @@ -1,15 +1,26 @@ import warnings +import os +import ConfigParser from stravalib import model from stravalib.client import Client -from stravalib.tests import TestBase -# This is the access token from the Strava examples :) -TEST_ACCESS_TOKEN = "83ebeabdec09f6670863766f792ead24d61fe3f9" +from stravalib.tests import TestBase, TESTS_DIR, RESOURCES_DIR + +TEST_CFG = os.path.join(TESTS_DIR, 'test.ini') class FunctionalTestBase(TestBase): def setUp(self): super(FunctionalTestBase, self).setUp() - self.client = Client(access_token=TEST_ACCESS_TOKEN) \ No newline at end of file + if not os.path.exists(TEST_CFG): + raise Exception("Unable to run the write tests without a tests.ini that defines an access_token with write privs.") + + cfg = ConfigParser.SafeConfigParser() + with open(TEST_CFG) as fp: + cfg.readfp(fp, 'test.ini') + access_token = cfg.get('write_tests', 'access_token') + + self.client = Client(access_token=access_token) + \ No newline at end of file diff --git a/stravalib/tests/functional/test_client.py b/stravalib/tests/functional/test_client.py index de8e7692..78a2dee0 100644 --- a/stravalib/tests/functional/test_client.py +++ b/stravalib/tests/functional/test_client.py @@ -25,6 +25,15 @@ def test_get_activity(self): # Ensure that iw as read in with correct units self.assertEquals(22.5308, float(uh.kilometers(activity.distance))) + def test_get_activity_laps(self): + activity = self.client.get_activity(165094211) + laps = list(self.client.get_activity_laps(165094211)) + self.assertEquals(5, len(laps)) + # This obviously is far from comprehensive, just a sanity check + self.assertEquals(u'Lap 1', laps[0].name) + self.assertEquals(178.0, laps[0].max_heartrate) + + def test_get_activity_zones(self): """ Test loading zones for activity. diff --git a/stravalib/tests/functional/test_client_write.py b/stravalib/tests/functional/test_client_write.py index f0288a2b..41d66e97 100644 --- a/stravalib/tests/functional/test_client_write.py +++ b/stravalib/tests/functional/test_client_write.py @@ -1,27 +1,12 @@ from __future__ import absolute_import, unicode_literals -import os -import ConfigParser from datetime import datetime, timedelta from io import BytesIO from stravalib import model, exc, attributes, unithelper as uh from stravalib.client import Client -from stravalib.tests import TestBase, TESTS_DIR, RESOURCES_DIR +from stravalib.tests.functional import FunctionalTestBase -TEST_CFG = os.path.join(TESTS_DIR, 'test.ini') - -class ClientWriteTest(TestBase): - - def setUp(self): - if not os.path.exists(TEST_CFG): - raise Exception("Unable to run the write tests without a tests.ini that defines an access_token with write privs.") - - cfg = ConfigParser.SafeConfigParser() - with open(TEST_CFG) as fp: - cfg.readfp(fp, 'test.ini') - access_token = cfg.get('write_tests', 'access_token') - - self.client = Client(access_token=access_token) +class ClientWriteTest(FunctionalTestBase): def test_create_activity(self): """