diff --git a/CHANGELOG.md b/CHANGELOG.md index 2865cdd3..fc92c28b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased - Add your changes below. ### Added @@ -15,6 +14,19 @@ Add your changes below. ### Removed +## [2.26.0] - 2026-03-03 + +### Added +- Created generic methods to get user saved items + +### Fixed +- Updated `/tracks` endpoints to `/items` +- Switching IDs to URIs to use `/me/library` endpoint +- Fixed playlist limit to 50 (according to API) +- Added warnings for deprecated methods + +### Removed + ## [2.25.2] - 2025-11-26 ### Added diff --git a/setup.py b/setup.py index 5b36cdc0..ecf40d98 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ setup( name='spotipy', - version='2.25.2', + version='2.26.0', description='A light weight Python library for the Spotify Web API', long_description=long_description, long_description_content_type="text/markdown", diff --git a/spotipy/client.py b/spotipy/client.py index 92fe1da2..898384dd 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -445,6 +445,11 @@ def artist_top_tracks(self, artist_id, country="US"): - country - limit the response to one particular country. """ + warnings.warn( + "You're using `artist_top_tracks(...)`, " + "which is marked as deprecated by Spotify.", + DeprecationWarning, + ) trid = self._get_id("artist", artist_id) return self._get("artists/" + trid + "/top-tracks", country=country) @@ -647,6 +652,11 @@ def user(self, user): Parameters: - user - the id of the usr """ + warnings.warn( + "You're using `user(...)`, " + "which is marked as deprecated by Spotify.", + DeprecationWarning, + ) return self._get("users/" + user) def current_user_playlists(self, limit=50, offset=0): @@ -680,7 +690,7 @@ def playlist_tracks( self, playlist_id, fields=None, - limit=100, + limit=50, offset=0, market=None, additional_types=("track",) @@ -712,7 +722,7 @@ def playlist_items( self, playlist_id, fields=None, - limit=100, + limit=50, offset=0, market=None, additional_types=("track", "episode") @@ -730,7 +740,7 @@ def playlist_items( """ plid = self._get_id("playlist", playlist_id) return self._get( - f"playlists/{plid}/tracks", + f"playlists/{plid}/items", limit=limit, offset=offset, fields=fields, @@ -826,6 +836,12 @@ def user_playlists(self, user, limit=50, offset=0): - limit - the number of items to return - offset - the index of the first item to return """ + warnings.warn( + "You're using `user_playlists(...)`, " + "which is marked as deprecated by Spotify. Use " + "current_user_playlists(...) instead.", + DeprecationWarning, + ) return self._get( f"users/{user}/playlists", limit=limit, offset=offset ) @@ -840,6 +856,12 @@ def user_playlist_create(self, user, name, public=True, collaborative=False, des - collaborative - is the created playlist collaborative - description - the description of the playlist """ + warnings.warn( + "You're using `user_playlist_create(...)`, " + "which is marked as deprecated by Spotify. Use " + "current_user_playlist_create(...) instead.", + DeprecationWarning, + ) data = { "name": name, "public": public, @@ -849,6 +871,24 @@ def user_playlist_create(self, user, name, public=True, collaborative=False, des return self._post(f"users/{user}/playlists", payload=data) + def current_user_playlist_create(self, name, public=True, collaborative=False, description=""): + """ Creates a playlist for the current user + + Parameters: + - name - the name of the playlist + - public - is the created playlist public + - collaborative - is the created playlist collaborative + - description - the description of the playlist + """ + data = { + "name": name, + "public": public, + "collaborative": collaborative, + "description": description + } + + return self._post("me/playlists", payload=data) + def user_playlist_change_details( self, user, @@ -1171,7 +1211,7 @@ def playlist_add_items( plid = self._get_id("playlist", playlist_id) ftracks = [self._get_uri("track", tid) for tid in items] return self._post( - f"playlists/{plid}/tracks", + f"playlists/{plid}/items", payload=ftracks, position=position, ) @@ -1187,7 +1227,7 @@ def playlist_replace_items(self, playlist_id, items): ftracks = [self._get_uri("track", tid) for tid in items] payload = {"uris": ftracks} return self._put( - f"playlists/{plid}/tracks", payload=payload + f"playlists/{plid}/items", payload=payload ) def playlist_reorder_items( @@ -1218,7 +1258,7 @@ def playlist_reorder_items( if snapshot_id: payload["snapshot_id"] = snapshot_id return self._put( - f"playlists/{plid}/tracks", payload=payload + f"playlists/{plid}/items", payload=payload ) def playlist_remove_all_occurrences_of_items( @@ -1235,11 +1275,11 @@ def playlist_remove_all_occurrences_of_items( 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]} + payload = {"items": [{"uri": track} for track in ftracks]} if snapshot_id: payload["snapshot_id"] = snapshot_id return self._delete( - f"playlists/{plid}/tracks", payload=payload + f"playlists/{plid}/items", payload=payload ) def playlist_remove_specific_occurrences_of_items( @@ -1266,14 +1306,14 @@ def playlist_remove_specific_occurrences_of_items( "positions": tr["positions"], } ) - payload = {"tracks": ftracks} + payload = {"items": ftracks} if snapshot_id: payload["snapshot_id"] = snapshot_id return self._delete( - f"playlists/{plid}/tracks", payload=payload + f"playlists/{plid}/items", payload=payload ) - def current_user_follow_playlist(self, playlist_id, public=True): + def current_user_follow_playlist(self, playlist_id): """ Add the current authenticated user as a follower of a playlist. @@ -1281,10 +1321,7 @@ def current_user_follow_playlist(self, playlist_id, public=True): - playlist_id - the id of the playlist """ - return self._put( - f"playlists/{playlist_id}/followers", - payload={"public": public} - ) + return self._put("me/library", uris=self._get_uri("playlist", playlist_id)) def playlist_is_following( self, playlist_id, user_ids @@ -1298,9 +1335,27 @@ def playlist_is_following( if they follow the playlist. Maximum: 5 ids. """ + warnings.warn( + "You're using `playlist_is_following(..., user_ids=...)`, " + "which is marked as deprecated by Spotify. Use ", + "current_user_follow_playlist(...) instead.", + DeprecationWarning, + ) + endpoint = f"playlists/{playlist_id}/followers/contains?ids={','.join(user_ids)}" return self._get(endpoint) + def current_user_saved_items(self, uris): + """ + Check if the current user is following the given artists, users, or playlists + + Parameters: + - uris - a list of URIs to check for following status. Maximum: 40 ids. + + """ + valid_uris = [uri for uri in uris if self._is_uri(uri)] + return self._get("me/library/contains", uris=",".join(valid_uris)) + def me(self): """ Get detailed profile information about the current user. An alias for the 'current_user' method. @@ -1347,8 +1402,8 @@ def current_user_saved_albums_add(self, albums=[]): - albums - a list of album URIs, URLs or IDs """ - alist = [self._get_id("album", a) for a in albums] - return self._put("me/albums?ids=" + ",".join(alist)) + alist = [self._get_uri("album", a) for a in albums] + return self._put("me/library", uris=",".join(alist)) def current_user_saved_albums_delete(self, albums=[]): """ Remove one or more albums from the current user's @@ -1357,8 +1412,8 @@ def current_user_saved_albums_delete(self, albums=[]): Parameters: - albums - a list of album URIs, URLs or IDs """ - alist = [self._get_id("album", a) for a in albums] - return self._delete("me/albums/?ids=" + ",".join(alist)) + alist = [self._get_uri("album", a) for a in albums] + return self._delete("me/library", uris=",".join(alist)) def current_user_saved_albums_contains(self, albums=[]): """ Check if one or more albums is already saved in @@ -1367,8 +1422,8 @@ def current_user_saved_albums_contains(self, albums=[]): Parameters: - albums - a list of album URIs, URLs or IDs """ - alist = [self._get_id("album", a) for a in albums] - return self._get("me/albums/contains?ids=" + ",".join(alist)) + alist = [self._get_uri("album", a) for a in albums] + return self._get("me/library/contains", uris=",".join(alist)) def current_user_saved_tracks(self, limit=20, offset=0, market=None): """ Gets a list of the tracks saved in the current authorized user's @@ -1391,8 +1446,8 @@ def current_user_saved_tracks_add(self, tracks=None): """ tlist = [] if tracks is not None: - tlist = [self._get_id("track", t) for t in tracks] - return self._put("me/tracks/?ids=" + ",".join(tlist)) + tlist = [self._get_uri("track", t) for t in tracks] + return self._put("me/library", uris=",".join(tlist)) def current_user_saved_tracks_delete(self, tracks=None): """ Remove one or more tracks from the current user's @@ -1403,8 +1458,8 @@ def current_user_saved_tracks_delete(self, tracks=None): """ tlist = [] if tracks is not None: - tlist = [self._get_id("track", t) for t in tracks] - return self._delete("me/tracks/?ids=" + ",".join(tlist)) + tlist = [self._get_uri("track", t) for t in tracks] + return self._delete("me/library", uris=",".join(tlist)) def current_user_saved_tracks_contains(self, tracks=None): """ Check if one or more tracks is already saved in @@ -1415,8 +1470,8 @@ def current_user_saved_tracks_contains(self, tracks=None): """ tlist = [] if tracks is not None: - tlist = [self._get_id("track", t) for t in tracks] - return self._get("me/tracks/contains?ids=" + ",".join(tlist)) + tlist = [self._get_uri("track", t) for t in tracks] + return self._get("me/library/contains", uris=",".join(tlist)) def current_user_saved_episodes(self, limit=20, offset=0, market=None): """ Gets a list of the episodes saved in the current authorized user's @@ -1439,8 +1494,8 @@ def current_user_saved_episodes_add(self, episodes=None): """ elist = [] if episodes is not None: - elist = [self._get_id("episode", e) for e in episodes] - return self._put("me/episodes/?ids=" + ",".join(elist)) + elist = [self._get_uri("episode", e) for e in episodes] + return self._put("me/library", uris=",".join(elist)) def current_user_saved_episodes_delete(self, episodes=None): """ Remove one or more episodes from the current user's @@ -1451,8 +1506,8 @@ def current_user_saved_episodes_delete(self, episodes=None): """ elist = [] if episodes is not None: - elist = [self._get_id("episode", e) for e in episodes] - return self._delete("me/episodes/?ids=" + ",".join(elist)) + elist = [self._get_uri("episode", e) for e in episodes] + return self._delete("me/library", uris=",".join(elist)) def current_user_saved_episodes_contains(self, episodes=None): """ Check if one or more episodes is already saved in @@ -1484,8 +1539,8 @@ def current_user_saved_shows_add(self, shows=[]): Parameters: - shows - a list of show URIs, URLs or IDs """ - slist = [self._get_id("show", s) for s in shows] - return self._put("me/shows?ids=" + ",".join(slist)) + slist = [self._get_uri("show", s) for s in shows] + return self._put("me/library", uris=",".join(slist)) def current_user_saved_shows_delete(self, shows=[]): """ Remove one or more shows from the current user's @@ -1494,8 +1549,8 @@ def current_user_saved_shows_delete(self, shows=[]): Parameters: - shows - a list of show URIs, URLs or IDs """ - slist = [self._get_id("show", s) for s in shows] - return self._delete("me/shows/?ids=" + ",".join(slist)) + slist = [self._get_uri("show", s) for s in shows] + return self._delete("me/library", uris=",".join(slist)) def current_user_saved_shows_contains(self, shows=[]): """ Check if one or more shows is already saved in @@ -1504,8 +1559,8 @@ def current_user_saved_shows_contains(self, shows=[]): Parameters: - shows - a list of show URIs, URLs or IDs """ - slist = [self._get_id("show", s) for s in shows] - return self._get("me/shows/contains?ids=" + ",".join(slist)) + slist = [self._get_uri("show", s) for s in shows] + return self._get("me/library/contains", uris=",".join(slist)) def current_user_followed_artists(self, limit=20, after=None): """ Gets a list of the artists followed by the current authorized user @@ -1528,11 +1583,11 @@ def current_user_following_artists(self, ids=None): Parameters: - ids - a list of artist URIs, URLs or IDs """ - idlist = [] + ulist = [] if ids is not None: - idlist = [self._get_id("artist", i) for i in ids] + ulist = [self._get_uri("artist", i) for i in ids] return self._get( - "me/following/contains", ids=",".join(idlist), type="artist" + "me/library/contains", uris=",".join(ulist) ) def current_user_following_users(self, ids=None): @@ -1543,11 +1598,11 @@ def current_user_following_users(self, ids=None): Parameters: - ids - a list of user URIs, URLs or IDs """ - idlist = [] + ulist = [] if ids is not None: - idlist = [self._get_id("user", i) for i in ids] + ulist = [self._get_uri("user", i) for i in ids] return self._get( - "me/following/contains", ids=",".join(idlist), type="user" + "me/library/contains", uris=",".join(ulist) ) def current_user_top_artists( @@ -1604,28 +1659,32 @@ def user_follow_artists(self, ids=[]): Parameters: - ids - a list of artist IDs """ - return self._put("me/following?type=artist&ids=" + ",".join(ids)) + alist = [self._get_uri("artist", a) for a in ids] + return self._put("me/library", uris=",".join(alist)) def user_follow_users(self, ids=[]): """ Follow one or more users Parameters: - ids - a list of user IDs """ - return self._put("me/following?type=user&ids=" + ",".join(ids)) + ulist = [self._get_uri("user", a) for a in ids] + return self._put("me/library", uris=",".join(ulist)) def user_unfollow_artists(self, ids=[]): """ Unfollow one or more artists Parameters: - ids - a list of artist IDs """ - return self._delete("me/following?type=artist&ids=" + ",".join(ids)) + alist = [self._get_uri("artist", a) for a in ids] + return self._delete("me/library", uris=",".join(alist)) def user_unfollow_users(self, ids=[]): """ Unfollow one or more users Parameters: - ids - a list of user IDs """ - return self._delete("me/following?type=user&ids=" + ",".join(ids)) + ulist = [self._get_uri("user", a) for a in ids] + return self._delete("me/library", uris=",".join(ulist)) def featured_playlists( self, locale=None, country=None, timestamp=None, limit=20, offset=0 @@ -1681,6 +1740,11 @@ def new_releases(self, country=None, limit=20, offset=0): (the first object). Use with limit to get the next set of items. """ + warnings.warn( + "You're using `new_release(...)`, " + "which is marked as deprecated by Spotify.", + DeprecationWarning, + ) return self._get( "browse/new-releases", country=country, limit=limit, offset=offset ) @@ -1696,6 +1760,11 @@ def category(self, category_id, country=None, locale=None): language code and an ISO 3166-1 alpha-2 country code, joined by an underscore. """ + warnings.warn( + "You're using `category(...)`, " + "which is marked as deprecated by Spotify.", + DeprecationWarning, + ) return self._get( "browse/categories/" + category_id, country=country, @@ -1718,6 +1787,11 @@ def categories(self, country=None, locale=None, limit=20, offset=0): (the first object). Use with limit to get the next set of items. """ + warnings.warn( + "You're using `categories(...)`, " + "which is marked as deprecated by Spotify.", + DeprecationWarning, + ) return self._get( "browse/categories", country=country,