diff --git a/CHANGELOG.md b/CHANGELOG.md index ce8ae26..d8c647a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support to search multiple markets at once. - Support to search all available Spotify markets. - - Allow for OAuth 2.0 authorization by instructing the user to open the URL in a browser instead of opening the browser. + - Support to test whether the current user is following certain + users or artists + - Proper replacements for all deprecated playlist endpoints + (See https://developer.spotify.com/community/news/2018/06/12/changes-to-playlist-uris/ and below) +- Allow for OAuth 2.0 authorization by instructing the user to open the URL in a browser instead of opening the browser. +### Deprecated + +- `user_playlist_change_details` in favor of `playlist_change_details` +- `user_playlist_unfollow` in favor of `current_user_unfollow_playlist` +- `user_playlist_add_tracks` in favor of `playlist_add_items` +- `user_playlist_replace_tracks` in favor of `playlist_replace_items` +- `user_playlist_reorder_tracks` in favor of `playlist_reorder_items` +- `user_playlist_remove_all_occurrences_of_tracks` in favor of + `playlist_remove_all_occurrences_of_items` +- `user_playlist_remove_specific_occurrences_of_tracks` in favor of + `playlist_remove_specific_occurrences_of_items` +- `user_playlist_follow_playlist` in favor of + `current_user_follow_playlist` +- `user_playlist_is_following` in favor of `playlist_is_following` +- `playlist_tracks` in favor of `playlist_items` ## [2.13.0] - 2020-06-25 diff --git a/examples/add_tracks_to_playlist.py b/examples/add_tracks_to_playlist.py index ad997de..28e6cf4 100644 --- a/examples/add_tracks_to_playlist.py +++ b/examples/add_tracks_to_playlist.py @@ -22,8 +22,7 @@ def main(): args = get_args() sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope)) - user_id = sp.me()['id'] - sp.user_playlist_add_tracks(user_id, args.playlist, args.tids) + sp.playlist_add_items(args.playlist, args.tids) if __name__ == '__main__': diff --git a/examples/change_playlist_details.py b/examples/change_playlist_details.py index 688eeba..759b94d 100644 --- a/examples/change_playlist_details.py +++ b/examples/change_playlist_details.py @@ -35,8 +35,7 @@ def get_args(): def main(): args = get_args() sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope)) - sp.user_playlist_change_details( - args.username, + sp.playlist_change_details( args.playlist, name=args.name, public=args.public or args.private, diff --git a/examples/playlist_all_non_local_tracks.py b/examples/playlist_all_non_local_tracks.py index 81ca47b..e0bcb21 100644 --- a/examples/playlist_all_non_local_tracks.py +++ b/examples/playlist_all_non_local_tracks.py @@ -10,20 +10,20 @@ sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials()) # load the first 100 songs tracks = [] -result = sp.playlist_tracks(PlaylistExample) +result = sp.playlist_items(PlaylistExample, additional_types=['track']) tracks.extend(result['items']) # if playlist is larger than 100 songs, continue loading it until end while result['next']: - result = sp.next(result) - tracks.extend(result['items']) + result = sp.next(result) + tracks.extend(result['items']) # remove all local songs -i = 0 # just for counting how many tracks are local +i = 0 # just for counting how many tracks are local for item in tracks: - if item['is_local']: - tracks.remove(item) - i += 1 + if item['is_local']: + tracks.remove(item) + i += 1 # print result diff --git a/examples/playlist_tracks.py b/examples/playlist_tracks.py index 70ca178..3476f87 100644 --- a/examples/playlist_tracks.py +++ b/examples/playlist_tracks.py @@ -8,9 +8,10 @@ pl_id = 'spotify:playlist:5RIbzhG2QqdkaP24iXLnZX' offset = 0 while True: - response = sp.playlist_tracks(pl_id, - offset=offset, - fields='items.track.id,total') + response = sp.playlist_items(pl_id, + offset=offset, + fields='items.track.id,total', + additional_types=['track']) pprint(response['items']) offset = offset + len(response['items']) print(offset, "/", response['total']) diff --git a/examples/remove_specific_tracks_from_playlist.py b/examples/remove_specific_tracks_from_playlist.py index f93f280..46dc1ad 100644 --- a/examples/remove_specific_tracks_from_playlist.py +++ b/examples/remove_specific_tracks_from_playlist.py @@ -22,8 +22,6 @@ else: scope = 'playlist-modify-public' sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope)) -user_id = sp.me()['id'] - -results = sp.user_playlist_remove_specific_occurrences_of_tracks( - user_id, playlist_id, track_ids) +results = sp.playlist_remove_specific_occurrences_of_items( + playlist_id, track_ids) pprint.pprint(results) diff --git a/examples/remove_tracks_from_playlist.py b/examples/remove_tracks_from_playlist.py index c7d6371..8a51c56 100644 --- a/examples/remove_tracks_from_playlist.py +++ b/examples/remove_tracks_from_playlist.py @@ -17,8 +17,6 @@ scope = 'playlist-modify-public' sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope)) -user_id = sp.me()['id'] - -results = sp.user_playlist_remove_all_occurrences_of_tracks( - user_id, playlist_id, track_ids) +results = sp.playlist_remove_all_occurrences_of_items( + playlist_id, track_ids) pprint.pprint(results) diff --git a/examples/replace_tracks_in_playlist.py b/examples/replace_tracks_in_playlist.py index 8648ffb..fe857c1 100644 --- a/examples/replace_tracks_in_playlist.py +++ b/examples/replace_tracks_in_playlist.py @@ -16,8 +16,6 @@ else: scope = 'playlist-modify-public' sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope)) -user_id = sp.me()['id'] -results = sp.user_playlist_replace_tracks(user_id, playlist_id, track_ids) +results = sp.playlist_replace_items(playlist_id, track_ids) pprint.pprint(results) - diff --git a/spotipy/client.py b/spotipy/client.py index 01863d9..bed0850 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -607,6 +607,34 @@ class Spotify(object): - 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, + fields=None, + limit=100, + offset=0, + market=None, + additional_types=("track", "episode") + ): + """ Get full details of the tracks and episodes 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 + """ plid = self._get_id("playlist", playlist_id) return self._get( "playlists/%s/tracks" % (plid), @@ -724,6 +752,10 @@ class Spotify(object): 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: @@ -735,18 +767,8 @@ class Spotify(object): - description - optional description of the playlist """ - data = {} - if isinstance(name, six.string_types): - data["name"] = name - if isinstance(public, bool): - data["public"] = public - if isinstance(collaborative, bool): - data["collaborative"] = collaborative - if isinstance(description, six.string_types): - data["description"] = description - return self._put( - "users/%s/playlists/%s" % (user, playlist_id), payload=data - ) + 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 @@ -755,13 +777,19 @@ class Spotify(object): - user - the id of the user - name - the name of the playlist """ - return self._delete( - "users/%s/playlists/%s/followers" % (user, playlist_id) + 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: @@ -770,13 +798,7 @@ class Spotify(object): - tracks - a list of track URIs, URLs or IDs - position - the position to add the tracks """ - plid = self._get_id("playlist", playlist_id) - ftracks = [self._get_uri("track", tid) for tid in tracks] - return self._post( - "users/%s/playlists/%s/tracks" % (user, plid), - payload=ftracks, - position=position, - ) + 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 @@ -786,12 +808,11 @@ class Spotify(object): - playlist_id - the id of the playlist - tracks - the list of track ids to add to the playlist """ - plid = self._get_id("playlist", playlist_id) - ftracks = [self._get_uri("track", tid) for tid in tracks] - payload = {"uris": ftracks} - return self._put( - "users/%s/playlists/%s/tracks" % (user, plid), payload=payload + 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, @@ -814,17 +835,13 @@ class Spotify(object): inserted - snapshot_id - optional playlist's snapshot ID """ - plid = self._get_id("playlist", playlist_id) - payload = { - "range_start": range_start, - "range_length": range_length, - "insert_before": insert_before, - } - if snapshot_id: - payload["snapshot_id"] = snapshot_id - return self._put( - "users/%s/playlists/%s/tracks" % (user, plid), payload=payload + warnings.warn( + "You should use `playlist_reorder_items(playlist_id, ...)` instead", + DeprecationWarning, ) + return self.playlist_reorder_items(playlist_id, range_start, + range_length, insert_before, + snapshot_id) def user_playlist_remove_all_occurrences_of_tracks( self, user, playlist_id, tracks, snapshot_id=None @@ -838,15 +855,14 @@ class Spotify(object): - snapshot_id - optional id of the playlist snapshot """ - - plid = self._get_id("playlist", playlist_id) - ftracks = [self._get_uri("track", tid) for tid in tracks] - payload = {"tracks": [{"uri": track} for track in ftracks]} - if snapshot_id: - payload["snapshot_id"] = snapshot_id - return self._delete( - "users/%s/playlists/%s/tracks" % (user, plid), payload=payload + 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 @@ -863,7 +879,11 @@ class Spotify(object): { "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: @@ -889,11 +909,11 @@ class Spotify(object): - playlist_id - the id of the playlist """ - return self._put( - "users/{}/playlists/{}/followers".format( - playlist_owner_id, playlist_id - ) + 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 @@ -908,9 +928,196 @@ class Spotify(object): if they follow the playlist. Maximum: 5 ids. """ - endpoint = "users/{}/playlists/{}/followers/contains?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, + name=None, + public=None, + collaborative=None, + description=None, + ): + """ Changes a playlist's name and/or public/private state + + Parameters: + - 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 + """ + + data = {} + if isinstance(name, six.string_types): + data["name"] = name + if isinstance(public, bool): + data["public"] = public + if isinstance(collaborative, bool): + data["collaborative"] = collaborative + if isinstance(description, six.string_types): + data["description"] = description + return self._put( + "playlists/%s" % (playlist_id), payload=data + ) + + def current_user_unfollow_playlist(self, playlist_id): + """ Unfollows (deletes) a playlist for the current authenticated + user + + Parameters: + - name - the name of the playlist + """ + return self._delete( + "playlists/%s/followers" % (playlist_id) + ) + + def playlist_add_items( + self, playlist_id, items, position=None + ): + """ Adds tracks/episodes to a playlist + + Parameters: + - playlist_id - the id of the playlist + - items - a list of track/episode URIs, URLs or IDs + - position - the position to add the tracks + """ + plid = self._get_id("playlist", playlist_id) + ftracks = [self._get_uri("track", tid) for tid in items] + return self._post( + "playlists/%s/tracks" % (plid), + payload=ftracks, + position=position, + ) + + def playlist_replace_items(self, playlist_id, items): + """ Replace all tracks/episodes in a playlist + + Parameters: + - playlist_id - the id of the playlist + - items - list of track/episode ids to comprise playlist + """ + plid = self._get_id("playlist", playlist_id) + ftracks = [self._get_uri("track", tid) for tid in items] + payload = {"uris": ftracks} + return self._put( + "playlists/%s/tracks" % (plid), payload=payload + ) + + def playlist_reorder_items( + self, + playlist_id, + range_start, + insert_before, + range_length=1, + snapshot_id=None, + ): + """ Reorder tracks in a playlist + + Parameters: + - 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 + """ + plid = self._get_id("playlist", playlist_id) + payload = { + "range_start": range_start, + "range_length": range_length, + "insert_before": insert_before, + } + if snapshot_id: + payload["snapshot_id"] = snapshot_id + return self._put( + "playlists/%s/tracks" % (plid), payload=payload + ) + + def playlist_remove_all_occurrences_of_items( + self, playlist_id, items, snapshot_id=None + ): + """ Removes all occurrences of the given tracks from the given playlist + + Parameters: + - playlist_id - the id of the playlist + - items - list of track/episode ids to remove from the playlist + - snapshot_id - optional id of the playlist snapshot + + """ + + plid = self._get_id("playlist", playlist_id) + ftracks = [self._get_uri("track", tid) for tid in items] + payload = {"tracks": [{"uri": track} for track in ftracks]} + if snapshot_id: + payload["snapshot_id"] = snapshot_id + return self._delete( + "playlists/%s/tracks" % (plid), payload=payload + ) + + def playlist_remove_specific_occurrences_of_items( + self, playlist_id, items, snapshot_id=None + ): + """ Removes all occurrences of the given tracks from the given playlist + + Parameters: + - playlist_id - the id of the playlist + - items - an array of objects containing Spotify URIs of the + tracks/episodes 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 + """ + + plid = self._get_id("playlist", playlist_id) + ftracks = [] + for tr in items: + 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( + "playlists/%s/tracks" % (plid), payload=payload + ) + + def current_user_follow_playlist(self, playlist_id): + """ + Add the current authenticated user as a follower of a playlist. + + Parameters: + - playlist_id - the id of the playlist + + """ + return self._put( + "playlists/{}/followers".format(playlist_id) + ) + + def playlist_is_following( + self, playlist_id, user_ids + ): + """ + Check to see if the given users are following the given playlist + + Parameters: + - 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. + + """ + endpoint = "playlists/{}/followers/contains?ids={}" return self._get( - endpoint.format(playlist_owner_id, playlist_id, ",".join(user_ids)) + endpoint.format(playlist_id, ",".join(user_ids)) ) def me(self): @@ -954,6 +1161,36 @@ class Spotify(object): "me/following", type="artist", limit=limit, after=after ) + def current_user_following_artists(self, ids=None): + """ Check if the current user is following certain artists + + Returns list of booleans respective to ids + + Parameters: + - ids - a list of artist URIs, URLs or IDs + """ + idlist = [] + if ids is not None: + idlist = [self._get_id("artist", i) for i in ids] + return self._get( + "me/following/contains", ids=",".join(idlist), type="artist" + ) + + def current_user_following_users(self, ids=None): + """ Check if the current user is following certain artists + + Returns list of booleans respective to ids + + Parameters: + - ids - a list of user URIs, URLs or IDs + """ + idlist = [] + if ids is not None: + idlist = [self._get_id("user", i) for i in ids] + return self._get( + "me/following/contains", ids=",".join(idlist), type="user" + ) + def current_user_saved_tracks_delete(self, tracks=None): """ Remove one or more tracks from the current user's "Your Music" library. diff --git a/tests/integration/test_user_endpoints.py b/tests/integration/test_user_endpoints.py index 490e8dd..d259f82 100644 --- a/tests/integration/test_user_endpoints.py +++ b/tests/integration/test_user_endpoints.py @@ -53,15 +53,14 @@ class SpotipyPlaylistApiTest(unittest.TestCase): self.assertTrue('items' in playlists) self.assertGreaterEqual(len(playlists['items']), 1) - def test_user_playlist_tracks(self): + def test_playlist_items(self): playlists = self.spotify.user_playlists(self.username, limit=5) self.assertTrue('items' in playlists) for playlist in playlists['items']: if playlist['uri'] != self.new_playlist_uri: continue - user = playlist['owner']['id'] pid = playlist['id'] - results = self.spotify.user_playlist_tracks(user, pid) + results = self.spotify.playlist_items(pid) self.assertEqual(len(results['items']), 0) def test_current_user_playlists(self): @@ -70,65 +69,60 @@ class SpotipyPlaylistApiTest(unittest.TestCase): self.assertGreaterEqual(len(playlists['items']), 1) self.assertLessEqual(len(playlists['items']), 10) - def test_user_playlist_follow(self): - user_to_follow = 'plamere' - user_to_follow_id = '4erXB04MxwRAVqcUEpu30O' - self.spotify.user_playlist_follow_playlist( - user_to_follow, user_to_follow_id) - follows = self.spotify.user_playlist_is_following( - user_to_follow, user_to_follow_id, [self.username]) + def test_current_user_follow_playlist(self): + playlist_to_follow_id = '4erXB04MxwRAVqcUEpu30O' + self.spotify.current_user_follow_playlist(playlist_to_follow_id) + follows = self.spotify.playlist_is_following( + playlist_to_follow_id, [self.username]) self.assertTrue(len(follows) == 1, 'proper follows length') self.assertTrue(follows[0], 'is following') - self.spotify.user_playlist_unfollow( - user_to_follow, user_to_follow_id) + self.spotify.current_user_unfollow_playlist(playlist_to_follow_id) - follows = self.spotify.user_playlist_is_following( - user_to_follow, user_to_follow_id, [self.username]) + follows = self.spotify.playlist_is_following( + playlist_to_follow_id, [self.username]) self.assertTrue(len(follows) == 1, 'proper follows length') self.assertFalse(follows[0], 'is no longer following') - def test_user_playlist_replace_tracks(self): + def test_playlist_replace_items(self): # add tracks to playlist - self.spotify.user_playlist_add_tracks( - self.username, self.new_playlist['id'], self.four_tracks) - playlist = self.spotify.user_playlist(self.username, self.new_playlist['id']) + self.spotify.playlist_add_items( + self.new_playlist['id'], self.four_tracks) + playlist = self.spotify.playlist(self.new_playlist['id']) self.assertEqual(playlist['tracks']['total'], 4) self.assertEqual(len(playlist['tracks']['items']), 4) # replace with 3 other tracks - self.spotify.user_playlist_replace_tracks(self.username, - self.new_playlist['id'], - self.other_tracks) - playlist = self.spotify.user_playlist(self.username, - self.new_playlist['id']) + self.spotify.playlist_replace_items(self.new_playlist['id'], + self.other_tracks) + playlist = self.spotify.playlist(self.new_playlist['id']) self.assertEqual(playlist['tracks']['total'], 3) self.assertEqual(len(playlist['tracks']['items']), 3) - self.spotify.user_playlist_remove_all_occurrences_of_tracks( - self.username, playlist['id'], self.other_tracks) - playlist = self.spotify.user_playlist(self.username, self.new_playlist['id']) + self.spotify.playlist_remove_all_occurrences_of_items( + playlist['id'], self.other_tracks) + playlist = self.spotify.playlist(self.new_playlist['id']) self.assertEqual(playlist["tracks"]["total"], 0) def test_get_playlist_by_id(self): pl = self.spotify.playlist(self.new_playlist['id']) self.assertEqual(pl["tracks"]["total"], 0) - def test_playlist_add_tracks(self): + def test_playlist_add_items(self): # add tracks to playlist - self.spotify.user_playlist_add_tracks( - self.username, self.new_playlist['id'], self.other_tracks) - playlist = self.spotify.user_playlist(self.username, self.new_playlist['id']) - self.assertEqual(playlist['tracks']['total'], 3) - self.assertEqual(len(playlist['tracks']['items']), 3) + self.spotify.playlist_add_items( + self.new_playlist['id'], self.other_tracks) + playlist = self.spotify.playlist_items(self.new_playlist['id']) + self.assertEqual(playlist['total'], 3) + self.assertEqual(len(playlist['items']), 3) - pl = self.spotify.playlist_tracks(self.new_playlist['id'], limit=2) + pl = self.spotify.playlist_items(self.new_playlist['id'], limit=2) self.assertEqual(len(pl["items"]), 2) - self.spotify.user_playlist_remove_all_occurrences_of_tracks( - self.username, playlist['id'], self.other_tracks) - playlist = self.spotify.user_playlist(self.username, self.new_playlist['id']) - self.assertEqual(playlist["tracks"]["total"], 0) + self.spotify.playlist_remove_all_occurrences_of_items( + self.new_playlist['id'], self.other_tracks) + playlist = self.spotify.playlist_items(self.new_playlist['id']) + self.assertEqual(playlist["total"], 0) def test_playlist_cover_image(self): # Upload random dog image @@ -332,23 +326,24 @@ class SpotipyFollowApiTests(unittest.TestCase): # 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)) + self.assertTrue(all(self.spotify.current_user_following_artists(artists))) # Unfollow these 2 artists self.spotify.user_unfollow_artists(artists) + self.assertFalse(any(self.spotify.current_user_following_artists(artists))) res = self.spotify.current_user_followed_artists() self.assertEqual(res['artists']['total'], current_user_followed_artists) def test_user_follows_and_unfollows_user(self): - # TODO improve after implementing `me/following/contains` users = ["11111204", "xlqeojt6n7on0j7coh9go8ifd"] # Follow 2 more users self.spotify.user_follow_users(users) + self.assertTrue(all(self.spotify.current_user_following_users(users))) # Unfollow these 2 users self.spotify.user_unfollow_users(users) + self.assertFalse(any(self.spotify.current_user_following_users(users))) class SpotipyPlayerApiTests(unittest.TestCase):