mirror of
https://github.com/spotipy-dev/spotipy.git
synced 2026-06-19 09:13:53 +00:00
Made cache_path and username optional (#567)
* Made cache_path and username optional * Update readme and example * Lint * Lint * Feedback / add warning
This commit is contained in:
parent
d448d33704
commit
d0ca977647
@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- `SpotifyImplicitGrant` warns of security considerations and recommends
|
- `SpotifyImplicitGrant` warns of security considerations and recommends
|
||||||
`SpotifyPKCE`
|
`SpotifyPKCE`
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Specifying a cache_path or username is now optional
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Using `SpotifyPKCE.get_authorization_url` will now generate a code
|
- Using `SpotifyPKCE.get_authorization_url` will now generate a code
|
||||||
|
|||||||
@ -50,7 +50,6 @@ from spotipy.oauth2 import SpotifyOAuth
|
|||||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(client_id="YOUR_APP_CLIENT_ID",
|
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(client_id="YOUR_APP_CLIENT_ID",
|
||||||
client_secret="YOUR_APP_CLIENT_SECRET",
|
client_secret="YOUR_APP_CLIENT_SECRET",
|
||||||
redirect_uri="YOUR_APP_REDIRECT_URI",
|
redirect_uri="YOUR_APP_REDIRECT_URI",
|
||||||
username="YOUR_SPOTIFY_USERNAME",
|
|
||||||
scope="user-library-read"))
|
scope="user-library-read"))
|
||||||
|
|
||||||
results = sp.current_user_saved_tracks()
|
results = sp.current_user_saved_tracks()
|
||||||
|
|||||||
@ -1,16 +1,8 @@
|
|||||||
# Shows the top tracks for a user
|
# Shows the top tracks for a user
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import spotipy
|
import spotipy
|
||||||
from spotipy.oauth2 import SpotifyOAuth
|
from spotipy.oauth2 import SpotifyOAuth
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
username = sys.argv[1]
|
|
||||||
else:
|
|
||||||
print("Usage: %s username" % (sys.argv[0],))
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
scope = 'user-top-read'
|
scope = 'user-top-read'
|
||||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
|
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,7 @@ if len(sys.argv) > 2:
|
|||||||
track_ids.append({"uri": tid, "positions": [int(pos)]})
|
track_ids.append({"uri": tid, "positions": [int(pos)]})
|
||||||
else:
|
else:
|
||||||
print(
|
print(
|
||||||
"Usage: %s username playlist_id track_id,pos track_id,pos ..." %
|
"Usage: %s playlist_id track_id,pos track_id,pos ..." %
|
||||||
(sys.argv[0],))
|
(sys.argv[0],))
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ if len(sys.argv) > 3:
|
|||||||
playlist_id = sys.argv[1]
|
playlist_id = sys.argv[1]
|
||||||
track_ids = sys.argv[2:]
|
track_ids = sys.argv[2:]
|
||||||
else:
|
else:
|
||||||
print("Usage: %s username playlist_id track_id ..." % (sys.argv[0],))
|
print("Usage: %s playlist_id track_id ..." % (sys.argv[0],))
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
scope = 'playlist-modify-public'
|
scope = 'playlist-modify-public'
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
|
# Shows artist info for a URN or URL
|
||||||
# shows artist info for a URN or URL
|
|
||||||
|
|
||||||
from spotipy.oauth2 import SpotifyClientCredentials
|
from spotipy.oauth2 import SpotifyClientCredentials
|
||||||
import spotipy
|
import spotipy
|
||||||
|
|||||||
@ -78,6 +78,17 @@ def _ensure_value(value, env_key):
|
|||||||
return _val
|
return _val
|
||||||
|
|
||||||
|
|
||||||
|
def _get_cache_path(cache_path, username):
|
||||||
|
if cache_path:
|
||||||
|
return cache_path
|
||||||
|
|
||||||
|
cache_path = ".cache"
|
||||||
|
if username:
|
||||||
|
cache_path += "-" + str(username)
|
||||||
|
|
||||||
|
return cache_path
|
||||||
|
|
||||||
|
|
||||||
class SpotifyAuthBase(object):
|
class SpotifyAuthBase(object):
|
||||||
def __init__(self, requests_session):
|
def __init__(self, requests_session):
|
||||||
if isinstance(requests_session, requests.Session):
|
if isinstance(requests_session, requests.Session):
|
||||||
@ -219,7 +230,6 @@ class SpotifyOAuth(SpotifyAuthBase):
|
|||||||
"""
|
"""
|
||||||
Implements Authorization Code Flow for Spotify's OAuth implementation.
|
Implements Authorization Code Flow for Spotify's OAuth implementation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
OAUTH_AUTHORIZE_URL = "https://accounts.spotify.com/authorize"
|
OAUTH_AUTHORIZE_URL = "https://accounts.spotify.com/authorize"
|
||||||
OAUTH_TOKEN_URL = "https://accounts.spotify.com/api/token"
|
OAUTH_TOKEN_URL = "https://accounts.spotify.com/api/token"
|
||||||
|
|
||||||
@ -241,15 +251,19 @@ class SpotifyOAuth(SpotifyAuthBase):
|
|||||||
Creates a SpotifyOAuth object
|
Creates a SpotifyOAuth object
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- client_id - the client id of your app
|
* client_id: Must be supplied or set as environment variable
|
||||||
- client_secret - the client secret of your app
|
* client_secret: Must be supplied or set as environment variable
|
||||||
- redirect_uri - the redirect URI of your app
|
* redirect_uri: Must be supplied or set as environment variable
|
||||||
- state - security state
|
* state: May be supplied, no verification is performed
|
||||||
- scope - the desired scope of the request
|
* scope: May be supplied, intuitively converted to proper format
|
||||||
- cache_path - path to location to save tokens
|
* cache_path: May be supplied, will otherwise be generated
|
||||||
- requests_timeout - tell Requests to stop waiting for a response
|
(takes precedence over `username`)
|
||||||
after a given number of seconds
|
* username: May be supplied or set as environment variable
|
||||||
- username - username of current client
|
(will set `cache_path` to `.cache-{username}`)
|
||||||
|
* proxies: Proxy for the requests library to route through
|
||||||
|
* show_dialog: Interpreted as boolean
|
||||||
|
* requests_timeout: Tell Requests to stop waiting for a response after a given number
|
||||||
|
of seconds
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super(SpotifyOAuth, self).__init__(requests_session)
|
super(SpotifyOAuth, self).__init__(requests_session)
|
||||||
@ -258,10 +272,10 @@ class SpotifyOAuth(SpotifyAuthBase):
|
|||||||
self.client_secret = client_secret
|
self.client_secret = client_secret
|
||||||
self.redirect_uri = redirect_uri
|
self.redirect_uri = redirect_uri
|
||||||
self.state = state
|
self.state = state
|
||||||
self.cache_path = cache_path
|
|
||||||
self.username = username or os.getenv(
|
self.username = username or os.getenv(
|
||||||
CLIENT_CREDS_ENV_VARS["client_username"]
|
CLIENT_CREDS_ENV_VARS["client_username"]
|
||||||
)
|
)
|
||||||
|
self.cache_path = _get_cache_path(cache_path, self.username)
|
||||||
self.scope = self._normalize_scope(scope)
|
self.scope = self._normalize_scope(scope)
|
||||||
self.proxies = proxies
|
self.proxies = proxies
|
||||||
self.requests_timeout = requests_timeout
|
self.requests_timeout = requests_timeout
|
||||||
@ -272,14 +286,6 @@ class SpotifyOAuth(SpotifyAuthBase):
|
|||||||
"""
|
"""
|
||||||
token_info = None
|
token_info = None
|
||||||
|
|
||||||
if not self.cache_path and self.username:
|
|
||||||
self.cache_path = ".cache-" + str(self.username)
|
|
||||||
elif not self.cache_path and not self.username:
|
|
||||||
raise SpotifyOauthError(
|
|
||||||
"You must either set a cache_path or a username."
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.cache_path:
|
|
||||||
try:
|
try:
|
||||||
f = open(self.cache_path)
|
f = open(self.cache_path)
|
||||||
token_info_string = f.read()
|
token_info_string = f.read()
|
||||||
@ -298,7 +304,8 @@ class SpotifyOAuth(SpotifyAuthBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
logger.warning("Couldn't read cache at: %s", self.cache_path)
|
||||||
|
|
||||||
return token_info
|
return token_info
|
||||||
|
|
||||||
def _save_token_info(self, token_info):
|
def _save_token_info(self, token_info):
|
||||||
@ -582,25 +589,30 @@ class SpotifyPKCE(SpotifyAuthBase):
|
|||||||
Creates Auth Manager with the PKCE Auth flow.
|
Creates Auth Manager with the PKCE Auth flow.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- client_id - the client id of your app
|
* client_id: Must be supplied or set as environment variable
|
||||||
- redirect_uri - the redirect URI of your app
|
* client_secret: Must be supplied or set as environment variable
|
||||||
- state - security state
|
* redirect_uri: Must be supplied or set as environment variable
|
||||||
- scope - the desired scope of the request
|
* state: May be supplied, no verification is performed
|
||||||
- cache_path - path to location to save tokens
|
* scope: May be supplied, intuitively converted to proper format
|
||||||
- username - username of current client
|
* cache_path: May be supplied, will otherwise be generated
|
||||||
- proxies - proxy for the requests library to route through
|
(takes precedence over `username`)
|
||||||
- requests_timeout - tell Requests to stop waiting for a response
|
* username: May be supplied or set as environment variable
|
||||||
after a given number of seconds
|
(will set `cache_path` to `.cache-{username}`)
|
||||||
|
* show_dialog: Interpreted as boolean
|
||||||
|
* proxies: Proxy for the requests library to route through
|
||||||
|
* requests_timeout: Tell Requests to stop waiting for a response after a given number
|
||||||
|
of seconds
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super(SpotifyPKCE, self).__init__(requests_session)
|
super(SpotifyPKCE, self).__init__(requests_session)
|
||||||
self.client_id = client_id
|
self.client_id = client_id
|
||||||
self.redirect_uri = redirect_uri
|
self.redirect_uri = redirect_uri
|
||||||
self.state = state
|
self.state = state
|
||||||
self.scope = self._normalize_scope(scope)
|
self.scope = self._normalize_scope(scope)
|
||||||
self.cache_path = cache_path
|
|
||||||
self.username = username or os.getenv(
|
self.username = username or os.getenv(
|
||||||
CLIENT_CREDS_ENV_VARS["client_username"]
|
CLIENT_CREDS_ENV_VARS["client_username"]
|
||||||
)
|
)
|
||||||
|
self.cache_path = _get_cache_path(cache_path, self.username)
|
||||||
self.proxies = proxies
|
self.proxies = proxies
|
||||||
self.requests_timeout = requests_timeout
|
self.requests_timeout = requests_timeout
|
||||||
|
|
||||||
@ -617,10 +629,10 @@ class SpotifyPKCE(SpotifyAuthBase):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def _get_code_verifier(self):
|
def _get_code_verifier(self):
|
||||||
''' Spotify PCKE code verifier - See step 1 of the reference guide below
|
""" Spotify PCKE code verifier - See step 1 of the reference guide below
|
||||||
Reference:
|
Reference:
|
||||||
https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow-with-proof-key-for-code-exchange-pkce
|
https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow-with-proof-key-for-code-exchange-pkce
|
||||||
'''
|
"""
|
||||||
# Range (33,96) is used to select between 44-128 base64 characters for the
|
# Range (33,96) is used to select between 44-128 base64 characters for the
|
||||||
# next operation. The range looks weird because base64 is 6 bytes
|
# next operation. The range looks weird because base64 is 6 bytes
|
||||||
import random
|
import random
|
||||||
@ -638,10 +650,10 @@ class SpotifyPKCE(SpotifyAuthBase):
|
|||||||
return verifier
|
return verifier
|
||||||
|
|
||||||
def _get_code_challenge(self):
|
def _get_code_challenge(self):
|
||||||
''' Spotify PCKE code challenge - See step 1 of the reference guide below
|
""" Spotify PCKE code challenge - See step 1 of the reference guide below
|
||||||
Reference:
|
Reference:
|
||||||
https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow-with-proof-key-for-code-exchange-pkce
|
https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow-with-proof-key-for-code-exchange-pkce
|
||||||
'''
|
"""
|
||||||
import hashlib
|
import hashlib
|
||||||
import base64
|
import base64
|
||||||
code_challenge_digest = hashlib.sha256(self.code_verifier.encode('utf-8')).digest()
|
code_challenge_digest = hashlib.sha256(self.code_verifier.encode('utf-8')).digest()
|
||||||
@ -744,14 +756,6 @@ class SpotifyPKCE(SpotifyAuthBase):
|
|||||||
"""
|
"""
|
||||||
token_info = None
|
token_info = None
|
||||||
|
|
||||||
if not self.cache_path and self.username:
|
|
||||||
self.cache_path = ".cache-" + str(self.username)
|
|
||||||
elif not self.cache_path and not self.username:
|
|
||||||
raise SpotifyOauthError(
|
|
||||||
"You must either set a cache_path or a username."
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.cache_path:
|
|
||||||
try:
|
try:
|
||||||
f = open(self.cache_path)
|
f = open(self.cache_path)
|
||||||
token_info_string = f.read()
|
token_info_string = f.read()
|
||||||
@ -770,7 +774,8 @@ class SpotifyPKCE(SpotifyAuthBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
logger.warning("Couldn't read cache at: %s", self.cache_path)
|
||||||
|
|
||||||
return token_info
|
return token_info
|
||||||
|
|
||||||
def _is_scope_subset(self, needle_scope, haystack_scope):
|
def _is_scope_subset(self, needle_scope, haystack_scope):
|
||||||
@ -971,7 +976,9 @@ class SpotifyImplicitGrant(SpotifyAuthBase):
|
|||||||
* state: May be supplied, no verification is performed
|
* state: May be supplied, no verification is performed
|
||||||
* scope: May be supplied, intuitively converted to proper format
|
* scope: May be supplied, intuitively converted to proper format
|
||||||
* cache_path: May be supplied, will otherwise be generated
|
* cache_path: May be supplied, will otherwise be generated
|
||||||
* username: Must be supplied or set as environment variable
|
(takes precedence over `username`)
|
||||||
|
* username: May be supplied or set as environment variable
|
||||||
|
(will set `cache_path` to `.cache-{username}`)
|
||||||
* show_dialog: Interpreted as boolean
|
* show_dialog: Interpreted as boolean
|
||||||
"""
|
"""
|
||||||
logger.warning("The OAuth standard no longer recommends the Implicit "
|
logger.warning("The OAuth standard no longer recommends the Implicit "
|
||||||
@ -983,10 +990,10 @@ class SpotifyImplicitGrant(SpotifyAuthBase):
|
|||||||
self.client_id = client_id
|
self.client_id = client_id
|
||||||
self.redirect_uri = redirect_uri
|
self.redirect_uri = redirect_uri
|
||||||
self.state = state
|
self.state = state
|
||||||
self.cache_path = cache_path
|
|
||||||
self.username = username or os.getenv(
|
self.username = username or os.getenv(
|
||||||
CLIENT_CREDS_ENV_VARS["client_username"]
|
CLIENT_CREDS_ENV_VARS["client_username"]
|
||||||
)
|
)
|
||||||
|
self.cache_path = _get_cache_path(cache_path, self.username)
|
||||||
self.scope = self._normalize_scope(scope)
|
self.scope = self._normalize_scope(scope)
|
||||||
self.show_dialog = show_dialog
|
self.show_dialog = show_dialog
|
||||||
self._session = None # As to not break inherited __del__
|
self._session = None # As to not break inherited __del__
|
||||||
@ -996,14 +1003,6 @@ class SpotifyImplicitGrant(SpotifyAuthBase):
|
|||||||
"""
|
"""
|
||||||
token_info = None
|
token_info = None
|
||||||
|
|
||||||
if not self.cache_path and self.username:
|
|
||||||
self.cache_path = ".cache-" + str(self.username)
|
|
||||||
elif not self.cache_path and not self.username:
|
|
||||||
raise SpotifyOauthError(
|
|
||||||
"You must either set a cache_path or a username."
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.cache_path:
|
|
||||||
try:
|
try:
|
||||||
f = open(self.cache_path)
|
f = open(self.cache_path)
|
||||||
token_info_string = f.read()
|
token_info_string = f.read()
|
||||||
@ -1020,20 +1019,17 @@ class SpotifyImplicitGrant(SpotifyAuthBase):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
logger.warning("Couldn't read cache at: %s", self.cache_path)
|
||||||
|
|
||||||
return token_info
|
return token_info
|
||||||
|
|
||||||
def _save_token_info(self, token_info):
|
def _save_token_info(self, token_info):
|
||||||
if not self.cache_path and self.username:
|
|
||||||
self.cache_path = ".cache-" + str(self.username)
|
|
||||||
if self.cache_path:
|
|
||||||
try:
|
try:
|
||||||
f = open(self.cache_path, "w")
|
f = open(self.cache_path, "w")
|
||||||
f.write(json.dumps(token_info))
|
f.write(json.dumps(token_info))
|
||||||
f.close()
|
f.close()
|
||||||
except IOError:
|
except IOError:
|
||||||
logger.warning('Couldn\'t write token to cache at: %s',
|
logger.warning("Couldn't write token to cache at: %s", self.cache_path)
|
||||||
self.cache_path)
|
|
||||||
|
|
||||||
def _is_scope_subset(self, needle_scope, haystack_scope):
|
def _is_scope_subset(self, needle_scope, haystack_scope):
|
||||||
needle_scope = set(needle_scope.split()) if needle_scope else set()
|
needle_scope = set(needle_scope.split()) if needle_scope else set()
|
||||||
|
|||||||
@ -21,7 +21,7 @@ CLIENT_CREDS_ENV_VARS = {
|
|||||||
|
|
||||||
|
|
||||||
def prompt_for_user_token(
|
def prompt_for_user_token(
|
||||||
username,
|
username=None,
|
||||||
scope=None,
|
scope=None,
|
||||||
client_id=None,
|
client_id=None,
|
||||||
client_secret=None,
|
client_secret=None,
|
||||||
@ -43,14 +43,14 @@ def prompt_for_user_token(
|
|||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
|
|
||||||
- username - the Spotify username
|
- username - the Spotify username (optional)
|
||||||
- scope - the desired scope of the request
|
- scope - the desired scope of the request (optional)
|
||||||
- client_id - the client id of your app
|
- client_id - the client id of your app (required)
|
||||||
- client_secret - the client secret of your app
|
- client_secret - the client secret of your app (required)
|
||||||
- redirect_uri - the redirect URI of your app
|
- redirect_uri - the redirect URI of your app (required)
|
||||||
- cache_path - path to location to save tokens
|
- cache_path - path to location to save tokens (optional)
|
||||||
- oauth_manager - Oauth manager object.
|
- oauth_manager - Oauth manager object (optional)
|
||||||
- show_dialog - If true, a login prompt always shows
|
- show_dialog - If true, a login prompt always shows (optional, defaults to False)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not oauth_manager:
|
if not oauth_manager:
|
||||||
@ -79,14 +79,13 @@ def prompt_for_user_token(
|
|||||||
)
|
)
|
||||||
raise spotipy.SpotifyException(550, -1, "no credentials set")
|
raise spotipy.SpotifyException(550, -1, "no credentials set")
|
||||||
|
|
||||||
cache_path = cache_path or ".cache-" + username
|
|
||||||
|
|
||||||
sp_oauth = oauth_manager or spotipy.SpotifyOAuth(
|
sp_oauth = oauth_manager or spotipy.SpotifyOAuth(
|
||||||
client_id,
|
client_id,
|
||||||
client_secret,
|
client_secret,
|
||||||
redirect_uri,
|
redirect_uri,
|
||||||
scope=scope,
|
scope=scope,
|
||||||
cache_path=cache_path,
|
cache_path=cache_path,
|
||||||
|
username=username,
|
||||||
show_dialog=show_dialog
|
show_dialog=show_dialog
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user