mirror of
https://github.com/spotipy-dev/spotipy.git
synced 2026-06-19 09:13:53 +00:00
Renamed the auth parameter of Spotify.__init__ to access_token for better clarity.
Removed the `client_credentials_manager` and `oauth_manager` parameters because they are redundant. Replaced the `set_auth` and `auth_manager` properties with standard attributes. Removed the following deprecated methods from `Spotify`: * `playlist_tracks` * `user_playlist` * `user_playlist_tracks` * `user_playlist_change_details` * `user_playlist_unfollow` * `user_playlist_add_tracks` * `user_playlist_replace_tracks` * `user_playlist_reorder_tracks` * `user_playlist_remove_all_occurrences_of_tracks` * `user_playlist_remove_specific_occurrences_of_tracks` * `user_playlist_follow_playlist` * `user_playlist_is_following` Removed the deprecated `as_dict` parameter from the `get_access_token` method of `SpotifyOAuth` and `SpotifyPKCE`. Removed the deprecated `get_cached_token` and `_save_token_info` methods of `SpotifyOAuth` and `SpotifyPKCE`. Removed `SpotifyImplicitGrant`. Removed `prompt_for_user_token`.
This commit is contained in:
parent
68ee2d6de8
commit
c8e045891b
@ -7,7 +7,7 @@ import spotipy
|
|||||||
logger = logging.getLogger('examples.artist_albums')
|
logger = logging.getLogger('examples.artist_albums')
|
||||||
logging.basicConfig(level='INFO')
|
logging.basicConfig(level='INFO')
|
||||||
|
|
||||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
sp = spotipy.Spotify(auth_manager=SpotifyClientCredentials())
|
||||||
|
|
||||||
|
|
||||||
def get_args():
|
def get_args():
|
||||||
|
|||||||
@ -69,6 +69,6 @@ def main():
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
client_credentials_manager = SpotifyClientCredentials()
|
auth_manager = SpotifyClientCredentials()
|
||||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||||
main()
|
main()
|
||||||
|
|||||||
@ -8,8 +8,8 @@ from spotipy.oauth2 import SpotifyClientCredentials
|
|||||||
logger = logging.getLogger('examples.artist_recommendations')
|
logger = logging.getLogger('examples.artist_recommendations')
|
||||||
logging.basicConfig(level='INFO')
|
logging.basicConfig(level='INFO')
|
||||||
|
|
||||||
client_credentials_manager = SpotifyClientCredentials()
|
auth_manager = SpotifyClientCredentials()
|
||||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||||
|
|
||||||
|
|
||||||
def get_args():
|
def get_args():
|
||||||
|
|||||||
@ -7,8 +7,8 @@ import time
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
client_credentials_manager = SpotifyClientCredentials()
|
auth_manager = SpotifyClientCredentials()
|
||||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
if len(sys.argv) > 1:
|
||||||
tid = sys.argv[1]
|
tid = sys.argv[1]
|
||||||
|
|||||||
@ -7,8 +7,8 @@ import time
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
client_credentials_manager = SpotifyClientCredentials()
|
auth_manager = SpotifyClientCredentials()
|
||||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||||
sp.trace = False
|
sp.trace = False
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
if len(sys.argv) > 1:
|
||||||
|
|||||||
@ -7,8 +7,8 @@ import time
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
client_credentials_manager = SpotifyClientCredentials()
|
auth_manager = SpotifyClientCredentials()
|
||||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||||
sp.trace = True
|
sp.trace = True
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
if len(sys.argv) > 1:
|
||||||
|
|||||||
@ -2,8 +2,8 @@ from spotipy.oauth2 import SpotifyClientCredentials
|
|||||||
import spotipy
|
import spotipy
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
|
||||||
client_credentials_manager = SpotifyClientCredentials()
|
auth_manager = SpotifyClientCredentials()
|
||||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||||
|
|
||||||
search_str = 'Muse'
|
search_str = 'Muse'
|
||||||
result = sp.search(search_str)
|
result = sp.search(search_str)
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
import spotipy
|
|
||||||
import spotipy.util as util
|
|
||||||
|
|
||||||
from pprint import pprint
|
|
||||||
|
|
||||||
while True:
|
|
||||||
username = input("Type the Spotify user ID to use: ")
|
|
||||||
token = util.prompt_for_user_token(username, show_dialog=True)
|
|
||||||
sp = spotipy.Spotify(token)
|
|
||||||
pprint(sp.me())
|
|
||||||
@ -4,7 +4,7 @@ from pprint import pprint
|
|||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
scope = "user-read-playback-state,user-modify-playback-state"
|
scope = "user-read-playback-state,user-modify-playback-state"
|
||||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyOAuth(scope=scope))
|
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
|
||||||
|
|
||||||
# Shows playing devices
|
# Shows playing devices
|
||||||
res = sp.devices()
|
res = sp.devices()
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import spotipy
|
|||||||
PlaylistExample = '37i9dQZEVXbMDoHDwVN2tF'
|
PlaylistExample = '37i9dQZEVXbMDoHDwVN2tF'
|
||||||
|
|
||||||
# create spotipy client
|
# create spotipy client
|
||||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
sp = spotipy.Spotify(auth_manager=SpotifyClientCredentials())
|
||||||
|
|
||||||
# load the first 100 songs
|
# load the first 100 songs
|
||||||
tracks = []
|
tracks = []
|
||||||
|
|||||||
@ -2,7 +2,7 @@ from spotipy.oauth2 import SpotifyClientCredentials
|
|||||||
import spotipy
|
import spotipy
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
|
||||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
sp = spotipy.Spotify(auth_manager=SpotifyClientCredentials())
|
||||||
|
|
||||||
pl_id = 'spotify:playlist:5RIbzhG2QqdkaP24iXLnZX'
|
pl_id = 'spotify:playlist:5RIbzhG2QqdkaP24iXLnZX'
|
||||||
offset = 0
|
offset = 0
|
||||||
|
|||||||
@ -2,8 +2,8 @@ from spotipy.oauth2 import SpotifyClientCredentials
|
|||||||
import spotipy
|
import spotipy
|
||||||
import json
|
import json
|
||||||
|
|
||||||
client_credentials_manager = SpotifyClientCredentials()
|
auth_manager = SpotifyClientCredentials()
|
||||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||||
|
|
||||||
playlist_id = 'spotify:user:spotifycharts:playlist:37i9dQZEVXbJiZcmkrIHGU'
|
playlist_id = 'spotify:user:spotifycharts:playlist:37i9dQZEVXbJiZcmkrIHGU'
|
||||||
results = sp.playlist(playlist_id)
|
results = sp.playlist(playlist_id)
|
||||||
|
|||||||
@ -10,6 +10,6 @@ if len(sys.argv) > 1:
|
|||||||
else:
|
else:
|
||||||
search_str = 'Radiohead'
|
search_str = 'Radiohead'
|
||||||
|
|
||||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
sp = spotipy.Spotify(auth_manager=SpotifyClientCredentials())
|
||||||
result = sp.search(search_str)
|
result = sp.search(search_str)
|
||||||
pprint.pprint(result)
|
pprint.pprint(result)
|
||||||
|
|||||||
@ -10,6 +10,6 @@ if len(sys.argv) > 1:
|
|||||||
else:
|
else:
|
||||||
urn = 'spotify:album:5yTx83u3qerZF7GRJu7eFk'
|
urn = 'spotify:album:5yTx83u3qerZF7GRJu7eFk'
|
||||||
|
|
||||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
sp = spotipy.Spotify(auth_manager=SpotifyClientCredentials())
|
||||||
album = sp.album(urn)
|
album = sp.album(urn)
|
||||||
pprint(album)
|
pprint(album)
|
||||||
|
|||||||
@ -10,7 +10,7 @@ if len(sys.argv) > 1:
|
|||||||
else:
|
else:
|
||||||
urn = 'spotify:artist:3jOstUTkEu2JkjvRdBA5Gu'
|
urn = 'spotify:artist:3jOstUTkEu2JkjvRdBA5Gu'
|
||||||
|
|
||||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
sp = spotipy.Spotify(auth_manager=SpotifyClientCredentials())
|
||||||
|
|
||||||
artist = sp.artist(urn)
|
artist = sp.artist(urn)
|
||||||
pprint(artist)
|
pprint(artist)
|
||||||
|
|||||||
@ -10,7 +10,7 @@ if len(sys.argv) > 1:
|
|||||||
else:
|
else:
|
||||||
urn = 'spotify:artist:3jOstUTkEu2JkjvRdBA5Gu'
|
urn = 'spotify:artist:3jOstUTkEu2JkjvRdBA5Gu'
|
||||||
|
|
||||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
sp = spotipy.Spotify(auth_manager=SpotifyClientCredentials())
|
||||||
response = sp.artist_top_tracks(urn)
|
response = sp.artist_top_tracks(urn)
|
||||||
|
|
||||||
for track in response['tracks']:
|
for track in response['tracks']:
|
||||||
|
|||||||
@ -9,8 +9,8 @@ if len(sys.argv) > 1:
|
|||||||
else:
|
else:
|
||||||
artist_name = 'weezer'
|
artist_name = 'weezer'
|
||||||
|
|
||||||
client_credentials_manager = SpotifyClientCredentials()
|
auth_manager = SpotifyClientCredentials()
|
||||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||||
result = sp.search(q='artist:' + artist_name, type='artist')
|
result = sp.search(q='artist:' + artist_name, type='artist')
|
||||||
try:
|
try:
|
||||||
name = result['artists']['items'][0]['name']
|
name = result['artists']['items'][0]['name']
|
||||||
|
|||||||
@ -10,7 +10,7 @@ if len(sys.argv) > 1:
|
|||||||
else:
|
else:
|
||||||
urn = 'spotify:track:0Svkvt5I79wficMFgaqEQJ'
|
urn = 'spotify:track:0Svkvt5I79wficMFgaqEQJ'
|
||||||
|
|
||||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
sp = spotipy.Spotify(auth_manager=SpotifyClientCredentials())
|
||||||
|
|
||||||
track = sp.track(urn)
|
track = sp.track(urn)
|
||||||
pprint(track)
|
pprint(track)
|
||||||
|
|||||||
@ -15,8 +15,8 @@ if __name__ == '__main__':
|
|||||||
file = sys.stdin
|
file = sys.stdin
|
||||||
tids = file.read().split()
|
tids = file.read().split()
|
||||||
|
|
||||||
client_credentials_manager = SpotifyClientCredentials()
|
auth_manager = SpotifyClientCredentials()
|
||||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||||
for start in range(0, len(tids), max_tracks_per_call):
|
for start in range(0, len(tids), max_tracks_per_call):
|
||||||
results = sp.tracks(tids[start: start + max_tracks_per_call])
|
results = sp.tracks(tids[start: start + max_tracks_per_call])
|
||||||
for track in results['tracks']:
|
for track in results['tracks']:
|
||||||
|
|||||||
@ -10,8 +10,8 @@ if len(sys.argv) > 1:
|
|||||||
else:
|
else:
|
||||||
username = 'plamere'
|
username = 'plamere'
|
||||||
|
|
||||||
client_credentials_manager = SpotifyClientCredentials()
|
auth_manager = SpotifyClientCredentials()
|
||||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||||
sp.trace = True
|
sp.trace = True
|
||||||
user = sp.user(username)
|
user = sp.user(username)
|
||||||
pprint.pprint(user)
|
pprint.pprint(user)
|
||||||
|
|||||||
@ -3,8 +3,8 @@ import spotipy
|
|||||||
|
|
||||||
birdy_uri = 'spotify:artist:2WX2uTcsvV5OnS0inACecP'
|
birdy_uri = 'spotify:artist:2WX2uTcsvV5OnS0inACecP'
|
||||||
|
|
||||||
client_credentials_manager = SpotifyClientCredentials()
|
auth_manager = SpotifyClientCredentials()
|
||||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||||
|
|
||||||
results = sp.artist_albums(birdy_uri, album_type='album')
|
results = sp.artist_albums(birdy_uri, album_type='album')
|
||||||
albums = results['items']
|
albums = results['items']
|
||||||
|
|||||||
@ -3,8 +3,8 @@ import spotipy
|
|||||||
|
|
||||||
lz_uri = 'spotify:artist:36QJpDe2go2KgaRleHCDTp'
|
lz_uri = 'spotify:artist:36QJpDe2go2KgaRleHCDTp'
|
||||||
|
|
||||||
client_credentials_manager = SpotifyClientCredentials()
|
auth_manager = SpotifyClientCredentials()
|
||||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||||
|
|
||||||
results = sp.artist_top_tracks(lz_uri)
|
results = sp.artist_top_tracks(lz_uri)
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
from spotipy.oauth2 import SpotifyClientCredentials
|
from spotipy.oauth2 import SpotifyClientCredentials
|
||||||
import spotipy
|
import spotipy
|
||||||
|
|
||||||
client_credentials_manager = SpotifyClientCredentials()
|
auth_manager = SpotifyClientCredentials()
|
||||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||||
|
|
||||||
results = sp.search(q='weezer', limit=20)
|
results = sp.search(q='weezer', limit=20)
|
||||||
for i, t in enumerate(results['tracks']['items']):
|
for i, t in enumerate(results['tracks']['items']):
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import sys
|
|||||||
from spotipy.oauth2 import SpotifyClientCredentials
|
from spotipy.oauth2 import SpotifyClientCredentials
|
||||||
import spotipy
|
import spotipy
|
||||||
|
|
||||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
sp = spotipy.Spotify(auth_manager=SpotifyClientCredentials())
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
if len(sys.argv) > 1:
|
||||||
name = ' '.join(sys.argv[1:])
|
name = ' '.join(sys.argv[1:])
|
||||||
|
|||||||
@ -9,8 +9,8 @@ import random
|
|||||||
usage: python title_chain.py [song name]
|
usage: python title_chain.py [song name]
|
||||||
'''
|
'''
|
||||||
|
|
||||||
client_credentials_manager = SpotifyClientCredentials()
|
auth_manager = SpotifyClientCredentials()
|
||||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||||
|
|
||||||
|
|
||||||
skiplist = {'dm', 'remix'}
|
skiplist = {'dm', 'remix'}
|
||||||
|
|||||||
@ -6,8 +6,8 @@ from spotipy.oauth2 import SpotifyClientCredentials
|
|||||||
import spotipy
|
import spotipy
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
client_credentials_manager = SpotifyClientCredentials()
|
auth_manager = SpotifyClientCredentials()
|
||||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
if len(sys.argv) > 1:
|
||||||
artist_name = ' '.join(sys.argv[1:])
|
artist_name = ' '.join(sys.argv[1:])
|
||||||
|
|||||||
@ -6,8 +6,8 @@ import sys
|
|||||||
import spotipy
|
import spotipy
|
||||||
from spotipy.oauth2 import SpotifyClientCredentials
|
from spotipy.oauth2 import SpotifyClientCredentials
|
||||||
|
|
||||||
client_credentials_manager = SpotifyClientCredentials()
|
auth_manager = SpotifyClientCredentials()
|
||||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||||
|
|
||||||
user = 'spotify'
|
user = 'spotify'
|
||||||
|
|
||||||
|
|||||||
@ -125,11 +125,9 @@ class Spotify:
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
auth=None,
|
access_token=None,
|
||||||
requests_session=True,
|
|
||||||
client_credentials_manager=None,
|
|
||||||
oauth_manager=None,
|
|
||||||
auth_manager=None,
|
auth_manager=None,
|
||||||
|
requests_session=True,
|
||||||
proxies=None,
|
proxies=None,
|
||||||
requests_timeout=5,
|
requests_timeout=5,
|
||||||
status_forcelist=None,
|
status_forcelist=None,
|
||||||
@ -141,19 +139,16 @@ class Spotify:
|
|||||||
"""
|
"""
|
||||||
Creates a Spotify API client.
|
Creates a Spotify API client.
|
||||||
|
|
||||||
:param auth: An access token (optional)
|
:param access_token: An access token (optional). If not None, then this parameter
|
||||||
|
will override the auth_manager parameter. Prefer `auth_manager` over this parameter
|
||||||
|
because otherwise you cannot refresh the `access_token`.
|
||||||
|
:param auth_manager:
|
||||||
|
SpotifyOauth, SpotifyClientCredentials, or SpotifyPKCE object
|
||||||
:param requests_session:
|
:param requests_session:
|
||||||
A Requests session object or a truthy value to create one.
|
A Requests session object or a true value to create one.
|
||||||
A falsy value disables sessions.
|
A false value disables sessions.
|
||||||
It should generally be a good idea to keep sessions enabled
|
It should generally be a good idea to keep sessions enabled
|
||||||
for performance reasons (connection pooling).
|
for performance reasons (connection pooling).
|
||||||
:param client_credentials_manager:
|
|
||||||
SpotifyClientCredentials object
|
|
||||||
:param oauth_manager:
|
|
||||||
SpotifyOAuth object
|
|
||||||
:param auth_manager:
|
|
||||||
SpotifyOauth, SpotifyClientCredentials,
|
|
||||||
or SpotifyImplicitGrant object
|
|
||||||
:param proxies:
|
:param proxies:
|
||||||
Definition of proxies (optional).
|
Definition of proxies (optional).
|
||||||
See Requests doc https://2.python-requests.org/en/master/user/advanced/#proxies
|
See Requests doc https://2.python-requests.org/en/master/user/advanced/#proxies
|
||||||
@ -173,17 +168,23 @@ class Spotify:
|
|||||||
The language parameter advertises what language the user prefers to see.
|
The language parameter advertises what language the user prefers to see.
|
||||||
See ISO-639-1 language code: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
|
See ISO-639-1 language code: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if access_token is not None and auth_manager is not None:
|
||||||
|
warnings.warn(
|
||||||
|
"Either `access_token` or `auth_manager` should be provided, "
|
||||||
|
"not both. `auth_manager` will be ignored.",
|
||||||
|
UserWarning
|
||||||
|
)
|
||||||
|
|
||||||
self.prefix = "https://api.spotify.com/v1/"
|
self.prefix = "https://api.spotify.com/v1/"
|
||||||
self._auth = auth
|
self.access_token = access_token
|
||||||
self.client_credentials_manager = client_credentials_manager
|
|
||||||
self.oauth_manager = oauth_manager
|
|
||||||
self.auth_manager = auth_manager
|
self.auth_manager = auth_manager
|
||||||
self.proxies = proxies
|
self.proxies = proxies
|
||||||
self.requests_timeout = requests_timeout
|
self.requests_timeout = requests_timeout
|
||||||
self.status_forcelist = status_forcelist or self.default_retry_codes
|
self.status_forcelist = status_forcelist or self.default_retry_codes
|
||||||
self.backoff_factor = backoff_factor
|
|
||||||
self.retries = retries
|
self.retries = retries
|
||||||
self.status_retries = status_retries
|
self.status_retries = status_retries
|
||||||
|
self.backoff_factor = backoff_factor
|
||||||
self.language = language
|
self.language = language
|
||||||
|
|
||||||
if isinstance(requests_session, requests.Session):
|
if isinstance(requests_session, requests.Session):
|
||||||
@ -194,21 +195,6 @@ class Spotify:
|
|||||||
else: # Use the Requests API module as a "session".
|
else: # Use the Requests API module as a "session".
|
||||||
self._session = requests.api
|
self._session = requests.api
|
||||||
|
|
||||||
def set_auth(self, auth):
|
|
||||||
self._auth = auth
|
|
||||||
|
|
||||||
@property
|
|
||||||
def auth_manager(self):
|
|
||||||
return self._auth_manager
|
|
||||||
|
|
||||||
@auth_manager.setter
|
|
||||||
def auth_manager(self, auth_manager):
|
|
||||||
if auth_manager is not None:
|
|
||||||
self._auth_manager = auth_manager
|
|
||||||
else:
|
|
||||||
self._auth_manager = (
|
|
||||||
self.client_credentials_manager or self.oauth_manager
|
|
||||||
)
|
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
"""Make sure the connection (pool) gets closed"""
|
"""Make sure the connection (pool) gets closed"""
|
||||||
@ -234,12 +220,12 @@ class Spotify:
|
|||||||
self._session.mount('https://', adapter)
|
self._session.mount('https://', adapter)
|
||||||
|
|
||||||
def _auth_headers(self):
|
def _auth_headers(self):
|
||||||
if self._auth:
|
if self.access_token:
|
||||||
return {"Authorization": f"Bearer {self._auth}"}
|
return {"Authorization": "Bearer {0}".format(self.access_token)}
|
||||||
if not self.auth_manager:
|
if not self.auth_manager:
|
||||||
return {}
|
return {}
|
||||||
try:
|
try:
|
||||||
token = self.auth_manager.get_access_token(as_dict=False)
|
token = self.auth_manager.get_access_token()
|
||||||
except TypeError:
|
except TypeError:
|
||||||
token = self.auth_manager.get_access_token()
|
token = self.auth_manager.get_access_token()
|
||||||
return {"Authorization": f"Bearer {token}"}
|
return {"Authorization": f"Bearer {token}"}
|
||||||
@ -670,34 +656,6 @@ class Spotify:
|
|||||||
additional_types=",".join(additional_types),
|
additional_types=",".join(additional_types),
|
||||||
)
|
)
|
||||||
|
|
||||||
def playlist_tracks(
|
|
||||||
self,
|
|
||||||
playlist_id,
|
|
||||||
fields=None,
|
|
||||||
limit=100,
|
|
||||||
offset=0,
|
|
||||||
market=None,
|
|
||||||
additional_types=("track",)
|
|
||||||
):
|
|
||||||
""" Get full details of the tracks of a playlist.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- playlist_id - the playlist ID, URI or URL
|
|
||||||
- fields - which fields to return
|
|
||||||
- limit - the maximum number of tracks to return
|
|
||||||
- offset - the index of the first track to return
|
|
||||||
- market - an ISO 3166-1 alpha-2 country code.
|
|
||||||
- additional_types - list of item types to return.
|
|
||||||
valid types are: track and episode
|
|
||||||
"""
|
|
||||||
warnings.warn(
|
|
||||||
"You should use `playlist_items(playlist_id, ...,"
|
|
||||||
"additional_types=('track',))` instead",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
return self.playlist_items(playlist_id, fields, limit, offset,
|
|
||||||
market, additional_types)
|
|
||||||
|
|
||||||
def playlist_items(
|
def playlist_items(
|
||||||
self,
|
self,
|
||||||
playlist_id,
|
playlist_id,
|
||||||
@ -752,55 +710,6 @@ class Spotify:
|
|||||||
content_type="image/jpeg",
|
content_type="image/jpeg",
|
||||||
)
|
)
|
||||||
|
|
||||||
def user_playlist(self, user, playlist_id=None, fields=None, market=None):
|
|
||||||
warnings.warn(
|
|
||||||
"You should use `playlist(playlist_id)` instead",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
|
|
||||||
""" Gets a single playlist of a user
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- user - the id of the user
|
|
||||||
- playlist_id - the id of the playlist
|
|
||||||
- fields - which fields to return
|
|
||||||
"""
|
|
||||||
if playlist_id is None:
|
|
||||||
return self._get(f"users/{user}/starred")
|
|
||||||
return self.playlist(playlist_id, fields=fields, market=market)
|
|
||||||
|
|
||||||
def user_playlist_tracks(
|
|
||||||
self,
|
|
||||||
user=None,
|
|
||||||
playlist_id=None,
|
|
||||||
fields=None,
|
|
||||||
limit=100,
|
|
||||||
offset=0,
|
|
||||||
market=None,
|
|
||||||
):
|
|
||||||
warnings.warn(
|
|
||||||
"You should use `playlist_tracks(playlist_id)` instead",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
|
|
||||||
""" Get full details of the tracks of a playlist owned by a user.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- user - the id of the user
|
|
||||||
- playlist_id - the id of the playlist
|
|
||||||
- fields - which fields to return
|
|
||||||
- limit - the maximum number of tracks to return
|
|
||||||
- offset - the index of the first track to return
|
|
||||||
- market - an ISO 3166-1 alpha-2 country code.
|
|
||||||
"""
|
|
||||||
return self.playlist_tracks(
|
|
||||||
playlist_id,
|
|
||||||
limit=limit,
|
|
||||||
offset=offset,
|
|
||||||
fields=fields,
|
|
||||||
market=market,
|
|
||||||
)
|
|
||||||
|
|
||||||
def user_playlists(self, user, limit=50, offset=0):
|
def user_playlists(self, user, limit=50, offset=0):
|
||||||
""" Gets playlists of a user
|
""" Gets playlists of a user
|
||||||
|
|
||||||
@ -832,233 +741,6 @@ class Spotify:
|
|||||||
|
|
||||||
return self._post(f"users/{user}/playlists", payload=data)
|
return self._post(f"users/{user}/playlists", payload=data)
|
||||||
|
|
||||||
def user_playlist_change_details(
|
|
||||||
self,
|
|
||||||
user,
|
|
||||||
playlist_id,
|
|
||||||
name=None,
|
|
||||||
public=None,
|
|
||||||
collaborative=None,
|
|
||||||
description=None,
|
|
||||||
):
|
|
||||||
""" This function is no longer in use, please use the recommended function in the warning!
|
|
||||||
|
|
||||||
Changes a playlist's name and/or public/private state
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- user - the id of the user
|
|
||||||
- playlist_id - the id of the playlist
|
|
||||||
- name - optional name of the playlist
|
|
||||||
- public - optional is the playlist public
|
|
||||||
- collaborative - optional is the playlist collaborative
|
|
||||||
- description - optional description of the playlist
|
|
||||||
"""
|
|
||||||
warnings.warn(
|
|
||||||
"You should use `playlist_change_details(playlist_id, ...)` instead",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.playlist_change_details(playlist_id, name, public,
|
|
||||||
collaborative, description)
|
|
||||||
|
|
||||||
def user_playlist_unfollow(self, user, playlist_id):
|
|
||||||
""" This function is no longer in use, please use the recommended function in the warning!
|
|
||||||
|
|
||||||
Unfollows (deletes) a playlist for a user
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- user - the id of the user
|
|
||||||
- name - the name of the playlist
|
|
||||||
"""
|
|
||||||
warnings.warn(
|
|
||||||
"You should use `current_user_unfollow_playlist(playlist_id)` instead",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
return self.current_user_unfollow_playlist(playlist_id)
|
|
||||||
|
|
||||||
def user_playlist_add_tracks(
|
|
||||||
self, user, playlist_id, tracks, position=None
|
|
||||||
):
|
|
||||||
""" This function is no longer in use, please use the recommended function in the warning!
|
|
||||||
|
|
||||||
Adds tracks to a playlist
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- user - the id of the user
|
|
||||||
- playlist_id - the id of the playlist
|
|
||||||
- tracks - a list of track URIs, URLs or IDs
|
|
||||||
- position - the position to add the tracks
|
|
||||||
"""
|
|
||||||
warnings.warn(
|
|
||||||
"You should use `playlist_add_items(playlist_id, tracks)` instead",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
|
|
||||||
tracks = [self._get_uri("track", tid) for tid in tracks]
|
|
||||||
return self.playlist_add_items(playlist_id, tracks, position)
|
|
||||||
|
|
||||||
def user_playlist_add_episodes(
|
|
||||||
self, user, playlist_id, episodes, position=None
|
|
||||||
):
|
|
||||||
""" This function is no longer in use, please use the recommended function in the warning!
|
|
||||||
|
|
||||||
Adds episodes to a playlist
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- user - the id of the user
|
|
||||||
- playlist_id - the id of the playlist
|
|
||||||
- episodes - a list of track URIs, URLs or IDs
|
|
||||||
- position - the position to add the episodes
|
|
||||||
"""
|
|
||||||
warnings.warn(
|
|
||||||
"You should use `playlist_add_items(playlist_id, episodes)` instead",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
|
|
||||||
episodes = [self._get_uri("episode", tid) for tid in episodes]
|
|
||||||
return self.playlist_add_items(playlist_id, episodes, position)
|
|
||||||
|
|
||||||
def user_playlist_replace_tracks(self, user, playlist_id, tracks):
|
|
||||||
""" This function is no longer in use, please use the recommended function in the warning!
|
|
||||||
|
|
||||||
Replace all tracks in a playlist for a user
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- user - the id of the user
|
|
||||||
- playlist_id - the id of the playlist
|
|
||||||
- tracks - the list of track ids to add to the playlist
|
|
||||||
"""
|
|
||||||
warnings.warn(
|
|
||||||
"You should use `playlist_replace_items(playlist_id, tracks)` instead",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
return self.playlist_replace_items(playlist_id, tracks)
|
|
||||||
|
|
||||||
def user_playlist_reorder_tracks(
|
|
||||||
self,
|
|
||||||
user,
|
|
||||||
playlist_id,
|
|
||||||
range_start,
|
|
||||||
insert_before,
|
|
||||||
range_length=1,
|
|
||||||
snapshot_id=None,
|
|
||||||
):
|
|
||||||
""" This function is no longer in use, please use the recommended function in the warning!
|
|
||||||
|
|
||||||
Reorder tracks in a playlist from a user
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- user - the id of the user
|
|
||||||
- playlist_id - the id of the playlist
|
|
||||||
- range_start - the position of the first track to be reordered
|
|
||||||
- range_length - optional the number of tracks to be reordered
|
|
||||||
(default: 1)
|
|
||||||
- insert_before - the position where the tracks should be
|
|
||||||
inserted
|
|
||||||
- snapshot_id - optional playlist's snapshot ID
|
|
||||||
"""
|
|
||||||
warnings.warn(
|
|
||||||
"You should use `playlist_reorder_items(playlist_id, ...)` instead",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
return self.playlist_reorder_items(playlist_id, range_start,
|
|
||||||
insert_before, range_length,
|
|
||||||
snapshot_id)
|
|
||||||
|
|
||||||
def user_playlist_remove_all_occurrences_of_tracks(
|
|
||||||
self, user, playlist_id, tracks, snapshot_id=None
|
|
||||||
):
|
|
||||||
""" This function is no longer in use, please use the recommended function in the warning!
|
|
||||||
|
|
||||||
Removes all occurrences of the given tracks from the given playlist
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- user - the id of the user
|
|
||||||
- playlist_id - the id of the playlist
|
|
||||||
- tracks - the list of track ids to remove from the playlist
|
|
||||||
- snapshot_id - optional id of the playlist snapshot
|
|
||||||
"""
|
|
||||||
warnings.warn(
|
|
||||||
"You should use `playlist_remove_all_occurrences_of_items"
|
|
||||||
"(playlist_id, tracks)` instead",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
return self.playlist_remove_all_occurrences_of_items(playlist_id,
|
|
||||||
tracks,
|
|
||||||
snapshot_id)
|
|
||||||
|
|
||||||
def user_playlist_remove_specific_occurrences_of_tracks(
|
|
||||||
self, user, playlist_id, tracks, snapshot_id=None
|
|
||||||
):
|
|
||||||
""" This function is no longer in use, please use the recommended function in the warning!
|
|
||||||
|
|
||||||
Removes all occurrences of the given tracks from the given playlist
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- user - the id of the user
|
|
||||||
- playlist_id - the id of the playlist
|
|
||||||
- tracks - an array of objects containing Spotify URIs of the
|
|
||||||
tracks to remove with their current positions in the
|
|
||||||
playlist. For example:
|
|
||||||
[ { "uri":"4iV5W9uYEdYUVa79Axb7Rh", "positions":[2] },
|
|
||||||
{ "uri":"1301WleyT98MSxVHPZCA6M", "positions":[7] } ]
|
|
||||||
- snapshot_id - optional id of the playlist snapshot
|
|
||||||
"""
|
|
||||||
warnings.warn(
|
|
||||||
"You should use `playlist_remove_specific_occurrences_of_items"
|
|
||||||
"(playlist_id, tracks)` instead",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
plid = self._get_id("playlist", playlist_id)
|
|
||||||
ftracks = []
|
|
||||||
for tr in tracks:
|
|
||||||
ftracks.append(
|
|
||||||
{
|
|
||||||
"uri": self._get_uri("track", tr["uri"]),
|
|
||||||
"positions": tr["positions"],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
payload = {"tracks": ftracks}
|
|
||||||
if snapshot_id:
|
|
||||||
payload["snapshot_id"] = snapshot_id
|
|
||||||
return self._delete(
|
|
||||||
f"users/{user}/playlists/{plid}/tracks", payload=payload
|
|
||||||
)
|
|
||||||
|
|
||||||
def user_playlist_follow_playlist(self, playlist_owner_id, playlist_id):
|
|
||||||
""" This function is no longer in use, please use the recommended function in the warning!
|
|
||||||
|
|
||||||
Add the current authenticated user as a follower of a playlist.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- playlist_owner_id - the user id of the playlist owner
|
|
||||||
- playlist_id - the id of the playlist
|
|
||||||
"""
|
|
||||||
warnings.warn(
|
|
||||||
"You should use `current_user_follow_playlist(playlist_id)` instead",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
return self.current_user_follow_playlist(playlist_id)
|
|
||||||
|
|
||||||
def user_playlist_is_following(
|
|
||||||
self, playlist_owner_id, playlist_id, user_ids
|
|
||||||
):
|
|
||||||
""" This function is no longer in use, please use the recommended function in the warning!
|
|
||||||
|
|
||||||
Check to see if the given users are following the given playlist
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- playlist_owner_id - the user id of the playlist owner
|
|
||||||
- playlist_id - the id of the playlist
|
|
||||||
- user_ids - the ids of the users that you want to check to see
|
|
||||||
if they follow the playlist. Maximum: 5 ids.
|
|
||||||
"""
|
|
||||||
warnings.warn(
|
|
||||||
"You should use `playlist_is_following(playlist_id, user_ids)` instead",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
return self.playlist_is_following(playlist_id, user_ids)
|
|
||||||
|
|
||||||
def playlist_change_details(
|
def playlist_change_details(
|
||||||
self,
|
self,
|
||||||
playlist_id,
|
playlist_id,
|
||||||
|
|||||||
@ -3,7 +3,6 @@ __all__ = [
|
|||||||
"SpotifyOAuth",
|
"SpotifyOAuth",
|
||||||
"SpotifyOauthError",
|
"SpotifyOauthError",
|
||||||
"SpotifyStateError",
|
"SpotifyStateError",
|
||||||
"SpotifyImplicitGrant",
|
|
||||||
"SpotifyPKCE"
|
"SpotifyPKCE"
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -11,7 +10,6 @@ import base64
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import warnings
|
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
@ -82,10 +80,7 @@ class SpotifyAuthBase:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_user_input(prompt):
|
def _get_user_input(prompt):
|
||||||
try:
|
return input(prompt)
|
||||||
return raw_input(prompt)
|
|
||||||
except NameError:
|
|
||||||
return input(prompt)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_token_expired(token_info):
|
def is_token_expired(token_info):
|
||||||
@ -133,10 +128,10 @@ class SpotifyClientCredentials(SpotifyAuthBase):
|
|||||||
self,
|
self,
|
||||||
client_id=None,
|
client_id=None,
|
||||||
client_secret=None,
|
client_secret=None,
|
||||||
|
cache_handler=None,
|
||||||
proxies=None,
|
proxies=None,
|
||||||
requests_session=True,
|
requests_session=True,
|
||||||
requests_timeout=None,
|
requests_timeout=None
|
||||||
cache_handler=None
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Creates a Client Credentials Flow Manager.
|
Creates a Client Credentials Flow Manager.
|
||||||
@ -154,14 +149,16 @@ class SpotifyClientCredentials(SpotifyAuthBase):
|
|||||||
Parameters:
|
Parameters:
|
||||||
* client_id: Must be supplied or set as environment variable
|
* client_id: Must be supplied or set as environment variable
|
||||||
* client_secret: Must be supplied or set as environment variable
|
* client_secret: Must be supplied or set as environment variable
|
||||||
* proxies: Optional, proxy for the requests library to route through
|
|
||||||
* requests_session: A Requests session
|
|
||||||
* requests_timeout: Optional, tell Requests to stop waiting for a response after
|
|
||||||
a given number of seconds
|
|
||||||
* cache_handler: An instance of the `CacheHandler` class to handle
|
* cache_handler: An instance of the `CacheHandler` class to handle
|
||||||
getting and saving cached authorization tokens.
|
getting and saving cached authorization tokens.
|
||||||
Optional, will otherwise use `CacheFileHandler`.
|
Optional, will otherwise use `CacheFileHandler`.
|
||||||
(takes precedence over `cache_path` and `username`)
|
* proxies: Optional, proxy for the requests library to route through
|
||||||
|
* requests_session: A Requests session object or a true value to create one.
|
||||||
|
A false value disables sessions.
|
||||||
|
It should generally be a good idea to keep sessions enabled
|
||||||
|
for performance reasons (connection pooling).
|
||||||
|
* requests_timeout: Optional, tell Requests to stop waiting for a response after
|
||||||
|
a given number of seconds
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -179,35 +176,25 @@ class SpotifyClientCredentials(SpotifyAuthBase):
|
|||||||
else:
|
else:
|
||||||
self.cache_handler = CacheFileHandler()
|
self.cache_handler = CacheFileHandler()
|
||||||
|
|
||||||
def get_access_token(self, as_dict=True, check_cache=True):
|
def get_access_token(self, check_cache=True):
|
||||||
"""
|
"""
|
||||||
If a valid access token is in memory, returns it
|
If a valid access token is in memory, returns it
|
||||||
Else fetches a new token and returns it
|
Else fetches a new token and returns it
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- as_dict - a boolean indicating if returning the access token
|
- check_cache - if true, checks for a locally stored token
|
||||||
as a token_info dictionary, otherwise it will be returned
|
before requesting a new token.
|
||||||
as a string.
|
|
||||||
"""
|
"""
|
||||||
if as_dict:
|
|
||||||
warnings.warn(
|
|
||||||
"You're using 'as_dict = True'."
|
|
||||||
"get_access_token will return the token string directly in future "
|
|
||||||
"versions. Please adjust your code accordingly, or use "
|
|
||||||
"get_cached_token instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
if check_cache:
|
if check_cache:
|
||||||
token_info = self.cache_handler.get_cached_token()
|
token_info = self.cache_handler.get_cached_token()
|
||||||
if token_info and not self.is_token_expired(token_info):
|
if token_info and not self.is_token_expired(token_info):
|
||||||
return token_info if as_dict else token_info["access_token"]
|
return token_info["access_token"]
|
||||||
|
|
||||||
token_info = self._request_access_token()
|
token_info = self._request_access_token()
|
||||||
token_info = self._add_custom_values_to_token_info(token_info)
|
token_info = self._add_custom_values_to_token_info(token_info)
|
||||||
self.cache_handler.save_token_to_cache(token_info)
|
self.cache_handler.save_token_to_cache(token_info)
|
||||||
return token_info if as_dict else token_info["access_token"]
|
return token_info["access_token"]
|
||||||
|
|
||||||
def _request_access_token(self):
|
def _request_access_token(self):
|
||||||
"""Gets client credentials access token """
|
"""Gets client credentials access token """
|
||||||
@ -260,14 +247,12 @@ class SpotifyOAuth(SpotifyAuthBase):
|
|||||||
redirect_uri=None,
|
redirect_uri=None,
|
||||||
state=None,
|
state=None,
|
||||||
scope=None,
|
scope=None,
|
||||||
cache_path=None,
|
cache_handler=None,
|
||||||
username=None,
|
|
||||||
proxies=None,
|
proxies=None,
|
||||||
show_dialog=False,
|
show_dialog=False,
|
||||||
requests_session=True,
|
requests_session=True,
|
||||||
requests_timeout=None,
|
requests_timeout=None,
|
||||||
open_browser=True,
|
open_browser=True
|
||||||
cache_handler=None
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Creates a SpotifyOAuth object
|
Creates a SpotifyOAuth object
|
||||||
@ -280,24 +265,19 @@ class SpotifyOAuth(SpotifyAuthBase):
|
|||||||
* scope: Optional, either a string of scopes, or an iterable with elements of type
|
* scope: Optional, either a string of scopes, or an iterable with elements of type
|
||||||
`Scope` or `str`. E.g.,
|
`Scope` or `str`. E.g.,
|
||||||
{Scope.user_modify_playback_state, Scope.user_library_read}
|
{Scope.user_modify_playback_state, Scope.user_library_read}
|
||||||
|
* cache_handler: An instance of the `CacheHandler` class to handle
|
||||||
iterable of scopes or comma separated string of scopes.
|
getting and saving cached authorization tokens.
|
||||||
e.g, "playlist-read-private,playlist-read-collaborative"
|
Optional, will otherwise use `CacheFileHandler`.
|
||||||
* cache_path: (deprecated) Optional, will otherwise be generated
|
|
||||||
(takes precedence over `username`)
|
|
||||||
* username: (deprecated) Optional or set as environment variable
|
|
||||||
(will set `cache_path` to `.cache-{username}`)
|
|
||||||
* proxies: Optional, proxy for the requests library to route through
|
* proxies: Optional, proxy for the requests library to route through
|
||||||
* show_dialog: Optional, interpreted as boolean
|
* show_dialog: Optional, interpreted as boolean
|
||||||
* requests_session: A Requests session
|
* requests_session: A Requests session object or a true value to create one.
|
||||||
|
A false value disables sessions.
|
||||||
|
It should generally be a good idea to keep sessions enabled
|
||||||
|
for performance reasons (connection pooling).
|
||||||
* requests_timeout: Optional, tell Requests to stop waiting for a response after
|
* requests_timeout: Optional, tell Requests to stop waiting for a response after
|
||||||
a given number of seconds
|
a given number of seconds
|
||||||
* open_browser: Optional, whether the web browser should be opened to
|
* open_browser: Optional, whether the web browser should be opened to
|
||||||
authorize a user
|
authorize a user
|
||||||
* cache_handler: An instance of the `CacheHandler` class to handle
|
|
||||||
getting and saving cached authorization tokens.
|
|
||||||
Optional, will otherwise use `CacheFileHandler`.
|
|
||||||
(takes precedence over `cache_path` and `username`)
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().__init__(requests_session)
|
super().__init__(requests_session)
|
||||||
@ -307,33 +287,14 @@ class SpotifyOAuth(SpotifyAuthBase):
|
|||||||
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)
|
||||||
if username or cache_path:
|
|
||||||
warnings.warn("Specifying cache_path or username as arguments to SpotifyOAuth " +
|
|
||||||
"will be deprecated. Instead, please create a CacheFileHandler " +
|
|
||||||
"instance with the desired cache_path and username and pass it " +
|
|
||||||
"to SpotifyOAuth as the cache_handler. For example:\n\n" +
|
|
||||||
"\tfrom spotipy.oauth2 import CacheFileHandler\n" +
|
|
||||||
"\thandler = CacheFileHandler(cache_path=cache_path, " +
|
|
||||||
"username=username)\n" +
|
|
||||||
"\tsp = spotipy.SpotifyOAuth(client_id, client_secret, " +
|
|
||||||
"redirect_uri," +
|
|
||||||
" cache_handler=handler)",
|
|
||||||
DeprecationWarning
|
|
||||||
)
|
|
||||||
if cache_handler:
|
|
||||||
warnings.warn("A cache_handler has been specified along with a cache_path or " +
|
|
||||||
"username. The cache_path and username arguments will be ignored.")
|
|
||||||
if cache_handler:
|
if cache_handler:
|
||||||
assert issubclass(cache_handler.__class__, CacheHandler), \
|
assert issubclass(cache_handler.__class__, CacheHandler), \
|
||||||
"cache_handler must be a subclass of CacheHandler: " + str(type(cache_handler)) \
|
"cache_handler must be a subclass of CacheHandler: " + str(type(cache_handler)) \
|
||||||
+ " != " + str(CacheHandler)
|
+ " != " + str(CacheHandler)
|
||||||
self.cache_handler = cache_handler
|
self.cache_handler = cache_handler
|
||||||
else:
|
else:
|
||||||
username = (username or os.getenv(CLIENT_CREDS_ENV_VARS["client_username"]))
|
self.cache_handler = CacheFileHandler()
|
||||||
self.cache_handler = CacheFileHandler(
|
|
||||||
username=username,
|
|
||||||
cache_path=cache_path
|
|
||||||
)
|
|
||||||
self.proxies = proxies
|
self.proxies = proxies
|
||||||
self.requests_timeout = requests_timeout
|
self.requests_timeout = requests_timeout
|
||||||
self.show_dialog = show_dialog
|
self.show_dialog = show_dialog
|
||||||
@ -475,24 +436,14 @@ class SpotifyOAuth(SpotifyAuthBase):
|
|||||||
return self.parse_response_code(response)
|
return self.parse_response_code(response)
|
||||||
return self.get_auth_response()
|
return self.get_auth_response()
|
||||||
|
|
||||||
def get_access_token(self, code=None, as_dict=True, check_cache=True):
|
def get_access_token(self, code=None, check_cache=True):
|
||||||
""" Gets the access token for the app given the code
|
""" Gets the access token for the app given the code
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- code - the response code
|
- code - the response code
|
||||||
- as_dict - a boolean indicating if returning the access token
|
- check_cache - if true, checks for a locally stored token
|
||||||
as a token_info dictionary, otherwise it will be returned
|
before requesting a new token
|
||||||
as a string.
|
|
||||||
"""
|
"""
|
||||||
if as_dict:
|
|
||||||
warnings.warn(
|
|
||||||
"You're using 'as_dict = True'."
|
|
||||||
"get_access_token will return the token string directly in future "
|
|
||||||
"versions. Please adjust your code accordingly, or use "
|
|
||||||
"get_cached_token instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
if check_cache:
|
if check_cache:
|
||||||
token_info = self.validate_token(self.cache_handler.get_cached_token())
|
token_info = self.validate_token(self.cache_handler.get_cached_token())
|
||||||
if token_info is not None:
|
if token_info is not None:
|
||||||
@ -500,7 +451,7 @@ class SpotifyOAuth(SpotifyAuthBase):
|
|||||||
token_info = self.refresh_access_token(
|
token_info = self.refresh_access_token(
|
||||||
token_info["refresh_token"]
|
token_info["refresh_token"]
|
||||||
)
|
)
|
||||||
return token_info if as_dict else token_info["access_token"]
|
return token_info["access_token"]
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"redirect_uri": self.redirect_uri,
|
"redirect_uri": self.redirect_uri,
|
||||||
@ -532,7 +483,7 @@ class SpotifyOAuth(SpotifyAuthBase):
|
|||||||
token_info = response.json()
|
token_info = response.json()
|
||||||
token_info = self._add_custom_values_to_token_info(token_info)
|
token_info = self._add_custom_values_to_token_info(token_info)
|
||||||
self.cache_handler.save_token_to_cache(token_info)
|
self.cache_handler.save_token_to_cache(token_info)
|
||||||
return token_info if as_dict else token_info["access_token"]
|
return token_info["access_token"]
|
||||||
except requests.exceptions.HTTPError as http_error:
|
except requests.exceptions.HTTPError as http_error:
|
||||||
self._handle_oauth_error(http_error)
|
self._handle_oauth_error(http_error)
|
||||||
|
|
||||||
@ -576,26 +527,6 @@ class SpotifyOAuth(SpotifyAuthBase):
|
|||||||
token_info["scope"] = self.scope
|
token_info["scope"] = self.scope
|
||||||
return token_info
|
return token_info
|
||||||
|
|
||||||
def get_cached_token(self):
|
|
||||||
warnings.warn("Calling get_cached_token directly on the SpotifyOAuth object will be " +
|
|
||||||
"deprecated. Instead, please specify a CacheFileHandler instance as " +
|
|
||||||
"the cache_handler in SpotifyOAuth and use the CacheFileHandler's " +
|
|
||||||
"get_cached_token method. You can replace:\n\tsp.get_cached_token()" +
|
|
||||||
"\n\nWith:\n\tsp.validate_token(sp.cache_handler.get_cached_token())",
|
|
||||||
DeprecationWarning
|
|
||||||
)
|
|
||||||
return self.validate_token(self.cache_handler.get_cached_token())
|
|
||||||
|
|
||||||
def _save_token_info(self, token_info):
|
|
||||||
warnings.warn("Calling _save_token_info directly on the SpotifyOAuth object will be " +
|
|
||||||
"deprecated. Instead, please specify a CacheFileHandler instance as " +
|
|
||||||
"the cache_handler in SpotifyOAuth and use the CacheFileHandler's " +
|
|
||||||
"save_token_to_cache method.",
|
|
||||||
DeprecationWarning
|
|
||||||
)
|
|
||||||
self.cache_handler.save_token_to_cache(token_info)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class SpotifyPKCE(SpotifyAuthBase):
|
class SpotifyPKCE(SpotifyAuthBase):
|
||||||
""" Implements PKCE Authorization Flow for client apps
|
""" Implements PKCE Authorization Flow for client apps
|
||||||
@ -612,18 +543,18 @@ class SpotifyPKCE(SpotifyAuthBase):
|
|||||||
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"
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(
|
||||||
client_id=None,
|
self,
|
||||||
redirect_uri=None,
|
client_id=None,
|
||||||
state=None,
|
redirect_uri=None,
|
||||||
scope=None,
|
state=None,
|
||||||
cache_path=None,
|
scope=None,
|
||||||
username=None,
|
cache_handler=None,
|
||||||
proxies=None,
|
proxies=None,
|
||||||
requests_timeout=None,
|
requests_timeout=None,
|
||||||
requests_session=True,
|
requests_session=True,
|
||||||
open_browser=True,
|
open_browser=True
|
||||||
cache_handler=None):
|
):
|
||||||
"""
|
"""
|
||||||
Creates Auth Manager with the PKCE Auth flow.
|
Creates Auth Manager with the PKCE Auth flow.
|
||||||
|
|
||||||
@ -631,22 +562,21 @@ class SpotifyPKCE(SpotifyAuthBase):
|
|||||||
* client_id: Must be supplied or set as environment variable
|
* client_id: Must be supplied or set as environment variable
|
||||||
* redirect_uri: Must be supplied or set as environment variable
|
* redirect_uri: Must be supplied or set as environment variable
|
||||||
* state: Optional, no verification is performed
|
* state: Optional, no verification is performed
|
||||||
* scope: Optional, either a list of scopes or comma separated string of scopes.
|
* scope: Optional, either a string of scopes, or an iterable with elements of type
|
||||||
e.g, "playlist-read-private,playlist-read-collaborative"
|
`Scope` or `str`. E.g.,
|
||||||
* cache_path: (deprecated) Optional, will otherwise be generated
|
{Scope.user_modify_playback_state, Scope.user_library_read}
|
||||||
(takes precedence over `username`)
|
|
||||||
* username: (deprecated) Optional or set as environment variable
|
|
||||||
(will set `cache_path` to `.cache-{username}`)
|
|
||||||
* proxies: Optional, proxy for the requests library to route through
|
|
||||||
* requests_timeout: Optional, tell Requests to stop waiting for a response after
|
|
||||||
a given number of seconds
|
|
||||||
* requests_session: A Requests session
|
|
||||||
* open_browser: Optional, whether the web browser should be opened to
|
|
||||||
authorize a user
|
|
||||||
* cache_handler: An instance of the `CacheHandler` class to handle
|
* cache_handler: An instance of the `CacheHandler` class to handle
|
||||||
getting and saving cached authorization tokens.
|
getting and saving cached authorization tokens.
|
||||||
Optional, will otherwise use `CacheFileHandler`.
|
Optional, will otherwise use `CacheFileHandler`.
|
||||||
(takes precedence over `cache_path` and `username`)
|
* proxies: Optional, proxy for the requests library to route through
|
||||||
|
* requests_timeout: Optional, tell Requests to stop waiting for a response after
|
||||||
|
a given number of seconds
|
||||||
|
* requests_session: A Requests session object or a true value to create one.
|
||||||
|
A false value disables sessions.
|
||||||
|
It should generally be a good idea to keep sessions enabled
|
||||||
|
for performance reasons (connection pooling).
|
||||||
|
* open_browser: Optional, thether or not the web browser should be opened to
|
||||||
|
authorize a user
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().__init__(requests_session)
|
super().__init__(requests_session)
|
||||||
@ -654,31 +584,12 @@ class SpotifyPKCE(SpotifyAuthBase):
|
|||||||
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)
|
||||||
if username or cache_path:
|
|
||||||
warnings.warn("Specifying cache_path or username as arguments to SpotifyPKCE " +
|
|
||||||
"will be deprecated. Instead, please create a CacheFileHandler " +
|
|
||||||
"instance with the desired cache_path and username and pass it " +
|
|
||||||
"to SpotifyPKCE as the cache_handler. For example:\n\n" +
|
|
||||||
"\tfrom spotipy.oauth2 import CacheFileHandler\n" +
|
|
||||||
"\thandler = CacheFileHandler(cache_path=cache_path, " +
|
|
||||||
"username=username)\n" +
|
|
||||||
"\tsp = spotipy.SpotifyImplicitGrant(client_id, client_secret, " +
|
|
||||||
"redirect_uri, cache_handler=handler)",
|
|
||||||
DeprecationWarning
|
|
||||||
)
|
|
||||||
if cache_handler:
|
|
||||||
warnings.warn("A cache_handler has been specified along with a cache_path or " +
|
|
||||||
"username. The cache_path and username arguments will be ignored.")
|
|
||||||
if cache_handler:
|
if cache_handler:
|
||||||
assert issubclass(type(cache_handler), CacheHandler), \
|
assert issubclass(type(cache_handler), CacheHandler), \
|
||||||
"type(cache_handler): " + str(type(cache_handler)) + " != " + str(CacheHandler)
|
"type(cache_handler): " + str(type(cache_handler)) + " != " + str(CacheHandler)
|
||||||
self.cache_handler = cache_handler
|
self.cache_handler = cache_handler
|
||||||
else:
|
else:
|
||||||
username = (username or os.getenv(CLIENT_CREDS_ENV_VARS["client_username"]))
|
self.cache_handler = CacheFileHandler()
|
||||||
self.cache_handler = CacheFileHandler(
|
|
||||||
username=username,
|
|
||||||
cache_path=cache_path
|
|
||||||
)
|
|
||||||
self.proxies = proxies
|
self.proxies = proxies
|
||||||
self.requests_timeout = requests_timeout
|
self.requests_timeout = requests_timeout
|
||||||
|
|
||||||
@ -941,285 +852,6 @@ class SpotifyPKCE(SpotifyAuthBase):
|
|||||||
def parse_auth_response_url(url):
|
def parse_auth_response_url(url):
|
||||||
return SpotifyOAuth.parse_auth_response_url(url)
|
return SpotifyOAuth.parse_auth_response_url(url)
|
||||||
|
|
||||||
def get_cached_token(self):
|
|
||||||
warnings.warn("Calling get_cached_token directly on the SpotifyPKCE object will be " +
|
|
||||||
"deprecated. Instead, please specify a CacheFileHandler instance as " +
|
|
||||||
"the cache_handler in SpotifyOAuth and use the CacheFileHandler's " +
|
|
||||||
"get_cached_token method. You can replace:\n\tsp.get_cached_token()" +
|
|
||||||
"\n\nWith:\n\tsp.validate_token(sp.cache_handler.get_cached_token())",
|
|
||||||
DeprecationWarning
|
|
||||||
)
|
|
||||||
return self.validate_token(self.cache_handler.get_cached_token())
|
|
||||||
|
|
||||||
def _save_token_info(self, token_info):
|
|
||||||
warnings.warn("Calling _save_token_info directly on the SpotifyOAuth object will be " +
|
|
||||||
"deprecated. Instead, please specify a CacheFileHandler instance as " +
|
|
||||||
"the cache_handler in SpotifyOAuth and use the CacheFileHandler's " +
|
|
||||||
"save_token_to_cache method.",
|
|
||||||
DeprecationWarning
|
|
||||||
)
|
|
||||||
self.cache_handler.save_token_to_cache(token_info)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class SpotifyImplicitGrant(SpotifyAuthBase):
|
|
||||||
""" Implements Implicit Grant Flow for client apps
|
|
||||||
|
|
||||||
This auth manager enables *user and non-user* endpoints with only
|
|
||||||
a client secret, redirect uri, and username. The user will need to
|
|
||||||
copy and paste a URI from the browser every hour.
|
|
||||||
|
|
||||||
Security Warning
|
|
||||||
-----------------
|
|
||||||
The OAuth standard no longer recommends the Implicit Grant Flow for
|
|
||||||
client-side code. Spotify has implemented the OAuth-suggested PKCE
|
|
||||||
extension that removes the need for a client secret in the
|
|
||||||
Authentication Code flow. Use the SpotifyPKCE auth manager instead
|
|
||||||
of SpotifyImplicitGrant.
|
|
||||||
|
|
||||||
SpotifyPKCE contains all the functionality of
|
|
||||||
SpotifyImplicitGrant, plus automatic response retrieval and
|
|
||||||
refreshable tokens. Only a few replacements need to be made:
|
|
||||||
|
|
||||||
* get_auth_response()['access_token'] ->
|
|
||||||
get_access_token(get_authorization_code())
|
|
||||||
* get_auth_response() ->
|
|
||||||
get_access_token(get_authorization_code()); get_cached_token()
|
|
||||||
* parse_response_token(url)['access_token'] ->
|
|
||||||
get_access_token(parse_response_code(url))
|
|
||||||
* parse_response_token(url) ->
|
|
||||||
get_access_token(parse_response_code(url)); get_cached_token()
|
|
||||||
|
|
||||||
The security concern in the Implicit Grant flow is that the token is
|
|
||||||
returned in the URL and can be intercepted through the browser. A
|
|
||||||
request with an authorization code and proof of origin could not be
|
|
||||||
easily intercepted without a compromised network.
|
|
||||||
"""
|
|
||||||
OAUTH_AUTHORIZE_URL = "https://accounts.spotify.com/authorize"
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
client_id=None,
|
|
||||||
redirect_uri=None,
|
|
||||||
state=None,
|
|
||||||
scope=None,
|
|
||||||
cache_path=None,
|
|
||||||
username=None,
|
|
||||||
show_dialog=False,
|
|
||||||
cache_handler=None):
|
|
||||||
""" Creates Auth Manager using the Implicit Grant flow
|
|
||||||
|
|
||||||
**See help(SpotifyImplicitGrant) for full Security Warning**
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
* client_id: Must be supplied or set as environment variable
|
|
||||||
* redirect_uri: Must be supplied or set as environment variable
|
|
||||||
* state: May be supplied, no verification is performed
|
|
||||||
* scope: Optional, either a string of scopes, or an iterable with elements of type
|
|
||||||
`Scope` or `str`. E.g.,
|
|
||||||
{Scope.user_modify_playback_state, Scope.user_library_read}
|
|
||||||
* cache_handler: An instance of the `CacheHandler` class to handle
|
|
||||||
getting and saving cached authorization tokens.
|
|
||||||
May be supplied, will otherwise use `CacheFileHandler`.
|
|
||||||
(takes precedence over `cache_path` and `username`)
|
|
||||||
* cache_path: (deprecated) May be supplied, will otherwise be generated
|
|
||||||
(takes precedence over `username`)
|
|
||||||
* username: (deprecated) May be supplied or set as environment variable
|
|
||||||
(will set `cache_path` to `.cache-{username}`)
|
|
||||||
* show_dialog: Interpreted as boolean
|
|
||||||
"""
|
|
||||||
logger.warning("The OAuth standard no longer recommends the Implicit "
|
|
||||||
"Grant Flow for client-side code. Use the SpotifyPKCE "
|
|
||||||
"auth manager instead of SpotifyImplicitGrant. For "
|
|
||||||
"more details and a guide to switching, see "
|
|
||||||
"help(SpotifyImplicitGrant).")
|
|
||||||
|
|
||||||
self.client_id = client_id
|
|
||||||
self.redirect_uri = redirect_uri
|
|
||||||
self.state = state
|
|
||||||
if username or cache_path:
|
|
||||||
warnings.warn("Specifying cache_path or username as arguments to " +
|
|
||||||
"SpotifyImplicitGrant will be deprecated. Instead, please create " +
|
|
||||||
"a CacheFileHandler instance with the desired cache_path and " +
|
|
||||||
"username and pass it to SpotifyImplicitGrant as the " +
|
|
||||||
"cache_handler. For example:\n\n" +
|
|
||||||
"\tfrom spotipy.oauth2 import CacheFileHandler\n" +
|
|
||||||
"\thandler = CacheFileHandler(cache_path=cache_path, " +
|
|
||||||
"username=username)\n" +
|
|
||||||
"\tsp = spotipy.SpotifyImplicitGrant(client_id, client_secret, " +
|
|
||||||
"redirect_uri, cache_handler=handler)",
|
|
||||||
DeprecationWarning
|
|
||||||
)
|
|
||||||
if cache_handler:
|
|
||||||
warnings.warn("A cache_handler has been specified along with a cache_path or " +
|
|
||||||
"username. The cache_path and username arguments will be ignored.")
|
|
||||||
if cache_handler:
|
|
||||||
assert issubclass(type(cache_handler), CacheHandler), \
|
|
||||||
"type(cache_handler): " + str(type(cache_handler)) + " != " + str(CacheHandler)
|
|
||||||
self.cache_handler = cache_handler
|
|
||||||
else:
|
|
||||||
username = (username or os.getenv(CLIENT_CREDS_ENV_VARS["client_username"]))
|
|
||||||
self.cache_handler = CacheFileHandler(
|
|
||||||
username=username,
|
|
||||||
cache_path=cache_path
|
|
||||||
)
|
|
||||||
self.scope = self._normalize_scope(scope)
|
|
||||||
self.show_dialog = show_dialog
|
|
||||||
self._session = None # As to not break inherited __del__
|
|
||||||
|
|
||||||
def validate_token(self, token_info):
|
|
||||||
if token_info is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# if scopes don't match, then bail
|
|
||||||
if "scope" not in token_info or not self._is_scope_subset(
|
|
||||||
self.scope, token_info["scope"]
|
|
||||||
):
|
|
||||||
return None
|
|
||||||
|
|
||||||
if self.is_token_expired(token_info):
|
|
||||||
return None
|
|
||||||
|
|
||||||
return token_info
|
|
||||||
|
|
||||||
def get_access_token(self,
|
|
||||||
state=None,
|
|
||||||
response=None,
|
|
||||||
check_cache=True):
|
|
||||||
""" Gets Auth Token from cache (preferred) or user interaction
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
* state: May be given, overrides (without changing) self.state
|
|
||||||
* response: URI with token, can break expiration checks
|
|
||||||
* check_cache: Interpreted as boolean
|
|
||||||
"""
|
|
||||||
if check_cache:
|
|
||||||
token_info = self.validate_token(self.cache_handler.get_cached_token())
|
|
||||||
if not (token_info is None or self.is_token_expired(token_info)):
|
|
||||||
return token_info["access_token"]
|
|
||||||
|
|
||||||
if response:
|
|
||||||
token_info = self.parse_response_token(response)
|
|
||||||
else:
|
|
||||||
token_info = self.get_auth_response(state)
|
|
||||||
token_info = self._add_custom_values_to_token_info(token_info)
|
|
||||||
self.cache_handler.save_token_to_cache(token_info)
|
|
||||||
|
|
||||||
return token_info["access_token"]
|
|
||||||
|
|
||||||
def get_authorize_url(self, state=None):
|
|
||||||
""" Gets the URL to use to authorize this app """
|
|
||||||
payload = {
|
|
||||||
"client_id": self.client_id,
|
|
||||||
"response_type": "token",
|
|
||||||
"redirect_uri": self.redirect_uri,
|
|
||||||
}
|
|
||||||
if self.scope:
|
|
||||||
payload["scope"] = self.scope
|
|
||||||
if state is None:
|
|
||||||
state = self.state
|
|
||||||
if state is not None:
|
|
||||||
payload["state"] = state
|
|
||||||
if self.show_dialog:
|
|
||||||
payload["show_dialog"] = True
|
|
||||||
|
|
||||||
urlparams = urllibparse.urlencode(payload)
|
|
||||||
|
|
||||||
return f"{self.OAUTH_AUTHORIZE_URL}?{urlparams}"
|
|
||||||
|
|
||||||
def parse_response_token(self, url, state=None):
|
|
||||||
""" Parse the response code in the given response url """
|
|
||||||
remote_state, token, t_type, exp_in = self.parse_auth_response_url(url)
|
|
||||||
if state is None:
|
|
||||||
state = self.state
|
|
||||||
if state is not None and remote_state != state:
|
|
||||||
raise SpotifyStateError(state, remote_state)
|
|
||||||
return {"access_token": token, "token_type": t_type,
|
|
||||||
"expires_in": exp_in, "state": state}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def parse_auth_response_url(url):
|
|
||||||
url_components = urlparse(url)
|
|
||||||
fragment_s = url_components.fragment
|
|
||||||
query_s = url_components.query
|
|
||||||
form = dict(i.split('=') for i
|
|
||||||
in (fragment_s or query_s or url).split('&'))
|
|
||||||
if "error" in form:
|
|
||||||
raise SpotifyOauthError(f"Received error from auth server: {form['error']}",
|
|
||||||
state=form["state"])
|
|
||||||
if "expires_in" in form:
|
|
||||||
form["expires_in"] = int(form["expires_in"])
|
|
||||||
return tuple(form.get(param) for param in ["state", "access_token",
|
|
||||||
"token_type", "expires_in"])
|
|
||||||
|
|
||||||
def _open_auth_url(self, state=None):
|
|
||||||
auth_url = self.get_authorize_url(state)
|
|
||||||
try:
|
|
||||||
webbrowser.open(auth_url)
|
|
||||||
logger.info("Opened %s in your browser", auth_url)
|
|
||||||
except webbrowser.Error:
|
|
||||||
logger.error("Please navigate here: %s", auth_url)
|
|
||||||
|
|
||||||
def get_auth_response(self, state=None):
|
|
||||||
""" Gets a new auth **token** with user interaction """
|
|
||||||
logger.info('User authentication requires interaction with your '
|
|
||||||
'web browser. Once you enter your credentials and '
|
|
||||||
'give authorization, you will be redirected to '
|
|
||||||
'a url. Paste that url you were directed to to '
|
|
||||||
'complete the authorization.')
|
|
||||||
|
|
||||||
redirect_info = urlparse(self.redirect_uri)
|
|
||||||
redirect_host, redirect_port = get_host_port(redirect_info.netloc)
|
|
||||||
# Implicit Grant tokens are returned in a hash fragment
|
|
||||||
# which is only available to the browser. Therefore, interactive
|
|
||||||
# URL retrieval is required.
|
|
||||||
if (redirect_host in ("127.0.0.1", "localhost")
|
|
||||||
and redirect_info.scheme == "http" and redirect_port):
|
|
||||||
logger.warning('Using a local redirect URI with a '
|
|
||||||
'port, likely expecting automatic '
|
|
||||||
'retrieval. Due to technical limitations, '
|
|
||||||
'the authentication token cannot be '
|
|
||||||
'automatically retrieved and must be '
|
|
||||||
'copied and pasted.')
|
|
||||||
|
|
||||||
self._open_auth_url(state)
|
|
||||||
logger.info('Paste that url you were directed to in order to '
|
|
||||||
'complete the authorization')
|
|
||||||
response = SpotifyImplicitGrant._get_user_input("Enter the URL you "
|
|
||||||
"were redirected to: ")
|
|
||||||
return self.parse_response_token(response, state)
|
|
||||||
|
|
||||||
def _add_custom_values_to_token_info(self, token_info):
|
|
||||||
"""
|
|
||||||
Store some values that aren't directly provided by a Web API
|
|
||||||
response.
|
|
||||||
"""
|
|
||||||
token_info["expires_at"] = int(time.time()) + token_info["expires_in"]
|
|
||||||
token_info["scope"] = self.scope
|
|
||||||
return token_info
|
|
||||||
|
|
||||||
def get_cached_token(self):
|
|
||||||
warnings.warn("Calling get_cached_token directly on the SpotifyImplicitGrant " +
|
|
||||||
"object will be deprecated. Instead, please specify a " +
|
|
||||||
"CacheFileHandler instance as the cache_handler in SpotifyOAuth " +
|
|
||||||
"and use the CacheFileHandler's get_cached_token method. " +
|
|
||||||
"You can replace:\n\tsp.get_cached_token()" +
|
|
||||||
"\n\nWith:\n\tsp.validate_token(sp.cache_handler.get_cached_token())",
|
|
||||||
DeprecationWarning
|
|
||||||
)
|
|
||||||
return self.validate_token(self.cache_handler.get_cached_token())
|
|
||||||
|
|
||||||
def _save_token_info(self, token_info):
|
|
||||||
warnings.warn("Calling _save_token_info directly on the SpotifyImplicitGrant " +
|
|
||||||
"object will be deprecated. Instead, please specify a " +
|
|
||||||
"CacheFileHandler instance as the cache_handler in SpotifyOAuth " +
|
|
||||||
"and use the CacheFileHandler's save_token_to_cache method.",
|
|
||||||
DeprecationWarning
|
|
||||||
)
|
|
||||||
self.cache_handler.save_token_to_cache(token_info)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class RequestHandler(BaseHTTPRequestHandler):
|
class RequestHandler(BaseHTTPRequestHandler):
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
|
|||||||
137
spotipy/util.py
137
spotipy/util.py
@ -2,7 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
""" Shows a user's playlists. This needs to be authenticated via OAuth. """
|
""" Shows a user's playlists. This needs to be authenticated via OAuth. """
|
||||||
|
|
||||||
__all__ = ["CLIENT_CREDS_ENV_VARS", "prompt_for_user_token"]
|
__all__ = ["CLIENT_CREDS_ENV_VARS"]
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
@ -23,91 +23,6 @@ CLIENT_CREDS_ENV_VARS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def prompt_for_user_token(
|
|
||||||
username=None,
|
|
||||||
scope=None,
|
|
||||||
client_id=None,
|
|
||||||
client_secret=None,
|
|
||||||
redirect_uri=None,
|
|
||||||
cache_path=None,
|
|
||||||
oauth_manager=None,
|
|
||||||
show_dialog=False
|
|
||||||
):
|
|
||||||
warnings.warn(
|
|
||||||
"'prompt_for_user_token' is deprecated."
|
|
||||||
"Use the following instead: "
|
|
||||||
" auth_manager=SpotifyOAuth(scope=scope)"
|
|
||||||
" spotipy.Spotify(auth_manager=auth_manager)",
|
|
||||||
DeprecationWarning
|
|
||||||
)
|
|
||||||
"""Prompt the user to login if necessary and returns a user token
|
|
||||||
suitable for use with the spotipy.Spotify constructor.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- username - the Spotify username. (optional)
|
|
||||||
- scope - the desired scope of the request. (optional)
|
|
||||||
- client_id - the client ID of your app. (required)
|
|
||||||
- client_secret - the client secret of your app. (required)
|
|
||||||
- redirect_uri - the redirect URI of your app. (required)
|
|
||||||
- cache_path - path to location to save tokens. (required)
|
|
||||||
- oauth_manager - OAuth manager object. (optional)
|
|
||||||
- show_dialog - If True, a login prompt always shows or defaults to False. (optional)
|
|
||||||
"""
|
|
||||||
if not oauth_manager:
|
|
||||||
if not client_id:
|
|
||||||
client_id = os.getenv("SPOTIPY_CLIENT_ID")
|
|
||||||
|
|
||||||
if not client_secret:
|
|
||||||
client_secret = os.getenv("SPOTIPY_CLIENT_SECRET")
|
|
||||||
|
|
||||||
if not redirect_uri:
|
|
||||||
redirect_uri = os.getenv("SPOTIPY_REDIRECT_URI")
|
|
||||||
|
|
||||||
if not client_id:
|
|
||||||
LOGGER.warning(
|
|
||||||
"""
|
|
||||||
You need to set your Spotify API credentials.
|
|
||||||
You can do this by setting environment variables like so:
|
|
||||||
|
|
||||||
export SPOTIPY_CLIENT_ID='your-spotify-client-id'
|
|
||||||
export SPOTIPY_CLIENT_SECRET='your-spotify-client-secret'
|
|
||||||
export SPOTIPY_REDIRECT_URI='your-app-redirect-url'
|
|
||||||
|
|
||||||
Get your credentials at
|
|
||||||
https://developer.spotify.com/my-applications
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
raise spotipy.SpotifyException(550, -1, "no credentials set")
|
|
||||||
|
|
||||||
sp_oauth = oauth_manager or spotipy.SpotifyOAuth(
|
|
||||||
client_id,
|
|
||||||
client_secret,
|
|
||||||
redirect_uri,
|
|
||||||
scope=scope,
|
|
||||||
cache_path=cache_path,
|
|
||||||
username=username,
|
|
||||||
show_dialog=show_dialog
|
|
||||||
)
|
|
||||||
|
|
||||||
# try to get a valid token for this user, from the cache,
|
|
||||||
# if not in the cache, then create a new (this will send
|
|
||||||
# the user to a web page where they can authorize this app)
|
|
||||||
|
|
||||||
token_info = sp_oauth.validate_token(sp_oauth.cache_handler.get_cached_token())
|
|
||||||
|
|
||||||
if not token_info:
|
|
||||||
code = sp_oauth.get_auth_response()
|
|
||||||
token = sp_oauth.get_access_token(code, as_dict=False)
|
|
||||||
else:
|
|
||||||
return token_info["access_token"]
|
|
||||||
|
|
||||||
# Auth'ed API request
|
|
||||||
if token:
|
|
||||||
return token
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_host_port(netloc):
|
def get_host_port(netloc):
|
||||||
""" Split the network location string into host and port and returns a tuple
|
""" Split the network location string into host and port and returns a tuple
|
||||||
where the host is a string and the the port is an integer.
|
where the host is a string and the the port is an integer.
|
||||||
@ -123,53 +38,3 @@ def get_host_port(netloc):
|
|||||||
port = None
|
port = None
|
||||||
|
|
||||||
return host, port
|
return host, port
|
||||||
|
|
||||||
|
|
||||||
def normalize_scope(scope):
|
|
||||||
"""Normalize the scope to verify that it is a list or tuple. A string
|
|
||||||
input will split the string by commas to create a list of scopes.
|
|
||||||
A list or tuple input is used directly.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- scope - a string representing scopes separated by commas,
|
|
||||||
or a list/tuple of scopes.
|
|
||||||
"""
|
|
||||||
if scope:
|
|
||||||
if isinstance(scope, str):
|
|
||||||
scopes = scope.split(',')
|
|
||||||
elif isinstance(scope, list) or isinstance(scope, tuple):
|
|
||||||
scopes = scope
|
|
||||||
else:
|
|
||||||
raise Exception(
|
|
||||||
"Unsupported scope value, please either provide a list of scopes, "
|
|
||||||
"or a string of scopes separated by commas."
|
|
||||||
)
|
|
||||||
return " ".join(sorted(scopes))
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class Retry(urllib3.Retry):
|
|
||||||
"""
|
|
||||||
Custom class for printing a warning when a rate/request limit is reached.
|
|
||||||
"""
|
|
||||||
def increment(
|
|
||||||
self,
|
|
||||||
method: str | None = None,
|
|
||||||
url: str | None = None,
|
|
||||||
response: urllib3.BaseHTTPResponse | None = None,
|
|
||||||
error: Exception | None = None,
|
|
||||||
_pool: urllib3.connectionpool.ConnectionPool | None = None,
|
|
||||||
_stacktrace: TracebackType | None = None,
|
|
||||||
) -> urllib3.Retry:
|
|
||||||
if response:
|
|
||||||
retry_header = response.headers.get("Retry-After")
|
|
||||||
if self.is_retry(method, response.status, bool(retry_header)):
|
|
||||||
logging.warning("Your application has reached a rate/request limit. "
|
|
||||||
f"Retry will occur after: {retry_header}")
|
|
||||||
return super().increment(method,
|
|
||||||
url,
|
|
||||||
response=response,
|
|
||||||
error=error,
|
|
||||||
_pool=_pool,
|
|
||||||
_stacktrace=_stacktrace)
|
|
||||||
|
|||||||
@ -70,7 +70,8 @@ class AuthTestSpotipy(unittest.TestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(self):
|
def setUpClass(self):
|
||||||
self.spotify = Spotify(
|
self.spotify = Spotify(
|
||||||
client_credentials_manager=SpotifyClientCredentials())
|
auth_manager=SpotifyClientCredentials()
|
||||||
|
)
|
||||||
self.spotify.trace = False
|
self.spotify.trace = False
|
||||||
|
|
||||||
def test_audio_analysis(self):
|
def test_audio_analysis(self):
|
||||||
@ -341,9 +342,9 @@ class AuthTestSpotipy(unittest.TestCase):
|
|||||||
self.assertTrue(find_album())
|
self.assertTrue(find_album())
|
||||||
|
|
||||||
def test_search_timeout(self):
|
def test_search_timeout(self):
|
||||||
client_credentials_manager = SpotifyClientCredentials()
|
auth_manager = SpotifyClientCredentials()
|
||||||
sp = spotipy.Spotify(requests_timeout=0.01,
|
sp = spotipy.Spotify(requests_timeout=0.01,
|
||||||
client_credentials_manager=client_credentials_manager)
|
auth_manager=auth_manager)
|
||||||
|
|
||||||
# depending on the timing or bandwidth, this raises a timeout or connection error
|
# depending on the timing or bandwidth, this raises a timeout or connection error
|
||||||
self.assertRaises((requests.exceptions.Timeout, requests.exceptions.ConnectionError),
|
self.assertRaises((requests.exceptions.Timeout, requests.exceptions.ConnectionError),
|
||||||
@ -352,17 +353,15 @@ class AuthTestSpotipy(unittest.TestCase):
|
|||||||
@unittest.skip("flaky test, need a better method to test retries")
|
@unittest.skip("flaky test, need a better method to test retries")
|
||||||
def test_max_retries_reached_get(self):
|
def test_max_retries_reached_get(self):
|
||||||
spotify_no_retry = Spotify(
|
spotify_no_retry = Spotify(
|
||||||
client_credentials_manager=SpotifyClientCredentials(),
|
auth_manager=SpotifyClientCredentials(),
|
||||||
retries=0)
|
retries=0)
|
||||||
i = 0
|
for i in range(100):
|
||||||
while i < 100:
|
|
||||||
try:
|
try:
|
||||||
spotify_no_retry.search(q='foo')
|
spotify_no_retry.search(q='foo')
|
||||||
except SpotifyException as e:
|
except SpotifyException as e:
|
||||||
self.assertIsInstance(e, SpotifyException)
|
self.assertIsInstance(e, SpotifyException)
|
||||||
self.assertEqual(e.http_status, 429)
|
self.assertEqual(e.http_status, 429)
|
||||||
return
|
return
|
||||||
i += 1
|
|
||||||
self.fail()
|
self.fail()
|
||||||
|
|
||||||
def test_album_search(self):
|
def test_album_search(self):
|
||||||
@ -460,7 +459,7 @@ class AuthTestSpotipy(unittest.TestCase):
|
|||||||
sess = requests.Session()
|
sess = requests.Session()
|
||||||
sess.headers["user-agent"] = "spotipy-test"
|
sess.headers["user-agent"] = "spotipy-test"
|
||||||
with_custom_session = spotipy.Spotify(
|
with_custom_session = spotipy.Spotify(
|
||||||
client_credentials_manager=SpotifyClientCredentials(),
|
auth_manager=SpotifyClientCredentials(),
|
||||||
requests_session=sess)
|
requests_session=sess)
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
with_custom_session.user(
|
with_custom_session.user(
|
||||||
@ -469,7 +468,7 @@ class AuthTestSpotipy(unittest.TestCase):
|
|||||||
|
|
||||||
def test_force_no_requests_session(self):
|
def test_force_no_requests_session(self):
|
||||||
with_no_session = spotipy.Spotify(
|
with_no_session = spotipy.Spotify(
|
||||||
client_credentials_manager=SpotifyClientCredentials(),
|
auth_manager=SpotifyClientCredentials(),
|
||||||
requests_session=False)
|
requests_session=False)
|
||||||
self.assertNotIsInstance(with_no_session._session, requests.Session)
|
self.assertNotIsInstance(with_no_session._session, requests.Session)
|
||||||
user = with_no_session.user(user="akx")
|
user = with_no_session.user(user="akx")
|
||||||
|
|||||||
@ -2,15 +2,30 @@ import os
|
|||||||
|
|
||||||
from spotipy import (
|
from spotipy import (
|
||||||
CLIENT_CREDS_ENV_VARS as CCEV,
|
CLIENT_CREDS_ENV_VARS as CCEV,
|
||||||
prompt_for_user_token,
|
|
||||||
Spotify,
|
Spotify,
|
||||||
SpotifyException,
|
SpotifyException,
|
||||||
SpotifyImplicitGrant,
|
SpotifyOAuth,
|
||||||
SpotifyPKCE
|
SpotifyPKCE,
|
||||||
|
CacheFileHandler
|
||||||
)
|
)
|
||||||
import unittest
|
import unittest
|
||||||
from tests import helpers
|
from tests import helpers
|
||||||
|
|
||||||
|
def _make_spotify(scopes=None, retries=None):
|
||||||
|
|
||||||
|
retries = retries or Spotify.max_retries
|
||||||
|
|
||||||
|
auth_manager = SpotifyOAuth(
|
||||||
|
scope=scopes,
|
||||||
|
cache_handler=CacheFileHandler()
|
||||||
|
)
|
||||||
|
|
||||||
|
spotify = Spotify(
|
||||||
|
auth_manager=auth_manager,
|
||||||
|
retries=retries
|
||||||
|
)
|
||||||
|
|
||||||
|
return spotify
|
||||||
|
|
||||||
class SpotipyPlaylistApiTest(unittest.TestCase):
|
class SpotipyPlaylistApiTest(unittest.TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -48,10 +63,10 @@ class SpotipyPlaylistApiTest(unittest.TestCase):
|
|||||||
'user-read-playback-state'
|
'user-read-playback-state'
|
||||||
)
|
)
|
||||||
|
|
||||||
token = prompt_for_user_token(cls.username, scope=scope)
|
cls.spotify = _make_spotify(scopes=scope)
|
||||||
|
cls.spotify_no_retry = _make_spotify(scopes=scope, retries=0)
|
||||||
|
|
||||||
|
|
||||||
cls.spotify = Spotify(auth=token)
|
|
||||||
cls.spotify_no_retry = Spotify(auth=token, retries=0)
|
|
||||||
cls.new_playlist_name = 'spotipy-playlist-test'
|
cls.new_playlist_name = 'spotipy-playlist-test'
|
||||||
cls.new_playlist = helpers.get_spotify_playlist(
|
cls.new_playlist = helpers.get_spotify_playlist(
|
||||||
cls.spotify, cls.new_playlist_name, cls.username) or \
|
cls.spotify, cls.new_playlist_name, cls.username) or \
|
||||||
@ -201,17 +216,6 @@ class SpotipyPlaylistApiTest(unittest.TestCase):
|
|||||||
return
|
return
|
||||||
self.fail()
|
self.fail()
|
||||||
|
|
||||||
def test_deprecated_starred(self):
|
|
||||||
pl = self.spotify.user_playlist(self.username)
|
|
||||||
self.assertTrue(pl["tracks"] is None)
|
|
||||||
self.assertTrue(pl["owner"] is None)
|
|
||||||
|
|
||||||
def test_deprecated_user_playlist(self):
|
|
||||||
# Test without user due to change from
|
|
||||||
# https://developer.spotify.com/community/news/2018/06/12/changes-to-playlist-uris/
|
|
||||||
pl = self.spotify.user_playlist(None, self.new_playlist['id'])
|
|
||||||
self.assertEqual(pl["tracks"]["total"], 0)
|
|
||||||
|
|
||||||
|
|
||||||
class SpotipyLibraryApiTests(unittest.TestCase):
|
class SpotipyLibraryApiTests(unittest.TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -240,10 +244,7 @@ class SpotipyLibraryApiTests(unittest.TestCase):
|
|||||||
'ugc-image-upload '
|
'ugc-image-upload '
|
||||||
'user-read-playback-state'
|
'user-read-playback-state'
|
||||||
)
|
)
|
||||||
|
cls.spotify = _make_spotify(scopes=scope)
|
||||||
token = prompt_for_user_token(cls.username, scope=scope)
|
|
||||||
|
|
||||||
cls.spotify = Spotify(auth=token)
|
|
||||||
|
|
||||||
def test_track_bad_id(self):
|
def test_track_bad_id(self):
|
||||||
with self.assertRaises(SpotifyException):
|
with self.assertRaises(SpotifyException):
|
||||||
@ -330,9 +331,7 @@ class SpotipyUserApiTests(unittest.TestCase):
|
|||||||
'user-read-playback-state'
|
'user-read-playback-state'
|
||||||
)
|
)
|
||||||
|
|
||||||
token = prompt_for_user_token(cls.username, scope=scope)
|
cls.spotify = _make_spotify(scopes=scope)
|
||||||
|
|
||||||
cls.spotify = Spotify(auth=token)
|
|
||||||
|
|
||||||
def test_basic_user_profile(self):
|
def test_basic_user_profile(self):
|
||||||
user = self.spotify.user(self.username)
|
user = self.spotify.user(self.username)
|
||||||
@ -360,9 +359,7 @@ class SpotipyUserApiTests(unittest.TestCase):
|
|||||||
class SpotipyBrowseApiTests(unittest.TestCase):
|
class SpotipyBrowseApiTests(unittest.TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
username = os.getenv(CCEV['client_username'])
|
cls.spotify = _make_spotify()
|
||||||
token = prompt_for_user_token(username)
|
|
||||||
cls.spotify = Spotify(auth=token)
|
|
||||||
|
|
||||||
def test_category(self):
|
def test_category(self):
|
||||||
rock_cat_id = '0JQ5DAqbMKFDXXwE9BDJAr'
|
rock_cat_id = '0JQ5DAqbMKFDXXwE9BDJAr'
|
||||||
@ -455,9 +452,7 @@ class SpotipyFollowApiTests(unittest.TestCase):
|
|||||||
'user-read-playback-state'
|
'user-read-playback-state'
|
||||||
)
|
)
|
||||||
|
|
||||||
token = prompt_for_user_token(cls.username, scope=scope)
|
cls.spotify = _make_spotify(scopes=scope)
|
||||||
|
|
||||||
cls.spotify = Spotify(auth=token)
|
|
||||||
|
|
||||||
def test_current_user_follows(self):
|
def test_current_user_follows(self):
|
||||||
response = self.spotify.current_user_followed_artists()
|
response = self.spotify.current_user_followed_artists()
|
||||||
@ -510,9 +505,7 @@ class SpotipyPlayerApiTests(unittest.TestCase):
|
|||||||
'user-read-playback-state'
|
'user-read-playback-state'
|
||||||
)
|
)
|
||||||
|
|
||||||
token = prompt_for_user_token(cls.username, scope=scope)
|
cls.spotify = _make_spotify(scopes=scope)
|
||||||
|
|
||||||
cls.spotify = Spotify(auth=token)
|
|
||||||
|
|
||||||
def test_devices(self):
|
def test_devices(self):
|
||||||
# No devices playing by default
|
# No devices playing by default
|
||||||
@ -526,23 +519,6 @@ class SpotipyPlayerApiTests(unittest.TestCase):
|
|||||||
# not much more to test if account is inactive and has no recently played tracks
|
# not much more to test if account is inactive and has no recently played tracks
|
||||||
|
|
||||||
|
|
||||||
class SpotipyImplicitGrantTests(unittest.TestCase):
|
|
||||||
@classmethod
|
|
||||||
def setUpClass(cls):
|
|
||||||
scope = (
|
|
||||||
'user-follow-read '
|
|
||||||
'user-follow-modify '
|
|
||||||
)
|
|
||||||
auth_manager = SpotifyImplicitGrant(scope=scope,
|
|
||||||
cache_path=".cache-implicittest")
|
|
||||||
cls.spotify = Spotify(auth_manager=auth_manager)
|
|
||||||
|
|
||||||
def test_current_user(self):
|
|
||||||
c_user = self.spotify.current_user()
|
|
||||||
user = self.spotify.user(c_user['id'])
|
|
||||||
self.assertEqual(c_user['display_name'], user['display_name'])
|
|
||||||
|
|
||||||
|
|
||||||
class SpotifyPKCETests(unittest.TestCase):
|
class SpotifyPKCETests(unittest.TestCase):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -551,7 +527,8 @@ class SpotifyPKCETests(unittest.TestCase):
|
|||||||
'user-follow-read '
|
'user-follow-read '
|
||||||
'user-follow-modify '
|
'user-follow-modify '
|
||||||
)
|
)
|
||||||
auth_manager = SpotifyPKCE(scope=scope, cache_path=".cache-pkcetest")
|
cache_handler = CacheFileHandler(cache_path=".cache-pkcetest")
|
||||||
|
auth_manager = SpotifyPKCE(scope=scope, cache_handler=cache_handler)
|
||||||
cls.spotify = Spotify(auth_manager=auth_manager)
|
cls.spotify = Spotify(auth_manager=auth_manager)
|
||||||
|
|
||||||
def test_current_user(self):
|
def test_current_user(self):
|
||||||
|
|||||||
@ -5,10 +5,11 @@ import unittest
|
|||||||
import unittest.mock as mock
|
import unittest.mock as mock
|
||||||
import urllib.parse as urllibparse
|
import urllib.parse as urllibparse
|
||||||
|
|
||||||
from spotipy import SpotifyOAuth, SpotifyImplicitGrant, SpotifyPKCE
|
from spotipy import SpotifyOAuth, SpotifyPKCE
|
||||||
from spotipy.cache_handler import MemoryCacheHandler
|
from spotipy.cache_handler import CacheHandler
|
||||||
from spotipy.oauth2 import SpotifyClientCredentials, SpotifyOauthError
|
from spotipy.oauth2 import SpotifyClientCredentials, SpotifyOauthError
|
||||||
from spotipy.oauth2 import SpotifyStateError
|
from spotipy.oauth2 import SpotifyStateError
|
||||||
|
from spotipy import CacheFileHandler
|
||||||
|
|
||||||
patch = mock.patch
|
patch = mock.patch
|
||||||
DEFAULT = mock.DEFAULT
|
DEFAULT = mock.DEFAULT
|
||||||
@ -38,10 +39,6 @@ def _make_oauth(*args, **kwargs):
|
|||||||
return SpotifyOAuth("CLID", "CLISEC", "REDIR", "STATE", *args, **kwargs)
|
return SpotifyOAuth("CLID", "CLISEC", "REDIR", "STATE", *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def _make_implicitgrantauth(*args, **kwargs):
|
|
||||||
return SpotifyImplicitGrant("CLID", "REDIR", "STATE", *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def _make_pkceauth(*args, **kwargs):
|
def _make_pkceauth(*args, **kwargs):
|
||||||
return SpotifyPKCE("CLID", "REDIR", "STATE", *args, **kwargs)
|
return SpotifyPKCE("CLID", "REDIR", "STATE", *args, **kwargs)
|
||||||
|
|
||||||
@ -60,13 +57,12 @@ class OAuthCacheTest(unittest.TestCase):
|
|||||||
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=False))
|
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=False))
|
||||||
is_token_expired.return_value = False
|
is_token_expired.return_value = False
|
||||||
|
|
||||||
spot = _make_oauth(scope, path)
|
cache_handler = CacheFileHandler(cache_path=path)
|
||||||
|
spot = _make_oauth(scope, cache_handler=cache_handler)
|
||||||
cached_tok = spot.validate_token(spot.cache_handler.get_cached_token())
|
cached_tok = spot.validate_token(spot.cache_handler.get_cached_token())
|
||||||
cached_tok_legacy = spot.get_cached_token()
|
|
||||||
|
|
||||||
opener.assert_called_with(path)
|
opener.assert_called_with(path)
|
||||||
self.assertIsNotNone(cached_tok)
|
self.assertIsNotNone(cached_tok)
|
||||||
self.assertIsNotNone(cached_tok_legacy)
|
|
||||||
self.assertEqual(refresh_access_token.call_count, 0)
|
self.assertEqual(refresh_access_token.call_count, 0)
|
||||||
|
|
||||||
@patch.multiple(SpotifyOAuth,
|
@patch.multiple(SpotifyOAuth,
|
||||||
@ -83,7 +79,8 @@ class OAuthCacheTest(unittest.TestCase):
|
|||||||
opener.return_value = token_file
|
opener.return_value = token_file
|
||||||
refresh_access_token.return_value = fresh_tok
|
refresh_access_token.return_value = fresh_tok
|
||||||
|
|
||||||
spot = _make_oauth(scope, path)
|
cache_handler = CacheFileHandler(cache_path=path)
|
||||||
|
spot = _make_oauth(scope, cache_handler=cache_handler)
|
||||||
spot.validate_token(spot.cache_handler.get_cached_token())
|
spot.validate_token(spot.cache_handler.get_cached_token())
|
||||||
|
|
||||||
is_token_expired.assert_called_with(expired_tok)
|
is_token_expired.assert_called_with(expired_tok)
|
||||||
@ -103,7 +100,9 @@ class OAuthCacheTest(unittest.TestCase):
|
|||||||
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=False))
|
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=False))
|
||||||
is_token_expired.return_value = False
|
is_token_expired.return_value = False
|
||||||
|
|
||||||
spot = _make_oauth(requested_scope, path)
|
cache_handler = CacheFileHandler(cache_path=path)
|
||||||
|
|
||||||
|
spot = _make_oauth(requested_scope, cache_handler=cache_handler)
|
||||||
cached_tok = spot.validate_token(spot.cache_handler.get_cached_token())
|
cached_tok = spot.validate_token(spot.cache_handler.get_cached_token())
|
||||||
|
|
||||||
opener.assert_called_with(path)
|
opener.assert_called_with(path)
|
||||||
@ -119,27 +118,13 @@ class OAuthCacheTest(unittest.TestCase):
|
|||||||
fi = _fake_file()
|
fi = _fake_file()
|
||||||
opener.return_value = fi
|
opener.return_value = fi
|
||||||
|
|
||||||
spot = SpotifyOAuth("CLID", "CLISEC", "REDIR", "STATE", scope, path)
|
cache_handler = CacheFileHandler(cache_path=path)
|
||||||
|
spot = SpotifyOAuth("CLID", "CLISEC", "REDIR", "STATE", scope, cache_handler=cache_handler)
|
||||||
spot.cache_handler.save_token_to_cache(tok)
|
spot.cache_handler.save_token_to_cache(tok)
|
||||||
|
|
||||||
opener.assert_called_with(path, 'w')
|
opener.assert_called_with(path, 'w')
|
||||||
self.assertTrue(fi.write.called)
|
self.assertTrue(fi.write.called)
|
||||||
|
|
||||||
@patch('spotipy.cache_handler.open', create=True)
|
|
||||||
def test_saves_to_cache_path_legacy(self, opener):
|
|
||||||
scope = "playlist-modify-private"
|
|
||||||
path = ".cache-username"
|
|
||||||
tok = _make_fake_token(1, 1, scope)
|
|
||||||
|
|
||||||
fi = _fake_file()
|
|
||||||
opener.return_value = fi
|
|
||||||
|
|
||||||
spot = SpotifyOAuth("CLID", "CLISEC", "REDIR", "STATE", scope, path)
|
|
||||||
spot._save_token_info(tok)
|
|
||||||
|
|
||||||
opener.assert_called_with(path, 'w')
|
|
||||||
self.assertTrue(fi.write.called)
|
|
||||||
|
|
||||||
def test_cache_handler(self):
|
def test_cache_handler(self):
|
||||||
scope = "playlist-modify-private"
|
scope = "playlist-modify-private"
|
||||||
tok = _make_fake_token(1, 1, scope)
|
tok = _make_fake_token(1, 1, scope)
|
||||||
@ -243,142 +228,6 @@ class TestSpotifyClientCredentials(unittest.TestCase):
|
|||||||
oauth.get_access_token(check_cache=False)
|
oauth.get_access_token(check_cache=False)
|
||||||
self.assertEqual(error.exception.error, 'invalid_client')
|
self.assertEqual(error.exception.error, 'invalid_client')
|
||||||
|
|
||||||
|
|
||||||
class ImplicitGrantCacheTest(unittest.TestCase):
|
|
||||||
|
|
||||||
@patch.object(SpotifyImplicitGrant, "is_token_expired", DEFAULT)
|
|
||||||
@patch('spotipy.cache_handler.open', create=True)
|
|
||||||
def test_gets_from_cache_path(self, opener, is_token_expired):
|
|
||||||
scope = "playlist-modify-private"
|
|
||||||
path = ".cache-username"
|
|
||||||
tok = _make_fake_token(1, 1, scope)
|
|
||||||
|
|
||||||
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=False))
|
|
||||||
is_token_expired.return_value = False
|
|
||||||
|
|
||||||
spot = _make_implicitgrantauth(scope, path)
|
|
||||||
cached_tok = spot.cache_handler.get_cached_token()
|
|
||||||
cached_tok_legacy = spot.get_cached_token()
|
|
||||||
|
|
||||||
opener.assert_called_with(path)
|
|
||||||
self.assertIsNotNone(cached_tok)
|
|
||||||
self.assertIsNotNone(cached_tok_legacy)
|
|
||||||
|
|
||||||
@patch.object(SpotifyImplicitGrant, "is_token_expired", DEFAULT)
|
|
||||||
@patch('spotipy.cache_handler.open', create=True)
|
|
||||||
def test_expired_token_returns_none(self, opener, is_token_expired):
|
|
||||||
scope = "playlist-modify-private"
|
|
||||||
path = ".cache-username"
|
|
||||||
expired_tok = _make_fake_token(0, None, scope)
|
|
||||||
|
|
||||||
token_file = _token_file(json.dumps(expired_tok, ensure_ascii=False))
|
|
||||||
opener.return_value = token_file
|
|
||||||
|
|
||||||
spot = _make_implicitgrantauth(scope, path)
|
|
||||||
cached_tok = spot.validate_token(spot.cache_handler.get_cached_token())
|
|
||||||
|
|
||||||
is_token_expired.assert_called_with(expired_tok)
|
|
||||||
opener.assert_any_call(path)
|
|
||||||
self.assertIsNone(cached_tok)
|
|
||||||
|
|
||||||
@patch.object(SpotifyImplicitGrant, "is_token_expired", DEFAULT)
|
|
||||||
@patch('spotipy.cache_handler.open', create=True)
|
|
||||||
def test_badly_scoped_token_bails(self, opener, is_token_expired):
|
|
||||||
token_scope = "playlist-modify-public"
|
|
||||||
requested_scope = "playlist-modify-private"
|
|
||||||
path = ".cache-username"
|
|
||||||
tok = _make_fake_token(1, 1, token_scope)
|
|
||||||
|
|
||||||
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=False))
|
|
||||||
is_token_expired.return_value = False
|
|
||||||
|
|
||||||
spot = _make_implicitgrantauth(requested_scope, path)
|
|
||||||
cached_tok = spot.validate_token(spot.cache_handler.get_cached_token())
|
|
||||||
|
|
||||||
opener.assert_called_with(path)
|
|
||||||
self.assertIsNone(cached_tok)
|
|
||||||
|
|
||||||
@patch('spotipy.cache_handler.open', create=True)
|
|
||||||
def test_saves_to_cache_path(self, opener):
|
|
||||||
scope = "playlist-modify-private"
|
|
||||||
path = ".cache-username"
|
|
||||||
tok = _make_fake_token(1, 1, scope)
|
|
||||||
|
|
||||||
fi = _fake_file()
|
|
||||||
opener.return_value = fi
|
|
||||||
|
|
||||||
spot = SpotifyImplicitGrant("CLID", "REDIR", "STATE", scope, path)
|
|
||||||
spot.cache_handler.save_token_to_cache(tok)
|
|
||||||
|
|
||||||
opener.assert_called_with(path, 'w')
|
|
||||||
self.assertTrue(fi.write.called)
|
|
||||||
|
|
||||||
@patch('spotipy.cache_handler.open', create=True)
|
|
||||||
def test_saves_to_cache_path_legacy(self, opener):
|
|
||||||
scope = "playlist-modify-private"
|
|
||||||
path = ".cache-username"
|
|
||||||
tok = _make_fake_token(1, 1, scope)
|
|
||||||
|
|
||||||
fi = _fake_file()
|
|
||||||
opener.return_value = fi
|
|
||||||
|
|
||||||
spot = SpotifyImplicitGrant("CLID", "REDIR", "STATE", scope, path)
|
|
||||||
spot._save_token_info(tok)
|
|
||||||
|
|
||||||
opener.assert_called_with(path, 'w')
|
|
||||||
self.assertTrue(fi.write.called)
|
|
||||||
|
|
||||||
|
|
||||||
class TestSpotifyImplicitGrant(unittest.TestCase):
|
|
||||||
|
|
||||||
def test_get_authorize_url_doesnt_pass_state_by_default(self):
|
|
||||||
auth = SpotifyImplicitGrant("CLID", "REDIR")
|
|
||||||
|
|
||||||
url = auth.get_authorize_url()
|
|
||||||
|
|
||||||
parsed_url = urllibparse.urlparse(url)
|
|
||||||
parsed_qs = urllibparse.parse_qs(parsed_url.query)
|
|
||||||
self.assertNotIn('state', parsed_qs)
|
|
||||||
|
|
||||||
def test_get_authorize_url_passes_state_from_constructor(self):
|
|
||||||
state = "STATE"
|
|
||||||
auth = SpotifyImplicitGrant("CLID", "REDIR", state)
|
|
||||||
|
|
||||||
url = auth.get_authorize_url()
|
|
||||||
|
|
||||||
parsed_url = urllibparse.urlparse(url)
|
|
||||||
parsed_qs = urllibparse.parse_qs(parsed_url.query)
|
|
||||||
self.assertEqual(parsed_qs['state'][0], state)
|
|
||||||
|
|
||||||
def test_get_authorize_url_passes_state_from_func_call(self):
|
|
||||||
state = "STATE"
|
|
||||||
auth = SpotifyImplicitGrant("CLID", "REDIR", "NOT STATE")
|
|
||||||
|
|
||||||
url = auth.get_authorize_url(state=state)
|
|
||||||
|
|
||||||
parsed_url = urllibparse.urlparse(url)
|
|
||||||
parsed_qs = urllibparse.parse_qs(parsed_url.query)
|
|
||||||
self.assertEqual(parsed_qs['state'][0], state)
|
|
||||||
|
|
||||||
def test_get_authorize_url_does_not_show_dialog_by_default(self):
|
|
||||||
auth = SpotifyImplicitGrant("CLID", "REDIR")
|
|
||||||
|
|
||||||
url = auth.get_authorize_url()
|
|
||||||
|
|
||||||
parsed_url = urllibparse.urlparse(url)
|
|
||||||
parsed_qs = urllibparse.parse_qs(parsed_url.query)
|
|
||||||
self.assertNotIn('show_dialog', parsed_qs)
|
|
||||||
|
|
||||||
def test_get_authorize_url_shows_dialog_when_requested(self):
|
|
||||||
auth = SpotifyImplicitGrant("CLID", "REDIR", show_dialog=True)
|
|
||||||
|
|
||||||
url = auth.get_authorize_url()
|
|
||||||
|
|
||||||
parsed_url = urllibparse.urlparse(url)
|
|
||||||
parsed_qs = urllibparse.parse_qs(parsed_url.query)
|
|
||||||
self.assertTrue(parsed_qs['show_dialog'])
|
|
||||||
|
|
||||||
|
|
||||||
class SpotifyPKCECacheTest(unittest.TestCase):
|
class SpotifyPKCECacheTest(unittest.TestCase):
|
||||||
|
|
||||||
@patch.multiple(SpotifyPKCE,
|
@patch.multiple(SpotifyPKCE,
|
||||||
@ -393,13 +242,12 @@ class SpotifyPKCECacheTest(unittest.TestCase):
|
|||||||
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=False))
|
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=False))
|
||||||
is_token_expired.return_value = False
|
is_token_expired.return_value = False
|
||||||
|
|
||||||
spot = _make_pkceauth(scope, path)
|
cache_handler = CacheFileHandler(cache_path=path)
|
||||||
|
spot = _make_pkceauth(scope, cache_handler=cache_handler)
|
||||||
cached_tok = spot.cache_handler.get_cached_token()
|
cached_tok = spot.cache_handler.get_cached_token()
|
||||||
cached_tok_legacy = spot.get_cached_token()
|
|
||||||
|
|
||||||
opener.assert_called_with(path)
|
opener.assert_called_with(path)
|
||||||
self.assertIsNotNone(cached_tok)
|
self.assertIsNotNone(cached_tok)
|
||||||
self.assertIsNotNone(cached_tok_legacy)
|
|
||||||
self.assertEqual(refresh_access_token.call_count, 0)
|
self.assertEqual(refresh_access_token.call_count, 0)
|
||||||
|
|
||||||
@patch.multiple(SpotifyPKCE,
|
@patch.multiple(SpotifyPKCE,
|
||||||
@ -416,7 +264,8 @@ class SpotifyPKCECacheTest(unittest.TestCase):
|
|||||||
opener.return_value = token_file
|
opener.return_value = token_file
|
||||||
refresh_access_token.return_value = fresh_tok
|
refresh_access_token.return_value = fresh_tok
|
||||||
|
|
||||||
spot = _make_pkceauth(scope, path)
|
cache_handler = CacheFileHandler(cache_path=path)
|
||||||
|
spot = _make_pkceauth(scope, cache_handler=cache_handler)
|
||||||
spot.validate_token(spot.cache_handler.get_cached_token())
|
spot.validate_token(spot.cache_handler.get_cached_token())
|
||||||
|
|
||||||
is_token_expired.assert_called_with(expired_tok)
|
is_token_expired.assert_called_with(expired_tok)
|
||||||
@ -436,7 +285,8 @@ class SpotifyPKCECacheTest(unittest.TestCase):
|
|||||||
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=False))
|
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=False))
|
||||||
is_token_expired.return_value = False
|
is_token_expired.return_value = False
|
||||||
|
|
||||||
spot = _make_pkceauth(requested_scope, path)
|
cache_handler = CacheFileHandler(cache_path=path)
|
||||||
|
spot = _make_pkceauth(requested_scope, cache_handler=cache_handler)
|
||||||
cached_tok = spot.validate_token(spot.cache_handler.get_cached_token())
|
cached_tok = spot.validate_token(spot.cache_handler.get_cached_token())
|
||||||
|
|
||||||
opener.assert_called_with(path)
|
opener.assert_called_with(path)
|
||||||
@ -452,27 +302,13 @@ class SpotifyPKCECacheTest(unittest.TestCase):
|
|||||||
fi = _fake_file()
|
fi = _fake_file()
|
||||||
opener.return_value = fi
|
opener.return_value = fi
|
||||||
|
|
||||||
spot = SpotifyPKCE("CLID", "REDIR", "STATE", scope, path)
|
cache_handler = CacheFileHandler(cache_path=path)
|
||||||
|
spot = SpotifyPKCE("CLID", "REDIR", "STATE", scope, cache_handler=cache_handler)
|
||||||
spot.cache_handler.save_token_to_cache(tok)
|
spot.cache_handler.save_token_to_cache(tok)
|
||||||
|
|
||||||
opener.assert_called_with(path, 'w')
|
opener.assert_called_with(path, 'w')
|
||||||
self.assertTrue(fi.write.called)
|
self.assertTrue(fi.write.called)
|
||||||
|
|
||||||
@patch('spotipy.cache_handler.open', create=True)
|
|
||||||
def test_saves_to_cache_path_legacy(self, opener):
|
|
||||||
scope = "playlist-modify-private"
|
|
||||||
path = ".cache-username"
|
|
||||||
tok = _make_fake_token(1, 1, scope)
|
|
||||||
|
|
||||||
fi = _fake_file()
|
|
||||||
opener.return_value = fi
|
|
||||||
|
|
||||||
spot = SpotifyPKCE("CLID", "REDIR", "STATE", scope, path)
|
|
||||||
spot._save_token_info(tok)
|
|
||||||
|
|
||||||
opener.assert_called_with(path, 'w')
|
|
||||||
self.assertTrue(fi.write.called)
|
|
||||||
|
|
||||||
|
|
||||||
class TestSpotifyPKCE(unittest.TestCase):
|
class TestSpotifyPKCE(unittest.TestCase):
|
||||||
|
|
||||||
|
|||||||
@ -88,3 +88,28 @@ class SpotipyScopeTest(TestCase):
|
|||||||
self.assertEqual(normalized_scope_string_2, "")
|
self.assertEqual(normalized_scope_string_2, "")
|
||||||
|
|
||||||
self.assertIsNone(self.normalize_scope(None))
|
self.assertIsNone(self.normalize_scope(None))
|
||||||
|
|
||||||
|
def test_all_scopes(self):
|
||||||
|
expected_scopes = {
|
||||||
|
Scope.user_read_currently_playing,
|
||||||
|
Scope.playlist_read_collaborative,
|
||||||
|
Scope.playlist_modify_private,
|
||||||
|
Scope.user_read_playback_position,
|
||||||
|
Scope.user_library_modify,
|
||||||
|
Scope.user_top_read,
|
||||||
|
Scope.user_read_playback_state,
|
||||||
|
Scope.user_read_email,
|
||||||
|
Scope.ugc_image_upload,
|
||||||
|
Scope.user_read_private,
|
||||||
|
Scope.playlist_modify_public,
|
||||||
|
Scope.user_library_read,
|
||||||
|
Scope.streaming,
|
||||||
|
Scope.user_read_recently_played,
|
||||||
|
Scope.user_follow_read,
|
||||||
|
Scope.user_follow_modify,
|
||||||
|
Scope.app_remote_control,
|
||||||
|
Scope.playlist_read_private,
|
||||||
|
Scope.user_modify_playback_state,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(expected_scopes, Scope.all())
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user