def test_too_many_requests(self): """ Tests the 429 response, given in case of exceeding the rate limit """ r = mock.Mock(spec=requests.Response) r.content = b"{'normal': 'resource'}" r.headers = {'Retry-After': '10'} f = Fitbit(**self.client_kwargs) f.client._request = lambda *args, **kwargs: r r.status_code = 429 try: f.user_profile_get() self.assertEqual(True, False) # Won't run if an exception's raised except exceptions.HTTPTooManyRequests: e = sys.exc_info()[1] self.assertEqual(e.retry_after_secs, 10)
def get_profile(self, token): """ Returns a user profile from a token """ fb = Fitbit(self.client_id, self.client_secret, access_token=token['access_token'], refresh_token=token['refresh_token'], expires_at=token['expires_at'], refresh_cb=update_token) profile = fb.user_profile_get() print("got profile") return dict(first_name=profile['user']['firstName'], last_name=profile['user']['lastName'], username=token['user_id'], token=token)
def fitbit_auth(): token = None error = None client_id = '227XNF' client_secret = '1a53508ac0bd0aa5ffa3a9f6de07cb9d' redirect_uri = ('https://%s/project/default/fitbit_auth' % request.env.http_host) code = request.vars.code oauth = FitbitOauth2Client(client_id, client_secret) print code try: token = oauth.fetch_access_token(code, redirect_uri) except MissingTokenError: error = 'Missing access token parameter.</br>Please check that you are using the correct client_secret' except MismatchingStateError: error ='CSRF Warning! Mismatching state' print token client = Fitbit(client_id, client_secret, access_token=token["access_token"], refresh_token=token["refresh_token"]) f_id = db.fitbit_user_t.insert( user_email=auth.user.email, fitbit_user_id=token["user_id"], access_token=token["access_token"], refresh_token=token["refresh_token"], expires_at=token["expires_at"], ) user = client.user_profile_get() goals = client.activities_daily_goal() wt_goal = client.body_weight_goal() print wt_goal u_id = db.user_t.insert( user_email=auth.user.email, dob=user["user"]["dateOfBirth"], sex=user["user"]["gender"], height=user["user"]["height"], image=user["user"]["avatar150"], steps_target=goals["goals"]["steps"], weight_target=wt_goal["goal"]["weight"], weight=user["user"]["weight"], ) redirect(URL('index'))
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
def test_response_ok(self): """ This mocks a pretty normal resource, that the request was authenticated, and data was returned. This test should just run and not raise any exceptions """ r = mock.Mock(spec=requests.Response) r.status_code = 200 r.content = b'{"normal": "resource"}' f = Fitbit(**self.client_kwargs) f.client._request = lambda *args, **kwargs: r f.user_profile_get() r.status_code = 202 f.user_profile_get() r.status_code = 204 f.user_profile_get()
def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Fitbit sensor.""" config_path = hass.config.path(FITBIT_CONFIG_FILE) if os.path.isfile(config_path): config_file = load_json(config_path) if config_file == DEFAULT_CONFIG: request_app_setup( hass, config, add_entities, config_path, discovery_info=None ) return False else: save_json(config_path, DEFAULT_CONFIG) request_app_setup(hass, config, add_entities, config_path, discovery_info=None) return False if "fitbit" in _CONFIGURING: hass.components.configurator.request_done(_CONFIGURING.pop("fitbit")) access_token = config_file.get(ATTR_ACCESS_TOKEN) refresh_token = config_file.get(ATTR_REFRESH_TOKEN) expires_at = config_file.get(ATTR_LAST_SAVED_AT) if None not in (access_token, refresh_token): authd_client = Fitbit( config_file.get(ATTR_CLIENT_ID), config_file.get(ATTR_CLIENT_SECRET), access_token=access_token, refresh_token=refresh_token, expires_at=expires_at, refresh_cb=lambda x: None, ) if int(time.time()) - expires_at > 3600: authd_client.client.refresh_token() unit_system = config.get(CONF_UNIT_SYSTEM) if unit_system == "default": authd_client.system = authd_client.user_profile_get()["user"]["locale"] if authd_client.system != "en_GB": if hass.config.units.is_metric: authd_client.system = "metric" else: authd_client.system = "en_US" else: authd_client.system = unit_system dev = [] registered_devs = authd_client.get_devices() clock_format = config.get(CONF_CLOCK_FORMAT) for resource in config.get(CONF_MONITORED_RESOURCES): # monitor battery for all linked FitBit devices if resource == "devices/battery": for dev_extra in registered_devs: dev.append( FitbitSensor( authd_client, config_path, resource, hass.config.units.is_metric, clock_format, dev_extra, ) ) else: dev.append( FitbitSensor( authd_client, config_path, resource, hass.config.units.is_metric, clock_format, ) ) add_entities(dev, True) else: oauth = FitbitOauth2Client( config_file.get(ATTR_CLIENT_ID), config_file.get(ATTR_CLIENT_SECRET) ) redirect_uri = f"{hass.config.api.base_url}{FITBIT_AUTH_CALLBACK_PATH}" fitbit_auth_start_url, _ = oauth.authorize_token_url( redirect_uri=redirect_uri, scope=[ "activity", "heartrate", "nutrition", "profile", "settings", "sleep", "weight", ], ) hass.http.register_redirect(FITBIT_AUTH_START, fitbit_auth_start_url) hass.http.register_view(FitbitAuthCallbackView(config, add_entities, oauth)) request_oauth_completion(hass)
def setup_platform( hass: HomeAssistant, config: ConfigType, add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the Fitbit sensor.""" config_path = hass.config.path(FITBIT_CONFIG_FILE) if os.path.isfile(config_path): config_file: ConfigType = cast(ConfigType, load_json(config_path)) if config_file == DEFAULT_CONFIG: request_app_setup(hass, config, add_entities, config_path, discovery_info=None) return else: save_json(config_path, DEFAULT_CONFIG) request_app_setup(hass, config, add_entities, config_path, discovery_info=None) return if "fitbit" in _CONFIGURING: configurator.request_done(hass, _CONFIGURING.pop("fitbit")) access_token: str | None = config_file.get(ATTR_ACCESS_TOKEN) refresh_token: str | None = config_file.get(ATTR_REFRESH_TOKEN) expires_at: int | None = config_file.get(ATTR_LAST_SAVED_AT) if (access_token is not None and refresh_token is not None and expires_at is not None): authd_client = Fitbit( config_file.get(CONF_CLIENT_ID), config_file.get(CONF_CLIENT_SECRET), access_token=access_token, refresh_token=refresh_token, expires_at=expires_at, refresh_cb=lambda x: None, ) if int(time.time()) - expires_at > 3600: authd_client.client.refresh_token() if (unit_system := config[CONF_UNIT_SYSTEM]) == "default": authd_client.system = authd_client.user_profile_get( )["user"]["locale"] if authd_client.system != "en_GB": if hass.config.units.is_metric: authd_client.system = "metric" else: authd_client.system = "en_US" else: authd_client.system = unit_system registered_devs = authd_client.get_devices() clock_format = config[CONF_CLOCK_FORMAT] monitored_resources = config[CONF_MONITORED_RESOURCES] entities = [ FitbitSensor( authd_client, config_path, description, hass.config.units.is_metric, clock_format, ) for description in FITBIT_RESOURCES_LIST if description.key in monitored_resources ] if "devices/battery" in monitored_resources: entities.extend([ FitbitSensor( authd_client, config_path, FITBIT_RESOURCE_BATTERY, hass.config.units.is_metric, clock_format, dev_extra, ) for dev_extra in registered_devs ]) add_entities(entities, True)
def run_api_poller(): cfg_path = try_getenv('CONFIG_PATH') db_host = try_getenv('DB_HOST') db_port = try_getenv('DB_PORT') db_user = try_getenv('DB_USER') db_password = try_getenv('DB_PASSWORD') db_name = try_getenv('DB_NAME') redirect_url = try_getenv('CALLBACK_URL') units = try_getenv('UNITS', 'it_IT') # These are required vars, that we first try to load from file client_id = try_load_var(cfg_path, 'client_id') client_secret = try_load_var(cfg_path, 'client_secret') access_token = try_load_var(cfg_path, 'access_token') refresh_token = try_load_var(cfg_path, 'refresh_token') expires_at = try_load_var(cfg_path, 'expires_at') # If any of the required vars is not in file, try to read from env # If read, save if not client_id: client_id = try_getenv('CLIENT_ID') save_var(cfg_path, 'client_id', client_id) if not client_secret: client_secret = try_getenv('CLIENT_SECRET') save_var(cfg_path, 'client_secret', client_secret) if not access_token: access_token = try_getenv('ACCESS_TOKEN') save_var(cfg_path, 'access_token', access_token) if not refresh_token: refresh_token = try_getenv('REFRESH_TOKEN') save_var(cfg_path, 'refresh_token', refresh_token) if not expires_at: expires_at = try_cast_to_int(try_getenv('EXPIRES_AT')) save_var(cfg_path, 'expires_at', str(expires_at)) logger.debug( "client_id: %s, client_secret: %s, access_token: %s, refresh_token: %s, expires_at: %s", client_id, client_secret, access_token, refresh_token, expires_at) if not client_id: logging.critical("client_id missing, aborting!") sys.exit(1) if not client_secret: logging.critical("client_secret missing, aborting!") sys.exit(1) if not access_token: logging.critical("access_token missing, aborting!") sys.exit(1) if not refresh_token: logging.critical("refresh_token missing, aborting!") sys.exit(1) api_client = Fitbit(client_id=client_id, client_secret=client_secret, access_token=access_token, refresh_token=refresh_token, redirect_uri=redirect_url, refresh_cb=partial(write_updated_credentials, cfg_path), system=Fitbit.METRIC) user_profile = None while True: try: user_profile = api_client.user_profile_get() break except Timeout as ex: logger.warning('Request timed out, retrying in 15 seconds...') time.sleep(15) except HTTPServerError as ex: logger.warning( 'Server returned exception (5xx), retrying in 15 seconds (%s)', ex) time.sleep(15) except HTTPTooManyRequests as ex: # 150 API calls done, and python-fitbit doesn't provide the retry-after header, so stop trying # and allow the limit to reset, even if it costs us one hour logger.info('API limit reached, sleeping for 3610 seconds!') time.sleep(3610) except Exception as ex: logger.exception('Got some unexpected exception') raise member_since = user_profile.get('user', {}).get('memberSince', '1970-01-01') member_since_dt = parse(member_since, ignoretz=True) member_since_ts = parse(member_since, ignoretz=True).timestamp() logger.info('User is member since: %s (ts: %s)', member_since, member_since_ts) cur_day = datetime.utcnow() db_client = InfluxDBClient(db_host, db_port, db_user, db_password, db_name) for one_db in db_client.get_list_database(): if one_db['name'] == db_name: break else: db_client.create_database(db_name) db_client.close() # First try to fill any gaps: between User_member_since and first_ts, # and then between last_ts and cur_day while True: for meas, series_list in BASE_SERIES.items(): for series in series_list: resource = '{}/{}'.format(meas, series) if '_' in meas: resource = resource.replace('_', '/', 1) if resource == 'sleep/sleep': # Sleep is special, is its own main category resource = 'sleep' db_client = InfluxDBClient(db_host, db_port, db_user, db_password, db_name) key_series = series if isinstance(series_list, dict) and series_list.get(series): # Datapoints are retrieved with all keys in the same dict, so makes no sense to retrieve individual # series names. Use one series as the key series. key_series = series_list[series]['key_series'] first_ts = get_first_timestamp_for_measurement(db_client, meas, key_series, min_ts=cur_day) last_ts = get_last_timestamp_for_measurement(db_client, meas, key_series, min_ts=cur_day) profile_to_first = int( (first_ts - member_since_dt) / timedelta(days=1)) last_to_current = int((cur_day - last_ts) / timedelta(days=1)) logger.debug( 'key_series: %s, first_ts: %s, last_ts: %s, profile_to_first: %s, last_to_current: %s', key_series, first_ts, last_ts, profile_to_first, last_to_current) db_client.close() intervals_to_fetch = [] if profile_to_first > 1: append_between_day_series(intervals_to_fetch, member_since_dt, first_ts) if last_to_current > 1: append_between_day_series(intervals_to_fetch, last_ts, cur_day) if not intervals_to_fetch: logger.info( 'No gaps to fetch for %s, %s: fetching last day only', meas, series) intervals_to_fetch.append(( cur_day, cur_day, )) # DB can't be open here, because fitbit_fetch_datapoints can hang for a long time if meas == 'sleep': api_client.API_VERSION = '1.2' datapoints = fitbit_fetch_datapoints(api_client, meas, series, resource, intervals_to_fetch) if meas == 'sleep': api_client.API_ENDPOINT = '1' converted_dps = [] for one_d in datapoints: if not one_d: continue if isinstance(series_list, dict) and series_list.get(series): new_dps = series_list[series]['transform'](one_d) for one_dd in new_dps: converted_dps.append( create_api_datapoint_meas_series( one_dd['meas'], one_dd['series'], one_dd['value'], one_dd['dateTime'])) else: converted_dps.append( create_api_datapoint_meas_series( meas, series, one_d.get('value'), one_d.get('dateTime'))) db_client = InfluxDBClient(db_host, db_port, db_user, db_password, db_name) precision = 'h' if meas == 'sleep': precision = 's' logger.debug( 'Going to write %s points, key_series: %s, first_ts: %s, last_ts: %s, profile_to_first: %s, last_to_current: %s', len(converted_dps), key_series, first_ts, last_ts, profile_to_first, last_to_current) logger.debug('First 3: %s', converted_dps[:3]) logger.debug('Last 3: %s', converted_dps[-3:]) if not db_client.write_points(converted_dps, time_precision=precision, batch_size=2500): logger.critical( 'key_series: %s, first_ts: %s, last_ts: %s, profile_to_first: %s, last_to_current: %s', key_series, first_ts, last_ts, profile_to_first, last_to_current) raise Exception('Unable to write points!') db_client.close() logger.info('All series processed, sleeping for 4h') time.sleep(3610 * 4) sys.exit(0)
def index(): if 'fitbit_access_token' not in session: return redirect(url_for('login')) client = Fitbit(CONSUMER_KEY, CONSUMER_SECRET, resource_owner_key=session['fitbit_access_token']['oauth_token'], resource_owner_secret=session['fitbit_access_token']['oauth_token_secret']) return str(client.user_profile_get())