Compare commits

...

19 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
Niko
b5be7fba6a
add advisory link 2025-11-26 21:16:13 +01:00
Niko
48dab6eb6b
bump version to 2.25.2 2025-11-26 21:11:15 +01:00
Yue (Knox) Liu
880b92d724
Merge commit from fork
* Sanitize error message output

Escape HTML characters in error message for security.

* Update CHANGELOG.md

---------

Co-authored-by: Niko <github@dieserniko.link>
2025-11-26 20:58:33 +01:00
john
a91d9feb51
Correct Spotify/Spotipy typo (#1209)
Correct Spotify/Spotipy typo. Document refers the user to the "Spotify GitHub repository" while the associated link is actually to the Spotipy GitHub repo.
2025-07-31 10:33:07 +02:00
Stéphane Bruckert
5a8b55f5e8
Deprecations in doc (#1202)
* Add documentation warnings to doc

* fix

* fix
2025-05-23 17:18:49 +01:00
Stéphane Bruckert
9dfb7177b8
Revert "Run integration tests from fork" (#1201)
This reverts commit 4f5759dbfb.
2025-05-15 18:02:54 +01:00
Shawn Falkner-Horine
6bc91ecf98
Fix README "examples" link. (#1195) (#1196) 2025-04-26 13:11:03 +02:00
Sebastian Held
1a8d9da033
add additional_types to current_user_playing_track() (#1193)
Co-authored-by: Sebastian Held <sebastian.held@imst.de>
Co-authored-by: Stéphane Bruckert <stephane.bruckert@gmail.com>
2025-04-21 22:41:02 +02:00
Stéphane Bruckert
5b018cf6af Fix main branch name 2025-04-21 15:52:33 +01:00
Stéphane Bruckert
743059989d Pin gha workflows to ubuntu-22.04 2025-04-21 15:48:54 +01:00
Stéphane Bruckert
4f5759dbfb
Run integration tests from fork 2025-04-21 15:44:04 +01:00
Niko
3ec8a2312c
Improve logging (#1191)
* improve retry warning

* changelog

* fix lint issue
2025-03-26 07:41:07 +01:00
Niko
4f01f7187d
Avoid garbage collection for requests.Session (#1189)
* workaround for garbage collection

* add missing import

* linting issues (wrong import order)

* fix imports
2025-03-07 20:02:43 +01:00
15 changed files with 323 additions and 118 deletions

View File

@ -4,7 +4,7 @@ on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
env:
SPOTIPY_CLIENT_ID: ${{ secrets.SPOTIPY_CLIENT_ID }}
SPOTIPY_CLIENT_SECRET: ${{ secrets.SPOTIPY_CLIENT_SECRET }}

View File

@ -4,8 +4,7 @@ on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Set up Python

View File

@ -10,8 +10,7 @@ on:
jobs:
build-n-publish:
name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Set up Python

View File

@ -6,10 +6,10 @@ on:
jobs:
# Enforces the update of a changelog file on every pull request
changelog:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: dangoslen/changelog-enforcer@v3.6.1
with:
changeLogPath: 'CHANGELOG.md'
skipLabel: 'skip-changelog'
skipLabels: 'skip-changelog'

View File

@ -4,7 +4,7 @@ on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]

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,32 @@ 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
- Adds `additional_types` parameter to retrieve currently playing episode
- Add deprecation warnings to documentation
### Fixed
- Fixed dead link in README.md
- Corrected Spotify/Spotipy typo in documentation
- Sanitize HTML error message output for OAuth flow: https://github.com/spotipy-dev/spotipy/security/advisories/GHSA-r77h-rpp9-w2xm
## [2.25.1] - 2025-02-27
### Added
@ -26,6 +51,8 @@ Add your changes below.
- Fixed scripts in examples directory that didn't run correctly
- Updated documentation for `Client.current_user_top_artists` to indicate maximum number of artists limit
- Set auth cache file permissions to `600`: https://github.com/spotipy-dev/spotipy/security/advisories/GHSA-pwhh-q4h6-w599
- Fixed `__del__` methods by preventing garbage collection for `requests.Session`
- Improved retry warning by using `logger` instead of `logging` and making sure that `retry_header` is an int
### Changed

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:
@ -75,9 +74,9 @@ Don't forget to add a short description of your change in the [CHANGELOG](CHANGE
### Publishing (by maintainer)
- Bump version in setup.py
- Bump and date changelog
- Add to changelog:
- Bump version in setup.py
- Bump and date changelog
- Add to changelog:
## Unreleased
Add your changes below.
@ -88,9 +87,8 @@ Don't forget to add a short description of your change in the [CHANGELOG](CHANGE
### Removed
- Commit changes
- Push tag to trigger PyPI build & release workflow
- Create github release https://github.com/plamere/spotipy/releases with the changelog content
- Commit changes
- Push tag to trigger PyPI build & release workflow
- 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

@ -36,7 +36,7 @@ pip install spotipy --upgrade
## Quick Start
A full set of examples can be found in the [online documentation](http://spotipy.readthedocs.org/) and in the [Spotipy examples directory](https://github.com/plamere/spotipy/tree/master/examples).
A full set of examples can be found in the [online documentation](http://spotipy.readthedocs.org/) and in the [Spotipy examples directory](https://github.com/spotipy-dev/spotipy-examples).
To get started, [install spotipy](#installation), create a new account or log in on https://developers.spotify.com/. Go to the [dashboard](https://developer.spotify.com/dashboard), create an app and add your new ID and SECRET (ID and SECRET can be found on an app setting) to your environment:

View File

@ -23,7 +23,7 @@ Install or upgrade *Spotipy* with::
pip install spotipy --upgrade
You can also obtain the source code from the `Spotify GitHub repository <https://github.com/plamere/spotipy>`_.
You can also obtain the source code from the `Spotipy GitHub repository <https://github.com/plamere/spotipy>`_.
Getting Started

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.1',
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

@ -11,7 +11,7 @@ from collections import defaultdict
import requests
from spotipy.exceptions import SpotifyException
from spotipy.util import Retry
from spotipy.util import REQUESTS_SESSION, Retry
logger = logging.getLogger(__name__)
@ -211,11 +211,8 @@ class Spotify:
def __del__(self):
"""Make sure the connection (pool) gets closed"""
try:
if isinstance(self._session, requests.Session):
self._session.close()
except AttributeError:
pass
if getattr(self, "_session", None) and isinstance(self._session, REQUESTS_SESSION):
self._session.close()
def _build_session(self):
self._session = requests.Session()
@ -403,10 +400,14 @@ 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
.. deprecated::
This method is deprecated and may be removed in a future version. Use
`artist_albums(..., include_groups='...')` instead.
Parameters:
- artist_id - the artist ID, URI or URL
- include_groups - the types of items to return. One or more of 'album', 'single',
@ -444,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)
@ -452,6 +458,9 @@ class Spotify:
identified artist. Similarity is based on analysis of the
Spotify community's listening history.
.. deprecated::
This endpoint has been removed by Spotify and is no longer available.
Parameters:
- artist_id - the artist ID, URI or URL
"""
@ -643,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):
@ -676,13 +690,17 @@ class Spotify:
self,
playlist_id,
fields=None,
limit=100,
limit=50,
offset=0,
market=None,
additional_types=("track",)
):
""" Get full details of the tracks of a playlist.
.. deprecated::
This method is deprecated and may be removed in a future version. Use
`playlist_items(playlist_id, ..., additional_types=('track',))` instead.
Parameters:
- playlist_id - the playlist ID, URI or URL
- fields - which fields to return
@ -704,7 +722,7 @@ class Spotify:
self,
playlist_id,
fields=None,
limit=100,
limit=50,
offset=0,
market=None,
additional_types=("track", "episode")
@ -722,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,
@ -755,18 +773,22 @@ class Spotify:
)
def user_playlist(self, user, playlist_id=None, fields=None, market=None):
warnings.warn(
"You should use `playlist(playlist_id)` instead",
DeprecationWarning,
)
""" Gets a single playlist of a user
.. deprecated::
This method is deprecated and may be removed in a future version. Use
`playlist(playlist_id)` instead.
Parameters:
- user - the id of the user
- playlist_id - the id of the playlist
- fields - which fields to return
"""
warnings.warn(
"You should use `playlist(playlist_id)` instead",
DeprecationWarning,
)
if playlist_id is None:
return self._get(f"users/{user}/starred")
return self.playlist(playlist_id, fields=fields, market=market)
@ -780,13 +802,12 @@ class Spotify:
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.
.. deprecated::
This method is deprecated and may be removed in a future version. Use
`playlist_tracks(playlist_id)` instead.
Parameters:
- user - the id of the user
- playlist_id - the id of the playlist
@ -795,6 +816,10 @@ class Spotify:
- offset - the index of the first track to return
- market - an ISO 3166-1 alpha-2 country code.
"""
warnings.warn(
"You should use `playlist_tracks(playlist_id)` instead",
DeprecationWarning,
)
return self.playlist_tracks(
playlist_id,
limit=limit,
@ -811,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
)
@ -825,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,
@ -834,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,
@ -847,6 +902,10 @@ class Spotify:
Changes a playlist's name and/or public/private state
.. deprecated::
This method is deprecated and may be removed in a future version. Use
`playlist_change_details(playlist_id, ...)` instead.
Parameters:
- user - the id of the user
- playlist_id - the id of the playlist
@ -868,6 +927,10 @@ class Spotify:
Unfollows (deletes) a playlist for a user
.. deprecated::
This method is deprecated and may be removed in a future version. Use
`current_user_unfollow_playlist(playlist_id)` instead.
Parameters:
- user - the id of the user
- name - the name of the playlist
@ -885,6 +948,10 @@ class Spotify:
Adds tracks to a playlist
.. deprecated::
This method is deprecated and may be removed in a future version. Use
`playlist_add_items(playlist_id, tracks)` instead.
Parameters:
- user - the id of the user
- playlist_id - the id of the playlist
@ -906,6 +973,10 @@ class Spotify:
Adds episodes to a playlist
.. deprecated::
This method is deprecated and may be removed in a future version. Use
`playlist_add_items(playlist_id, episodes)` instead.
Parameters:
- user - the id of the user
- playlist_id - the id of the playlist
@ -925,6 +996,10 @@ class Spotify:
Replace all tracks in a playlist for a user
.. deprecated::
This method is deprecated and may be removed in a future version. Use
`playlist_replace_items(playlist_id, tracks)` instead.
Parameters:
- user - the id of the user
- playlist_id - the id of the playlist
@ -949,6 +1024,10 @@ class Spotify:
Reorder tracks in a playlist from a user
.. deprecated::
This method is deprecated and may be removed in a future version. Use
`playlist_reorder_items(playlist_id, ...)` instead.
Parameters:
- user - the id of the user
- playlist_id - the id of the playlist
@ -974,6 +1053,10 @@ class Spotify:
Removes all occurrences of the given tracks from the given playlist
.. deprecated::
This method is deprecated and may be removed in a future version. Use
`playlist_remove_all_occurrences_of_items(playlist_id, tracks)` instead.
Parameters:
- user - the id of the user
- playlist_id - the id of the playlist
@ -994,7 +1077,10 @@ class Spotify:
):
""" This function is no longer in use, please use the recommended function in the warning!
Removes all occurrences of the given tracks from the given playlist
Removes specific occurrences of the given tracks from the given playlist
.. deprecated::
This endpoint has been removed by Spotify and is no longer available.
Parameters:
- user - the id of the user
@ -1007,8 +1093,8 @@ class Spotify:
- snapshot_id - optional id of the playlist snapshot
"""
warnings.warn(
"You should use `playlist_remove_specific_occurrences_of_items"
"(playlist_id, tracks)` instead",
"You're using `user_playlist_remove_specific_occurrences_of_tracks(...)`, "
"which is marked as deprecated by Spotify.",
DeprecationWarning,
)
plid = self._get_id("playlist", playlist_id)
@ -1032,6 +1118,10 @@ class Spotify:
Add the current authenticated user as a follower of a playlist.
.. deprecated::
This method is deprecated and may be removed in a future version. Use
`current_user_follow_playlist(playlist_id)` instead.
Parameters:
- playlist_owner_id - the user id of the playlist owner
- playlist_id - the id of the playlist
@ -1049,6 +1139,10 @@ class Spotify:
Check to see if the given users are following the given playlist
.. deprecated::
This method is deprecated and may be removed in a future version. Use
`playlist_is_following(playlist_id, user_ids)` instead.
Parameters:
- playlist_owner_id - the user id of the playlist owner
- playlist_id - the id of the playlist
@ -1117,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,
)
@ -1133,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(
@ -1164,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(
@ -1181,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(
@ -1212,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.
@ -1227,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
@ -1244,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.
@ -1259,10 +1368,20 @@ class Spotify:
"""
return self.me()
def current_user_playing_track(self):
def current_user_playing_track(self, market=None, additional_types=("track",)):
""" Get information about the current users currently playing track.
Parameters:
- market - An ISO 3166-1 alpha-2 country code or the
string from_token.
- additional_types - list of item types to return.
valid types are: track and episode
"""
return self._get("me/player/currently-playing")
return self._get(
"me/player/currently-playing",
market=market,
additional_types=",".join(additional_types)
)
def current_user_saved_albums(self, limit=20, offset=0, market=None):
""" Gets a list of the albums saved in the current authorized user's
@ -1283,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
@ -1293,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
@ -1303,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
@ -1327,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
@ -1339,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
@ -1351,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
@ -1375,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
@ -1387,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
@ -1420,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
@ -1430,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
@ -1440,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
@ -1464,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):
@ -1479,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(
@ -1540,34 +1659,41 @@ 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
):
""" Get a list of Spotify featured playlists
.. deprecated::
This endpoint has been removed by Spotify and is no longer available.
Parameters:
- locale - The desired language, consisting of a lowercase ISO
639-1 alpha-2 language code and an uppercase ISO 3166-1 alpha-2
@ -1614,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
)
@ -1629,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,
@ -1651,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,
@ -1664,6 +1805,9 @@ class Spotify:
):
""" Get a list of playlists for a specific Spotify category
.. deprecated::
This endpoint has been removed by Spotify and is no longer available.
Parameters:
- category_id - The Spotify category ID for the category.
@ -1701,6 +1845,9 @@ class Spotify:
(at least one of `seed_artists`, `seed_tracks` and `seed_genres`
are needed)
.. deprecated::
This endpoint has been removed by Spotify and is no longer available.
Parameters:
- seed_artists - a list of artist IDs, URIs or URLs
- seed_tracks - a list of track IDs, URIs or URLs
@ -1761,17 +1908,24 @@ class Spotify:
return self._get("recommendations", **params)
def recommendation_genre_seeds(self):
""" Get a list of genres available for the recommendations function.
.. deprecated::
This endpoint has been removed by Spotify and is no longer available.
"""
warnings.warn(
"You're using `recommendation_genre_seeds(...)`, "
"which is marked as deprecated by Spotify.",
DeprecationWarning,
)
""" Get a list of genres available for the recommendations function.
"""
return self._get("recommendations/available-genre-seeds")
def audio_analysis(self, track_id):
""" Get audio analysis for a track based upon its Spotify ID
.. deprecated::
This endpoint has been removed by Spotify and is no longer available.
Parameters:
- track_id - a track URI, URL or ID
"""
@ -1785,6 +1939,10 @@ class Spotify:
def audio_features(self, tracks=[]):
""" Get audio features for one or multiple tracks based upon their Spotify IDs
.. deprecated::
This endpoint has been removed by Spotify and is no longer available.
Parameters:
- tracks - a list of track URIs, URLs or IDs, maximum: 100 ids
"""

View File

@ -8,6 +8,7 @@ __all__ = [
]
import base64
import html
import logging
import os
import time
@ -21,7 +22,8 @@ import requests
from spotipy.cache_handler import CacheFileHandler, CacheHandler
from spotipy.exceptions import SpotifyOauthError, SpotifyStateError
from spotipy.util import CLIENT_CREDS_ENV_VARS, get_host_port, normalize_scope
from spotipy.util import (CLIENT_CREDS_ENV_VARS, REQUESTS_SESSION,
get_host_port, normalize_scope)
logger = logging.getLogger(__name__)
@ -122,7 +124,7 @@ class SpotifyAuthBase:
def __del__(self):
"""Make sure the connection (pool) gets closed"""
if isinstance(self._session, requests.Session):
if getattr(self, "_session", None) and isinstance(self._session, REQUESTS_SESSION):
self._session.close()
@ -185,7 +187,7 @@ class SpotifyClientCredentials(SpotifyAuthBase):
Else fetches a new token and returns it
Parameters:
- as_dict - a boolean indicating if returning the access token
- as_dict: (deprecated) a boolean indicating if returning the access token
as a token_info dictionary, otherwise it will be returned
as a string.
"""
@ -483,8 +485,8 @@ class SpotifyOAuth(SpotifyAuthBase):
""" 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
- code: the response code
- as_dict: (deprecated) a boolean indicating if returning the access token
as a token_info dictionary, otherwise it will be returned
as a string.
"""
@ -577,6 +579,11 @@ class SpotifyOAuth(SpotifyAuthBase):
return token_info
def get_cached_token(self):
""" Gets the cached token for the app
.. deprecated::
This method is deprecated and may be removed in a future version.
"""
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 " +
@ -1203,6 +1210,11 @@ class SpotifyImplicitGrant(SpotifyAuthBase):
return token_info
def get_cached_token(self):
""" Gets the cached token for the app
.. deprecated::
This method is deprecated and may be removed in a future version.
"""
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 " +
@ -1241,7 +1253,7 @@ class RequestHandler(BaseHTTPRequestHandler):
if self.server.auth_code:
status = "successful"
elif self.server.error:
status = f"failed ({self.server.error})"
status = f"failed ({html.escape(str(self.server.error))})"
else:
self._write("<html><body><h1>Invalid request</h1></body></html>")
return

View File

@ -9,11 +9,12 @@ import os
import warnings
from types import TracebackType
import requests
import urllib3
import spotipy
LOGGER = logging.getLogger(__name__)
logger = logging.getLogger(__name__)
CLIENT_CREDS_ENV_VARS = {
"client_id": "SPOTIPY_CLIENT_ID",
@ -22,6 +23,9 @@ CLIENT_CREDS_ENV_VARS = {
"redirect_uri": "SPOTIPY_REDIRECT_URI",
}
# workaround for garbage collection
REQUESTS_SESSION = requests.Session
def prompt_for_user_token(
username=None,
@ -33,15 +37,11 @@ def prompt_for_user_token(
oauth_manager=None,
show_dialog=False
):
warnings.warn(
"'prompt_for_user_token' is deprecated."
"Use the following instead: "
" auth_manager=SpotifyOAuth(scope=scope)"
" spotipy.Spotify(auth_manager=auth_manager)",
DeprecationWarning
)
"""Prompt the user to login if necessary and returns a user token
suitable for use with the spotipy.Spotify constructor.
""" Prompt the user to login if necessary and returns a user token
suitable for use with the spotipy.Spotify constructor.
.. deprecated::
This method is deprecated and may be removed in a future version.
Parameters:
- username - the Spotify username. (optional)
@ -53,6 +53,14 @@ def prompt_for_user_token(
- oauth_manager - OAuth manager object. (optional)
- show_dialog - If True, a login prompt always shows or defaults to False. (optional)
"""
warnings.warn(
"'prompt_for_user_token' is deprecated."
"Use the following instead: "
" auth_manager=SpotifyOAuth(scope=scope)"
" spotipy.Spotify(auth_manager=auth_manager)",
DeprecationWarning
)
if not oauth_manager:
if not client_id:
client_id = os.getenv("SPOTIPY_CLIENT_ID")
@ -64,7 +72,7 @@ def prompt_for_user_token(
redirect_uri = os.getenv("SPOTIPY_REDIRECT_URI")
if not client_id:
LOGGER.warning(
logger.warning(
"""
You need to set your Spotify API credentials.
You can do this by setting environment variables like so:
@ -166,8 +174,9 @@ class Retry(urllib3.Retry):
if response:
retry_header = response.headers.get("Retry-After")
if self.is_retry(method, response.status, bool(retry_header)):
logging.warning("Your application has reached a rate/request limit. "
f"Retry will occur after: {retry_header}")
retry_header = retry_header or 0
logger.warning("Your application has reached a rate/request limit. "
f"Retry will occur after: {retry_header} s")
return super().increment(method,
url,
response=response,

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