class FitBitHelper(object): """ Wraps around Fitbit api and contains useful methods for interacting with FitBit data """ def __init__(self, user_id): self.user_id = user_id # look up the user in the database self.user = current.db( current.db.auth_user.id == user_id).select().first() if self.user is None: raise ValueError( "User {user_id} wasn't found in the database!".format( user_id=user_id)) self.fitbit = Fitbit(current.client_id, current.client_secret, access_token=self.user.token['access_token'], refresh_token=self.user.token['refresh_token'], expires_at=self.user.token['expires_at'], refresh_cb=_update_token) def get_sleep_history(self, period=datetime.timedelta(days=14)): """ Gets a slice of sleep history ending with today The default slice is two weeks. """ # get a two week sleep history today = datetime.datetime.today() old = today - datetime.timedelta(days=14) return self.fitbit.time_series("sleep", user_id=self.user.username, base_date=old, end_date=today)
def steps(client: FitbitApi) -> t.Optional[int]: data = client.time_series(resource="activities/steps", period="1w") entries = data["activities-steps"] yesterday = next( ( entry for entry in entries if pendulum.parse(entry["dateTime"]).day == pendulum.yesterday().day ), None, ) return int(yesterday["value"]) if yesterday else None
def run(args): client_id, client_secret, auth_data = get_fitbit_credentials() if client_id is None: return 1 fitbit_api = Fitbit(client_id=client_id, client_secret=client_secret, access_token=auth_data['access_token'], refresh_token=auth_data['refresh_token'], expires_at=auth_data['expires_at']) try: if args.start_date: try: start_date = dt.datetime.strptime(args.start_date, DATE_FORMAT) except ValueError: log.error("'{}' does not look like a date in format {}".format( args.start_date, DATE_FORMAT)) return 1 end_date = dt.datetime.now() hr_data = fitbit_api.time_series('heart', base_date=start_date, end_date=end_date) else: hr_data = fitbit_api.time_series('heart', period=args.period) except TokenExpiredError as e: log.exception(e) log.error('You need to reauthenticate with fitbit oauth') return 1 except TokenUpdated as e: log.exception(e) log.error('You need to reauthenticate with fitbit oauth') return 1 print(json.dumps(hr_data, indent=4)) return 0
def maybe_sync_data_from_fitbit(): r = db(db.fitbit_user_t.user_email == auth.user.email).select(db.fitbit_user_t.ALL) if len(r) == 0: return False else: if time.time() > r[0].expires_at: oauth = OAuth2Session(client_id) token = oauth.refresh_token( refresh_token_url, refresh_token=r[0].refresh_token, auth=requests.auth.HTTPBasicAuth(client_id, client_secret) ) r = db(db.fitbit_user_t.user_email == auth.user.email).update(access_token = token["access_token"], refresh_token = token["refresh_token"], expires_at = token["expires_at"]) r = db(db.fitbit_user_t.user_email == auth.user.email).select(db.fitbit_user_t.ALL) client = Fitbit(client_id, client_secret, access_token=r[0].access_token, refresh_token=r[0].refresh_token) user = client.user_profile_get() act = client.activity_stats() steps = client.time_series(resource="activities/steps", period="7d") wt_goal = client.body_weight_goal() day = time.gmtime().tm_wday day_list = [0, 1, 2, 3, 4, 5, 6] new_day_list = day_list[day+1:] + day_list[:day+1] row_dict = {} row_dict["user_email"] = auth.user.email week_val = 0 for idx, day in enumerate(new_day_list): step_val = int(steps['activities-steps'][idx]['value']) row_dict["d" + str(day)] = step_val week_val = week_val + step_val row_dict["week_total"] = week_val row_dict["lifetime"] = act["lifetime"]["total"]["steps"] row_dict["last_updated_day"] = day u = db.user_t(db.user_t.user_email == auth.user.email).update(weight=user["user"]["weight"], weight_target=wt_goal["goal"]["weight"]) s = db.steps_t.update_or_insert((db.steps_t.user_email == auth.user.email), **row_dict) return True
class FitbitUser: service = FitbitService def __init__( self, gargling_id: int, first_name: str, access_token: str, refresh_token: str, expires_at: int, service_user_id: None, ): self.gargling_id = gargling_id self.first_name = first_name self.client = FitbitApi( config.fitbit_client_id, config.fitbit_client_secret, access_token=access_token, refresh_token=refresh_token, expires_at=expires_at, refresh_cb=self.service.persist_token, system=FitbitApi.METRIC, ) def _steps_api_call(self, date: pendulum.Date) -> dict: # no test coverage kwargs = { "resource": "activities/steps", "base_date": date, "period": "1d" } exc = None data = None for _ in range(10): try: data = self.client.time_series(**kwargs) break except fitbit.exceptions.HTTPServerError as e: log.info("Error fetching fitbit data. Retrying") exc = e continue if data is None: assert exc is not None raise exc return data def steps(self, date: pendulum.Date) -> t.Optional[int]: data = self._steps_api_call(date) if not data["activities-steps"]: return 0 entry = data["activities-steps"][0] return int(entry["value"]) if entry else 0 def _weight_api_call(self, date: pendulum.Date) -> dict: # no test coverage return self.client.get_bodyweight(base_date=date, period="1w") def _bodyfat_api_call(self, date: pendulum.Date) -> dict: # no test coverage return self.client.get_bodyfat(base_date=date, period="1w") def body(self, date: pendulum.Date) -> dict: def most_recent(entries: list[dict]) -> t.Optional[dict]: if len(entries) == 0: return None for entry in entries: parsed = pendulum.parse(f"{entry['date']}T{entry['time']}") assert isinstance(parsed, pendulum.DateTime) entry["datetime"] = parsed.date() entries.sort(key=itemgetter("datetime"), reverse=True) return entries[0] weight = None elapsed = None fat = None weight_data = self._weight_api_call(date) weight_entry = most_recent(weight_data["weight"]) if weight_entry is not None: if weight_entry["datetime"] == date: weight = weight_entry["weight"] else: elapsed = (date - weight_entry["datetime"]).days fat_data = self._bodyfat_api_call(date) fat_entry = most_recent(fat_data["fat"]) if fat_entry is not None and fat_entry["datetime"] == date: fat = fat_entry["fat"] return { "weight": weight, "elapsed": elapsed, "fat": fat, }