Compare commits

...

6 Commits

Author SHA1 Message Date
Fabian Wunsch
351d4223d0
Reduce artist_album limit (#1232)
Reduces the limit so that a query called without explicit limit is valid
with the new changes to the spotify API.
2026-03-11 22:07:45 +01:00
Niko
6787aabe0f
Merge pull request #1230 from spotipy-dev/pr/1228
* #1227 - Updated methods related to API changes

- Updated /tracks endpoints to /items
- Switching IDs to URIs for /me/library endpoint
- Fixed playlist limit to 50 (according to API)
- Added warnings for deprecated methods

* #1227 - Formatted code and updated changelog

* #1227 - Improved HTTP URLs computation with kwargs

* bump version to 2.26.0

---------
2026-03-03 17:34:10 +01:00
Cédric Tonarelli
fa7049ea1d
Fix: Web API Changes of February 2026 (#1228)
* #1227 - Updated methods related to API changes

- Updated /tracks endpoints to /items
- Switching IDs to URIs for /me/library endpoint
- Fixed playlist limit to 50 (according to API)
- Added warnings for deprecated methods

* #1227 - Formatted code and updated changelog

* #1227 - Improved HTTP URLs computation with kwargs

* bump version to 2.26.0

---------

Co-authored-by: Niko <github@dieserniko.link>
2026-03-03 17:30:58 +01:00
dependabot[bot]
c52a29f6d2
Update sphinx-rtd-theme requirement from ~=3.0.2 to ~=3.1.0 (#1226)
Updates the requirements on [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) to permit the latest version.
- [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst)
- [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/3.0.2...3.1.0)

---
updated-dependencies:
- dependency-name: sphinx-rtd-theme
  dependency-version: 3.1.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-16 20:28:54 +01:00
Niko
179d3e486c
Update skipLabel to skipLabels in workflow 2025-12-23 18:43:11 +01:00
Stephane Bruckert
9119b6a070 Fix lint workflow 2025-11-27 07:33:35 +00:00
7 changed files with 154 additions and 67 deletions

View File

@ -12,4 +12,4 @@ jobs:
- uses: dangoslen/changelog-enforcer@v3.6.1
with:
changeLogPath: 'CHANGELOG.md'
skipLabel: 'skip-changelog'
skipLabels: 'skip-changelog'

View File

@ -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

View File

@ -39,8 +39,7 @@ To give you a flavour of what we mean, here are some examples of what PRs go whe
Just choose v3 if you are unsure which branch to work on.
### Create virtual environment, install dependencies, run tests:
### Create virtual environment, install dependencies, run tests
```bash
$ virtualenv --python=python3 env
@ -51,7 +50,7 @@ $ source env/bin/activate
### Lint
pip install .[test]
pip install ".[test]"
To automatically fix some of the code style:
@ -90,7 +89,6 @@ Don't forget to add a short description of your change in the [CHANGELOG](CHANGE
- Commit changes
- Push tag to trigger PyPI build & release workflow
- Create github release https://github.com/plamere/spotipy/releases with the changelog content
- Create github release <https://github.com/plamere/spotipy/releases> with the changelog content
for the version and a short name that describes the main addition
- Verify doc uses latest https://readthedocs.org/projects/spotipy/
- Verify doc uses latest <https://readthedocs.org/projects/spotipy/>

View File

@ -1,3 +1,3 @@
Sphinx~=8.1.3
sphinx-rtd-theme~=3.0.2
sphinx-rtd-theme~=3.1.0
redis>=3.5.3

View File

@ -13,15 +13,15 @@ extra_reqs = {
],
'test': [
'autopep8>=2.3.2',
'flake8>=7.1.1',
'flake8-string-format>=0.3.0',
'isort>=5.13.2'
'flake8>=7.3.0',
'flake8-use-fstring>=1.4',
'isort>=7.0.0'
]
}
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",

View File

@ -400,7 +400,7 @@ class Spotify:
return self._get("artists/?ids=" + ",".join(tlist))
def artist_albums(
self, artist_id, album_type=None, include_groups=None, country=None, limit=20, offset=0
self, artist_id, album_type=None, include_groups=None, country=None, limit=10, offset=0
):
""" Get Spotify catalog information about an artist's albums
@ -445,6 +445,11 @@ class Spotify:
- 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 @@ class Spotify:
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 @@ class Spotify:
self,
playlist_id,
fields=None,
limit=100,
limit=50,
offset=0,
market=None,
additional_types=("track",)
@ -712,7 +722,7 @@ class Spotify:
self,
playlist_id,
fields=None,
limit=100,
limit=50,
offset=0,
market=None,
additional_types=("track", "episode")
@ -730,7 +740,7 @@ class Spotify:
"""
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 @@ class Spotify:
- 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 @@ class Spotify:
- 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 @@ class Spotify:
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 @@ class Spotify:
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 @@ class Spotify:
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 @@ class Spotify:
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 @@ class Spotify:
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 @@ class Spotify:
"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 @@ class Spotify:
- 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 @@ class Spotify:
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 @@ class Spotify:
- 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 @@ class Spotify:
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 @@ class Spotify:
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 @@ class Spotify:
"""
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 @@ class Spotify:
"""
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 @@ class Spotify:
"""
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 @@ class Spotify:
"""
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 @@ class Spotify:
"""
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 @@ class Spotify:
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 @@ class Spotify:
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 @@ class Spotify:
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 @@ class Spotify:
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 @@ class Spotify:
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 @@ class Spotify:
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 @@ class Spotify:
(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 @@ class Spotify:
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 @@ class Spotify:
(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,

View File

@ -8,6 +8,9 @@ commands=python -m unittest discover -v tests/unit
max-line-length = 99
exclude=
.git,
.venv,
build,
dist,
docs,
examples
examples,
spotipy.egg-info