Beispiel #1
0
def write_ride_streams(streams, ride):
    """
    Store GPS track for activity as LINESTRING in db.

    :param streams: The Strava :class:`stravalib.orm.Activity` object.
    :type streams: list[stravalib.orm.Stream]

    :param ride: The db model object for ride.
    :type ride: :class:`bafs.orm.Ride`
    """
    try:
        streams_dict = {s.type: s for s in streams}
        """ :type: dict[str,stravalib.orm.Stream] """
        lonlat_points = [(lon,lat) for (lat,lon) in streams_dict['latlng'].data]

        if not lonlat_points:
            raise ValueError("No data points in latlng streams.")
    except (KeyError, ValueError) as x:
        log.info("No GPS track for activity {} (skipping): {}".format(ride, x), exc_info=log.isEnabledFor(logging.DEBUG))
        ride.track_fetched = None
    else:
        # Start by removing any existing segments for the ride.
        meta.engine.execute(RideTrack.__table__.delete().where(RideTrack.ride_id == ride.id))

        gps_track = WKTSpatialElement(wktutils.linestring_wkt(lonlat_points))

        ride_track = RideTrack()
        ride_track.gps_track = gps_track
        ride_track.ride_id = ride.id
        ride_track.elevation_stream = streams_dict['altitude'].data
        ride_track.time_stream = streams_dict['time'].data
        meta.scoped_session().add(ride_track)

    ride.track_fetched = True
def webhook_challenge():
    client = Client()
    strava_request = {
        k: request.args.get(k)
        for k in ('hub.challenge', 'hub.mode', 'hub.verify_token')
    }
    log.info("Webhook challenge: {}".format(strava_request))
    challenge_resp = client.handle_subscription_callback(
        strava_request, verify_token=app.config['STRAVA_VERIFY_TOKEN'])
    return jsonify(challenge_resp)
Beispiel #3
0
    def is_excluded(activity):
        activity_end_date = (activity.start_date_local + activity.elapsed_time)
        if end_date and activity_end_date > end_date:
            log.info(
                "Skipping ride {0} ({1!r}) because date ({2}) is after competition end date ({3})"
                .format(activity.id, activity.name, activity_end_date,
                        end_date))
            return True

        for keyword in exclude_keywords:
            if keyword.lower() in activity.name.lower():
                log.info(
                    "Skipping ride {0} ({1!r}) due to presence of exlusion keyword: {2!r}"
                    .format(activity.id, activity.name, keyword))
                return True
        else:
            return False
Beispiel #4
0
    def is_excluded(activity):
        activity_end_date = (activity.start_date_local + activity.elapsed_time)
        if end_date and activity_end_date > end_date:
            log.info(
                "Skipping ride {0} ({1!r}) because date ({2}) is after competition end date ({3})".format(activity.id,
                                                                                                          activity.name,
                                                                                                          activity_end_date,
                                                                                                          end_date))
            return True

        for keyword in exclude_keywords:
            if keyword.lower() in activity.name.lower():
                log.info("Skipping ride {0} ({1!r}) due to presence of exlusion keyword: {2!r}".format(activity.id,
                                                                                                       activity.name,
                                                                                                       keyword))
                return True
        else:
            return False
Beispiel #5
0
def write_ride_streams(streams, ride):
    """
    Store GPS track for activity as LINESTRING in db.

    :param streams: The Strava :class:`stravalib.orm.Activity` object.
    :type streams: list[stravalib.orm.Stream]

    :param ride: The db model object for ride.
    :type ride: :class:`bafs.orm.Ride`
    """
    try:
        streams_dict = {s.type: s for s in streams}
        """ :type: dict[str,stravalib.orm.Stream] """
        lonlat_points = [(lon, lat)
                         for (lat, lon) in streams_dict['latlng'].data]

        if not lonlat_points:
            raise ValueError("No data points in latlng streams.")
    except (KeyError, ValueError) as x:
        log.info("No GPS track for activity {} (skipping): {}".format(ride, x),
                 exc_info=log.isEnabledFor(logging.DEBUG))
        ride.track_fetched = None
    else:
        # Start by removing any existing segments for the ride.
        meta.engine.execute(
            RideTrack.__table__.delete().where(RideTrack.ride_id == ride.id))

        gps_track = WKTSpatialElement(wktutils.linestring_wkt(lonlat_points))

        ride_track = RideTrack()
        ride_track.gps_track = gps_track
        ride_track.ride_id = ride.id
        ride_track.elevation_stream = streams_dict['altitude'].data
        ride_track.time_stream = streams_dict['time'].data
        meta.scoped_session().add(ride_track)

    ride.track_fetched = True
Beispiel #6
0
def write_ride_photos_nonprimary(activity_photos, ride):
    """
    Writes out non-primary photos (currently only instagram) associated with a ride to the database.

    :param activity_photos: Photos for an activity.
    :type activity_photos: list[stravalib.orm.ActivityPhoto]

    :param ride: The db model object for ride.
    :type ride: bafs.orm.Ride
    """
    # [{u'activity_id': 414980300,
    #   u'activity_name': u'Pimmit Run CX',
    #   u'caption': u'Pimmit Run cx',
    #   u'created_at': u'2015-10-17T20:51:02Z',
    #   u'created_at_local': u'2015-10-17T16:51:02Z',
    #   u'id': 106409096,
    #   u'ref': u'https://instagram.com/p/88qaqZvrBI/',
    #   u'resource_state': 2,
    #   u'sizes': {u'0': [150, 150]},
    #   u'source': 2,
    #   u'type': u'InstagramPhoto',
    #   u'uid': u'1097938959360503880_297644011',
    #   u'unique_id': None,
    #   u'uploaded_at': u'2015-10-17T17:55:45Z',
    #   u'urls': {u'0': u'https://instagram.com/p/88qaqZvrBI/media?size=t'}}]

    meta.engine.execute(RidePhoto.__table__.delete().where(
        and_(RidePhoto.ride_id == ride.id, RidePhoto.primary == False)))

    insta_client = insta.configured_instagram_client()

    for activity_photo in activity_photos:

        # If it's already in the db, then skip it.
        existing = meta.scoped_session().query(RidePhoto).get(
            activity_photo.uid)
        if existing:
            log.info("Skipping photo {} because it's already in database: {}".
                     format(activity_photo, existing))
            continue

        try:
            media = insta_client.media(activity_photo.uid)

            photo = RidePhoto(id=activity_photo.uid,
                              ride_id=ride.id,
                              ref=activity_photo.ref,
                              caption=activity_photo.caption)

            photo.img_l = media.get_standard_resolution_url()
            photo.img_t = media.get_thumbnail_url()

            meta.scoped_session().add(photo)

            log.debug(
                "Writing (non-primary) ride photo: {p_id}: {photo!r}".format(
                    p_id=photo.id, photo=photo))

            meta.scoped_session().flush()
        except (InstagramAPIError, InstagramClientError) as e:
            if e.status_code == 400:
                log.warning(
                    "Skipping photo {0} for ride {1}; user is set to private".
                    format(activity_photo, ride))
            elif e.status_code == 404:
                log.warning(
                    "Skipping photo {0} for ride {1}; not found".format(
                        activity_photo, ride))
            else:
                log.exception(
                    "Error fetching instagram photo {0} (skipping)".format(
                        activity_photo))

    ride.photos_fetched = True
Beispiel #7
0
def write_ride(activity):
    """
    Takes the specified activity and writes it to the database.

    :param activity: The Strava :class:`stravalib.orm.Activity` object.
    :type activity: stravalib.orm.Activity

    :return: A tuple including the written Ride model object, whether to resync segment efforts, and whether to resync photos.
    :rtype: bafs.orm.Ride
    """

    if activity.start_latlng:
        start_geo = WKTSpatialElement('POINT({lon} {lat})'.format(
            lat=activity.start_latlng.lat, lon=activity.start_latlng.lon))
    else:
        start_geo = None

    if activity.end_latlng:
        end_geo = WKTSpatialElement('POINT({lon} {lat})'.format(
            lat=activity.end_latlng.lat, lon=activity.end_latlng.lon))
    else:
        end_geo = None

    athlete_id = activity.athlete.id

    # Fail fast for invalid data (this can happen with manual-entry rides)
    assert activity.elapsed_time is not None
    assert activity.moving_time is not None
    assert activity.distance is not None

    # Find the model object for that athlete (or create if doesn't exist)
    athlete = meta.scoped_session().query(Athlete).get(athlete_id)
    if not athlete:
        # The athlete has to exist since otherwise we wouldn't be able to query their rides
        raise ValueError(
            "Somehow you are attempting to write rides for an athlete not found in the database."
        )

    if start_geo is not None or end_geo is not None:
        ride_geo = RideGeo()
        ride_geo.start_geo = start_geo
        ride_geo.end_geo = end_geo
        ride_geo.ride_id = activity.id
        meta.scoped_session().merge(ride_geo)

    ride = meta.scoped_session().query(Ride).get(activity.id)
    new_ride = (ride is None)
    if ride is None:
        ride = Ride(activity.id)

    # Check to see if we need to pull down efforts for this ride
    if new_ride:
        ride.detail_fetched = False  # Just to be explicit

        if not activity.manual:
            ride.track_fetched = False

        # photo_count refers to instagram photos
        if activity.photo_count > 1:
            ride.photos_fetched = False
        else:
            ride.photos_fetched = None

    else:
        if round(ride.distance, 2) != round(
                float(unithelper.miles(activity.distance)), 2):
            log.info(
                "Queing resync of details for activity {0!r}: distance mismatch ({1} != {2})"
                .format(activity, ride.distance,
                        unithelper.miles(activity.distance)))
            ride.detail_fetched = False
            ride.track_fetched = False

    ride.athlete = athlete

    update_ride_from_activity(strava_activity=activity, ride=ride)

    meta.scoped_session().add(ride)

    return ride
def webhook_activity():
    log.info("Activity webhook: {}".format(request.json))
    return jsonify()
Beispiel #9
0
def write_ride_photos_nonprimary(activity_photos, ride):
    """
    Writes out non-primary photos (currently only instagram) associated with a ride to the database.

    :param activity_photos: Photos for an activity.
    :type activity_photos: list[stravalib.orm.ActivityPhoto]

    :param ride: The db model object for ride.
    :type ride: bafs.orm.Ride
    """
    # [{u'activity_id': 414980300,
    #   u'activity_name': u'Pimmit Run CX',
    #   u'caption': u'Pimmit Run cx',
    #   u'created_at': u'2015-10-17T20:51:02Z',
    #   u'created_at_local': u'2015-10-17T16:51:02Z',
    #   u'id': 106409096,
    #   u'ref': u'https://instagram.com/p/88qaqZvrBI/',
    #   u'resource_state': 2,
    #   u'sizes': {u'0': [150, 150]},
    #   u'source': 2,
    #   u'type': u'InstagramPhoto',
    #   u'uid': u'1097938959360503880_297644011',
    #   u'unique_id': None,
    #   u'uploaded_at': u'2015-10-17T17:55:45Z',
    #   u'urls': {u'0': u'https://instagram.com/p/88qaqZvrBI/media?size=t'}}]

    meta.engine.execute(RidePhoto.__table__.delete().where(and_(RidePhoto.ride_id == ride.id,
                                                              RidePhoto.primary == False)))

    insta_client = insta.configured_instagram_client()

    for activity_photo in activity_photos:

        # If it's already in the db, then skip it.
        existing = meta.scoped_session().query(RidePhoto).get(activity_photo.uid)
        if existing:
            log.info("Skipping photo {} because it's already in database: {}".format(activity_photo, existing))
            continue

        try:
            media = insta_client.media(activity_photo.uid)

            photo = RidePhoto(id=activity_photo.uid,
                              ride_id=ride.id,
                              ref=activity_photo.ref,
                              caption=activity_photo.caption)

            photo.img_l = media.get_standard_resolution_url()
            photo.img_t = media.get_thumbnail_url()

            meta.scoped_session().add(photo)

            log.debug("Writing (non-primary) ride photo: {p_id}: {photo!r}".format(p_id=photo.id, photo=photo))

            meta.scoped_session().flush()
        except (InstagramAPIError, InstagramClientError) as e:
            if e.status_code == 400:
                log.warning("Skipping photo {0} for ride {1}; user is set to private".format(activity_photo, ride))
            elif e.status_code == 404:
                log.warning("Skipping photo {0} for ride {1}; not found".format(activity_photo, ride))
            else:
                log.exception("Error fetching instagram photo {0} (skipping)".format(activity_photo))


    ride.photos_fetched = True
Beispiel #10
0
def write_ride(activity):
    """
    Takes the specified activity and writes it to the database.

    :param activity: The Strava :class:`stravalib.orm.Activity` object.
    :type activity: stravalib.orm.Activity

    :return: A tuple including the written Ride model object, whether to resync segment efforts, and whether to resync photos.
    :rtype: bafs.orm.Ride
    """

    if activity.start_latlng:
        start_geo = WKTSpatialElement('POINT({lon} {lat})'.format(lat=activity.start_latlng.lat,
                                                                  lon=activity.start_latlng.lon))
    else:
        start_geo = None

    if activity.end_latlng:
        end_geo = WKTSpatialElement('POINT({lon} {lat})'.format(lat=activity.end_latlng.lat,
                                                                lon=activity.end_latlng.lon))
    else:
        end_geo = None

    athlete_id = activity.athlete.id

    # Fail fast for invalid data (this can happen with manual-entry rides)
    assert activity.elapsed_time is not None
    assert activity.moving_time is not None
    assert activity.distance is not None

    # Find the model object for that athlete (or create if doesn't exist)
    athlete = meta.scoped_session().query(Athlete).get(athlete_id)
    if not athlete:
        # The athlete has to exist since otherwise we wouldn't be able to query their rides
        raise ValueError("Somehow you are attempting to write rides for an athlete not found in the database.")

    if start_geo is not None or end_geo is not None:
        ride_geo = RideGeo()
        ride_geo.start_geo = start_geo
        ride_geo.end_geo = end_geo
        ride_geo.ride_id = activity.id
        meta.scoped_session().merge(ride_geo)

    ride = meta.scoped_session().query(Ride).get(activity.id)
    new_ride = (ride is None)
    if ride is None:
        ride = Ride(activity.id)

    # Check to see if we need to pull down efforts for this ride
    if new_ride:
        ride.detail_fetched = False  # Just to be explicit

        if not activity.manual:
            ride.track_fetched = False

        # photo_count refers to instagram photos
        if activity.photo_count > 1:
            ride.photos_fetched = False
        else:
            ride.photos_fetched = None

    else:
        if round(ride.distance, 2) != round(float(unithelper.miles(activity.distance)), 2):
            log.info("Queing resync of details for activity {0!r}: distance mismatch ({1} != {2})".format(activity,
                                                                                                          ride.distance,
                                                                                                          unithelper.miles(activity.distance)))
            ride.detail_fetched = False
            ride.track_fetched = False

    ride.athlete = athlete


    update_ride_from_activity(strava_activity=activity, ride=ride)


    meta.scoped_session().add(ride)

    return ride
Beispiel #11
0
def webhook_activity():
    log.info("Activity webhook: {}".format(request.json))
    return jsonify()
Beispiel #12
0
def webhook_challenge():
    client = Client()
    strava_request = {k: request.args.get(k) for k in ('hub.challenge', 'hub.mode', 'hub.verify_token')}
    log.info("Webhook challenge: {}".format(strava_request))
    challenge_resp = client.handle_subscription_callback(strava_request, verify_token=config.STRAVA_VERIFY_TOKEN)
    return jsonify(challenge_resp)