92 lines
3.2 KiB
Python
92 lines
3.2 KiB
Python
#
|
|
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
|
#
|
|
|
|
from abc import ABC
|
|
from typing import Any, Iterable, Mapping, MutableMapping, Optional
|
|
|
|
import requests
|
|
from airbyte_cdk.sources.streams.http import HttpStream
|
|
from dateutil import parser
|
|
|
|
|
|
# Basic full refresh stream
|
|
class StravaStream(HttpStream, ABC):
|
|
url_base = "https://www.strava.com/api/v3/"
|
|
|
|
def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]:
|
|
return None
|
|
|
|
def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapping]:
|
|
yield response.json()
|
|
|
|
|
|
# Basic incremental stream
|
|
class IncrementalStravaStream(StravaStream, ABC):
|
|
def __init__(self, after, **kwargs):
|
|
super().__init__(**kwargs)
|
|
self.after = parser.parse(after).timestamp()
|
|
|
|
per_page = 30 # default strava value
|
|
curr_page = 1
|
|
|
|
@property
|
|
def cursor_field(self) -> str:
|
|
return "start_date"
|
|
|
|
def get_updated_state(self, current_stream_state: MutableMapping[str, Any], latest_record: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
return {self.cursor_field: max(latest_record.get(self.cursor_field, ""), current_stream_state.get(self.cursor_field, ""))}
|
|
|
|
def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]:
|
|
if len(response.json()) != 0:
|
|
self.curr_page += 1
|
|
return {"page": self.curr_page}
|
|
|
|
def request_params(
|
|
self, stream_state: Mapping[str, Any], stream_slice: Mapping[str, any] = None, next_page_token: Mapping[str, Any] = None
|
|
) -> MutableMapping[str, Any]:
|
|
return {"per_page": self.per_page, "page": self.curr_page, "after": self.after}
|
|
|
|
def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapping]:
|
|
yield from response.json()
|
|
|
|
def stream_slices(self, stream_state: Mapping[str, Any] = None, **kwargs) -> Iterable[Optional[Mapping[str, any]]]:
|
|
if stream_state:
|
|
self.after = parser.parse(stream_state.get(self.cursor_field)).timestamp()
|
|
return [{"after": self.after}]
|
|
|
|
|
|
class AthleteStats(StravaStream):
|
|
"""
|
|
Returns the activity stats of an athlete. Only includes data from activities set to Everyone visibilty.
|
|
API Docs: https://developers.strava.com/docs/reference/#api-Athletes-getStats
|
|
Endpoint: https://www.strava.com/api/v3/<id>/stats
|
|
"""
|
|
|
|
primary_key = ""
|
|
|
|
def __init__(self, athlete_id: int, **kwargs):
|
|
super().__init__(**kwargs)
|
|
self.athlete_id = athlete_id
|
|
|
|
def path(
|
|
self, stream_state: Mapping[str, Any] = None, stream_slice: Mapping[str, Any] = None, next_page_token: Mapping[str, Any] = None
|
|
) -> str:
|
|
athlete_id = self.athlete_id
|
|
return f"athletes/{athlete_id}/stats"
|
|
|
|
|
|
class Activities(IncrementalStravaStream):
|
|
"""
|
|
Returns the activities of an athlete.
|
|
API Docs: https://developers.strava.com/docs/reference/#api-Activities-getLoggedInAthleteActivities
|
|
Endpoint: https://www.strava.com/api/v3/athlete/activities
|
|
"""
|
|
|
|
primary_key = "id"
|
|
|
|
def path(
|
|
self, stream_state: Mapping[str, Any] = None, stream_slice: Mapping[str, Any] = None, next_page_token: Mapping[str, Any] = None
|
|
) -> str:
|
|
return "athlete/activities"
|