Repository navigation
Unclear how to stay authed for longer than a day #119
Description
Activity
Sorry @stevenirby the decumentation is a bit lacking on this (non-existent). What you need to do is supply an extra kwarg in the
Fitbitconstructor calledrefresh_cb. The value should be a function that accepts one argument: atoken. The value of that argument will be a dictionary with the keys['access_token', 'refresh_token', 'expires_at'].When python fitbit makes a call and discovers the access token is expired, it will automatically refresh the token and pass the new token to the function specified by
refresh_cb. You will need to implement this function to store your new token somewhere persistent. The current fitbit client will automatically use the new token so there is no need to update anything there, but the next time you create a fitbit client, you will need to use the updated token that you saved. Does that make sense?See the function we use in
django-fitbitfor an example: https://github.com/orcasgit/django-fitbit/blob/3e696d45d58dac1a097d1ed7f82339895418705e/fitapp/models.py#L24Reacted by Jeremy GrifskiOh wow, thanks for the quick reply! I figured it was close to that. I think I'm good now.
Do you need some help updating docs or the README?
@stevenirby we would totally appreciate help with the docs!
For those of us with little experience with code, could you possibly write out what lines need to be added and where? It'd be a massive help! thanks!
Hello!
First, thanks for putting together this api and all the help provided a long the way.
I'm still having trouble with refreshing the token and setting uprefresh_cbcorrectly. I've approached setting uprefresh_cbin several ways. Including:- assigning
refresh_cbto an anonymous function. Like the following:
lambda x: None
or
lambda x: {}
I also added more specific fields to the token argument to the refresh_cb constructor. I actually replicated the function shown in: https://github.com/orcasgit/django-fitbit/blob/3e696d45d58dac1a097d1ed7f82339895418705e/fitapp/models.py#L24
which makes a lot of sense! And so I replicated the function:
def refresh_cb(self, token): """ Called when the OAuth token has been refreshed """ self.access_token = token['access_token'] self.refresh_token = token['refresh_token'] self.expires_at = token['expires_at'] self.saveFor background, this is how initialize the fitbit object
authd = fitbit.Fitbit(consumer_key, consumer_secret, access_token=access_token, refresh_token=refresh_token, redirect_uri='http://localhost:8080', expires_at=expires_at, refresh_cb=refresh_cb)But I still get the error. I've tried to add print statements the api.py to trace the error and the error is triggered in the call to
_requestspecifically whereresponse = self.session.request(method, url, **kwargs)From what concerns me here is that code breaks before
token_updaterin_requestis called.
Help would be appreciated!in make request no url in make_request with url in _request printing **kwargs headers -> {'Accept-Language': 'en_US'} data -> {} client_id -> _client_id_ #not shown here for privacy client_secret -> _client_secret_ #not shown here for privacy Traceback (most recent call last): File "simple_request.py", line 104, in <module> sleep_data = authd.get_sleep(date) File "/Users/lpina/Repositories/python-fitbit/fitbit/api.py", line 833, in get_sleep return self.make_request(url) File "/Users/lpina/Repositories/python-fitbit/fitbit/api.py", line 279, in make_request response = self.client.make_request(*args, **kwargs) File "/Users/lpina/Repositories/python-fitbit/fitbit/api.py", line 109, in make_request **kwargs File "/Users/lpina/Repositories/python-fitbit/fitbit/api.py", line 80, in _request response = self.session.request(method, url, **kwargs) File "/Library/Python/2.7/site-packages/requests_oauthlib/oauth2_session.py", line 341, in request self.auto_refresh_url, auth=auth, **kwargs File "/Library/Python/2.7/site-packages/requests_oauthlib/oauth2_session.py", line 309, in refresh_token self.token = self._client.parse_request_body_response(r.text, scope=self.scope) File "/Library/Python/2.7/site-packages/oauthlib/oauth2/rfc6749/clients/base.py", line 409, in parse_request_body_response self.token = parse_token_response(body, scope=scope) File "/Library/Python/2.7/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 376, in parse_token_response validate_token_parameters(params) File "/Library/Python/2.7/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 383, in validate_token_parameters raise_from_error(params.get('error'), params) File "/Library/Python/2.7/site-packages/oauthlib/oauth2/rfc6749/errors.py", line 325, in raise_from_error raise cls(**kwargs) oauthlib.oauth2.rfc6749.errors.InvalidGrantError: (invalid_grant)For background here's my version of the python-fitbit repository:
Reacted by Devasena Inupakutika- assigning
I haven't had time to come back to this, but I too had problems making this work.
The call back function I passed along never fired. So I'm not sure how to make this work.
@stevenirby, thanks for the quick reply. Hopefully others can chime in.
Update! I think it I got it work. Here's how I got mine to work:
def r_cb(token): """ Called when the OAuth token has been refreshed """ access_token = token['access_token'] refresh_token = token['refresh_token'] expires_at = token['expires_at']where the variables on the left ( access_token, refresh_token, expires_at) are local variables that get updated when r_cb is called.
Reacted by Devasena Inupakutika@lrpina That could work, but you need to make sure to save those values somewhere permanent (like a database) and use them the next time you create a
Fitbitobject for the user. Does that make sense?@brad, yes! I was just showing an example for others to view what the callback function looks like. Many of use couldn't figure out how to write the refresh_cb function properly. For example, it was unclear what the token dictionary needed to include.
But yes, thanks for clarification. Callback function (in my case r_cb) needs to save the access_token, refresh_token, expires_at to permanent place, such as a database.
Reacted by Devasena Inupakutika and Greg HilstonThanks for the notes on this issue. I was able to fix a problem this problem home-assistant/core#9183 for Home Assistant.
This feature is incredibly valuable and, with the help of this thread, I'm able to successfully execute the callback. My issue is this: because the callback function can only take one variable, I'm at a loss as to how to store the
tokensdictionary in my database. I have multiple users and need to be able to insert the credentials with the correctuser_id.I am fully aware that my [in]ability do this is directly proportional to my [lack of] skill as a python programmer so I'm not asking for a change in the code (or even the documentation), but for your advice on how you would solve this problem. Thanks!
For example:
def refresh_callback(tokens): """ Called when the OAuth token has been refreshed """ fbm.store_tokens(study_id, tokens) # a db call in my model if __name__ == "__main__": result = [] for a in active_user_tokens: study_id = a['study_id'] authd_client = fitbit.Fitbit(config.CLIENT_ID, config.CLIENT_SECRET, access_token=a['credentials']['access_token'], refresh_token=a['credentials']['refresh_token'], expires_at =a['credentials']['expires_in'], refresh_cb=refresh_callback) result.append(authd_client.sleep()) print(result)
This code gives me the sleep data (and I can print the new tokens inside the
refresh_callbackfunction, but I need access to those variables inside the loop so that I can update the database bya['user_id'].::facepalm::
global variable
code updated to reflect what actually works@stuboo Take a look at how we did it in django-fitbit. The refresh callback is actually a method on the DB model class so it has access to everything it needs:
def refresh_cb(self, token): """ Called when the OAuth token has been refreshed """ self.access_token = token['access_token'] self.refresh_token = token['refresh_token'] self.expires_at = token['expires_at'] self.save()Will something like that work for your case?
Has anyone had any success getting the refresh callback function to fire? My code is as follows
import datetime import fitbit import json def refresh_callback(token): f_data = { 'access_token': token['access_token'], 'refresh_token': token['refresh_token'], 'expires_at': token['expires_at'] } with open('access_token_cache_file.json', 'w') as outfile: json.dump(f_data, outfile) with open('fitbit_config.json') as json_file: data = json.load(json_file) fitbit_user_id = data['fitbit']['user_id'] fitbit_client_id = data['fitbit']['client_id'] with open('access_token_cache_file.json') as json_file: data = json.load(json_file) access_token = data['access_token'] refresh_token = data['refresh_token'] expires_at = data['expires_at'] client = fitbit.Fitbit(fitbit_user_id, fitbit_client_id, access_token=access_token, refresh_token=refresh_token, expires_at=expires_at, refresh_cb=refresh_callback) date = datetime.datetime.today() print(client.activities(date))After my token expires, I get the following exceptions (traceback below):
Traceback (most recent call last): File "/home/pete7863/.local/lib/python3.5/site-packages/requests_oauthlib/oauth2_session.py", line 395, in request http_method=method, body=data, headers=headers) File "/home/pete7863/.local/lib/python3.5/site-packages/oauthlib/oauth2/rfc6749/clients/base.py", line 198, in add_token raise TokenExpiredError() oauthlib.oauth2.rfc6749.errors.TokenExpiredError: (token_expired) During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/home/pete7863/influxdbmylife/fitbit/main.py", line 102, in <module> write_day(date, client, fb.activities(date)) File "/home/pete7863/.local/lib/python3.5/site-packages/fitbit/utils.py", line 38, in _curried return _curried_func(*(args+moreargs), **dict(kwargs, **morekwargs)) File "/home/pete7863/.local/lib/python3.5/site-packages/fitbit/api.py", line 348, in _COLLECTION_RESOURCE return self.make_request(url, data) File "/home/pete7863/.local/lib/python3.5/site-packages/fitbit/api.py", line 256, in make_request response = self.client.make_request(*args, **kwargs) File "/home/pete7863/.local/lib/python3.5/site-packages/fitbit/api.py", line 96, in make_request **kwargs File "/home/pete7863/.local/lib/python3.5/site-packages/fitbit/api.py", line 68, in _request response = self.session.request(method, url, **kwargs) File "/home/pete7863/.local/lib/python3.5/site-packages/requests_oauthlib/oauth2_session.py", line 408, in request self.auto_refresh_url, auth=auth, **kwargs File "/home/pete7863/.local/lib/python3.5/site-packages/requests_oauthlib/oauth2_session.py", line 374, in refresh_token self.token = self._client.parse_request_body_response(r.text, scope=self.scope) File "/home/pete7863/.local/lib/python3.5/site-packages/oauthlib/oauth2/rfc6749/clients/base.py", line 415, in parse_request_body_response self.token = parse_token_response(body, scope=scope) File "/home/pete7863/.local/lib/python3.5/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 425, in parse_token_response validate_token_parameters(params) File "/home/pete7863/.local/lib/python3.5/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 432, in validate_token_parameters raise_from_error(params.get('error'), params) File "/home/pete7863/.local/lib/python3.5/site-packages/oauthlib/oauth2/rfc6749/errors.py", line 405, in raise_from_error raise cls(**kwargs) oauthlib.oauth2.rfc6749.errors.InvalidClientError: (invalid_client)Is there something simple I am missing? I've tried to strip down my code to the barest form. Any help is greatly appreciated!
I think I figured my issue out. For some reason I was initializing with the user_id and the client_id as the first two parameters when initializing the Fitbit client. Once I fixed this issue, my callback started working properly.
My issue with refresh_token is that it is worded as something that is automatic, but it is never called within the code itself. The token_updater also doesn't exist or at least I couldn't figure out how to make it exist through passing some sort of function originally.
So my solution was to modify the source code and call it myself.
`
def refresh_token(self):token = {} token = self.session.refresh_token( self.refresh_token_url, auth=HTTPBasicAuth(self.client_id, self.client_secret) return token`
In my main code I create the fitbit object with stored credentials and try to access data. If that doesn't work then I directly call the refresh_token() function. And if THAT doesn't work then I go through the entire reauthorization process.
I'm not sure how to keep my app authed for longer than one day.
Here is what I'm doing:
gather_keys_oauth2.pyto get tokens.How do I auth the app for much longer time?