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
b6c3961d5f
commit
bd80181816
26
CHANGELOG.md
26
CHANGELOG.md
@ -16,11 +16,35 @@ While this is unreleased, please only add v3 features here. Rebasing master onto
|
||||
### Changed
|
||||
|
||||
* Made `CacheHandler` an abstract base class
|
||||
|
||||
* Modified the return structure of the `audio_features` function (wrapping the [Get Audio Features for Several Tracks](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-several-audio-features) API) to conform to the return structure of the similar methods listed below. The functions wrapping these APIs do not unwrap the single key JSON response, and this is currently the only function that does this.
|
||||
* [Get Several Tracks](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-several-tracks)
|
||||
* [Get Multiple Artists](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-multiple-artists)
|
||||
* [Get Multiple Albums](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-multiple-albums)
|
||||
* 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
|
||||
|
||||
* 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`.
|
||||
|
||||
## Unreleased [2.x.x]
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ import spotipy
|
||||
logger = logging.getLogger('examples.artist_albums')
|
||||
logging.basicConfig(level='INFO')
|
||||
|
||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyClientCredentials())
|
||||
|
||||
|
||||
def get_args():
|
||||
|
||||
@ -68,6 +68,6 @@ def main():
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
auth_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||
main()
|
||||
|
||||
@ -8,8 +8,8 @@ from spotipy.oauth2 import SpotifyClientCredentials
|
||||
logger = logging.getLogger('examples.artist_recommendations')
|
||||
logging.basicConfig(level='INFO')
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
auth_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||
|
||||
|
||||
def get_args():
|
||||
|
||||
@ -8,8 +8,8 @@ import time
|
||||
import sys
|
||||
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
auth_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
tid = sys.argv[1]
|
||||
|
||||
@ -9,8 +9,8 @@ import time
|
||||
import sys
|
||||
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
auth_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||
sp.trace = False
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
|
||||
@ -10,8 +10,8 @@ import time
|
||||
import sys
|
||||
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
auth_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||
sp.trace = True
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
|
||||
@ -2,8 +2,8 @@ from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
from pprint import pprint
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
auth_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||
|
||||
search_str = 'Muse'
|
||||
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
|
||||
|
||||
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
|
||||
res = sp.devices()
|
||||
|
||||
@ -6,7 +6,7 @@ import spotipy
|
||||
PlaylistExample = '37i9dQZEVXbMDoHDwVN2tF'
|
||||
|
||||
# create spotipy client
|
||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyClientCredentials())
|
||||
|
||||
# load the first 100 songs
|
||||
tracks = []
|
||||
|
||||
@ -2,7 +2,7 @@ from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
from pprint import pprint
|
||||
|
||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyClientCredentials())
|
||||
|
||||
pl_id = 'spotify:playlist:5RIbzhG2QqdkaP24iXLnZX'
|
||||
offset = 0
|
||||
|
||||
@ -2,8 +2,8 @@ from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
import json
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
auth_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||
|
||||
playlist_id = 'spotify:user:spotifycharts:playlist:37i9dQZEVXbJiZcmkrIHGU'
|
||||
results = sp.playlist(playlist_id)
|
||||
|
||||
@ -10,6 +10,6 @@ if len(sys.argv) > 1:
|
||||
else:
|
||||
search_str = 'Radiohead'
|
||||
|
||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyClientCredentials())
|
||||
result = sp.search(search_str)
|
||||
pprint.pprint(result)
|
||||
|
||||
@ -11,6 +11,6 @@ if len(sys.argv) > 1:
|
||||
else:
|
||||
urn = 'spotify:album:5yTx83u3qerZF7GRJu7eFk'
|
||||
|
||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyClientCredentials())
|
||||
album = sp.album(urn)
|
||||
pprint(album)
|
||||
|
||||
@ -10,7 +10,7 @@ if len(sys.argv) > 1:
|
||||
else:
|
||||
urn = 'spotify:artist:3jOstUTkEu2JkjvRdBA5Gu'
|
||||
|
||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyClientCredentials())
|
||||
|
||||
artist = sp.artist(urn)
|
||||
pprint(artist)
|
||||
|
||||
@ -9,7 +9,7 @@ if len(sys.argv) > 1:
|
||||
else:
|
||||
urn = 'spotify:artist:3jOstUTkEu2JkjvRdBA5Gu'
|
||||
|
||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyClientCredentials())
|
||||
response = sp.artist_top_tracks(urn)
|
||||
|
||||
for track in response['tracks']:
|
||||
|
||||
@ -10,8 +10,8 @@ if len(sys.argv) > 1:
|
||||
else:
|
||||
artist_name = 'weezer'
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
auth_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||
result = sp.search(q='artist:' + artist_name, type='artist')
|
||||
try:
|
||||
name = result['artists']['items'][0]['name']
|
||||
|
||||
@ -10,7 +10,7 @@ if len(sys.argv) > 1:
|
||||
else:
|
||||
urn = 'spotify:track:0Svkvt5I79wficMFgaqEQJ'
|
||||
|
||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyClientCredentials())
|
||||
|
||||
track = sp.track(urn)
|
||||
pprint(track)
|
||||
|
||||
@ -15,8 +15,8 @@ if __name__ == '__main__':
|
||||
file = sys.stdin
|
||||
tids = file.read().split()
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
auth_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||
for start in range(0, len(tids), max_tracks_per_call):
|
||||
results = sp.tracks(tids[start: start + max_tracks_per_call])
|
||||
for track in results['tracks']:
|
||||
|
||||
@ -10,8 +10,8 @@ if len(sys.argv) > 1:
|
||||
else:
|
||||
username = 'plamere'
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
auth_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||
sp.trace = True
|
||||
user = sp.user(username)
|
||||
pprint.pprint(user)
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
auth_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||
|
||||
results = sp.search(q='weezer', limit=20)
|
||||
for i, t in enumerate(results['tracks']['items']):
|
||||
|
||||
@ -3,8 +3,8 @@ import spotipy
|
||||
|
||||
birdy_uri = 'spotify:artist:2WX2uTcsvV5OnS0inACecP'
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
auth_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||
|
||||
results = sp.artist_albums(birdy_uri, album_type='album')
|
||||
albums = results['items']
|
||||
|
||||
@ -4,8 +4,8 @@ import spotipy
|
||||
|
||||
lz_uri = 'spotify:artist:36QJpDe2go2KgaRleHCDTp'
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
auth_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||
|
||||
results = sp.artist_top_tracks(lz_uri)
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import sys
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
|
||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyClientCredentials())
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
name = ' '.join(sys.argv[1:])
|
||||
|
||||
@ -9,8 +9,8 @@ import random
|
||||
usage: python title_chain.py [song name]
|
||||
'''
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
auth_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||
|
||||
|
||||
skiplist = set(['dm', 'remix'])
|
||||
|
||||
@ -6,8 +6,8 @@ from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
import sys
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
auth_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
artist_name = ' '.join(sys.argv[1:])
|
||||
|
||||
@ -6,8 +6,8 @@ import sys
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
auth_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(auth_manager=auth_manager)
|
||||
|
||||
user = 'spotify'
|
||||
|
||||
|
||||
@ -98,11 +98,9 @@ class Spotify(object):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
auth=None,
|
||||
requests_session=True,
|
||||
client_credentials_manager=None,
|
||||
oauth_manager=None,
|
||||
access_token=None,
|
||||
auth_manager=None,
|
||||
requests_session=True,
|
||||
proxies=None,
|
||||
requests_timeout=5,
|
||||
status_forcelist=None,
|
||||
@ -114,19 +112,16 @@ class Spotify(object):
|
||||
"""
|
||||
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:
|
||||
A Requests session object or a truthy value to create one.
|
||||
A falsy value disables sessions.
|
||||
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).
|
||||
:param client_credentials_manager:
|
||||
SpotifyClientCredentials object
|
||||
:param oauth_manager:
|
||||
SpotifyOAuth object
|
||||
:param auth_manager:
|
||||
SpotifyOauth, SpotifyClientCredentials,
|
||||
or SpotifyImplicitGrant object
|
||||
:param proxies:
|
||||
Definition of proxies (optional).
|
||||
See Requests doc https://2.python-requests.org/en/master/user/advanced/#proxies
|
||||
@ -146,17 +141,23 @@ class Spotify(object):
|
||||
The language parameter advertises what language the user prefers to see.
|
||||
See ISO-639 language code: https://www.loc.gov/standards/iso639-2/php/code_list.php
|
||||
"""
|
||||
|
||||
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._auth = auth
|
||||
self.client_credentials_manager = client_credentials_manager
|
||||
self.oauth_manager = oauth_manager
|
||||
self.access_token = access_token
|
||||
self.auth_manager = auth_manager
|
||||
self.proxies = proxies
|
||||
self.requests_timeout = requests_timeout
|
||||
self.status_forcelist = status_forcelist or self.default_retry_codes
|
||||
self.backoff_factor = backoff_factor
|
||||
self.retries = retries
|
||||
self.status_retries = status_retries
|
||||
self.backoff_factor = backoff_factor
|
||||
self.language = language
|
||||
|
||||
if isinstance(requests_session, requests.Session):
|
||||
@ -167,21 +168,6 @@ class Spotify(object):
|
||||
else: # Use the Requests API module as a "session".
|
||||
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):
|
||||
"""Make sure the connection (pool) gets closed"""
|
||||
@ -204,12 +190,12 @@ class Spotify(object):
|
||||
self._session.mount('https://', adapter)
|
||||
|
||||
def _auth_headers(self):
|
||||
if self._auth:
|
||||
return {"Authorization": "Bearer {0}".format(self._auth)}
|
||||
if self.access_token:
|
||||
return {"Authorization": "Bearer {0}".format(self.access_token)}
|
||||
if not self.auth_manager:
|
||||
return {}
|
||||
try:
|
||||
token = self.auth_manager.get_access_token(as_dict=False)
|
||||
token = self.auth_manager.get_access_token()
|
||||
except TypeError:
|
||||
token = self.auth_manager.get_access_token()
|
||||
return {"Authorization": "Bearer {0}".format(token)}
|
||||
@ -615,34 +601,6 @@ class Spotify(object):
|
||||
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 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.
|
||||
- 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(
|
||||
self,
|
||||
playlist_id,
|
||||
@ -697,55 +655,6 @@ class Spotify(object):
|
||||
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 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("users/%s/starred" % user)
|
||||
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):
|
||||
""" Gets playlists of a user
|
||||
|
||||
@ -777,197 +686,6 @@ class Spotify(object):
|
||||
|
||||
return self._post("users/%s/playlists" % (user,), payload=data)
|
||||
|
||||
def user_playlist_change_details(
|
||||
self,
|
||||
user,
|
||||
playlist_id,
|
||||
name=None,
|
||||
public=None,
|
||||
collaborative=None,
|
||||
description=None,
|
||||
):
|
||||
warnings.warn(
|
||||
"You should use `playlist_change_details(playlist_id, ...)` instead",
|
||||
DeprecationWarning,
|
||||
)
|
||||
""" 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
|
||||
"""
|
||||
|
||||
return self.playlist_change_details(playlist_id, name, public,
|
||||
collaborative, description)
|
||||
|
||||
def user_playlist_unfollow(self, user, playlist_id):
|
||||
""" 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
|
||||
):
|
||||
warnings.warn(
|
||||
"You should use `playlist_add_items(playlist_id, tracks)` instead",
|
||||
DeprecationWarning,
|
||||
)
|
||||
""" 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
|
||||
"""
|
||||
return self.playlist_add_items(playlist_id, tracks, position)
|
||||
|
||||
def user_playlist_replace_tracks(self, user, playlist_id, tracks):
|
||||
""" Replace all tracks in a playlist
|
||||
|
||||
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,
|
||||
):
|
||||
""" Reorder tracks in a playlist
|
||||
|
||||
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
|
||||
):
|
||||
""" 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
|
||||
):
|
||||
""" 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(
|
||||
"users/%s/playlists/%s/tracks" % (user, plid), payload=payload
|
||||
)
|
||||
|
||||
def user_playlist_follow_playlist(self, playlist_owner_id, playlist_id):
|
||||
"""
|
||||
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
|
||||
):
|
||||
"""
|
||||
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(
|
||||
self,
|
||||
playlist_id,
|
||||
|
||||
@ -5,7 +5,6 @@ __all__ = [
|
||||
"SpotifyOAuth",
|
||||
"SpotifyOauthError",
|
||||
"SpotifyStateError",
|
||||
"SpotifyImplicitGrant",
|
||||
"SpotifyPKCE"
|
||||
]
|
||||
|
||||
@ -179,10 +178,10 @@ class SpotifyClientCredentials(SpotifyAuthBase):
|
||||
self,
|
||||
client_id=None,
|
||||
client_secret=None,
|
||||
cache_handler=None,
|
||||
proxies=None,
|
||||
requests_session=True,
|
||||
requests_timeout=None,
|
||||
cache_handler=None
|
||||
requests_timeout=None
|
||||
):
|
||||
"""
|
||||
Creates a Client Credentials Flow Manager.
|
||||
@ -200,14 +199,16 @@ class SpotifyClientCredentials(SpotifyAuthBase):
|
||||
Parameters:
|
||||
* client_id: 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
|
||||
getting and saving cached authorization tokens.
|
||||
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
|
||||
|
||||
"""
|
||||
|
||||
@ -225,35 +226,25 @@ class SpotifyClientCredentials(SpotifyAuthBase):
|
||||
else:
|
||||
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
|
||||
Else feches a new token and returns it
|
||||
|
||||
Parameters:
|
||||
- as_dict - a boolean indicating if returning the access token
|
||||
as a token_info dictionary, otherwise it will be returned
|
||||
as a string.
|
||||
- check_cache - if true, checks for a locally stored token
|
||||
before requesting a new token.
|
||||
"""
|
||||
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:
|
||||
token_info = self.cache_handler.get_cached_token()
|
||||
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._add_custom_values_to_token_info(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):
|
||||
"""Gets client credentials access token """
|
||||
@ -309,14 +300,12 @@ class SpotifyOAuth(SpotifyAuthBase):
|
||||
redirect_uri=None,
|
||||
state=None,
|
||||
scope=None,
|
||||
cache_path=None,
|
||||
username=None,
|
||||
cache_handler=None,
|
||||
proxies=None,
|
||||
show_dialog=False,
|
||||
requests_session=True,
|
||||
requests_timeout=None,
|
||||
open_browser=True,
|
||||
cache_handler=None
|
||||
open_browser=True
|
||||
):
|
||||
"""
|
||||
Creates a SpotifyOAuth object
|
||||
@ -329,24 +318,19 @@ class SpotifyOAuth(SpotifyAuthBase):
|
||||
* 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}
|
||||
|
||||
iterable of scopes or comma separated string of scopes.
|
||||
e.g, "playlist-read-private,playlist-read-collaborative"
|
||||
* 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}`)
|
||||
* cache_handler: An instance of the `CacheHandler` class to handle
|
||||
getting and saving cached authorization tokens.
|
||||
Optional, will otherwise use `CacheFileHandler`.
|
||||
* proxies: Optional, proxy for the requests library to route through
|
||||
* 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
|
||||
a given number of seconds
|
||||
* open_browser: Optional, whether or not the web browser should be opened to
|
||||
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(SpotifyOAuth, self).__init__(requests_session)
|
||||
@ -356,34 +340,13 @@ class SpotifyOAuth(SpotifyAuthBase):
|
||||
self.redirect_uri = redirect_uri
|
||||
self.state = state
|
||||
self.scope = self._normalize_scope(scope)
|
||||
username = (username or os.getenv(CLIENT_CREDS_ENV_VARS["client_username"]))
|
||||
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:
|
||||
assert issubclass(cache_handler.__class__, CacheHandler), \
|
||||
"cache_handler must be a subclass of CacheHandler: " + str(type(cache_handler)) \
|
||||
+ " != " + str(CacheHandler)
|
||||
self.cache_handler = cache_handler
|
||||
else:
|
||||
|
||||
self.cache_handler = CacheFileHandler(
|
||||
username=username,
|
||||
cache_path=cache_path
|
||||
)
|
||||
self.cache_handler = CacheFileHandler()
|
||||
self.proxies = proxies
|
||||
self.requests_timeout = requests_timeout
|
||||
self.show_dialog = show_dialog
|
||||
@ -527,24 +490,14 @@ class SpotifyOAuth(SpotifyAuthBase):
|
||||
return self.parse_response_code(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
|
||||
|
||||
Parameters:
|
||||
- code - the response code
|
||||
- as_dict - a boolean indicating if returning the access token
|
||||
as a token_info dictionary, otherwise it will be returned
|
||||
as a string.
|
||||
- check_cache - if true, checks for a locally stored token
|
||||
before requesting a new token
|
||||
"""
|
||||
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:
|
||||
token_info = self.validate_token(self.cache_handler.get_cached_token())
|
||||
if token_info is not None:
|
||||
@ -552,7 +505,7 @@ class SpotifyOAuth(SpotifyAuthBase):
|
||||
token_info = self.refresh_access_token(
|
||||
token_info["refresh_token"]
|
||||
)
|
||||
return token_info if as_dict else token_info["access_token"]
|
||||
return token_info["access_token"]
|
||||
|
||||
payload = {
|
||||
"redirect_uri": self.redirect_uri,
|
||||
@ -589,7 +542,7 @@ class SpotifyOAuth(SpotifyAuthBase):
|
||||
token_info = response.json()
|
||||
token_info = self._add_custom_values_to_token_info(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 refresh_access_token(self, refresh_token):
|
||||
payload = {
|
||||
@ -636,26 +589,6 @@ class SpotifyOAuth(SpotifyAuthBase):
|
||||
token_info["scope"] = self.scope
|
||||
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):
|
||||
""" Implements PKCE Authorization Flow for client apps
|
||||
@ -672,18 +605,18 @@ class SpotifyPKCE(SpotifyAuthBase):
|
||||
OAUTH_AUTHORIZE_URL = "https://accounts.spotify.com/authorize"
|
||||
OAUTH_TOKEN_URL = "https://accounts.spotify.com/api/token"
|
||||
|
||||
def __init__(self,
|
||||
client_id=None,
|
||||
redirect_uri=None,
|
||||
state=None,
|
||||
scope=None,
|
||||
cache_path=None,
|
||||
username=None,
|
||||
proxies=None,
|
||||
requests_timeout=None,
|
||||
requests_session=True,
|
||||
open_browser=True,
|
||||
cache_handler=None):
|
||||
def __init__(
|
||||
self,
|
||||
client_id=None,
|
||||
redirect_uri=None,
|
||||
state=None,
|
||||
scope=None,
|
||||
cache_handler=None,
|
||||
proxies=None,
|
||||
requests_timeout=None,
|
||||
requests_session=True,
|
||||
open_browser=True
|
||||
):
|
||||
"""
|
||||
Creates Auth Manager with the PKCE Auth flow.
|
||||
|
||||
@ -694,20 +627,18 @@ class SpotifyPKCE(SpotifyAuthBase):
|
||||
* 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_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
|
||||
* 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, thether or not the web browser should be opened to
|
||||
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`)
|
||||
* 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(SpotifyPKCE, self).__init__(requests_session)
|
||||
@ -715,31 +646,13 @@ class SpotifyPKCE(SpotifyAuthBase):
|
||||
self.redirect_uri = redirect_uri
|
||||
self.state = state
|
||||
self.scope = self._normalize_scope(scope)
|
||||
username = (username or os.getenv(CLIENT_CREDS_ENV_VARS["client_username"]))
|
||||
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:
|
||||
assert issubclass(type(cache_handler), CacheHandler), \
|
||||
"type(cache_handler): " + str(type(cache_handler)) + " != " + str(CacheHandler)
|
||||
assert issubclass(cache_handler.__class__, CacheHandler), \
|
||||
"cache_handler must be a subclass of CacheHandler: " + str(type(cache_handler)) \
|
||||
+ " != " + str(CacheHandler)
|
||||
self.cache_handler = cache_handler
|
||||
else:
|
||||
self.cache_handler = CacheFileHandler(
|
||||
username=username,
|
||||
cache_path=cache_path
|
||||
)
|
||||
self.cache_handler = CacheFileHandler()
|
||||
self.proxies = proxies
|
||||
self.requests_timeout = requests_timeout
|
||||
|
||||
@ -1018,286 +931,6 @@ class SpotifyPKCE(SpotifyAuthBase):
|
||||
def 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 of 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 Implict 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(SpotifyImplictGrant) 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(SpotifyImplictGrant).")
|
||||
|
||||
self.client_id = client_id
|
||||
self.redirect_uri = redirect_uri
|
||||
self.state = state
|
||||
username = (username or os.getenv(CLIENT_CREDS_ENV_VARS["client_username"]))
|
||||
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:
|
||||
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 "%s?%s" % (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("Received error from auth server: "
|
||||
"{}".format(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):
|
||||
def do_GET(self):
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
""" Shows a user's playlists (need to be authenticated via oauth) """
|
||||
|
||||
__all__ = ["CLIENT_CREDS_ENV_VARS", "prompt_for_user_token"]
|
||||
__all__ = ["CLIENT_CREDS_ENV_VARS"]
|
||||
|
||||
import logging
|
||||
import os
|
||||
@ -18,95 +18,6 @@ CLIENT_CREDS_ENV_VARS = {
|
||||
"redirect_uri": "SPOTIPY_REDIRECT_URI",
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
)
|
||||
""" prompts the user to login if necessary and returns
|
||||
the 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 (optional)
|
||||
- oauth_manager - Oauth manager object (optional)
|
||||
- show_dialog - If true, a login prompt always shows (optional, defaults to False)
|
||||
|
||||
"""
|
||||
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, the 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):
|
||||
if ":" in netloc:
|
||||
host, port = netloc.split(":", 1)
|
||||
|
||||
@ -58,7 +58,8 @@ class AuthTestSpotipy(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(self):
|
||||
self.spotify = Spotify(
|
||||
client_credentials_manager=SpotifyClientCredentials())
|
||||
auth_manager=SpotifyClientCredentials()
|
||||
)
|
||||
self.spotify.trace = False
|
||||
|
||||
def test_audio_analysis(self):
|
||||
@ -232,9 +233,9 @@ class AuthTestSpotipy(unittest.TestCase):
|
||||
self.assertTrue(found)
|
||||
|
||||
def test_search_timeout(self):
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
auth_manager = SpotifyClientCredentials()
|
||||
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"
|
||||
self.assertRaises((requests.exceptions.Timeout, requests.exceptions.ConnectionError),
|
||||
@ -242,17 +243,15 @@ class AuthTestSpotipy(unittest.TestCase):
|
||||
|
||||
def test_max_retries_reached_get(self):
|
||||
spotify_no_retry = Spotify(
|
||||
client_credentials_manager=SpotifyClientCredentials(),
|
||||
auth_manager=SpotifyClientCredentials(),
|
||||
retries=0)
|
||||
i = 0
|
||||
while i < 100:
|
||||
for i in range(100):
|
||||
try:
|
||||
spotify_no_retry.search(q='foo')
|
||||
except SpotifyException as e:
|
||||
self.assertIsInstance(e, SpotifyException)
|
||||
self.assertEqual(e.http_status, 429)
|
||||
return
|
||||
i += 1
|
||||
self.fail()
|
||||
|
||||
def test_album_search(self):
|
||||
@ -350,7 +349,7 @@ class AuthTestSpotipy(unittest.TestCase):
|
||||
sess = requests.Session()
|
||||
sess.headers["user-agent"] = "spotipy-test"
|
||||
with_custom_session = spotipy.Spotify(
|
||||
client_credentials_manager=SpotifyClientCredentials(),
|
||||
auth_manager=SpotifyClientCredentials(),
|
||||
requests_session=sess)
|
||||
self.assertTrue(
|
||||
with_custom_session.user(
|
||||
@ -359,7 +358,7 @@ class AuthTestSpotipy(unittest.TestCase):
|
||||
|
||||
def test_force_no_requests_session(self):
|
||||
with_no_session = spotipy.Spotify(
|
||||
client_credentials_manager=SpotifyClientCredentials(),
|
||||
auth_manager=SpotifyClientCredentials(),
|
||||
requests_session=False)
|
||||
self.assertNotIsInstance(with_no_session._session, requests.Session)
|
||||
user = with_no_session.user(user="akx")
|
||||
|
||||
@ -2,15 +2,30 @@ import os
|
||||
|
||||
from spotipy import (
|
||||
CLIENT_CREDS_ENV_VARS as CCEV,
|
||||
prompt_for_user_token,
|
||||
Spotify,
|
||||
SpotifyException,
|
||||
SpotifyImplicitGrant,
|
||||
SpotifyPKCE
|
||||
SpotifyOAuth,
|
||||
SpotifyPKCE,
|
||||
CacheFileHandler
|
||||
)
|
||||
import unittest
|
||||
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):
|
||||
@classmethod
|
||||
@ -48,10 +63,10 @@ class SpotipyPlaylistApiTest(unittest.TestCase):
|
||||
'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 = helpers.get_spotify_playlist(
|
||||
cls.spotify, cls.new_playlist_name, cls.username) or \
|
||||
@ -119,8 +134,7 @@ class SpotipyPlaylistApiTest(unittest.TestCase):
|
||||
self.assertEqual(pl["tracks"]["total"], 0)
|
||||
|
||||
def test_max_retries_reached_post(self):
|
||||
i = 0
|
||||
while i < 500:
|
||||
for i in range(500):
|
||||
try:
|
||||
self.spotify_no_retry.playlist_change_details(
|
||||
self.new_playlist['id'], description="test")
|
||||
@ -128,7 +142,6 @@ class SpotipyPlaylistApiTest(unittest.TestCase):
|
||||
self.assertIsInstance(e, SpotifyException)
|
||||
self.assertEqual(e.http_status, 429)
|
||||
return
|
||||
i += 1
|
||||
self.fail()
|
||||
|
||||
def test_playlist_add_items(self):
|
||||
@ -188,17 +201,6 @@ class SpotipyPlaylistApiTest(unittest.TestCase):
|
||||
return
|
||||
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):
|
||||
@classmethod
|
||||
@ -227,10 +229,7 @@ class SpotipyLibraryApiTests(unittest.TestCase):
|
||||
'ugc-image-upload '
|
||||
'user-read-playback-state'
|
||||
)
|
||||
|
||||
token = prompt_for_user_token(cls.username, scope=scope)
|
||||
|
||||
cls.spotify = Spotify(auth=token)
|
||||
cls.spotify = _make_spotify(scopes=scope)
|
||||
|
||||
def test_track_bad_id(self):
|
||||
with self.assertRaises(SpotifyException):
|
||||
@ -305,9 +304,7 @@ class SpotipyUserApiTests(unittest.TestCase):
|
||||
'user-read-playback-state'
|
||||
)
|
||||
|
||||
token = prompt_for_user_token(cls.username, scope=scope)
|
||||
|
||||
cls.spotify = Spotify(auth=token)
|
||||
cls.spotify = _make_spotify(scopes=scope)
|
||||
|
||||
def test_basic_user_profile(self):
|
||||
user = self.spotify.user(self.username)
|
||||
@ -335,9 +332,7 @@ class SpotipyUserApiTests(unittest.TestCase):
|
||||
class SpotipyBrowseApiTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
username = os.getenv(CCEV['client_username'])
|
||||
token = prompt_for_user_token(username)
|
||||
cls.spotify = Spotify(auth=token)
|
||||
cls.spotify = _make_spotify()
|
||||
|
||||
def test_category(self):
|
||||
response = self.spotify.category('rock')
|
||||
@ -383,9 +378,7 @@ class SpotipyFollowApiTests(unittest.TestCase):
|
||||
'user-read-playback-state'
|
||||
)
|
||||
|
||||
token = prompt_for_user_token(cls.username, scope=scope)
|
||||
|
||||
cls.spotify = Spotify(auth=token)
|
||||
cls.spotify = _make_spotify(scopes=scope)
|
||||
|
||||
def test_current_user_follows(self):
|
||||
response = self.spotify.current_user_followed_artists()
|
||||
@ -438,9 +431,7 @@ class SpotipyPlayerApiTests(unittest.TestCase):
|
||||
'user-read-playback-state'
|
||||
)
|
||||
|
||||
token = prompt_for_user_token(cls.username, scope=scope)
|
||||
|
||||
cls.spotify = Spotify(auth=token)
|
||||
cls.spotify = _make_spotify(scopes=scope)
|
||||
|
||||
def test_devices(self):
|
||||
# No devices playing by default
|
||||
@ -454,39 +445,6 @@ class SpotipyPlayerApiTests(unittest.TestCase):
|
||||
# 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_user_follows_and_unfollows_artist(self):
|
||||
# Initially follows 1 artist
|
||||
current_user_followed_artists = self.spotify.current_user_followed_artists()[
|
||||
'artists']['total']
|
||||
|
||||
# Follow 2 more artists
|
||||
artists = ["6DPYiyq5kWVQS4RGwxzPC7", "0NbfKEOTQCcwd6o7wSDOHI"]
|
||||
self.spotify.user_follow_artists(artists)
|
||||
res = self.spotify.current_user_followed_artists()
|
||||
self.assertEqual(res['artists']['total'], current_user_followed_artists + len(artists))
|
||||
|
||||
# Unfollow these 2 artists
|
||||
self.spotify.user_unfollow_artists(artists)
|
||||
res = self.spotify.current_user_followed_artists()
|
||||
self.assertEqual(res['artists']['total'], current_user_followed_artists)
|
||||
|
||||
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):
|
||||
|
||||
@classmethod
|
||||
@ -495,7 +453,8 @@ class SpotifyPKCETests(unittest.TestCase):
|
||||
'user-follow-read '
|
||||
'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)
|
||||
|
||||
def test_user_follows_and_unfollows_artist(self):
|
||||
|
||||
@ -5,10 +5,11 @@ import unittest
|
||||
|
||||
import six.moves.urllib.parse as urllibparse
|
||||
|
||||
from spotipy import SpotifyOAuth, SpotifyImplicitGrant, SpotifyPKCE
|
||||
from spotipy.cache_handler import MemoryCacheHandler
|
||||
from spotipy import SpotifyOAuth, SpotifyPKCE
|
||||
from spotipy.cache_handler import CacheHandler
|
||||
from spotipy.oauth2 import SpotifyClientCredentials, SpotifyOauthError
|
||||
from spotipy.oauth2 import SpotifyStateError
|
||||
from spotipy import CacheFileHandler
|
||||
|
||||
try:
|
||||
import unittest.mock as mock
|
||||
@ -43,10 +44,6 @@ def _make_oauth(*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):
|
||||
return SpotifyPKCE("CLID", "REDIR", "STATE", *args, **kwargs)
|
||||
|
||||
@ -65,13 +62,12 @@ class OAuthCacheTest(unittest.TestCase):
|
||||
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=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_legacy = spot.get_cached_token()
|
||||
|
||||
opener.assert_called_with(path)
|
||||
self.assertIsNotNone(cached_tok)
|
||||
self.assertIsNotNone(cached_tok_legacy)
|
||||
self.assertEqual(refresh_access_token.call_count, 0)
|
||||
|
||||
@patch.multiple(SpotifyOAuth,
|
||||
@ -88,7 +84,8 @@ class OAuthCacheTest(unittest.TestCase):
|
||||
opener.return_value = token_file
|
||||
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())
|
||||
|
||||
is_token_expired.assert_called_with(expired_tok)
|
||||
@ -108,7 +105,9 @@ class OAuthCacheTest(unittest.TestCase):
|
||||
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=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())
|
||||
|
||||
opener.assert_called_with(path)
|
||||
@ -124,27 +123,13 @@ class OAuthCacheTest(unittest.TestCase):
|
||||
fi = _fake_file()
|
||||
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)
|
||||
|
||||
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 = 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):
|
||||
scope = "playlist-modify-private"
|
||||
tok = _make_fake_token(1, 1, scope)
|
||||
@ -248,142 +233,6 @@ class TestSpotifyClientCredentials(unittest.TestCase):
|
||||
oauth.get_access_token()
|
||||
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):
|
||||
|
||||
@patch.multiple(SpotifyPKCE,
|
||||
@ -398,13 +247,12 @@ class SpotifyPKCECacheTest(unittest.TestCase):
|
||||
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=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_legacy = spot.get_cached_token()
|
||||
|
||||
opener.assert_called_with(path)
|
||||
self.assertIsNotNone(cached_tok)
|
||||
self.assertIsNotNone(cached_tok_legacy)
|
||||
self.assertEqual(refresh_access_token.call_count, 0)
|
||||
|
||||
@patch.multiple(SpotifyPKCE,
|
||||
@ -421,7 +269,8 @@ class SpotifyPKCECacheTest(unittest.TestCase):
|
||||
opener.return_value = token_file
|
||||
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())
|
||||
|
||||
is_token_expired.assert_called_with(expired_tok)
|
||||
@ -441,7 +290,8 @@ class SpotifyPKCECacheTest(unittest.TestCase):
|
||||
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=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())
|
||||
|
||||
opener.assert_called_with(path)
|
||||
@ -457,27 +307,13 @@ class SpotifyPKCECacheTest(unittest.TestCase):
|
||||
fi = _fake_file()
|
||||
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)
|
||||
|
||||
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 = 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):
|
||||
|
||||
|
||||
@ -88,3 +88,28 @@ class SpotipyScopeTest(TestCase):
|
||||
self.assertEqual(normalized_scope_string_2, "")
|
||||
|
||||
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