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)
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
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
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 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
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()
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
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_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)