Skip to content
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
125 changes: 98 additions & 27 deletions strava/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,14 @@
__version__ = "1.0"


BASE_API = "http://www.strava.com/api/v1"
BASE_API = "http://www.strava.com/api"

from collections import defaultdict
from datetime import date, timedelta
import json

try:
import urllib2
except ImportError:
import urllib
urllib2 = False

import urllib2
import urllib

class APIError(Exception):
pass
Expand All @@ -35,25 +31,41 @@ def __init__(self, oid):
self._id = oid

#noinspection PyUnresolvedReferences
def load(self, url, key):
if urllib2:
try:
def load(self, url, key=None, params=None):
try:
if params:
urllib.urlencode(params)
req = urllib2.Request("%s%s?%s" % (BASE_API, url, urllib.urlencode(params)))
else:
req = urllib2.Request(BASE_API + url)
rsp = urllib2.urlopen(req)
except urllib2.HTTPError as e:
raise APIError("%s: request failed: %s" % (url, e))
else:
try:
rsp = urllib.request.urlopen(BASE_API + url)
except urllib.error.HTTPError as e:
raise APIError("%s: request failed: %s" % (url, e))
rsp = urllib2.urlopen(req)
except urllib2.HTTPError as e:
raise APIError("%s: request failed: %s" % (url, e))
txt = rsp.read().decode('utf-8')

try:
return json.loads(txt)[key]
if key:
return json.loads(txt)[key]
else:
return json.loads(txt)
except (ValueError, KeyError) as e:
raise APIError("%s: parsing response failed: %s" % (url, e))

def post(self, url, data):
params = urllib.urlencode(data)
try:
req = urllib2.Request(BASE_API + url)
req.add_data(params)
rsp = urllib2.urlopen(req)
except urllib2.HTTPError as e:
raise APIError("%s: request failed: %s" % (url, e))
txt = rsp.read().decode('utf-8')

try:
return json.loads(txt)
except (ValueError) as e:
raise APIError("%s: parsing response failed: %s" % (url, e))

@property
def id(self):
return self._id
Expand All @@ -68,16 +80,15 @@ class Athlete(StravaObject):
"""
def __init__(self, oid):
super(Athlete, self).__init__(oid)
self._url = "/rides?athleteId=%s" % self.id

def rides(self, start_date=None):
out = []
params = {'athleteId': self.id}

url = self._url
if start_date:
url += "&startDate=%s" % start_date.isoformat()
params['startDate'] = start_date.isoformat()

for ride in self.load(url, "rides"):
for ride in self.load('/v1/rides', "rides", params):
out.append(Ride(ride["id"], ride["name"]))

return out
Expand All @@ -95,6 +106,29 @@ def ride_stats(self, days=7):
return stats


class AuthenticatedAthlete(Athlete):
def __init__(self, oid, token, attr):
super(AuthenticatedAthlete, self).__init__(oid)
self.token = token
self._attr = attr

def rides(self, start_date=None):
rides = super(AuthenticatedAthlete, self).rides(start_date)
out = []
for ride in rides:
out.append(AuthenticatedRide(ride.id, ride.name, self.token))
return out


def login(email, password):
"""Logs in to the strava API, and returns an AuthenticatedAthlete"""
response = StravaObject(None).post("/v2/authentication/login",
{'email':email, 'password':password})
token = response['token']
details = response['athlete']
_id = response['athlete']['id']
return AuthenticatedAthlete(_id, token, details)

class Ride(StravaObject):
"""Information about a single ride.

Expand All @@ -121,16 +155,53 @@ def detail(self):
@property
def segments(self):
if not self._segments:
for effort in self.load("/rides/%s/efforts" % self.id, "efforts"):
for effort in self.load("/v1/rides/%s/efforts" % self.id, "efforts"):
self._segments.append(Segment(effort))
return self._segments

class AuthenticatedRide(Ride):
def __init__(self, oid, name, token):
super(AuthenticatedRide, self).__init__(oid, name)
self.token = token
self._streams = None

@property
def streams(self):
if not self._streams:
self._streams = RideStreams(self.id, self.token)
return self._streams

class RideStreams(StravaObject):
def __init__(self, oid, token):
super(RideStreams, self).__init__(oid)

# In other places we lazy load details to save a round trip. But in
# this case the only reason you want the stream is to get all the
# detail. So fetch it now.
self._attr = self.load("/v1/streams/%i" % self.id, params={'token':token})

# TODO: This allows you to access all the data from the stream, as long
# as you happen to know what that is. But perhaps we should write out
# @property methods for each of them. Perhaps explicit is better than
# implicit, but it does seem like a lot of boilerplate code.
def __getattr__(self, name):
return self._attr[name]

@property
def speed(self):
out = [0.0]

# Skip the first tuple, as time is always 0, and the division always
# leads to a ZeroDivisionError.
for i in zip(self.distance, self.time)[1:]:
out.append(i[0]/i[1])
return out

class RideDetail(StravaObject):

def __init__(self, oid):
super(RideDetail, self).__init__(oid)
self._attr = self.load("/rides/%s" % oid, 'ride')
self._attr = self.load("/v1/rides/%s" % oid, 'ride')

@property
def athlete(self):
Expand Down Expand Up @@ -228,8 +299,8 @@ def detail(self):
class SegmentDetail(StravaObject):
def __init__(self, segment_id, effort_id):
super(SegmentDetail, self).__init__(segment_id)
self._effort_attr = self.load("/efforts/%s" % effort_id, "effort")
self._segment_attr = self.load("/segments/%s" % segment_id, "segment")
self._effort_attr = self.load("/v1/efforts/%s" % effort_id, "effort")
self._segment_attr = self.load("/v1/segments/%s" % segment_id, "segment")

@property
def distance(self):
Expand Down