Automatic refresh of Authorization Code Flow Tokens in long-running Applications (#428)

* auto-refresh user token

* example for a long-running user-request app

* wrap long lines

* combine duplicate code into _refresh_token_if_expired method

* add changelog entry
This commit is contained in:
Peter 2020-01-22 21:01:30 +01:00 committed by Stéphane Bruckert
parent 9df45e858e
commit 87a72a060f
5 changed files with 68 additions and 16 deletions

View File

@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
- Auto-Refresh token in long-running apps when using Authorization Code Flow
## [2.7.1] - 2020-01-20
### Changed

42
examples/long_running.py Normal file
View File

@ -0,0 +1,42 @@
# long running example (need to be authenticated via oauth)
import sys
import time
from datetime import datetime
import spotipy
import spotipy.util as util
if len(sys.argv) > 1:
username = sys.argv[1]
else:
print("Whoops, need your username!")
print("usage: python user_playlists.py [username]")
sys.exit()
SCOPE = ','.join([
'user-modify-playback-state',
'user-read-playback-state',
'user-read-currently-playing'
])
oauth = util.prompt_for_user_token(
username,
redirect_uri="http://localhost/",
scope=SCOPE)
if oauth:
sp = spotipy.Spotify(auth=oauth)
# sp.trace = True
# sp.trace_out = True
start = datetime.now()
while True:
current_playback = sp.current_playback()
print(datetime.now() - start,
"is_playing?",
None if current_playback is None
else current_playback['is_playing'])
time.sleep(60)
else:
print("Can't get token for", username)

View File

@ -63,7 +63,7 @@ class Spotify(object):
"""
Creates a Spotify API client.
:param auth: An authorization token (optional)
:param auth: An authenticated OAuth2 Instance
:param requests_session:
A Requests session object or a truthy value to create one.
A falsy value disables sessions.
@ -94,7 +94,9 @@ class Spotify(object):
def _auth_headers(self):
if self._auth:
return {'Authorization': 'Bearer {0}'.format(self._auth)}
token_info = self._auth.get_cached_token()
access_token = token_info['access_token']
return {'Authorization': 'Bearer {0}'.format(access_token)}
elif self.client_credentials_manager:
token = self.client_credentials_manager.get_access_token()
return {'Authorization': 'Bearer {0}'.format(token)}

View File

@ -16,7 +16,6 @@ import sys
import time
import requests
# Workaround to support both python 2 & 3
import six
import six.moves.urllib.parse as urllibparse
@ -135,32 +134,41 @@ class SpotifyOAuth(object):
self.cache_path = cache_path
self.scope = self._normalize_scope(scope)
self.proxies = proxies
self.token_info = None
def get_cached_token(self):
''' Gets a cached auth token
'''
token_info = None
if self.token_info is not None:
self._refresh_token_if_expired()
return self.token_info
if self.cache_path:
try:
f = open(self.cache_path)
token_info_string = f.read()
f.close()
token_info = json.loads(token_info_string)
self.token_info = json.loads(token_info_string)
# if scopes don't match, then bail
if 'scope' not in token_info or not self._is_scope_subset(
self.scope, token_info['scope']):
if 'scope' not in self.token_info or not self._is_scope_subset(
self.scope, self.token_info['scope']):
return None
if self.is_token_expired(token_info):
token_info = self.refresh_access_token(
token_info['refresh_token'])
self._refresh_token_if_expired()
except IOError:
pass
return token_info
return self.token_info
def _refresh_token_if_expired(self):
if self.is_token_expired(self.token_info):
self.token_info = self.refresh_access_token(
self.token_info['refresh_token'])
def _save_token_info(self, token_info):
self.token_info = token_info
if self.cache_path:
try:
f = open(self.cache_path, 'w')

View File

@ -103,9 +103,7 @@ def prompt_for_user_token(username, scope=None, client_id=None,
print()
code = sp_oauth.parse_response_code(response)
token_info = sp_oauth.get_access_token(code)
sp_oauth.get_access_token(code)
# Auth'ed API request
if token_info:
return token_info['access_token']
else:
return None
return sp_oauth