Compare commits

..

No commits in common. "master" and "2.25.1" have entirely different histories.

15 changed files with 118 additions and 323 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@ 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased ## Unreleased
Add your changes below. Add your changes below.
### Added ### Added
@ -14,32 +15,6 @@ Add your changes below.
### Removed ### 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 ## [2.25.1] - 2025-02-27
### Added ### Added
@ -51,8 +26,6 @@ Add your changes below.
- Fixed scripts in examples directory that didn't run correctly - 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 - 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 - 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 ### Changed

View File

@ -39,7 +39,8 @@ 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. 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 ```bash
$ virtualenv --python=python3 env $ virtualenv --python=python3 env
@ -50,7 +51,7 @@ $ source env/bin/activate
### Lint ### Lint
pip install ".[test]" pip install .[test]
To automatically fix some of the code style: To automatically fix some of the code style:
@ -74,9 +75,9 @@ Don't forget to add a short description of your change in the [CHANGELOG](CHANGE
### Publishing (by maintainer) ### Publishing (by maintainer)
- Bump version in setup.py - Bump version in setup.py
- Bump and date changelog - Bump and date changelog
- Add to changelog: - Add to changelog:
## Unreleased ## Unreleased
Add your changes below. Add your changes below.
@ -87,8 +88,9 @@ Don't forget to add a short description of your change in the [CHANGELOG](CHANGE
### Removed ### Removed
- Commit changes - Commit changes
- Push tag to trigger PyPI build & release workflow - 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 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 ## 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/spotipy-dev/spotipy-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/plamere/spotipy/tree/master/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: 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 pip install spotipy --upgrade
You can also obtain the source code from the `Spotipy GitHub repository <https://github.com/plamere/spotipy>`_. You can also obtain the source code from the `Spotify GitHub repository <https://github.com/plamere/spotipy>`_.
Getting Started Getting Started

View File

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

View File

@ -13,15 +13,15 @@ extra_reqs = {
], ],
'test': [ 'test': [
'autopep8>=2.3.2', 'autopep8>=2.3.2',
'flake8>=7.3.0', 'flake8>=7.1.1',
'flake8-use-fstring>=1.4', 'flake8-string-format>=0.3.0',
'isort>=7.0.0' 'isort>=5.13.2'
] ]
} }
setup( setup(
name='spotipy', name='spotipy',
version='2.26.0', version='2.25.1',
description='A light weight Python library for the Spotify Web API', description='A light weight Python library for the Spotify Web API',
long_description=long_description, long_description=long_description,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",

View File

@ -11,7 +11,7 @@ from collections import defaultdict
import requests import requests
from spotipy.exceptions import SpotifyException from spotipy.exceptions import SpotifyException
from spotipy.util import REQUESTS_SESSION, Retry from spotipy.util import Retry
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -211,8 +211,11 @@ class Spotify:
def __del__(self): def __del__(self):
"""Make sure the connection (pool) gets closed""" """Make sure the connection (pool) gets closed"""
if getattr(self, "_session", None) and isinstance(self._session, REQUESTS_SESSION): try:
if isinstance(self._session, requests.Session):
self._session.close() self._session.close()
except AttributeError:
pass
def _build_session(self): def _build_session(self):
self._session = requests.Session() self._session = requests.Session()
@ -400,14 +403,10 @@ class Spotify:
return self._get("artists/?ids=" + ",".join(tlist)) return self._get("artists/?ids=" + ",".join(tlist))
def artist_albums( def artist_albums(
self, artist_id, album_type=None, include_groups=None, country=None, limit=10, offset=0 self, artist_id, album_type=None, include_groups=None, country=None, limit=20, offset=0
): ):
""" Get Spotify catalog information about an artist's albums """ 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: Parameters:
- artist_id - the artist ID, URI or URL - artist_id - the artist ID, URI or URL
- include_groups - the types of items to return. One or more of 'album', 'single', - include_groups - the types of items to return. One or more of 'album', 'single',
@ -445,11 +444,6 @@ class Spotify:
- country - limit the response to one particular country. - 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) trid = self._get_id("artist", artist_id)
return self._get("artists/" + trid + "/top-tracks", country=country) return self._get("artists/" + trid + "/top-tracks", country=country)
@ -458,9 +452,6 @@ class Spotify:
identified artist. Similarity is based on analysis of the identified artist. Similarity is based on analysis of the
Spotify community's listening history. Spotify community's listening history.
.. deprecated::
This endpoint has been removed by Spotify and is no longer available.
Parameters: Parameters:
- artist_id - the artist ID, URI or URL - artist_id - the artist ID, URI or URL
""" """
@ -652,11 +643,6 @@ class Spotify:
Parameters: Parameters:
- user - the id of the usr - 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) return self._get("users/" + user)
def current_user_playlists(self, limit=50, offset=0): def current_user_playlists(self, limit=50, offset=0):
@ -690,17 +676,13 @@ class Spotify:
self, self,
playlist_id, playlist_id,
fields=None, fields=None,
limit=50, limit=100,
offset=0, offset=0,
market=None, market=None,
additional_types=("track",) additional_types=("track",)
): ):
""" Get full details of the tracks of a playlist. """ 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: Parameters:
- playlist_id - the playlist ID, URI or URL - playlist_id - the playlist ID, URI or URL
- fields - which fields to return - fields - which fields to return
@ -722,7 +704,7 @@ class Spotify:
self, self,
playlist_id, playlist_id,
fields=None, fields=None,
limit=50, limit=100,
offset=0, offset=0,
market=None, market=None,
additional_types=("track", "episode") additional_types=("track", "episode")
@ -740,7 +722,7 @@ class Spotify:
""" """
plid = self._get_id("playlist", playlist_id) plid = self._get_id("playlist", playlist_id)
return self._get( return self._get(
f"playlists/{plid}/items", f"playlists/{plid}/tracks",
limit=limit, limit=limit,
offset=offset, offset=offset,
fields=fields, fields=fields,
@ -773,22 +755,18 @@ class Spotify:
) )
def user_playlist(self, user, playlist_id=None, fields=None, market=None): def user_playlist(self, user, playlist_id=None, fields=None, market=None):
""" Gets a single playlist of a user warnings.warn(
"You should use `playlist(playlist_id)` instead",
DeprecationWarning,
)
.. deprecated:: """ Gets a single playlist of a user
This method is deprecated and may be removed in a future version. Use
`playlist(playlist_id)` instead.
Parameters: Parameters:
- user - the id of the user - user - the id of the user
- playlist_id - the id of the playlist - playlist_id - the id of the playlist
- fields - which fields to return - fields - which fields to return
""" """
warnings.warn(
"You should use `playlist(playlist_id)` instead",
DeprecationWarning,
)
if playlist_id is None: if playlist_id is None:
return self._get(f"users/{user}/starred") return self._get(f"users/{user}/starred")
return self.playlist(playlist_id, fields=fields, market=market) return self.playlist(playlist_id, fields=fields, market=market)
@ -802,11 +780,12 @@ class Spotify:
offset=0, offset=0,
market=None, market=None,
): ):
""" Get full details of the tracks of a playlist owned by a user. warnings.warn(
"You should use `playlist_tracks(playlist_id)` instead",
DeprecationWarning,
)
.. deprecated:: """ Get full details of the tracks of a playlist owned by a user.
This method is deprecated and may be removed in a future version. Use
`playlist_tracks(playlist_id)` instead.
Parameters: Parameters:
- user - the id of the user - user - the id of the user
@ -816,10 +795,6 @@ class Spotify:
- offset - the index of the first track to return - offset - the index of the first track to return
- market - an ISO 3166-1 alpha-2 country code. - market - an ISO 3166-1 alpha-2 country code.
""" """
warnings.warn(
"You should use `playlist_tracks(playlist_id)` instead",
DeprecationWarning,
)
return self.playlist_tracks( return self.playlist_tracks(
playlist_id, playlist_id,
limit=limit, limit=limit,
@ -836,12 +811,6 @@ class Spotify:
- limit - the number of items to return - limit - the number of items to return
- offset - the index of the first item 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( return self._get(
f"users/{user}/playlists", limit=limit, offset=offset f"users/{user}/playlists", limit=limit, offset=offset
) )
@ -856,12 +825,6 @@ class Spotify:
- collaborative - is the created playlist collaborative - collaborative - is the created playlist collaborative
- description - the description of the playlist - 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 = { data = {
"name": name, "name": name,
"public": public, "public": public,
@ -871,24 +834,6 @@ class Spotify:
return self._post(f"users/{user}/playlists", payload=data) 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( def user_playlist_change_details(
self, self,
user, user,
@ -902,10 +847,6 @@ class Spotify:
Changes a playlist's name and/or public/private state 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: Parameters:
- user - the id of the user - user - the id of the user
- playlist_id - the id of the playlist - playlist_id - the id of the playlist
@ -927,10 +868,6 @@ class Spotify:
Unfollows (deletes) a playlist for a user 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: Parameters:
- user - the id of the user - user - the id of the user
- name - the name of the playlist - name - the name of the playlist
@ -948,10 +885,6 @@ class Spotify:
Adds tracks to a playlist 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: Parameters:
- user - the id of the user - user - the id of the user
- playlist_id - the id of the playlist - playlist_id - the id of the playlist
@ -973,10 +906,6 @@ class Spotify:
Adds episodes to a playlist 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: Parameters:
- user - the id of the user - user - the id of the user
- playlist_id - the id of the playlist - playlist_id - the id of the playlist
@ -996,10 +925,6 @@ class Spotify:
Replace all tracks in a playlist for a user 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: Parameters:
- user - the id of the user - user - the id of the user
- playlist_id - the id of the playlist - playlist_id - the id of the playlist
@ -1024,10 +949,6 @@ class Spotify:
Reorder tracks in a playlist from a user 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: Parameters:
- user - the id of the user - user - the id of the user
- playlist_id - the id of the playlist - playlist_id - the id of the playlist
@ -1053,10 +974,6 @@ class Spotify:
Removes all occurrences of the given tracks from the given playlist 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: Parameters:
- user - the id of the user - user - the id of the user
- playlist_id - the id of the playlist - playlist_id - the id of the playlist
@ -1077,10 +994,7 @@ class Spotify:
): ):
""" This function is no longer in use, please use the recommended function in the warning! """ This function is no longer in use, please use the recommended function in the warning!
Removes specific occurrences of the given tracks from the given playlist Removes all occurrences of the given tracks from the given playlist
.. deprecated::
This endpoint has been removed by Spotify and is no longer available.
Parameters: Parameters:
- user - the id of the user - user - the id of the user
@ -1093,8 +1007,8 @@ class Spotify:
- snapshot_id - optional id of the playlist snapshot - snapshot_id - optional id of the playlist snapshot
""" """
warnings.warn( warnings.warn(
"You're using `user_playlist_remove_specific_occurrences_of_tracks(...)`, " "You should use `playlist_remove_specific_occurrences_of_items"
"which is marked as deprecated by Spotify.", "(playlist_id, tracks)` instead",
DeprecationWarning, DeprecationWarning,
) )
plid = self._get_id("playlist", playlist_id) plid = self._get_id("playlist", playlist_id)
@ -1118,10 +1032,6 @@ class Spotify:
Add the current authenticated user as a follower of a playlist. 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: Parameters:
- playlist_owner_id - the user id of the playlist owner - playlist_owner_id - the user id of the playlist owner
- playlist_id - the id of the playlist - playlist_id - the id of the playlist
@ -1139,10 +1049,6 @@ class Spotify:
Check to see if the given users are following the given playlist 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: Parameters:
- playlist_owner_id - the user id of the playlist owner - playlist_owner_id - the user id of the playlist owner
- playlist_id - the id of the playlist - playlist_id - the id of the playlist
@ -1211,7 +1117,7 @@ class Spotify:
plid = self._get_id("playlist", playlist_id) plid = self._get_id("playlist", playlist_id)
ftracks = [self._get_uri("track", tid) for tid in items] ftracks = [self._get_uri("track", tid) for tid in items]
return self._post( return self._post(
f"playlists/{plid}/items", f"playlists/{plid}/tracks",
payload=ftracks, payload=ftracks,
position=position, position=position,
) )
@ -1227,7 +1133,7 @@ class Spotify:
ftracks = [self._get_uri("track", tid) for tid in items] ftracks = [self._get_uri("track", tid) for tid in items]
payload = {"uris": ftracks} payload = {"uris": ftracks}
return self._put( return self._put(
f"playlists/{plid}/items", payload=payload f"playlists/{plid}/tracks", payload=payload
) )
def playlist_reorder_items( def playlist_reorder_items(
@ -1258,7 +1164,7 @@ class Spotify:
if snapshot_id: if snapshot_id:
payload["snapshot_id"] = snapshot_id payload["snapshot_id"] = snapshot_id
return self._put( return self._put(
f"playlists/{plid}/items", payload=payload f"playlists/{plid}/tracks", payload=payload
) )
def playlist_remove_all_occurrences_of_items( def playlist_remove_all_occurrences_of_items(
@ -1275,11 +1181,11 @@ class Spotify:
plid = self._get_id("playlist", playlist_id) plid = self._get_id("playlist", playlist_id)
ftracks = [self._get_uri("track", tid) for tid in items] ftracks = [self._get_uri("track", tid) for tid in items]
payload = {"items": [{"uri": track} for track in ftracks]} payload = {"tracks": [{"uri": track} for track in ftracks]}
if snapshot_id: if snapshot_id:
payload["snapshot_id"] = snapshot_id payload["snapshot_id"] = snapshot_id
return self._delete( return self._delete(
f"playlists/{plid}/items", payload=payload f"playlists/{plid}/tracks", payload=payload
) )
def playlist_remove_specific_occurrences_of_items( def playlist_remove_specific_occurrences_of_items(
@ -1306,14 +1212,14 @@ class Spotify:
"positions": tr["positions"], "positions": tr["positions"],
} }
) )
payload = {"items": ftracks} payload = {"tracks": ftracks}
if snapshot_id: if snapshot_id:
payload["snapshot_id"] = snapshot_id payload["snapshot_id"] = snapshot_id
return self._delete( return self._delete(
f"playlists/{plid}/items", payload=payload f"playlists/{plid}/tracks", payload=payload
) )
def current_user_follow_playlist(self, playlist_id): def current_user_follow_playlist(self, playlist_id, public=True):
""" """
Add the current authenticated user as a follower of a playlist. Add the current authenticated user as a follower of a playlist.
@ -1321,7 +1227,10 @@ class Spotify:
- playlist_id - the id of the playlist - playlist_id - the id of the playlist
""" """
return self._put("me/library", uris=self._get_uri("playlist", playlist_id)) return self._put(
f"playlists/{playlist_id}/followers",
payload={"public": public}
)
def playlist_is_following( def playlist_is_following(
self, playlist_id, user_ids self, playlist_id, user_ids
@ -1335,27 +1244,9 @@ class Spotify:
if they follow the playlist. Maximum: 5 ids. 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)}" endpoint = f"playlists/{playlist_id}/followers/contains?ids={','.join(user_ids)}"
return self._get(endpoint) 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): def me(self):
""" Get detailed profile information about the current user. """ Get detailed profile information about the current user.
An alias for the 'current_user' method. An alias for the 'current_user' method.
@ -1368,20 +1259,10 @@ class Spotify:
""" """
return self.me() return self.me()
def current_user_playing_track(self, market=None, additional_types=("track",)): def current_user_playing_track(self):
""" Get information about the current users currently playing 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( return self._get("me/player/currently-playing")
"me/player/currently-playing",
market=market,
additional_types=",".join(additional_types)
)
def current_user_saved_albums(self, limit=20, offset=0, market=None): 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 """ Gets a list of the albums saved in the current authorized user's
@ -1402,8 +1283,8 @@ class Spotify:
- albums - a list of album URIs, URLs or IDs - albums - a list of album URIs, URLs or IDs
""" """
alist = [self._get_uri("album", a) for a in albums] alist = [self._get_id("album", a) for a in albums]
return self._put("me/library", uris=",".join(alist)) return self._put("me/albums?ids=" + ",".join(alist))
def current_user_saved_albums_delete(self, albums=[]): def current_user_saved_albums_delete(self, albums=[]):
""" Remove one or more albums from the current user's """ Remove one or more albums from the current user's
@ -1412,8 +1293,8 @@ class Spotify:
Parameters: Parameters:
- albums - a list of album URIs, URLs or IDs - albums - a list of album URIs, URLs or IDs
""" """
alist = [self._get_uri("album", a) for a in albums] alist = [self._get_id("album", a) for a in albums]
return self._delete("me/library", uris=",".join(alist)) return self._delete("me/albums/?ids=" + ",".join(alist))
def current_user_saved_albums_contains(self, albums=[]): def current_user_saved_albums_contains(self, albums=[]):
""" Check if one or more albums is already saved in """ Check if one or more albums is already saved in
@ -1422,8 +1303,8 @@ class Spotify:
Parameters: Parameters:
- albums - a list of album URIs, URLs or IDs - albums - a list of album URIs, URLs or IDs
""" """
alist = [self._get_uri("album", a) for a in albums] alist = [self._get_id("album", a) for a in albums]
return self._get("me/library/contains", uris=",".join(alist)) return self._get("me/albums/contains?ids=" + ",".join(alist))
def current_user_saved_tracks(self, limit=20, offset=0, market=None): 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 """ Gets a list of the tracks saved in the current authorized user's
@ -1446,8 +1327,8 @@ class Spotify:
""" """
tlist = [] tlist = []
if tracks is not None: if tracks is not None:
tlist = [self._get_uri("track", t) for t in tracks] tlist = [self._get_id("track", t) for t in tracks]
return self._put("me/library", uris=",".join(tlist)) return self._put("me/tracks/?ids=" + ",".join(tlist))
def current_user_saved_tracks_delete(self, tracks=None): def current_user_saved_tracks_delete(self, tracks=None):
""" Remove one or more tracks from the current user's """ Remove one or more tracks from the current user's
@ -1458,8 +1339,8 @@ class Spotify:
""" """
tlist = [] tlist = []
if tracks is not None: if tracks is not None:
tlist = [self._get_uri("track", t) for t in tracks] tlist = [self._get_id("track", t) for t in tracks]
return self._delete("me/library", uris=",".join(tlist)) return self._delete("me/tracks/?ids=" + ",".join(tlist))
def current_user_saved_tracks_contains(self, tracks=None): def current_user_saved_tracks_contains(self, tracks=None):
""" Check if one or more tracks is already saved in """ Check if one or more tracks is already saved in
@ -1470,8 +1351,8 @@ class Spotify:
""" """
tlist = [] tlist = []
if tracks is not None: if tracks is not None:
tlist = [self._get_uri("track", t) for t in tracks] tlist = [self._get_id("track", t) for t in tracks]
return self._get("me/library/contains", uris=",".join(tlist)) return self._get("me/tracks/contains?ids=" + ",".join(tlist))
def current_user_saved_episodes(self, limit=20, offset=0, market=None): 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 """ Gets a list of the episodes saved in the current authorized user's
@ -1494,8 +1375,8 @@ class Spotify:
""" """
elist = [] elist = []
if episodes is not None: if episodes is not None:
elist = [self._get_uri("episode", e) for e in episodes] elist = [self._get_id("episode", e) for e in episodes]
return self._put("me/library", uris=",".join(elist)) return self._put("me/episodes/?ids=" + ",".join(elist))
def current_user_saved_episodes_delete(self, episodes=None): def current_user_saved_episodes_delete(self, episodes=None):
""" Remove one or more episodes from the current user's """ Remove one or more episodes from the current user's
@ -1506,8 +1387,8 @@ class Spotify:
""" """
elist = [] elist = []
if episodes is not None: if episodes is not None:
elist = [self._get_uri("episode", e) for e in episodes] elist = [self._get_id("episode", e) for e in episodes]
return self._delete("me/library", uris=",".join(elist)) return self._delete("me/episodes/?ids=" + ",".join(elist))
def current_user_saved_episodes_contains(self, episodes=None): def current_user_saved_episodes_contains(self, episodes=None):
""" Check if one or more episodes is already saved in """ Check if one or more episodes is already saved in
@ -1539,8 +1420,8 @@ class Spotify:
Parameters: Parameters:
- shows - a list of show URIs, URLs or IDs - shows - a list of show URIs, URLs or IDs
""" """
slist = [self._get_uri("show", s) for s in shows] slist = [self._get_id("show", s) for s in shows]
return self._put("me/library", uris=",".join(slist)) return self._put("me/shows?ids=" + ",".join(slist))
def current_user_saved_shows_delete(self, shows=[]): def current_user_saved_shows_delete(self, shows=[]):
""" Remove one or more shows from the current user's """ Remove one or more shows from the current user's
@ -1549,8 +1430,8 @@ class Spotify:
Parameters: Parameters:
- shows - a list of show URIs, URLs or IDs - shows - a list of show URIs, URLs or IDs
""" """
slist = [self._get_uri("show", s) for s in shows] slist = [self._get_id("show", s) for s in shows]
return self._delete("me/library", uris=",".join(slist)) return self._delete("me/shows/?ids=" + ",".join(slist))
def current_user_saved_shows_contains(self, shows=[]): def current_user_saved_shows_contains(self, shows=[]):
""" Check if one or more shows is already saved in """ Check if one or more shows is already saved in
@ -1559,8 +1440,8 @@ class Spotify:
Parameters: Parameters:
- shows - a list of show URIs, URLs or IDs - shows - a list of show URIs, URLs or IDs
""" """
slist = [self._get_uri("show", s) for s in shows] slist = [self._get_id("show", s) for s in shows]
return self._get("me/library/contains", uris=",".join(slist)) return self._get("me/shows/contains?ids=" + ",".join(slist))
def current_user_followed_artists(self, limit=20, after=None): def current_user_followed_artists(self, limit=20, after=None):
""" Gets a list of the artists followed by the current authorized user """ Gets a list of the artists followed by the current authorized user
@ -1583,11 +1464,11 @@ class Spotify:
Parameters: Parameters:
- ids - a list of artist URIs, URLs or IDs - ids - a list of artist URIs, URLs or IDs
""" """
ulist = [] idlist = []
if ids is not None: if ids is not None:
ulist = [self._get_uri("artist", i) for i in ids] idlist = [self._get_id("artist", i) for i in ids]
return self._get( return self._get(
"me/library/contains", uris=",".join(ulist) "me/following/contains", ids=",".join(idlist), type="artist"
) )
def current_user_following_users(self, ids=None): def current_user_following_users(self, ids=None):
@ -1598,11 +1479,11 @@ class Spotify:
Parameters: Parameters:
- ids - a list of user URIs, URLs or IDs - ids - a list of user URIs, URLs or IDs
""" """
ulist = [] idlist = []
if ids is not None: if ids is not None:
ulist = [self._get_uri("user", i) for i in ids] idlist = [self._get_id("user", i) for i in ids]
return self._get( return self._get(
"me/library/contains", uris=",".join(ulist) "me/following/contains", ids=",".join(idlist), type="user"
) )
def current_user_top_artists( def current_user_top_artists(
@ -1659,41 +1540,34 @@ class Spotify:
Parameters: Parameters:
- ids - a list of artist IDs - ids - a list of artist IDs
""" """
alist = [self._get_uri("artist", a) for a in ids] return self._put("me/following?type=artist&ids=" + ",".join(ids))
return self._put("me/library", uris=",".join(alist))
def user_follow_users(self, ids=[]): def user_follow_users(self, ids=[]):
""" Follow one or more users """ Follow one or more users
Parameters: Parameters:
- ids - a list of user IDs - ids - a list of user IDs
""" """
ulist = [self._get_uri("user", a) for a in ids] return self._put("me/following?type=user&ids=" + ",".join(ids))
return self._put("me/library", uris=",".join(ulist))
def user_unfollow_artists(self, ids=[]): def user_unfollow_artists(self, ids=[]):
""" Unfollow one or more artists """ Unfollow one or more artists
Parameters: Parameters:
- ids - a list of artist IDs - ids - a list of artist IDs
""" """
alist = [self._get_uri("artist", a) for a in ids] return self._delete("me/following?type=artist&ids=" + ",".join(ids))
return self._delete("me/library", uris=",".join(alist))
def user_unfollow_users(self, ids=[]): def user_unfollow_users(self, ids=[]):
""" Unfollow one or more users """ Unfollow one or more users
Parameters: Parameters:
- ids - a list of user IDs - ids - a list of user IDs
""" """
ulist = [self._get_uri("user", a) for a in ids] return self._delete("me/following?type=user&ids=" + ",".join(ids))
return self._delete("me/library", uris=",".join(ulist))
def featured_playlists( def featured_playlists(
self, locale=None, country=None, timestamp=None, limit=20, offset=0 self, locale=None, country=None, timestamp=None, limit=20, offset=0
): ):
""" Get a list of Spotify featured playlists """ Get a list of Spotify featured playlists
.. deprecated::
This endpoint has been removed by Spotify and is no longer available.
Parameters: Parameters:
- locale - The desired language, consisting of a lowercase ISO - locale - The desired language, consisting of a lowercase ISO
639-1 alpha-2 language code and an uppercase ISO 3166-1 alpha-2 639-1 alpha-2 language code and an uppercase ISO 3166-1 alpha-2
@ -1740,11 +1614,6 @@ class Spotify:
(the first object). Use with limit to get the next set of (the first object). Use with limit to get the next set of
items. items.
""" """
warnings.warn(
"You're using `new_release(...)`, "
"which is marked as deprecated by Spotify.",
DeprecationWarning,
)
return self._get( return self._get(
"browse/new-releases", country=country, limit=limit, offset=offset "browse/new-releases", country=country, limit=limit, offset=offset
) )
@ -1760,11 +1629,6 @@ class Spotify:
language code and an ISO 3166-1 alpha-2 country code, joined language code and an ISO 3166-1 alpha-2 country code, joined
by an underscore. by an underscore.
""" """
warnings.warn(
"You're using `category(...)`, "
"which is marked as deprecated by Spotify.",
DeprecationWarning,
)
return self._get( return self._get(
"browse/categories/" + category_id, "browse/categories/" + category_id,
country=country, country=country,
@ -1787,11 +1651,6 @@ class Spotify:
(the first object). Use with limit to get the next set of (the first object). Use with limit to get the next set of
items. items.
""" """
warnings.warn(
"You're using `categories(...)`, "
"which is marked as deprecated by Spotify.",
DeprecationWarning,
)
return self._get( return self._get(
"browse/categories", "browse/categories",
country=country, country=country,
@ -1805,9 +1664,6 @@ class Spotify:
): ):
""" Get a list of playlists for a specific Spotify category """ Get a list of playlists for a specific Spotify category
.. deprecated::
This endpoint has been removed by Spotify and is no longer available.
Parameters: Parameters:
- category_id - The Spotify category ID for the category. - category_id - The Spotify category ID for the category.
@ -1845,9 +1701,6 @@ class Spotify:
(at least one of `seed_artists`, `seed_tracks` and `seed_genres` (at least one of `seed_artists`, `seed_tracks` and `seed_genres`
are needed) are needed)
.. deprecated::
This endpoint has been removed by Spotify and is no longer available.
Parameters: Parameters:
- seed_artists - a list of artist IDs, URIs or URLs - seed_artists - a list of artist IDs, URIs or URLs
- seed_tracks - a list of track IDs, URIs or URLs - seed_tracks - a list of track IDs, URIs or URLs
@ -1908,24 +1761,17 @@ class Spotify:
return self._get("recommendations", **params) return self._get("recommendations", **params)
def recommendation_genre_seeds(self): 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( warnings.warn(
"You're using `recommendation_genre_seeds(...)`, " "You're using `recommendation_genre_seeds(...)`, "
"which is marked as deprecated by Spotify.", "which is marked as deprecated by Spotify.",
DeprecationWarning, DeprecationWarning,
) )
""" Get a list of genres available for the recommendations function.
"""
return self._get("recommendations/available-genre-seeds") return self._get("recommendations/available-genre-seeds")
def audio_analysis(self, track_id): def audio_analysis(self, track_id):
""" Get audio analysis for a track based upon its Spotify 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: Parameters:
- track_id - a track URI, URL or ID - track_id - a track URI, URL or ID
""" """
@ -1939,10 +1785,6 @@ class Spotify:
def audio_features(self, tracks=[]): def audio_features(self, tracks=[]):
""" Get audio features for one or multiple tracks based upon their Spotify IDs """ 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: Parameters:
- tracks - a list of track URIs, URLs or IDs, maximum: 100 ids - tracks - a list of track URIs, URLs or IDs, maximum: 100 ids
""" """

View File

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

View File

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

View File

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