"""
Entity classes for representing the various Strava datatypes.
"""
import abc
import logging
from collections import Sequence
from stravalib import exc
from stravalib import unithelper as uh
from stravalib.attributes import (META, SUMMARY, DETAILED, Attribute,
TimestampAttribute, LocationAttribute,
EntityCollection, EntityAttribute,
TimeIntervalAttribute, TimezoneAttribute,
DateAttribute)
[docs]class BaseEntity(object):
"""
A base class for all entities in the system, including objects that may not
be first-class entities in Strava.
"""
__metaclass__ = abc.ABCMeta
def __init__(self, **kwargs):
self.log = logging.getLogger('{0.__module__}.{0.__name__}'.format(self.__class__))
self.from_dict(kwargs)
[docs] def from_dict(self, d):
"""
Populates this object from specified dict.
Only defined attributes will be set; warnings will be logged for invalid attributes.
"""
for (k,v) in d.items():
# Only set defined attributes.
if hasattr(self.__class__, k):
self.log.debug("Setting attribute `{0}` [{1}] on entity {2} with value {3!r}".format(k, getattr(self.__class__, k).__class__.__name__, self, v))
try:
setattr(self, k, v)
except AttributeError:
raise AttributeError("Error setting attribute {0} on entity {1}, value: {2!r}".format(k, self, v))
else:
self.log.warning("No such attribute {0} on entity {1}".format(k, self))
@classmethod
[docs] def deserialize(cls, v):
"""
Creates a new object based on serialized (dict) struct.
"""
o = cls()
o.from_dict(v)
return o
def __repr__(self):
attrs = []
if hasattr(self.__class__, 'id'):
attrs.append('id={0}'.format(self.id))
if hasattr(self.__class__, 'name'):
attrs.append('name={0!r}'.format(self.name))
if hasattr(self.__class__, 'resource_state'):
attrs.append('resource_state={0}'.format(self.resource_state))
return '<{0} {1}>'.format(self.__class__.__name__, ' '.join(attrs))
[docs]class ResourceStateEntity(BaseEntity):
"""
Mixin for entities that include the resource_state attribute.
"""
resource_state = Attribute(int, (META,SUMMARY,DETAILED)) #: The detail-level for this entity.
[docs]class IdentifiableEntity(ResourceStateEntity):
"""
Mixin for entities that include an ID attribute.
"""
id = Attribute(int, (META,SUMMARY,DETAILED)) #: The numeric ID for this entity.
[docs]class BoundEntity(BaseEntity):
"""
Base class for entities that support lazy loading additional data using a bound client.
"""
bind_client = None #: The :class:`stravalib.client.Client` that can be used to load related resources.
def __init__(self, bind_client=None, **kwargs):
"""
Base entity initializer, which accepts a client parameter that creates a "bound" entity
which can perform additional lazy loading of content.
:param bind_client: The client instance to bind to this entity.
:type bind_client: :class:`stravalib.simple.Client`
"""
self.bind_client = bind_client
super(BoundEntity, self).__init__(**kwargs)
@classmethod
[docs] def deserialize(cls, v, bind_client=None):
"""
Creates a new object based on serialized (dict) struct.
"""
if v is None:
return None
o = cls(bind_client=bind_client)
o.from_dict(v)
return o
def assert_bind_client(self):
if self.bind_client is None:
raise exc.UnboundEntity("Unable to fetch objects for unbound {0} entity.".format(self.__class__))
[docs]class LoadableEntity(BoundEntity, IdentifiableEntity):
"""
Base class for entities that are bound and have an ID associated with them.
In theory these entities can be "expaned" by additional Client queries. In practice this is not
implemented, since usefulness is limited due to resource-state limitations, etc.
"""
[docs] def expand(self):
"""
Expand this object with data from the bound client.
(THIS IS NOT IMPLEMENTED CURRENTLY.)
"""
raise NotImplementedError() # This is a little harder now due to resource states, etc.
[docs]class Club(LoadableEntity):
"""
Class to represent a club.
Currently summary and detail resource states have the same attributes.
"""
name = Attribute(unicode, (SUMMARY,DETAILED)) #: Name of the club.
profile_medium = Attribute(unicode, (SUMMARY,DETAILED)) #: URL to a 62x62 pixel club picture
profile = Attribute(unicode, (SUMMARY,DETAILED)) #: URL to a 124x124 pixel club picture
@property
[docs] def members(self):
""" An iterator of :class:`stravalib.model.Athlete` members of this club. """
if self._members is None:
self.assert_bind_client()
self._members = self.bind_client.get_club_members(self.id)
return self._members
@property
[docs] def activities(self):
""" An iterator of reverse-chronological :class:`stravalib.model.Activity` activities for this club. """
if self._activities is None:
self.assert_bind_client()
self._activities = self.bind_client.get_club_activities(self.id)
return self._activities
[docs]class Gear(IdentifiableEntity):
"""
"""
id = Attribute(unicode, (META,SUMMARY,DETAILED)) #: Alpha-numeric gear ID.
name = Attribute(unicode, (SUMMARY,DETAILED)) #: Name athlete entered for bike (does not apply to shoes)
distance = Attribute(float, (SUMMARY,DETAILED), units=uh.meters) #: Distance for this bike/shoes.
primary = Attribute(bool, (SUMMARY,DETAILED)) #: athlete's default bike/shoes
brand_name = Attribute(unicode, (DETAILED,)) #: Brand name of bike/shoes.
model_name = Attribute(unicode, (DETAILED,)) #: Modelname of bike/shoes.
description = Attribute(unicode, (DETAILED,)) #: Description of bike/shoe item.
@classmethod
[docs] def deserialize(cls, v):
"""
Creates a new object based on serialized (dict) struct.
"""
if v is None:
return None
if cls == Gear and v.get('resource_state') == 3:
if 'frame_type' in v:
o = Bike()
else:
o = Shoe()
else:
o = cls()
o.from_dict(v)
return o
[docs]class Bike(Gear):
"""
Represents an athlete's bike.
"""
frame_type = Attribute(int, (DETAILED,)) #: (detailed-only) Type of bike frame.
[docs]class Shoe(Gear):
"""
Represent's an athlete's pair of shoes.
"""
[docs]class ActivityTotals(BaseEntity):
"""
Represent ytd/recent/all run/ride totals.
"""
achievement_count = Attribute(int) #: How many achievements
count = Attribute(int) #: How many activities
distance = Attribute(float, units=uh.meters) #: Total distance travelled
elapsed_time = TimeIntervalAttribute() #: :class:`datetime.timedelta` of total elapsed time
elevation_gain = Attribute(float, units=uh.meters) #: Total elevation gain
moving_time = TimeIntervalAttribute() #: :class:`datetime.timedelta` of total moving time
[docs]class Athlete(LoadableEntity):
"""
Represents a Strava athlete.
"""
firstname = Attribute(unicode, (SUMMARY,DETAILED)) #: Athlete's first name.
lastname = Attribute(unicode, (SUMMARY,DETAILED)) #: Athlete's last name.
profile_medium = Attribute(unicode, (SUMMARY,DETAILED)) #: URL to a 62x62 pixel profile picture
profile = Attribute(unicode, (SUMMARY,DETAILED)) #: URL to a 124x124 pixel profile picture
city = Attribute(unicode, (SUMMARY,DETAILED)) #: Athlete's home city
state = Attribute(unicode, (SUMMARY,DETAILED)) #: Athlete's home state
country = Attribute(unicode, (SUMMARY,DETAILED)) #: Athlete's home country
sex = Attribute(unicode, (SUMMARY,DETAILED)) #: Athlete's sex ('M', 'F' or null)
friend = Attribute(unicode, (SUMMARY,DETAILED)) #: 'pending', 'accepted', 'blocked' or 'null' the authenticated athlete's following status of this athlete
follower = Attribute(unicode, (SUMMARY,DETAILED)) #: 'pending', 'accepted', 'blocked' or 'null' this athlete's following status of the authenticated athlete
premium = Attribute(bool, (SUMMARY,DETAILED)) #: Whether athlete is a premium member (true/false)
created_at = TimestampAttribute((SUMMARY,DETAILED)) #: :class:`datetime.datetime` when athlete record was created.
updated_at = TimestampAttribute((SUMMARY,DETAILED)) #: :class:`datetime.datetime` when athlete record was last updated.
approve_followers = Attribute(bool, (SUMMARY,DETAILED)) #: Whether athlete has elected to approve followers
follower_count = Attribute(int, (DETAILED,)) #: (detailed-only) How many people are following this athlete
friend_count = Attribute(int, (DETAILED,)) #: (detailed-only) How many people is this athlete following
mutual_friend_count = Attribute(int, (DETAILED,)) #: (detailed-only) How many people are both following and being followed by this athlete
date_preference = Attribute(unicode, (DETAILED,)) #: (detailed-only) Athlete's preferred date representation (e.g. "%m/%d/%Y")
measurement_preference = Attribute(unicode, (DETAILED,)) #: (detailed-only) How athlete prefers to see measurements (i.e. "feet" (or what "meters"?))
email = Attribute(unicode, (DETAILED,)) #: (detailed-only) Athlete's email address
clubs = EntityCollection(Club, (DETAILED,)) #: (detailed-only) Which clubs athlete belongs to. (:class:`list` of :class:`stravalib.model.Club`)
bikes = EntityCollection(Bike, (DETAILED,)) #: (detailed-only) Which bikes this athlete owns. (:class:`list` of :class:`stravalib.model.Bike`)
shoes = EntityCollection(Shoe, (DETAILED,)) #: (detailed-only) Which shoes this athlete owns. (:class:`list` of :class:`stravalib.model.Shoe`)
# Some undocumented summary & detailed attributes
ytd_run_totals = EntityAttribute(ActivityTotals, (SUMMARY, DETAILED)) #: (undocumented) Year-to-date totals for runs. (:class:`stravalib.model.ActivityTotals`)
recent_run_totals = EntityAttribute(ActivityTotals, (SUMMARY, DETAILED)) #: (undocumented) Recent totals for runs. (:class:`stravalib.model.ActivityTotals`)
all_run_totals = EntityAttribute(ActivityTotals, (SUMMARY, DETAILED)) #: (undocumented) All-time totals for runs. (:class:`stravalib.model.ActivityTotals`)
ytd_ride_totals = EntityAttribute(ActivityTotals, (SUMMARY, DETAILED)) #: (undocumented) Year-to-date totals for rides. (:class:`stravalib.model.ActivityTotals`)
recent_ride_totals = EntityAttribute(ActivityTotals, (SUMMARY, DETAILED)) #: (undocumented) Recent totals for rides. (:class:`stravalib.model.ActivityTotals`)
all_ride_totals = EntityAttribute(ActivityTotals, (SUMMARY, DETAILED)) #: (undocumented) All-time totals for rides. (:class:`stravalib.model.ActivityTotals`)
super_user = Attribute(bool, (SUMMARY,DETAILED)) #: (undocumented) Whether athlete is a super user (not
biggest_ride_distance = Attribute(float, (SUMMARY,DETAILED), units=uh.meters) #: (undocumented) Longest ride for athlete.
biggest_climb_elevation_gain = Attribute(float, (SUMMARY,DETAILED), units=uh.meters) #: (undocumented) Greatest single elevation gain for athlete.
email_language = Attribute(unicode, (SUMMARY,DETAILED)) #: The user's preferred lang/locale (e.g. en-US)
# A bunch more undocumented detailed-resolution attribs
weight = Attribute(float, (DETAILED,), units=uh.kg) #: (undocumented, detailed-only) Athlete's configured weight.
max_heartrate = Attribute(float, (DETAILED,)) #: (undocumented, detailed-only) Athlete's configured max HR
username = Attribute(unicode, (DETAILED,)) #: (undocumented, detailed-only) Athlete's username.
description = Attribute(unicode, (DETAILED,)) #: (undocumented, detailed-only) Athlete's personal description
instagram_username = Attribute(unicode, (DETAILED,)) #: (undocumented, detailed-only) Associated instagram username
offer_in_app_payment = Attribute(bool, (DETAILED,)) #: (undocumented, detailed-only)
global_privacy = Attribute(bool, (DETAILED,)) #: (undocumented, detailed-only) Whether athlete has global privacy enabled.
receive_newsletter = Attribute(bool, (DETAILED,)) #: (undocumented, detailed-only) Whether athlete has elected to receive newsletter
email_kom_lost = Attribute(bool, (DETAILED,)) #: (undocumented, detailed-only) Whether athlete has elected to receive emails when KOMs are lost.
dateofbirth = DateAttribute((DETAILED,)) #: (undocumented, detailed-only) Athlete's date of birth
facebook_sharing_enabled = Attribute(bool, (DETAILED,)) #: (undocumented, detailed-only) Whether Athlete has enabled sharing on Facebook
ftp = Attribute(unicode, (DETAILED,)) # (undocumented, detailed-only)
profile_original = Attribute(unicode, (DETAILED,)) #: (undocumented, detailed-only)
premium_expiration_date = Attribute(int, (DETAILED,)) #: (undocumented, detailed-only) When does premium membership expire (:class:`int` unix epoch)
email_send_follower_notices = Attribute(bool, (DETAILED,)) #: (undocumented, detailed-only)
plan = Attribute(unicode, (DETAILED,)) #: (undocumented, detailed-only)
agreed_to_terms = Attribute(unicode, (DETAILED,)) #: (undocumented, detailed-only) Whether athlete has agreed to terms
follower_request_count = Attribute(int, (DETAILED,)) #: (undocumented, detailed-only) How many people have requested to follow this athlete
email_facebook_twitter_friend_joins = Attribute(bool, (DETAILED,)) #: (undocumented, detailed-only) Whether athlete has elected to receve emails when a twitter or facebook friend joins Strava
receive_kudos_emails = Attribute(bool, (DETAILED,)) #: (undocumented, detailed-only) Whether athlete has elected to receive emails on kudos
receive_follower_feed_emails = Attribute(bool, (DETAILED,)) #: (undocumented, detailed-only) Whether athlete has elected to receive emails on new followers
receive_comment_emails = Attribute(bool, (DETAILED,)) #: (undocumented, detailed-only) Whether athlete has elected to receive emails on activity comments
sample_race_distance = Attribute(int, (DETAILED,)) # (undocumented, detailed-only)
sample_race_time = Attribute(int, (DETAILED,)) # (undocumented, detailed-only)
_friends = None
_followers = None
def __repr__(self):
fname = self.firstname and self.firstname.encode('utf-8')
lname = self.lastname and self.lastname.encode('utf-8')
return '<Athlete id={id} firstname={fname} lastname={lname}>'.format(id=self.id,
fname=fname,
lname=lname)
@property
[docs] def friends(self):
"""
Iterator of :class:`stravalib.model.Athlete` 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
[docs] def followers(self):
"""
Iterator of :class:`stravalib.model.Athlete` objects for this activity.
"""
if self._followers is None:
self.assert_bind_client()
if self.follower_count > 0:
self._followers = self.bind_client.get_athlete_followers(self.id)
else:
# Shortcut if we know there aren't any
self._followers = []
return self._followers
class ActivityComment(LoadableEntity):
activity_id = Attribute(int, (META,SUMMARY,DETAILED)) #: ID of activity
text = Attribute(unicode, (META,SUMMARY,DETAILED)) #: Text of comment
created_at = TimestampAttribute((SUMMARY,DETAILED)) #: :class:`datetime.datetime` when was coment created
athlete = EntityAttribute(Athlete, (SUMMARY,DETAILED)) #: Associated :class:`stravalib.model.Athlete` (summary-level representation)
class ActivityPhoto(LoadableEntity):
activity_id = Attribute(int, (META,SUMMARY,DETAILED)) #: ID of activity
ref = Attribute(unicode, (META,SUMMARY,DETAILED)) #: ref eg. "http://instagram.com/p/eAvA-tir85/"
uid = Attribute(unicode, (META,SUMMARY,DETAILED)) #: unique id
caption = Attribute(unicode, (META,SUMMARY,DETAILED)) #: caption on photo
type = Attribute(unicode, (META,SUMMARY,DETAILED)) #: type of photo #left this off to prevent name clash
uploaded_at = TimestampAttribute((SUMMARY,DETAILED)) #: :class:`datetime.datetime` when was phto uploaded
created_at = TimestampAttribute((SUMMARY,DETAILED)) #: :class:`datetime.datetime` when was phto created
location = LocationAttribute() #: Start lat/lon of photo
[docs]class ActivityKudos(LoadableEntity):
"""
activity kudos are a subset of athlete properties.
"""
firstname = Attribute(unicode, (SUMMARY,DETAILED)) #: Athlete's first name.
lastname = Attribute(unicode, (SUMMARY,DETAILED)) #: Athlete's last name.
profile_medium = Attribute(unicode, (SUMMARY,DETAILED)) #: URL to a 62x62 pixel profile picture
profile = Attribute(unicode, (SUMMARY,DETAILED)) #: URL to a 124x124 pixel profile picture
city = Attribute(unicode, (SUMMARY,DETAILED)) #: Athlete's home city
state = Attribute(unicode, (SUMMARY,DETAILED)) #: Athlete's home state
country = Attribute(unicode, (SUMMARY,DETAILED)) #: Athlete's home country
sex = Attribute(unicode, (SUMMARY,DETAILED)) #: Athlete's sex ('M', 'F' or null)
friend = Attribute(unicode, (SUMMARY,DETAILED)) #: 'pending', 'accepted', 'blocked' or 'null' the authenticated athlete's following status of this athlete
follower = Attribute(unicode, (SUMMARY,DETAILED)) #: 'pending', 'accepted', 'blocked' or 'null' this athlete's following status of the authenticated athlete
premium = Attribute(bool, (SUMMARY,DETAILED)) #: Whether athlete is a premium member (true/false)
created_at = TimestampAttribute((SUMMARY,DETAILED)) #: :class:`datetime.datetime` when athlete record was created.
updated_at = TimestampAttribute((SUMMARY,DETAILED)) #: :class:`datetime.datetime` when athlete record was last updated.
approve_followers = Attribute(bool, (SUMMARY,DETAILED)) #: Whether athlete has elected to approve followers
class ActivityLap(LoadableEntity):
name = Attribute(unicode, (SUMMARY,DETAILED)) #: Name of lap
activity = EntityAttribute("Activity", (SUMMARY,DETAILED)) #: The associated :class:`stravalib.model.Activity`
athlete = EntityAttribute(Athlete, (SUMMARY,DETAILED)) #: The associated :class:`stravalib.model.Athlete`
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
start_date_local = TimestampAttribute((SUMMARY,DETAILED), tzinfo=None) #: :class:`datetime.datetime` when lap was started local
distance = Attribute(float, (SUMMARY,DETAILED), units=uh.meters) #: The distance for this lap.
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,), 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
max_heartrate = Attribute(float, (SUMMARY,DETAILED,)) #: Max heartrate for lap
lap_index = Attribute(int, (SUMMARY,DETAILED)) #: Index of lap
class Map(IdentifiableEntity):
id = Attribute(unicode, (SUMMARY,DETAILED)) #: Alpha-numeric identifier
polyline = Attribute(str, (SUMMARY,DETAILED)) #: Google polyline encoding
summary_polyline = Attribute(str, (SUMMARY,DETAILED)) #: Google polyline encoding for summary shape
[docs]class Split(BaseEntity):
"""
A split -- may be metric or standard units (which has no bearing
on the units used in this object, just the binning of values).
"""
distance = Attribute(float, units=uh.meters) #: Distance for this split
elapsed_time = TimeIntervalAttribute() #: :class:`datetime.timedelta` of elapsed time for split
elevation_difference = Attribute(float, units=uh.meters) #: Elevation difference for split
moving_time = TimeIntervalAttribute() #: :class:`datetime.timedelta` of moving time for split
average_heartrate = Attribute(float) #: Average HR for split
split = Attribute(int) #: Which split number
[docs]class SegmentExplorerResult(LoadableEntity):
"""
Represents a segment result from the segment explorer feature.
(These are not full segment objects, but the segment object can be fetched
via the 'segment' property of this object.)
"""
_segment = None
id = Attribute(int) #: ID of the segment.
name = Attribute(unicode) #: Name of the segment
climb_category = Attribute(int) #: Climb category for the segment (0 is higher)
climb_category_desc = Attribute(unicode) #: Climb category text
avg_grade = Attribute(float) #: Average grade for segment.
start_latlng = LocationAttribute() #: Start lat/lon for segment
end_latlng = LocationAttribute() #: End lat/lon for segment
elev_difference = Attribute(float, units=uh.meters) #: Total elevation difference over segment.
distance = Attribute(float, units=uh.meters) #: Distance of segment.
points = Attribute(str) #: Encoded Google polyline of points in segment
@property
[docs] def segment(self):
""" Associated (full) :class:`stravalib.model.Segment` object. """
if self._segment is None:
self.assert_bind_client()
if self.id is not None:
self._segment = self.bind_client.get_segment(self.id)
return self._segment
[docs]class Segment(LoadableEntity):
"""
Represents a single Strava segment.
"""
_leaderboard = None
name = Attribute(unicode, (SUMMARY,DETAILED)) #: Name of the segment.
activity_type = Attribute(unicode, (SUMMARY,DETAILED)) #: Activity type of segment ('Ride' or 'Run')
distance = Attribute(float, (SUMMARY,DETAILED), units=uh.meters) #: Distance of segment
average_grade = Attribute(float, (SUMMARY,DETAILED)) #: Average grade (%) for segment
maximum_grade = Attribute(float, (SUMMARY,DETAILED)) #: Maximum grade (%) for segment
elevation_high = Attribute(float, (SUMMARY,DETAILED), units=uh.meters) #: The highest point of the segment.
elevation_low = Attribute(float, (SUMMARY,DETAILED), units=uh.meters) #: The lowest point of the segment.
start_latlng = LocationAttribute((SUMMARY,DETAILED)) #: The start lat/lon (:class:`tuple`)
end_latlng = LocationAttribute((SUMMARY,DETAILED)) #: The end lat/lon (:class:`tuple`)
start_latitude = Attribute(float, (SUMMARY,DETAILED)) #: The start latitude (:class:`float`)
end_latitude = Attribute(float, (SUMMARY,DETAILED)) #: The end latitude (:class:`float`)
start_longitude = Attribute(float, (SUMMARY,DETAILED)) #: The start longitude (:class:`float`)
end_longitude = Attribute(float, (SUMMARY,DETAILED)) #: The end longitude (:class:`float`)
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
# detailed attribs
created_at = TimestampAttribute((DETAILED,)) #: :class:`datetime.datetime` when was segment created.
updated_at = TimestampAttribute((DETAILED,)) #: :class:`datetime.datetime` when was segment last updated.
total_elevation_gain = Attribute(float, (DETAILED,), units=uh.meters) #: What is total elevation gain for segment.
map = EntityAttribute(Map, (DETAILED,)) #: :class:`stravalib.model.Map` object for segment.
effort_count = Attribute(int, (DETAILED,)) #: How many times has this segment been ridden.
athlete_count = Attribute(int, (DETAILED,)) #: How many athletes have ridden this segment
hazardous = Attribute(bool, (DETAILED,)) #: Whether this segment has been flagged as hazardous
star_count = Attribute(int, (DETAILED,)) #: number of stars on this segment.
@property
[docs] def leaderboard(self):
"""
The :class:`stravalib.model.SegmentLeaderboard` object for this segment.
"""
if self._leaderboard is None:
self.assert_bind_client()
if self.id is not None:
self._leaderboard = self.bind_client.get_segment_leaderboard(self.id)
return self._leaderboard
[docs]class BaseEffort(LoadableEntity):
"""
Base class for a best effort or segment effort.
"""
name = Attribute(unicode, (SUMMARY,DETAILED)) #: The name of the segment
segment = EntityAttribute(Segment, (SUMMARY,DETAILED)) #: The associated :class:`stravalib.model.Segment` for this effort
activity = EntityAttribute("Activity", (SUMMARY,DETAILED)) #: The associated :class:`stravalib.model.Activity`
athlete = EntityAttribute(Athlete, (SUMMARY,DETAILED)) #: The associated :class:`stravalib.model.Athlete`
kom_rank = Attribute(int, (SUMMARY,DETAILED)) #: 1-10 segment KOM ranking for athlete at time of upload
pr_rank = Attribute(int, (SUMMARY,DETAILED)) #: 1-3 personal record ranking for athlete at time of upload
moving_time = TimeIntervalAttribute((SUMMARY,DETAILED)) #: :class:`datetime.timedelta`
elapsed_time = TimeIntervalAttribute((SUMMARY,DETAILED))#: :class:`datetime.timedelta`
start_date = TimestampAttribute((SUMMARY,DETAILED)) #: :class:`datetime.datetime` when effort was started in GMT
start_date_local = TimestampAttribute((SUMMARY,DETAILED), tzinfo=None) #: :class:`datetime.datetime` when effort was started in activity timezone for this effort
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
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
[docs]class BestEffort(BaseEffort):
"""
Class representing a best effort (e.g. best time for 5k)
"""
[docs]class SegmentEffort(BaseEffort):
"""
Class representing a best effort on a particular segment.
"""
hidden = Attribute(bool, (SUMMARY,DETAILED,)) # indicates a hidden/non-important effort when returned as part of an activity, value may change over time.
[docs]class Activity(LoadableEntity):
"""
Represents an activity (ride, run, etc.).
"""
# "Constants" for types of activities
RIDE = "Ride"
RUN = "Run"
SWIM = "Swim"
HIKE = "Hike"
WALK = "Walk"
NORDICSKI = "NordicSki"
ALPINESKI = "AlpineSki"
BACKCOUNTRYSKI = "BackcountrySki"
ICESKATE = "IceSkate"
INLINESKATE = "InlineSkate"
KITESURF = "Kitesurf"
ROLLERSKI = "RollerSki"
WINDSURF = "Windsurf"
WORKOUT = "Workout"
SNOWBOARD = "Snowboard"
SNOWSHOE = "Snowshoe"
_comments = None
_zones = None
_kudos = None
_photos = None
#_gear = None
_laps = None
TYPES = (RIDE, RUN, SWIM, HIKE, WALK, NORDICSKI, ALPINESKI, BACKCOUNTRYSKI,
ICESKATE, INLINESKATE, KITESURF, ROLLERSKI, WINDSURF, WORKOUT,
SNOWBOARD, SNOWSHOE)
guid = Attribute(unicode, (SUMMARY,DETAILED)) #: (undocumented)
external_id = Attribute(unicode, (SUMMARY,DETAILED)) #: An external ID for the activity (relevant when specified during upload).
upload_id = Attribute(unicode, (SUMMARY,DETAILED)) #: The upload ID for an activit.
athlete = EntityAttribute(Athlete, (SUMMARY,DETAILED)) #: The associated :class:`stravalib.model.Athlete` that performed this activity.
name = Attribute(unicode, (SUMMARY,DETAILED)) #: The name of the activity.
distance = Attribute(float, (SUMMARY,DETAILED), units=uh.meters) #: The distance for the activity.
moving_time = TimeIntervalAttribute((SUMMARY,DETAILED)) #: The moving time duration for this activity.
elapsed_time = TimeIntervalAttribute((SUMMARY,DETAILED)) #: The total elapsed time (including stopped time) for this activity.
total_elevation_gain = Attribute(float, (SUMMARY,DETAILED), units=uh.meters) #: Total elevation gain for activity.
type = Attribute(unicode, (SUMMARY,DETAILED)) #: The activity type.
start_date = TimestampAttribute((SUMMARY,DETAILED)) #: :class:`datetime.datetime` when activity was started in GMT
start_date_local = TimestampAttribute((SUMMARY,DETAILED), tzinfo=None) #: :class:`datetime.datetime` when activity was started in activity timezone
timezone = TimezoneAttribute((SUMMARY,DETAILED)) #: The timezone for activity.
start_latlng = LocationAttribute((SUMMARY,DETAILED))#: The start location (lat/lon :class:`tuple`)
end_latlng = LocationAttribute((SUMMARY,DETAILED)) #: The end location (lat/lon :class:`tuple`)
location_city = Attribute(unicode, (SUMMARY,DETAILED)) #: The activity location city
location_state = Attribute(unicode, (SUMMARY,DETAILED)) #: The activity location state
location_country = Attribute(unicode, (SUMMARY,DETAILED)) #: The activity location state
start_latitude = Attribute(float, (SUMMARY,DETAILED)) #: The start latitude
start_longitude = Attribute(float, (SUMMARY,DETAILED)) #: The start longitude
achievement_count = Attribute(int, (SUMMARY,DETAILED)) #: How many achievements earned for the activity
kudos_count = Attribute(int, (SUMMARY,DETAILED)) #: How many kudos received for activity
comment_count = Attribute(int, (SUMMARY,DETAILED)) #: How many comments for activity.
athlete_count = Attribute(int, (SUMMARY,DETAILED)) #: How many other athlete's participated in activity
photo_count = Attribute(int, (SUMMARY,DETAILED)) #: How many photos linked to activity
map = EntityAttribute(Map, (SUMMARY,DETAILED)) #: :class:`stravavlib.model.Map` of activity.
trainer = Attribute(bool, (SUMMARY,DETAILED)) #: Whether activity was performed on a stationary trainer.
commute = Attribute(bool, (SUMMARY,DETAILED)) #: Whether activity is a commute.
manual = Attribute(bool, (SUMMARY,DETAILED)) #: Whether activity was manually entered.
private = Attribute(bool, (SUMMARY,DETAILED)) #: Whether activity is private
flagged = Attribute(bool, (SUMMARY,DETAILED)) #: Whether activity was flagged.
gear_id = Attribute(unicode, (SUMMARY,DETAILED)) #: Which bike/shoes were used on activity.
gear = EntityAttribute(Gear, (DETAILED,))
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
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
best_efforts = EntityCollection(BestEffort, (DETAILED,)) #: :class:`list` of metric :class:`stravalib.model.BestEffort` summaries
segment_efforts = EntityCollection(SegmentEffort, (DETAILED,)) #: :class:`list` of :class:`stravalib.model.SegmentEffort` efforts for activity.
splits_metric = EntityCollection(Split, (DETAILED,)) #: :class:`list` of metric :class:`stravalib.model.Split` summaries (running activities only)
splits_standard = EntityCollection(Split, (DETAILED,)) #: :class:`list` of standard/imperial :class:`stravalib.model.Split` summaries (running activities only)
# Undocumented attributes
average_watts = Attribute(float, (SUMMARY,DETAILED)) #: (undocumented) Average power during activity
average_heartrate = Attribute(float, (SUMMARY,DETAILED)) #: (undocumented) Average HR during activity
max_heartrate = Attribute(int, (SUMMARY,DETAILED)) #: (undocumented) Max HR during activity
average_cadence = Attribute(float, (SUMMARY,DETAILED)) #: (undocumented) Average cadence during activity
kilojoules = Attribute(float, (SUMMARY,DETAILED)) #: (undocumented) Kilojoules of energy used during activity
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)
@property
@property
[docs] def laps(self):
"""
Iterator of :class:`stravalib.model.ActivityLap` objects for this activity.
"""
if self._laps is None:
self.assert_bind_client()
self._laps = self.bind_client.get_activity_laps(self.id)
return self._laps
@property
[docs] def zones(self):
"""
:class:`list` of :class:`stravalib.model.ActivityZone` objects for this activity.
"""
if self._zones is None:
self.assert_bind_client()
self._zones = self.bind_client.get_activity_zones(self.id)
return self._zones
@property
[docs] def kudos(self):
"""
:class:`list` of :class:`stravalib.model.ActivityKudos` objects for this activity.
"""
if self._kudos is None:
self.assert_bind_client()
self._kudos = self.bind_client.get_activity_kudos(self.id)
return self._kudos
@property
[docs] def photos(self):
"""
:class:`list` of :class:`stravalib.model.ActivityPhoto` objects for this activity.
"""
if self._photos is None:
if self.photo_count > 0:
self.assert_bind_client()
self._photos = self.bind_client.get_activity_photos(self.id)
else:
self._photos = []
return self._photos
[docs]class SegmentLeaderboardEntry(BoundEntity):
"""
Represents a single entry on a segment leaderboard.
The :class:`stravalib.model.SegmentLeaderboard` object is essentially a collection
of instances of this class.
"""
_athlete = None
_activity = None
_effort = None
effort_id = Attribute(int) #: The numeric ID for the segment effort.
athlete_id = Attribute(int) #: The numeric ID for the athlete.
athlete_name = Attribute(unicode) #: The athlete's name.
athlete_gender = Attribute(unicode) #: The athlete's sex (M/F)
athlete_profile = Attribute(unicode) #: Link to athlete profile photo
average_hr = Attribute(float) #: The athlete's average HR for this effort
average_watts = Attribute(float) #: The athlete's average power for this effort
distance = Attribute(float, units=uh.meters) #: The distance for this effort.
elapsed_time = TimeIntervalAttribute() #: The elapsed time for this effort
moving_time = TimeIntervalAttribute() #: The moving time for this effort
start_date = TimestampAttribute((SUMMARY,DETAILED)) #: :class:`datetime.datetime` when this effot was started in GMT
start_date_local = TimestampAttribute((SUMMARY,DETAILED), tzinfo=None) #: :class:`datetime.datetime` when this effort was started in activity timezone
activity_id = Attribute(int) #: The numeric ID of the associated activity for this effort.
rank = Attribute(int) #: The rank on the leaderboard.
def __repr__(self):
return '<SegmentLeaderboardEntry rank={0} athlete_name={1!r}>'.format(self.rank, self.athlete_name)
@property
[docs] def athlete(self):
""" The related :class:`stravalib.model.Athlete` (performs additional server fetch). """
if self._athlete is None:
self.assert_bind_client()
if self.athlete_id is not None:
self._athlete = self.bind_client.get_athlete(self.athlete_id)
return self._athlete
@property
[docs] def activity(self):
""" The related :class:`stravalib.model.Activity` (performs additional server fetch). """
if self._activity is None:
self.assert_bind_client()
if self.activity_id is not None:
self._activity = self.bind_client.get_activity(self.activity_id)
return self._activity
@property
[docs] def effort(self):
""" The related :class:`stravalib.model.SegmentEffort` (performs additional server fetch). """
if self._effort is None:
self.assert_bind_client()
if self.effort_id is not None:
self._effort = self.bind_client.get_segment_effort(self.effort_id)
return self._effort
[docs]class SegmentLeaderboard(Sequence, BoundEntity):
"""
The ranked leaderboard for a segment.
This class is effectively a collection of :class:`stravalib.model.SegmentLeaderboardEntry` objects.
"""
effort_count = Attribute(int)
entry_count = Attribute(int)
entries = EntityCollection(SegmentLeaderboardEntry)
def __iter__(self):
return iter(self.entries)
def __len__(self):
return len(self.entries)
def __contains__(self, k):
return k in self.entries
def __getitem__(self, k):
return self.entries[k]
[docs]class DistributionBucket(BaseEntity):
"""
A single distribution bucket object, used for activity zones.
"""
max = Attribute(int) #: Max datatpoint
min = Attribute(int) #: Min datapoint
time = Attribute(int, units=uh.seconds) #: Time in seconds (*not* a :class:`datetime.timedelta`)
[docs]class BaseActivityZone(LoadableEntity):
"""
Base class for activity zones.
A collection of :class:`stravalib.model.DistributionBucket` objects.
"""
distribution_buckets = EntityCollection(DistributionBucket, (SUMMARY, DETAILED)) #: The collection of :class:`stravalib.model.DistributionBucket` objects
type = Attribute(unicode, (SUMMARY, DETAILED)) #: Type of activity zone (heartrate, power, pace).
sensor_based = Attribute(bool, (SUMMARY, DETAILED)) #: Whether zone data is sensor-based (as opposed to calculated)
@classmethod
[docs] def deserialize(cls, v, bind_client=None):
"""
Creates a new object based on serialized (dict) struct.
"""
if v is None:
return None
az_classes = {'heartrate': HeartrateActivityZone,
'power': PowerActivityZone,
'pace': PaceActivityZone}
try:
clazz = az_classes[v['type']]
except KeyError:
raise ValueError("Unsupported activity zone type: {0}".format(v['type']))
else:
o = clazz(bind_client=bind_client)
o.from_dict(v)
return o
[docs]class HeartrateActivityZone(BaseActivityZone):
"""
Activity zone for heart rate.
"""
score = Attribute(int, (SUMMARY, DETAILED)) #: The score (suffer score) for this HR zone.
points = Attribute(int, (SUMMARY, DETAILED)) #: The points for this HR zone.
custom_zones = Attribute(bool, (SUMMARY, DETAILED)) #: Whether athlete has setup custom zones.
max = Attribute(int, (SUMMARY, DETAILED)) #: The max heartrate
[docs]class PaceActivityZone(BaseActivityZone):
"""
Activity zone for pace.
"""
score = Attribute(int, (SUMMARY, DETAILED)) #: The score for this zone.
sample_race_distance = Attribute(int, (SUMMARY, DETAILED), units=uh.meters) #: (Not sure?)
sample_race_time = TimeIntervalAttribute((SUMMARY, DETAILED)) #: (Not sure?)
[docs]class PowerActivityZone(BaseActivityZone):
"""
Activity zone for power.
"""
# these 2 below were removed according to June 3, 2014 update @
# http://strava.github.io/api/v3/changelog/
bike_weight = Attribute(float, (SUMMARY, DETAILED), units=uh.kgs) #: Weight of bike being used (factored into power calculations)
athlete_weight = Attribute(float, (SUMMARY, DETAILED), units=uh.kgs) #: Weight of athlete (factored into power calculations)
class Stream(LoadableEntity):
type = Attribute(unicode)
data = Attribute(list,) #: array of stream values
series_type = Attribute(unicode, ) #:
original_size = Attribute(int, ) #:
resolution = Attribute(unicode, ) #:
def __repr__(self):
return '<Stream type={} resolution={} original_size={}>'.format(self.type,
self.resolution,
self.original_size,)