Ejemplo n.º 1
0
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
    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
Ejemplo n.º 2
0
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]
Ejemplo n.º 3
0
class BaseEffort(LoadableEntity):
    """
    Base class for a best effort or segment effort.
    """
    name = Attribute(str, (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
    device_watts = Attribute(bool, (SUMMARY, DETAILED))  #: True if the watts are from a power meter, false if estimated
    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

    achievements = EntityCollection(SegmentEfforAchievement, (SUMMARY, DETAILED))  #: Undocumented attribute includes list of achievements for this effort.
Ejemplo n.º 4
0
class Activity(LoadableEntity):
    """
    Represents an activity (ride, run, etc.).
    """
    # "Constants" for types of activities
    RIDE                = "Ride"
    RUN                 = "Run"
    SWIM                = "Swim"
    WALK                = "Walk"

    ALPINESKI           = "AlpineSki"
    BACKCOUNTRYSKI      = "BackcountrySki"
    CANOEING            = "Canoeing"
    CROSSCOUNTRYSKIING  = "CrossCountrySkiing"
    CROSSFIT            = "Crossfit"
    ELLIPTICAL          = "Elliptical"
    HIKE                = "Hike"
    ICESKATE            = "IceSkate"
    INLINESKATE         = "InlineSkate"
    KAYAKING            = "Kayaking"
    KITESURF            = "Kitesurf"
    NORDICSKI           = "NordicSki"
    ROCKCLIMBING        = "RockClimbing"
    ROLLERSKI           = "RollerSki"
    ROWING              = "Rowing"
    SNOWBOARD           = "Snowboard"
    SNOWSHOE            = "Snowshoe"
    STAIRSTEPPER        = "StairStepper"
    STANDUPPADDLING     = "StandUpPaddling"
    SURFING             = "Surfing"
    WEIGHTTRAINING      = "WeightTraining"
    WINDSURF            = "Windsurf"
    WORKOUT             = "Workout"
    YOGA                = "Yoga"

    _comments = None
    _zones = None
    _kudos = None
    _photos = None
    #_gear = None
    _laps = None

    TYPES = ( RIDE, RUN, SWIM, WALK, ALPINESKI, BACKCOUNTRYSKI, CANOEING,
              CROSSCOUNTRYSKIING, CROSSFIT, ELLIPTICAL, HIKE, ICESKATE,
              INLINESKATE, KAYAKING, KITESURF, NORDICSKI, ROCKCLIMBING,
              ROLLERSKI, ROWING, SNOWBOARD, SNOWSHOE, STAIRSTEPPER,
              STANDUPPADDLING, SURFING, WEIGHTTRAINING, WINDSURF, WORKOUT, YOGA)

    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
    weighted_average_watts = Attribute(int, (SUMMARY,DETAILED)) # rides with power meter data only similar to xPower or Normalized Power
    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
    device_watts = Attribute(bool, (SUMMARY,DETAILED)) # true if the watts are from a power meter, false if estimated
    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
    def comments(self):
        """
        Iterator of :class:`stravalib.model.ActivityComment` objects for this activity.
        """
        if self._comments is None:
            self.assert_bind_client()
            if self.comment_count > 0:
                self._comments = self.bind_client.get_activity_comments(self.id)
            else:
                # Shortcut if we know there aren't any
                self._comments = []
        return self._comments

    @property
    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
    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
    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
    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
Ejemplo n.º 5
0
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
    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
    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
Ejemplo n.º 6
0
class Athlete(LoadableEntity):
    """
    Represents a Strava athlete.
    """
    firstname = Attribute(str, (SUMMARY, DETAILED))  #: Athlete's first name.
    lastname = Attribute(str, (SUMMARY, DETAILED))  #: Athlete's last name.
    profile_medium = Attribute(str, (SUMMARY, DETAILED))  #: URL to a 62x62 pixel profile picture
    profile = Attribute(str, (SUMMARY, DETAILED))  #: URL to a 124x124 pixel profile picture
    city = Attribute(str, (SUMMARY, DETAILED))  #: Athlete's home city
    state = Attribute(str, (SUMMARY, DETAILED))  #: Athlete's home state
    country = Attribute(str, (SUMMARY, DETAILED))  #: Athlete's home country
    sex = Attribute(str, (SUMMARY, DETAILED))  #: Athlete's sex ('M', 'F' or null)
    friend = Attribute(str, (SUMMARY, DETAILED))  #: 'pending', 'accepted', 'blocked' or 'null' the authenticated athlete's following status of this athlete
    follower = Attribute(str, (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

    badge_type_id = Attribute(int, (SUMMARY, DETAILED))  #: (undocumented)

    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
    athlete_type = ChoicesAttribute(str, (DETAILED,), choices={0: "cyclist", 1: "runner"})  #: athlete's default sport: 0 is cyclist, 1 is runner
    date_preference = Attribute(str, (DETAILED,))  #: (detailed-only) Athlete's preferred date representation (e.g. "%m/%d/%Y")
    measurement_preference = Attribute(str, (DETAILED,))  #: (detailed-only) How athlete prefers to see measurements (i.e. "feet" (or what "meters"?))
    email = Attribute(str, (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`)

    super_user = Attribute(bool, (SUMMARY, DETAILED))  #: (undocumented) Whether athlete is a super user (not

    email_language = Attribute(str, (SUMMARY, DETAILED))  #: The user's preferred lang/locale (e.g. en-US)

    # A bunch of 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(str, (DETAILED))  #: (undocumented, detailed-only) Athlete's username.
    description = Attribute(str, (DETAILED,))  #: (undocumented, detailed-only) Athlete's personal description
    instagram_username = Attribute(str, (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(str, (DETAILED,))   #: (undocumented, detailed-only)
    profile_original = Attribute(str, (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(str, (DETAILED,))  #: (undocumented, detailed-only)
    agreed_to_terms = Attribute(str, (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
    _stats = None
    _is_authenticated = 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)

    def is_authenticated_athlete(self):
        """
        :return: Boolean as to whether the athlete is the authenticated athlete.
        """
        if self._is_authenticated is None:
            if self.resource_state == DETAILED:
                # If the athlete is in detailed state it must be the authenticated athlete
                self._is_authenticated = True
            else:
                # We need to check this athlete's id matches the authenticated athlete's id
                self.assert_bind_client()
                authenticated_athlete = self.bind_client.get_athlete()
                self._is_authenticated = authenticated_athlete.id == self.id
        return self._is_authenticated

    @property
    def friends(self):
        """
        :return: Iterator of :class:`stravalib.model.Athlete` friend objects for this athlete.
        """
        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 followers(self):
        """
        :return: Iterator of :class:`stravalib.model.Athlete` followers objects for this athlete.
        """
        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

    @property
    def stats(self):
        """
        :return: Associated :class:`stravalib.model.AthleteStats`
        """
        if not self.is_authenticated_athlete():
            raise exc.NotAuthenticatedAthlete("Statistics are only available for the authenticated athlete")
        if self._stats is None:
            self.assert_bind_client()
            self._stats = self.bind_client.get_athlete_stats(self.id)
        return self._stats