mirror of
https://github.com/spotipy-dev/spotipy.git
synced 2026-06-19 09:13:53 +00:00
Compare commits
No commits in common. "master" and "2.25.0" have entirely different histories.
15
.github/workflows/integration_tests.yml
vendored
15
.github/workflows/integration_tests.yml
vendored
@ -1,26 +1,25 @@
|
||||
name: Integration tests
|
||||
|
||||
on: [push, pull_request]
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SPOTIPY_CLIENT_ID: ${{ secrets.SPOTIPY_CLIENT_ID }}
|
||||
SPOTIPY_CLIENT_SECRET: ${{ secrets.SPOTIPY_CLIENT_SECRET }}
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
|
||||
PYTHON_VERSION: "3.10"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install .
|
||||
pip install .[test]
|
||||
- name: Run non user endpoints integration tests
|
||||
run: |
|
||||
python -m unittest discover -v tests/integration/non_user_endpoints
|
||||
|
||||
23
.github/workflows/lint.yml
vendored
23
.github/workflows/lint.yml
vendored
@ -1,23 +0,0 @@
|
||||
name: Lint
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x" # Lint can be done on latest Python only
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install .[test]
|
||||
- name: Check pep8 with flake8
|
||||
run: |
|
||||
flake8 . --count --show-source --statistics
|
||||
- name: Check sorted imports with isort
|
||||
run: |
|
||||
isort . -c
|
||||
3
.github/workflows/publish.yml
vendored
3
.github/workflows/publish.yml
vendored
@ -10,7 +10,8 @@ on:
|
||||
jobs:
|
||||
build-n-publish:
|
||||
name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
|
||||
4
.github/workflows/pull_request.yml
vendored
4
.github/workflows/pull_request.yml
vendored
@ -6,10 +6,10 @@ on:
|
||||
jobs:
|
||||
# Enforces the update of a changelog file on every pull request
|
||||
changelog:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dangoslen/changelog-enforcer@v3.6.1
|
||||
with:
|
||||
changeLogPath: 'CHANGELOG.md'
|
||||
skipLabels: 'skip-changelog'
|
||||
skipLabel: 'skip-changelog'
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
name: Unit tests
|
||||
name: Tests
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
|
||||
@ -17,7 +18,12 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install .
|
||||
pip install .[test]
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
pip install -Iv enum34==1.1.6 # https://bitbucket.org/stoneleaf/enum34/issues/27/enum34-118-broken
|
||||
pip install flake8
|
||||
flake8 . --count --show-source --statistics
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
python -m unittest discover -v tests/unit
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "examples"]
|
||||
path = examples
|
||||
url = git@github.com:spotipy-dev/spotipy-examples.git
|
||||
68
CHANGELOG.md
68
CHANGELOG.md
@ -14,58 +14,9 @@ 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
|
||||
|
||||
- Added examples for audiobooks, shows and episodes methods to examples directory
|
||||
|
||||
### Fixed
|
||||
|
||||
- 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
|
||||
|
||||
- Updated get_cached_token and save_token_to_cache methods to utilize Python's Context Management Protocol
|
||||
- Added except clause to get_cached_token method to handle json decode errors
|
||||
- Added warnings and updated docs due to Spotify's deprecation of HTTP and "localhost" redirect URIs
|
||||
- Use newer string formatters (<https://pyformat.info>)
|
||||
- Marked `recommendation_genre_seeds` as deprecated
|
||||
|
||||
## [2.25.0] - 2025-03-01
|
||||
|
||||
### Added
|
||||
|
||||
- Added unit tests for queue functions
|
||||
- Added detailed function docstrings to 'util.py', including descriptions and special sections that lists arguments, returns, and raises.
|
||||
- Updated order of instructions for Python and pip package manager installation in TUTORIAL.md
|
||||
@ -84,62 +35,44 @@ Add your changes below.
|
||||
- featured_playlists
|
||||
- category_playlists
|
||||
- Added FAQ entry for inaccessible playlists
|
||||
- Workflow to check for f-strings
|
||||
|
||||
### Changed
|
||||
|
||||
- Split test and lint workflows
|
||||
- Updated get_cached_token and save_token_to_cache methods to utilize Python's Context Management Protocol
|
||||
- Added except clause to get_cached_token method to handle json decode errors
|
||||
|
||||
### Fixed
|
||||
|
||||
- Audiobook integration tests
|
||||
- Edited docstrings for certain functions in client.py for functions that are no longer in use and have been replaced.
|
||||
- `current_user_unfollow_playlist()` now supports playlist IDs, URLs, and URIs rather than previously where it only supported playlist IDs.
|
||||
|
||||
### Removed
|
||||
|
||||
- `mock` no longer listed as a test dependency. Only built-in `unittest.mock` is actually used.
|
||||
|
||||
## [2.24.0] - 2024-05-30
|
||||
|
||||
### Added
|
||||
|
||||
- Added `MemcacheCacheHandler`, a cache handler that stores the token info using pymemcache.
|
||||
- Added support for audiobook endpoints: `get_audiobook`, `get_audiobooks`, and `get_audiobook_chapters`.
|
||||
- Added integration tests for audiobook endpoints.
|
||||
- Added `update` field to `current_user_follow_playlist`.
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated get_cached_token and save_token_to_cache methods to utilize Python's Context Management Protocol
|
||||
- Added except clause to get_cached_token method to handle json decode errors
|
||||
|
||||
- Fixed error obfuscation when Spotify class is being inherited and an error is raised in the Child's `__init__`
|
||||
- Replaced `artist_albums(album_type=...)` with `artist_albums(include_groups=...)` due to an API change.
|
||||
- Updated `_regex_spotify_url` to ignore `/intl-<countrycode>` in Spotify links
|
||||
- Improved README, docs and examples
|
||||
|
||||
### Fixed
|
||||
|
||||
- Readthedocs build
|
||||
- Split `test_current_user_save_and_usave_tracks` unit test
|
||||
|
||||
### Removed
|
||||
|
||||
- Drop support for EOL Python 3.7
|
||||
|
||||
## [2.23.0] - 2023-04-07
|
||||
|
||||
### Added
|
||||
|
||||
- Added optional `encoder_cls` argument to `CacheFileHandler`, which overwrite default encoder for token before writing to disk
|
||||
- Integration tests for searching multiple types in multiple markets (non-user endpoints)
|
||||
- Publish to PyPI action
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed the regex for matching playlist URIs with the format spotify:user:USERNAME:playlist:PLAYLISTID.
|
||||
- `search_markets` now factors the counts of all types in the `total` rather than just the first type ([#534](https://github.com/spotipy-dev/spotipy/issues/534))
|
||||
|
||||
@ -524,7 +457,6 @@ Add your changes below.
|
||||
### Changed
|
||||
|
||||
- Made instructions in the CONTRIBUTING.md file more clear such that it is easier to onboard and there are no conflicts with TUTORIAL.md
|
||||
|
||||
## [2.5.0] - 2020-01-11
|
||||
|
||||
Added follow and player endpoints
|
||||
|
||||
@ -9,37 +9,16 @@ If you would like to contribute to spotipy follow these steps:
|
||||
export SPOTIPY_CLIENT_ID=client_id_here
|
||||
export SPOTIPY_CLIENT_SECRET=client_secret_here
|
||||
export SPOTIPY_CLIENT_USERNAME=client_username_here # This is actually an id not spotify display name and can be found [here](https://www.spotify.com/us/account/overview/)
|
||||
export SPOTIPY_REDIRECT_URI=http://127.0.0.1:8080 # Make url is set in app you created to get your ID and SECRET
|
||||
export SPOTIPY_REDIRECT_URI=http://localhost:8080 # Make url is set in app you created to get your ID and SECRET
|
||||
|
||||
# Windows
|
||||
$env:SPOTIPY_CLIENT_ID="client_id_here"
|
||||
$env:SPOTIPY_CLIENT_SECRET="client_secret_here"
|
||||
$env:SPOTIPY_CLIENT_USERNAME="client_username_here"
|
||||
$env:SPOTIPY_REDIRECT_URI="http://127.0.0.1:8080"
|
||||
$env:SPOTIPY_REDIRECT_URI="http://localhost:8080"
|
||||
```
|
||||
|
||||
### Branch Overview
|
||||
|
||||
After restarting development on version 3, we decided to restrict commits to certain branches in order to push the development forward.
|
||||
To give you a flavour of what we mean, here are some examples of what PRs go where:
|
||||
|
||||
**v3**:
|
||||
|
||||
- any kind of refactoring
|
||||
- better documentation
|
||||
- enhancements
|
||||
- code styles
|
||||
|
||||
**master (v2)**:
|
||||
|
||||
- bug fixes
|
||||
- deprecations
|
||||
- new endpoints (until we release v3)
|
||||
- basic functionality
|
||||
|
||||
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
|
||||
@ -50,23 +29,20 @@ $ source env/bin/activate
|
||||
|
||||
### Lint
|
||||
|
||||
pip install ".[test]"
|
||||
|
||||
To automatically fix some of the code style:
|
||||
To automatically fix the code style:
|
||||
|
||||
pip install autopep8
|
||||
autopep8 --in-place --aggressive --recursive .
|
||||
|
||||
To verify the code style:
|
||||
|
||||
pip install flake8
|
||||
flake8 .
|
||||
|
||||
To make sure if the import lists are stored correctly:
|
||||
|
||||
isort . -c
|
||||
|
||||
Sort them automatically with:
|
||||
|
||||
isort .
|
||||
pip install isort
|
||||
isort . -c -v
|
||||
|
||||
### Changelog
|
||||
|
||||
@ -74,9 +50,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.
|
||||
@ -87,8 +63,9 @@ 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/
|
||||
|
||||
4
FAQ.md
4
FAQ.md
@ -6,7 +6,7 @@ spotipy can only return fields documented on the Spotify web API https://develop
|
||||
|
||||
### How to use spotipy in an API?
|
||||
|
||||
Check out [this example Flask app](https://github.com/spotipy-dev/spotipy-examples/tree/main/apps/flask_api)
|
||||
Check out [this example Flask app](examples/app.py)
|
||||
|
||||
### How can I store tokens in a database rather than on the filesystem?
|
||||
|
||||
@ -51,7 +51,7 @@ must be specified: `search("abba", market="DE")`.
|
||||
If you cannot open a browser, set `open_browser=False` when instantiating SpotifyOAuth or SpotifyPKCE. You will be
|
||||
prompted to open the authorization URI manually.
|
||||
|
||||
See the [headless auth example](https://github.com/spotipy-dev/spotipy-examples/blob/main/scripts/headless.py).
|
||||
See the [headless auth example](examples/headless.py).
|
||||
|
||||
### My application is not responding
|
||||
|
||||
|
||||
@ -36,9 +36,9 @@ 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/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 ([step-by-step video](https://www.youtube.com/watch?v=kaBVN8uP358)):
|
||||
|
||||
### Example without user authentication
|
||||
|
||||
|
||||
@ -27,7 +27,7 @@ Spotipy relies on the Spotify API. In order to use the Spotify API, you'll need
|
||||
|
||||
A. Visit the [Spotify developer portal](https://developer.spotify.com/dashboard/). If you already have a Spotify account, click "Log in" and enter your username and password. Otherwise, click "Sign up" and follow the steps to create an account. After you've signed in or signed up, begin by clicking on your profile name at the top right of your screen and then click “Dashboard” to go to Spotify’s Developer Dashboard.
|
||||
|
||||
B. Check the box "Accept the Spotify Developer Terms of Service" and then click "Accept the terms". On the next page, verify your email address if you haven't already. Click the "Create an App" button. Enter any name and description you'd like for your new app. Next, add "http://127.0.0.1:1234" (or any other port number of your choosing) to the "Redirect URI" secction. Check the box "I understand and agree with Spotify's Developer Terms of Service and Design Guidelines" and then click the "Save" button.
|
||||
B. Check the box "Accept the Spotify Developer Terms of Service" and then click "Accept the terms". On the next page, verify your email address if you haven't already. Click the "Create an App" button. Enter any name and description you'd like for your new app. Next, add "http://localhost:1234" (or any other port number of your choosing) to the "Redirect URI" secction. Check the box "I understand and agree with Spotify's Developer Terms of Service and Design Guidelines" and then click the "Save" button.
|
||||
|
||||
C. Click on "Settings". Underneath "Client ID", you'll see a "View Client Secret" link. Click the link to reveal your Client secret and copy both your Client secret and your Client ID somewhere so that you can access them later.
|
||||
|
||||
|
||||
@ -10,8 +10,8 @@
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
|
||||
@ -23,7 +23,7 @@ Install or upgrade *Spotipy* with::
|
||||
|
||||
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
|
||||
@ -110,9 +110,9 @@ to your application at
|
||||
The ``redirect_uri`` argument or ``SPOTIPY_REDIRECT_URI`` environment variable
|
||||
must match the redirect URI added to your application in your Dashboard.
|
||||
The redirect URI can be any valid URI (it does not need to be accessible)
|
||||
such as ``http://example.com`` or ``http://127.0.0.1:9090``.
|
||||
such as ``http://example.com``, ``http://localhost`` or ``http://127.0.0.1:9090``.
|
||||
|
||||
.. note:: If you choose an `http`-scheme URL, and it's for
|
||||
.. note:: If you choose an `http`-scheme URL, and it's for `localhost` or
|
||||
`127.0.0.1`, **AND** it specifies a port, then spotipy will instantiate
|
||||
a server on the indicated response to receive the access token from the
|
||||
response at the end of the oauth flow [see the code](https://github.com/plamere/spotipy/blob/master/spotipy/oauth2.py#L483-L490).
|
||||
@ -145,7 +145,7 @@ class SpotifyClientCredentials that can be used to authenticate requests like so
|
||||
playlists = sp.user_playlists('spotify')
|
||||
while playlists:
|
||||
for i, playlist in enumerate(playlists['items']):
|
||||
print(f"{i + 1 + playlists['offset']:4d} {playlist['uri']} {playlist['name']}")
|
||||
print("%4d %s %s" % (i + 1 + playlists['offset'], playlist['uri'], playlist['name']))
|
||||
if playlists['next']:
|
||||
playlists = sp.next(playlists)
|
||||
else:
|
||||
@ -254,8 +254,8 @@ artist's name::
|
||||
artist = items[0]
|
||||
print(artist['name'], artist['images'][0]['url'])
|
||||
|
||||
There are many more examples of how to use *Spotipy* in the `spotipy-examples
|
||||
repository <https://github.com/spotipy-dev/spotipy-examples>`_ on GitHub.
|
||||
There are many more examples of how to use *Spotipy* in the `Examples
|
||||
Directory <https://github.com/plamere/spotipy/tree/master/examples>`_ on GitHub.
|
||||
|
||||
API Reference
|
||||
==============
|
||||
@ -309,7 +309,7 @@ Export the needed Environment variables:::
|
||||
export SPOTIPY_CLIENT_ID=client_id_here
|
||||
export SPOTIPY_CLIENT_SECRET=client_secret_here
|
||||
export SPOTIPY_CLIENT_USERNAME=client_username_here # This is actually an id not spotify display name
|
||||
export SPOTIPY_REDIRECT_URI=http://127.0.0.1:8080 # Make url is set in app you created to get your ID and SECRET
|
||||
export SPOTIPY_REDIRECT_URI=http://localhost:8080 # Make url is set in app you created to get your ID and SECRET
|
||||
|
||||
Create virtual environment, install dependencies, run tests:::
|
||||
$ virtualenv --python=python3.12 env
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
Sphinx~=8.1.3
|
||||
sphinx-rtd-theme~=3.1.0
|
||||
Sphinx~=7.4.7
|
||||
sphinx-rtd-theme~=2.0.0
|
||||
redis>=3.5.3
|
||||
|
||||
1
examples
1
examples
@ -1 +0,0 @@
|
||||
Subproject commit c610a79705ef4aa55e4d61572a012f77b6f7245d
|
||||
27
examples/add_a_saved_album.py
Normal file
27
examples/add_a_saved_album.py
Normal file
@ -0,0 +1,27 @@
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
logger = logging.getLogger('examples.add_a_saved_album')
|
||||
logging.basicConfig(level='DEBUG')
|
||||
|
||||
scope = 'user-library-modify'
|
||||
|
||||
|
||||
def get_args():
|
||||
parser = argparse.ArgumentParser(description='Creates a playlist for user')
|
||||
parser.add_argument('-a', '--aids', action='append',
|
||||
required=True, help='Album ids')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
args = get_args()
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
|
||||
sp.current_user_saved_albums_add(albums=args.aids)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
29
examples/add_a_saved_track.py
Normal file
29
examples/add_a_saved_track.py
Normal file
@ -0,0 +1,29 @@
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
scope = 'user-library-modify'
|
||||
|
||||
logger = logging.getLogger('examples.add_a_saved_track')
|
||||
logging.basicConfig(level='DEBUG')
|
||||
|
||||
|
||||
def get_args():
|
||||
parser = argparse.ArgumentParser(description='Add tracks to Your '
|
||||
'Collection of saved tracks')
|
||||
parser.add_argument('-t', '--tids', action='append',
|
||||
required=True, help='Track ids')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
args = get_args()
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
|
||||
|
||||
sp.current_user_saved_tracks_add(tracks=args.tids)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
29
examples/add_tracks_to_playlist.py
Normal file
29
examples/add_tracks_to_playlist.py
Normal file
@ -0,0 +1,29 @@
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
logger = logging.getLogger('examples.add_tracks_to_playlist')
|
||||
logging.basicConfig(level='DEBUG')
|
||||
scope = 'playlist-modify-public'
|
||||
|
||||
|
||||
def get_args():
|
||||
parser = argparse.ArgumentParser(description='Adds track to user playlist')
|
||||
parser.add_argument('-u', '--uris', action='append',
|
||||
required=True, help='Track ids')
|
||||
parser.add_argument('-p', '--playlist', required=True,
|
||||
help='Playlist to add track to')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
args = get_args()
|
||||
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
|
||||
sp.playlist_add_items(args.playlist, args.uris)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
113
examples/app.py
Normal file
113
examples/app.py
Normal file
@ -0,0 +1,113 @@
|
||||
"""
|
||||
Prerequisites
|
||||
|
||||
pip3 install spotipy Flask Flask-Session
|
||||
|
||||
// from your [app settings](https://developer.spotify.com/dashboard/applications)
|
||||
export SPOTIPY_CLIENT_ID=client_id_here
|
||||
export SPOTIPY_CLIENT_SECRET=client_secret_here
|
||||
export SPOTIPY_REDIRECT_URI='http://127.0.0.1:8080' // must contain a port
|
||||
// SPOTIPY_REDIRECT_URI must be added to your [app settings](https://developer.spotify.com/dashboard/applications)
|
||||
OPTIONAL
|
||||
// in development environment for debug output
|
||||
export FLASK_ENV=development
|
||||
// so that you can invoke the app outside the file's directory include
|
||||
export FLASK_APP=/path/to/spotipy/examples/app.py
|
||||
|
||||
// on Windows, use `SET` instead of `export`
|
||||
|
||||
Run app.py
|
||||
|
||||
python3 app.py OR python3 -m flask run
|
||||
NOTE: If receiving "port already in use" error, try other ports: 5000, 8090, 8888, etc...
|
||||
(will need to be updated in your Spotify app and SPOTIPY_REDIRECT_URI variable)
|
||||
"""
|
||||
|
||||
import os
|
||||
from flask import Flask, session, request, redirect
|
||||
from flask_session import Session
|
||||
import spotipy
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SECRET_KEY'] = os.urandom(64)
|
||||
app.config['SESSION_TYPE'] = 'filesystem'
|
||||
app.config['SESSION_FILE_DIR'] = './.flask_session/'
|
||||
Session(app)
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
|
||||
cache_handler = spotipy.cache_handler.FlaskSessionCacheHandler(session)
|
||||
auth_manager = spotipy.oauth2.SpotifyOAuth(scope='user-read-currently-playing playlist-modify-private',
|
||||
cache_handler=cache_handler,
|
||||
show_dialog=True)
|
||||
|
||||
if request.args.get("code"):
|
||||
# Step 2. Being redirected from Spotify auth page
|
||||
auth_manager.get_access_token(request.args.get("code"))
|
||||
return redirect('/')
|
||||
|
||||
if not auth_manager.validate_token(cache_handler.get_cached_token()):
|
||||
# Step 1. Display sign in link when no token
|
||||
auth_url = auth_manager.get_authorize_url()
|
||||
return f'<h2><a href="{auth_url}">Sign in</a></h2>'
|
||||
|
||||
# Step 3. Signed in, display data
|
||||
spotify = spotipy.Spotify(auth_manager=auth_manager)
|
||||
return f'<h2>Hi {spotify.me()["display_name"]}, ' \
|
||||
f'<small><a href="/sign_out">[sign out]<a/></small></h2>' \
|
||||
f'<a href="/playlists">my playlists</a> | ' \
|
||||
f'<a href="/currently_playing">currently playing</a> | ' \
|
||||
f'<a href="/current_user">me</a>' \
|
||||
|
||||
|
||||
|
||||
@app.route('/sign_out')
|
||||
def sign_out():
|
||||
session.pop("token_info", None)
|
||||
return redirect('/')
|
||||
|
||||
|
||||
@app.route('/playlists')
|
||||
def playlists():
|
||||
cache_handler = spotipy.cache_handler.FlaskSessionCacheHandler(session)
|
||||
auth_manager = spotipy.oauth2.SpotifyOAuth(cache_handler=cache_handler)
|
||||
if not auth_manager.validate_token(cache_handler.get_cached_token()):
|
||||
return redirect('/')
|
||||
|
||||
spotify = spotipy.Spotify(auth_manager=auth_manager)
|
||||
return spotify.current_user_playlists()
|
||||
|
||||
|
||||
@app.route('/currently_playing')
|
||||
def currently_playing():
|
||||
cache_handler = spotipy.cache_handler.FlaskSessionCacheHandler(session)
|
||||
auth_manager = spotipy.oauth2.SpotifyOAuth(cache_handler=cache_handler)
|
||||
if not auth_manager.validate_token(cache_handler.get_cached_token()):
|
||||
return redirect('/')
|
||||
spotify = spotipy.Spotify(auth_manager=auth_manager)
|
||||
track = spotify.current_user_playing_track()
|
||||
if not track is None:
|
||||
return track
|
||||
return "No track currently playing."
|
||||
|
||||
|
||||
@app.route('/current_user')
|
||||
def current_user():
|
||||
cache_handler = spotipy.cache_handler.FlaskSessionCacheHandler(session)
|
||||
auth_manager = spotipy.oauth2.SpotifyOAuth(cache_handler=cache_handler)
|
||||
if not auth_manager.validate_token(cache_handler.get_cached_token()):
|
||||
return redirect('/')
|
||||
spotify = spotipy.Spotify(auth_manager=auth_manager)
|
||||
return spotify.current_user()
|
||||
|
||||
|
||||
'''
|
||||
Following lines allow application to be run more conveniently with
|
||||
`python app.py` (Make sure you're using python3)
|
||||
(Also includes directive to leverage pythons threading capacity.)
|
||||
'''
|
||||
if __name__ == '__main__':
|
||||
app.run(threaded=True, port=int(os.environ.get("PORT",
|
||||
os.environ.get("SPOTIPY_REDIRECT_URI", 8080).split(":")[-1])))
|
||||
55
examples/artist_albums.py
Normal file
55
examples/artist_albums.py
Normal file
@ -0,0 +1,55 @@
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
|
||||
logger = logging.getLogger('examples.artist_albums')
|
||||
logging.basicConfig(level='INFO')
|
||||
|
||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
||||
|
||||
|
||||
def get_args():
|
||||
parser = argparse.ArgumentParser(description='Gets albums from artist')
|
||||
parser.add_argument('-a', '--artist', required=True,
|
||||
help='Name of Artist')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def get_artist(name):
|
||||
results = sp.search(q='artist:' + name, type='artist')
|
||||
items = results['artists']['items']
|
||||
if len(items) > 0:
|
||||
return items[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def show_artist_albums(artist):
|
||||
albums = []
|
||||
results = sp.artist_albums(artist['id'], album_type='album')
|
||||
albums.extend(results['items'])
|
||||
while results['next']:
|
||||
results = sp.next(results)
|
||||
albums.extend(results['items'])
|
||||
seen = set() # to avoid dups
|
||||
albums.sort(key=lambda album: album['name'].lower())
|
||||
for album in albums:
|
||||
name = album['name']
|
||||
if name not in seen:
|
||||
logger.info('ALBUM: %s', name)
|
||||
seen.add(name)
|
||||
|
||||
|
||||
def main():
|
||||
args = get_args()
|
||||
artist = get_artist(args.artist)
|
||||
if artist:
|
||||
show_artist_albums(artist)
|
||||
else:
|
||||
logger.error("Can't find artist: %s", artist)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
74
examples/artist_discography.py
Normal file
74
examples/artist_discography.py
Normal file
@ -0,0 +1,74 @@
|
||||
# Shows the list of all songs sung by the artist or the band
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
|
||||
logger = logging.getLogger('examples.artist_discography')
|
||||
logging.basicConfig(level='INFO')
|
||||
|
||||
|
||||
def get_args():
|
||||
parser = argparse.ArgumentParser(description='Shows albums and tracks for '
|
||||
'given artist')
|
||||
parser.add_argument('-a', '--artist', required=True,
|
||||
help='Name of Artist')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def get_artist(name):
|
||||
results = sp.search(q='artist:' + name, type='artist')
|
||||
items = results['artists']['items']
|
||||
if len(items) > 0:
|
||||
return items[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def show_album_tracks(album):
|
||||
tracks = []
|
||||
results = sp.album_tracks(album['id'])
|
||||
tracks.extend(results['items'])
|
||||
while results['next']:
|
||||
results = sp.next(results)
|
||||
tracks.extend(results['items'])
|
||||
for i, track in enumerate(tracks):
|
||||
logger.info('%s. %s', i + 1, track['name'])
|
||||
|
||||
|
||||
def show_artist_albums(artist):
|
||||
albums = []
|
||||
results = sp.artist_albums(artist['id'], album_type='album')
|
||||
albums.extend(results['items'])
|
||||
while results['next']:
|
||||
results = sp.next(results)
|
||||
albums.extend(results['items'])
|
||||
logger.info('Total albums: %s', len(albums))
|
||||
unique = set() # skip duplicate albums
|
||||
for album in albums:
|
||||
name = album['name'].lower()
|
||||
if name not in unique:
|
||||
logger.info('ALBUM: %s', name)
|
||||
unique.add(name)
|
||||
show_album_tracks(album)
|
||||
|
||||
|
||||
def show_artist(artist):
|
||||
logger.info('====%s====', artist['name'])
|
||||
logger.info('Popularity: %s', artist['popularity'])
|
||||
if len(artist['genres']) > 0:
|
||||
logger.info('Genres: %s', ','.join(artist['genres']))
|
||||
|
||||
|
||||
def main():
|
||||
args = get_args()
|
||||
artist = get_artist(args.artist)
|
||||
show_artist(artist)
|
||||
show_artist_albums(artist)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
main()
|
||||
48
examples/artist_recommendations.py
Normal file
48
examples/artist_recommendations.py
Normal file
@ -0,0 +1,48 @@
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
|
||||
|
||||
logger = logging.getLogger('examples.artist_recommendations')
|
||||
logging.basicConfig(level='INFO')
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
|
||||
|
||||
def get_args():
|
||||
parser = argparse.ArgumentParser(description='Recommendations for the '
|
||||
'given artist')
|
||||
parser.add_argument('-a', '--artist', required=True, help='Name of Artist')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def get_artist(name):
|
||||
results = sp.search(q='artist:' + name, type='artist')
|
||||
items = results['artists']['items']
|
||||
if len(items) > 0:
|
||||
return items[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def show_recommendations_for_artist(artist):
|
||||
results = sp.recommendations(seed_artists=[artist['id']])
|
||||
for track in results['tracks']:
|
||||
logger.info('Recommendation: %s - %s', track['name'],
|
||||
track['artists'][0]['name'])
|
||||
|
||||
|
||||
def main():
|
||||
args = get_args()
|
||||
artist = get_artist(args.artist)
|
||||
if artist:
|
||||
show_recommendations_for_artist(artist)
|
||||
else:
|
||||
logger.error("Can't find that artist", args.artist)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
22
examples/audio_analysis_for_track.py
Normal file
22
examples/audio_analysis_for_track.py
Normal file
@ -0,0 +1,22 @@
|
||||
# shows audio analysis for the given track
|
||||
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import json
|
||||
import spotipy
|
||||
import time
|
||||
import sys
|
||||
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
tid = sys.argv[1]
|
||||
else:
|
||||
tid = 'spotify:track:4TTV7EcfroSLWzXRY6gLv6'
|
||||
|
||||
start = time.time()
|
||||
analysis = sp.audio_analysis(tid)
|
||||
delta = time.time() - start
|
||||
print(json.dumps(analysis, indent=4))
|
||||
print(f"analysis retrieved in {delta:.2f} seconds")
|
||||
34
examples/audio_features.py
Normal file
34
examples/audio_features.py
Normal file
@ -0,0 +1,34 @@
|
||||
# shows acoustic features for tracks for the given artist
|
||||
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import json
|
||||
import spotipy
|
||||
import time
|
||||
import sys
|
||||
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
sp.trace = False
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
artist_name = ' '.join(sys.argv[1:])
|
||||
else:
|
||||
artist_name = 'weezer'
|
||||
|
||||
results = sp.search(q=artist_name, limit=50)
|
||||
tids = []
|
||||
for i, t in enumerate(results['tracks']['items']):
|
||||
print(' ', i, t['name'])
|
||||
tids.append(t['uri'])
|
||||
|
||||
start = time.time()
|
||||
features = sp.audio_features(tids)
|
||||
delta = time.time() - start
|
||||
for feature in features:
|
||||
print(json.dumps(feature, indent=4))
|
||||
print()
|
||||
analysis = sp._get(feature['analysis_url'])
|
||||
print(json.dumps(analysis, indent=4))
|
||||
print()
|
||||
print(f"features retrieved in {delta:.2f} seconds")
|
||||
33
examples/audio_features_analysis.py
Normal file
33
examples/audio_features_analysis.py
Normal file
@ -0,0 +1,33 @@
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
|
||||
# Import the extra necessary libraries for this example
|
||||
# These libraries are not included in the default packages
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
import seaborn as sns
|
||||
|
||||
# Set up Spotify credentials
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
|
||||
# Fetch audio features of tracks from any playlist
|
||||
playlist_id = '37i9dQZEVXbMDoHDwVN2tF'
|
||||
results = sp.playlist_tracks(playlist_id)
|
||||
tracks = results['items']
|
||||
track_ids = [track['track']['id'] for track in tracks]
|
||||
audio_features = sp.audio_features(track_ids)
|
||||
|
||||
# Create a DataFrame of audio features
|
||||
df = pd.DataFrame(audio_features)
|
||||
df = df[['danceability', 'energy', 'speechiness', 'acousticness',
|
||||
'instrumentalness', 'liveness', 'valence', 'tempo']]
|
||||
|
||||
# Generate a correlation matrix
|
||||
correlation_matrix = df.corr()
|
||||
|
||||
# Plot the correlation matrix using seaborn
|
||||
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm')
|
||||
|
||||
# Show the plot
|
||||
plt.show()
|
||||
22
examples/audio_features_for_track.py
Normal file
22
examples/audio_features_for_track.py
Normal file
@ -0,0 +1,22 @@
|
||||
# shows acoustic features for tracks for the given artist
|
||||
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import json
|
||||
import spotipy
|
||||
import time
|
||||
import sys
|
||||
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
sp.trace = True
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
tids = sys.argv[1:]
|
||||
print(tids)
|
||||
|
||||
start = time.time()
|
||||
features = sp.audio_features(tids)
|
||||
delta = time.time() - start
|
||||
print(json.dumps(features, indent=4))
|
||||
print(f"features retrieved in {delta:.2f} seconds")
|
||||
47
examples/change_playlist_details.py
Normal file
47
examples/change_playlist_details.py
Normal file
@ -0,0 +1,47 @@
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
logger = logging.getLogger('examples.change_playlist_details')
|
||||
logging.basicConfig(level='DEBUG')
|
||||
|
||||
scope = 'playlist-modify-public playlist-modify-private'
|
||||
|
||||
|
||||
def get_args():
|
||||
parser = argparse.ArgumentParser(description='Modify details of playlist')
|
||||
parser.add_argument('-p', '--playlist', required=True,
|
||||
help='Playlist id to alter details')
|
||||
parser.add_argument('-n', '--name', required=False,
|
||||
help='Name of playlist')
|
||||
parser.add_argument('--public', action='store_true', required=False,
|
||||
help='Include param if playlist is public')
|
||||
parser.add_argument('--private', action='store_false', required=False,
|
||||
default=None,
|
||||
help='Include param to make playlist is private')
|
||||
parser.add_argument('-c', '--collaborative', action='store_true',
|
||||
required=False, default=None,
|
||||
help='Include param if playlist is collaborative')
|
||||
parser.add_argument('-i', '--independent', action='store_false',
|
||||
required=False, default=None,
|
||||
help='Include param to make playlist non collaborative')
|
||||
parser.add_argument('-d', '--description', default=None, required=False,
|
||||
help='Description of playlist')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
args = get_args()
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
|
||||
sp.playlist_change_details(
|
||||
args.playlist,
|
||||
name=args.name,
|
||||
public=args.public or args.private,
|
||||
collaborative=args.collaborative or args.independent,
|
||||
description=args.description)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
10
examples/client_credentials_flow.py
Normal file
10
examples/client_credentials_flow.py
Normal file
@ -0,0 +1,10 @@
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
from pprint import pprint
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
|
||||
search_str = 'Muse'
|
||||
result = sp.search(search_str)
|
||||
pprint(result)
|
||||
19
examples/contains_a_saved_track.py
Normal file
19
examples/contains_a_saved_track.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Prints whether a track exists in your collection of saved tracks
|
||||
|
||||
import pprint
|
||||
import sys
|
||||
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
scope = 'user-library-read'
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
tid = sys.argv[1]
|
||||
else:
|
||||
print(f"Usage: {sys.argv[0]} track-id ...")
|
||||
sys.exit()
|
||||
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
|
||||
results = sp.current_user_saved_tracks_contains(tracks=[tid])
|
||||
pprint.pprint(results)
|
||||
31
examples/create_playlist.py
Normal file
31
examples/create_playlist.py
Normal file
@ -0,0 +1,31 @@
|
||||
# Creates a playlist for a user
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
logger = logging.getLogger('examples.create_playlist')
|
||||
logging.basicConfig(level='DEBUG')
|
||||
|
||||
|
||||
def get_args():
|
||||
parser = argparse.ArgumentParser(description='Creates a playlist for user')
|
||||
parser.add_argument('-p', '--playlist', required=True,
|
||||
help='Name of Playlist')
|
||||
parser.add_argument('-d', '--description', required=False, default='',
|
||||
help='Description of Playlist')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
args = get_args()
|
||||
scope = "playlist-modify-public"
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
|
||||
user_id = sp.me()['id']
|
||||
sp.user_playlist_create(user_id, args.playlist, description=args.description)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
19
examples/delete_a_saved_track.py
Normal file
19
examples/delete_a_saved_track.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Delete a track from 'Your Collection' of saved tracks
|
||||
|
||||
import pprint
|
||||
import sys
|
||||
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
scope = 'user-library-modify'
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
tid = sys.argv[1]
|
||||
else:
|
||||
print(f"Usage: {sys.argv[0]} track-id ...")
|
||||
sys.exit()
|
||||
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
|
||||
results = sp.current_user_saved_tracks_delete(tracks=[tid])
|
||||
pprint.pprint(results)
|
||||
27
examples/follow_playlist.py
Normal file
27
examples/follow_playlist.py
Normal file
@ -0,0 +1,27 @@
|
||||
import argparse
|
||||
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
|
||||
def get_args():
|
||||
parser = argparse.ArgumentParser(description='Follows a playlist based on playlist ID')
|
||||
parser.add_argument('-p', '--playlist', required=True, help='Playlist ID')
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
args = get_args()
|
||||
|
||||
if args.playlist is None:
|
||||
# Uses the Spotify Global Top 50 playlist
|
||||
spotipy.Spotify(auth_manager=SpotifyOAuth()).current_user_follow_playlist(
|
||||
'37i9dQZEVXbMDoHDwVN2tF')
|
||||
|
||||
else:
|
||||
spotipy.Spotify(auth_manager=SpotifyOAuth()).current_user_follow_playlist(args.playlist)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
8
examples/headless.py
Normal file
8
examples/headless.py
Normal file
@ -0,0 +1,8 @@
|
||||
import spotipy
|
||||
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
# set open_browser=False to prevent Spotipy from attempting to open the default browser
|
||||
spotify = spotipy.Spotify(auth_manager=SpotifyOAuth(open_browser=False))
|
||||
|
||||
print(spotify.me())
|
||||
10
examples/multiple_accounts.py
Normal file
10
examples/multiple_accounts.py
Normal file
@ -0,0 +1,10 @@
|
||||
import spotipy
|
||||
import spotipy.util as util
|
||||
|
||||
from pprint import pprint
|
||||
|
||||
while True:
|
||||
username = input("Type the Spotify user ID to use: ")
|
||||
token = util.prompt_for_user_token(username, show_dialog=True)
|
||||
sp = spotipy.Spotify(token)
|
||||
pprint(sp.me())
|
||||
11
examples/my_playlists.py
Normal file
11
examples/my_playlists.py
Normal file
@ -0,0 +1,11 @@
|
||||
# Shows a user's playlists
|
||||
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
scope = 'playlist-read-private'
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
|
||||
|
||||
results = sp.current_user_playlists(limit=50)
|
||||
for i, item in enumerate(results['items']):
|
||||
print("%d %s" % (i, item['name']))
|
||||
17
examples/my_top_artists.py
Normal file
17
examples/my_top_artists.py
Normal file
@ -0,0 +1,17 @@
|
||||
# Shows the top artists for a user
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
scope = 'user-top-read'
|
||||
ranges = ['short_term', 'medium_term', 'long_term']
|
||||
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
|
||||
|
||||
for sp_range in ['short_term', 'medium_term', 'long_term']:
|
||||
print("range:", sp_range)
|
||||
|
||||
results = sp.current_user_top_artists(time_range=sp_range, limit=50)
|
||||
|
||||
for i, item in enumerate(results['items']):
|
||||
print(i, item['name'])
|
||||
print()
|
||||
16
examples/my_top_tracks.py
Normal file
16
examples/my_top_tracks.py
Normal file
@ -0,0 +1,16 @@
|
||||
# Shows the top tracks for a user
|
||||
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
scope = 'user-top-read'
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
|
||||
|
||||
ranges = ['short_term', 'medium_term', 'long_term']
|
||||
|
||||
for sp_range in ranges:
|
||||
print("range:", sp_range)
|
||||
results = sp.current_user_top_tracks(time_range=sp_range, limit=50)
|
||||
for i, item in enumerate(results['items']):
|
||||
print(i, item['name'], '//', item['artists'][0]['name'])
|
||||
print()
|
||||
53
examples/personalized_playlist.py
Normal file
53
examples/personalized_playlist.py
Normal file
@ -0,0 +1,53 @@
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
# Import the extra necessary libraries for this example
|
||||
# These libraries are not included in the default packages
|
||||
import pandas as pd
|
||||
from sklearn.cluster import KMeans
|
||||
|
||||
# Set up Spotify credentials
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(
|
||||
client_id="YOUR_APP_CLIENT_ID",
|
||||
client_secret="YOUR_APP_CLIENT_SECRET",
|
||||
redirect_uri="YOUR_APP_REDIRECT_URI",
|
||||
scope="playlist-modify-private,user-library-read"))
|
||||
|
||||
|
||||
# get the user's username
|
||||
username = sp.me()['id']
|
||||
|
||||
# Get the user's liked tracks
|
||||
saved_tracks = sp.current_user_saved_tracks(limit=50)['items']
|
||||
|
||||
# Extract audio features for liked tracks
|
||||
track_ids = []
|
||||
audio_features = []
|
||||
for track in saved_tracks:
|
||||
track_ids.append(track['track']['id'])
|
||||
audio_features.append(sp.audio_features(track['track']['id'])[0])
|
||||
|
||||
# Create a DataFrame from the audio features
|
||||
df = pd.DataFrame(audio_features)
|
||||
|
||||
# Perform clustering on some audio features
|
||||
features = df[['danceability', 'energy', 'valence', 'acousticness']]
|
||||
kmeans = KMeans(n_clusters=5, random_state=42, n_init=10)
|
||||
df['cluster'] = kmeans.fit_predict(features)
|
||||
|
||||
# Select a representative track from each cluster
|
||||
representative_tracks = []
|
||||
for cluster in range(5):
|
||||
cluster_tracks = df[df['cluster'] == cluster]
|
||||
representative_track = cluster_tracks.iloc[0]['id']
|
||||
representative_tracks.append(representative_track)
|
||||
|
||||
# Create a playlist with the representative tracks
|
||||
playlist = sp.user_playlist_create(
|
||||
user=username, name='Personalized Playlist', public=False)
|
||||
sp.playlist_add_items(
|
||||
playlist_id=playlist['id'], items=representative_tracks)
|
||||
|
||||
# Print the URL of the created playlist
|
||||
print("Playlist created successfully. You can access it at:",
|
||||
playlist['external_urls']['spotify'])
|
||||
21
examples/player.py
Normal file
21
examples/player.py
Normal file
@ -0,0 +1,21 @@
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
from pprint import pprint
|
||||
from time import sleep
|
||||
|
||||
scope = "user-read-playback-state,user-modify-playback-state"
|
||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyOAuth(scope=scope))
|
||||
|
||||
# Shows playing devices
|
||||
res = sp.devices()
|
||||
pprint(res)
|
||||
|
||||
# Change track
|
||||
sp.start_playback(uris=['spotify:track:6gdLoMygLsgktydTQ71b15'])
|
||||
|
||||
# Change volume
|
||||
sp.volume(100)
|
||||
sleep(2)
|
||||
sp.volume(50)
|
||||
sleep(2)
|
||||
sp.volume(100)
|
||||
12
examples/playlist_add_items.py
Normal file
12
examples/playlist_add_items.py
Normal file
@ -0,0 +1,12 @@
|
||||
# Add a list of items (URI) to a playlist (URI)
|
||||
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(client_id="YOUR_APP_CLIENT_ID",
|
||||
client_secret="YOUR_APP_CLIENT_SECRET",
|
||||
redirect_uri="YOUR_APP_REDIRECT_URI",
|
||||
scope="playlist-modify-private"
|
||||
))
|
||||
|
||||
sp.playlist_add_items('playlist_id', ['list_of_items'])
|
||||
30
examples/playlist_all_non_local_tracks.py
Normal file
30
examples/playlist_all_non_local_tracks.py
Normal file
@ -0,0 +1,30 @@
|
||||
# get all non-local tracks of a playlist
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
|
||||
# playlist id of global top 50
|
||||
PlaylistExample = '37i9dQZEVXbMDoHDwVN2tF'
|
||||
|
||||
# create spotipy client
|
||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
||||
|
||||
# load the first 100 songs
|
||||
tracks = []
|
||||
result = sp.playlist_items(PlaylistExample, additional_types=['track'])
|
||||
tracks.extend(result['items'])
|
||||
|
||||
# if playlist is larger than 100 songs, continue loading it until end
|
||||
while result['next']:
|
||||
result = sp.next(result)
|
||||
tracks.extend(result['items'])
|
||||
|
||||
# remove all local songs
|
||||
i = 0 # just for counting how many tracks are local
|
||||
for item in tracks:
|
||||
if item['is_local']:
|
||||
tracks.remove(item)
|
||||
i += 1
|
||||
|
||||
|
||||
# print result
|
||||
print("Playlist length: " + str(len(tracks)) + "\nExcluding: " + str(i))
|
||||
21
examples/playlist_tracks.py
Normal file
21
examples/playlist_tracks.py
Normal file
@ -0,0 +1,21 @@
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
from pprint import pprint
|
||||
|
||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
||||
|
||||
pl_id = 'spotify:playlist:5RIbzhG2QqdkaP24iXLnZX'
|
||||
offset = 0
|
||||
|
||||
while True:
|
||||
response = sp.playlist_items(pl_id,
|
||||
offset=offset,
|
||||
fields='items.track.id,total',
|
||||
additional_types=['track'])
|
||||
|
||||
if len(response['items']) == 0:
|
||||
break
|
||||
|
||||
pprint(response['items'])
|
||||
offset = offset + len(response['items'])
|
||||
print(offset, "/", response['total'])
|
||||
10
examples/read_a_playlist.py
Normal file
10
examples/read_a_playlist.py
Normal file
@ -0,0 +1,10 @@
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
import json
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
|
||||
playlist_id = 'spotify:user:spotifycharts:playlist:37i9dQZEVXbJiZcmkrIHGU'
|
||||
results = sp.playlist(playlist_id)
|
||||
print(json.dumps(results, indent=4))
|
||||
26
examples/remove_specific_tracks_from_playlist.py
Normal file
26
examples/remove_specific_tracks_from_playlist.py
Normal file
@ -0,0 +1,26 @@
|
||||
# Removes tracks from a playlist
|
||||
|
||||
import pprint
|
||||
import sys
|
||||
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
if len(sys.argv) > 2:
|
||||
playlist_id = sys.argv[1]
|
||||
track_ids_and_positions = sys.argv[2:]
|
||||
track_ids = []
|
||||
for t_pos in sys.argv[2:]:
|
||||
tid, pos = t_pos.split(',')
|
||||
track_ids.append({"uri": tid, "positions": [int(pos)]})
|
||||
else:
|
||||
print(
|
||||
f"Usage: {sys.argv[0]} playlist_id track_id,pos track_id,pos ...")
|
||||
sys.exit()
|
||||
|
||||
scope = 'playlist-modify-public'
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
|
||||
|
||||
results = sp.playlist_remove_specific_occurrences_of_items(
|
||||
playlist_id, track_ids)
|
||||
pprint.pprint(results)
|
||||
22
examples/remove_tracks_from_playlist.py
Normal file
22
examples/remove_tracks_from_playlist.py
Normal file
@ -0,0 +1,22 @@
|
||||
# Removes tracks from playlist
|
||||
import pprint
|
||||
import sys
|
||||
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
|
||||
if len(sys.argv) > 2:
|
||||
playlist_id = sys.argv[2]
|
||||
track_ids = sys.argv[3:]
|
||||
else:
|
||||
print(f"Usage: {sys.argv[0]} playlist_id track_id ...")
|
||||
sys.exit()
|
||||
|
||||
scope = 'playlist-modify-public'
|
||||
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
|
||||
|
||||
results = sp.playlist_remove_all_occurrences_of_items(
|
||||
playlist_id, track_ids)
|
||||
pprint.pprint(results)
|
||||
21
examples/replace_tracks_in_playlist.py
Normal file
21
examples/replace_tracks_in_playlist.py
Normal file
@ -0,0 +1,21 @@
|
||||
# Replaces all tracks in a playlist
|
||||
|
||||
import pprint
|
||||
import sys
|
||||
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
if len(sys.argv) > 3:
|
||||
playlist_id = sys.argv[1]
|
||||
track_ids = sys.argv[2:]
|
||||
else:
|
||||
print(f"Usage: {sys.argv[0]} playlist_id track_id ...")
|
||||
sys.exit()
|
||||
|
||||
scope = 'playlist-modify-public'
|
||||
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
|
||||
|
||||
results = sp.playlist_replace_items(playlist_id, track_ids)
|
||||
pprint.pprint(results)
|
||||
15
examples/search.py
Normal file
15
examples/search.py
Normal file
@ -0,0 +1,15 @@
|
||||
# shows artist info for a URN or URL
|
||||
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
import sys
|
||||
import pprint
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
search_str = sys.argv[1]
|
||||
else:
|
||||
search_str = 'Radiohead'
|
||||
|
||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
||||
result = sp.search(search_str)
|
||||
pprint.pprint(result)
|
||||
15
examples/show_album.py
Normal file
15
examples/show_album.py
Normal file
@ -0,0 +1,15 @@
|
||||
# shows album info for a URN or URL
|
||||
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
import sys
|
||||
from pprint import pprint
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
urn = sys.argv[1]
|
||||
else:
|
||||
urn = 'spotify:album:5yTx83u3qerZF7GRJu7eFk'
|
||||
|
||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
||||
album = sp.album(urn)
|
||||
pprint(album)
|
||||
16
examples/show_artist.py
Normal file
16
examples/show_artist.py
Normal file
@ -0,0 +1,16 @@
|
||||
# shows artist info for a URN or URL
|
||||
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
import sys
|
||||
from pprint import pprint
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
urn = sys.argv[1]
|
||||
else:
|
||||
urn = 'spotify:artist:3jOstUTkEu2JkjvRdBA5Gu'
|
||||
|
||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
||||
|
||||
artist = sp.artist(urn)
|
||||
pprint(artist)
|
||||
17
examples/show_artist_top_tracks.py
Normal file
17
examples/show_artist_top_tracks.py
Normal file
@ -0,0 +1,17 @@
|
||||
# shows artist info for a URN or URL
|
||||
# scope is not required for this function
|
||||
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
import sys
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
urn = sys.argv[1]
|
||||
else:
|
||||
urn = 'spotify:artist:3jOstUTkEu2JkjvRdBA5Gu'
|
||||
|
||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
||||
response = sp.artist_top_tracks(urn)
|
||||
|
||||
for track in response['tracks']:
|
||||
print(track['name'])
|
||||
27
examples/show_featured_artists.py
Normal file
27
examples/show_featured_artists.py
Normal file
@ -0,0 +1,27 @@
|
||||
# Shows all artists featured on an album
|
||||
|
||||
# usage: featured_artists.py spotify:album:[album urn]
|
||||
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import sys
|
||||
import spotipy
|
||||
from pprint import pprint
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
urn = sys.argv[1]
|
||||
else:
|
||||
urn = 'spotify:album:5yTx83u3qerZF7GRJu7eFk'
|
||||
|
||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
||||
album = sp.album(urn)
|
||||
|
||||
featured_artists = set()
|
||||
|
||||
items = album['tracks']['items']
|
||||
|
||||
for item in items:
|
||||
for ele in item['artists']:
|
||||
if 'name' in ele:
|
||||
featured_artists.add(ele['name'])
|
||||
|
||||
pprint(featured_artists)
|
||||
19
examples/show_featured_playlists.py
Normal file
19
examples/show_featured_playlists.py
Normal file
@ -0,0 +1,19 @@
|
||||
# shows artist info for a URN or URL
|
||||
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth())
|
||||
|
||||
response = sp.featured_playlists()
|
||||
print(response['message'])
|
||||
|
||||
while response:
|
||||
playlists = response['playlists']
|
||||
for i, item in enumerate(playlists['items']):
|
||||
print(playlists['offset'] + i, item['name'])
|
||||
|
||||
if playlists['next']:
|
||||
response = sp.next(playlists)
|
||||
else:
|
||||
response = None
|
||||
22
examples/show_my_saved_tracks.py
Normal file
22
examples/show_my_saved_tracks.py
Normal file
@ -0,0 +1,22 @@
|
||||
# Shows a user's saved tracks (need to be authenticated via oauth)
|
||||
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
scope = 'user-library-read'
|
||||
|
||||
|
||||
def show_tracks(results):
|
||||
for item in results['items']:
|
||||
track = item['track']
|
||||
print("%32.32s %s" % (track['artists'][0]['name'], track['name']))
|
||||
|
||||
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
|
||||
|
||||
results = sp.current_user_saved_tracks()
|
||||
show_tracks(results)
|
||||
|
||||
while results['next']:
|
||||
results = sp.next(results)
|
||||
show_tracks(results)
|
||||
18
examples/show_new_releases.py
Normal file
18
examples/show_new_releases.py
Normal file
@ -0,0 +1,18 @@
|
||||
# shows artist info for a URN or URL
|
||||
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth())
|
||||
|
||||
response = sp.new_releases()
|
||||
|
||||
while response:
|
||||
albums = response['albums']
|
||||
for i, item in enumerate(albums['items']):
|
||||
print(albums['offset'] + i, item['name'])
|
||||
|
||||
if albums['next']:
|
||||
response = sp.next(albums)
|
||||
else:
|
||||
response = None
|
||||
24
examples/show_related.py
Normal file
24
examples/show_related.py
Normal file
@ -0,0 +1,24 @@
|
||||
# shows related artists for the given seed artist
|
||||
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
import sys
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
artist_name = sys.argv[1]
|
||||
else:
|
||||
artist_name = 'weezer'
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
result = sp.search(q='artist:' + artist_name, type='artist')
|
||||
try:
|
||||
name = result['artists']['items'][0]['name']
|
||||
uri = result['artists']['items'][0]['uri']
|
||||
|
||||
related = sp.artist_related_artists(uri)
|
||||
print('Related artists for', name)
|
||||
for artist in related['artists']:
|
||||
print(' ', artist['name'])
|
||||
except BaseException:
|
||||
print("usage show_related.py [artist-name]")
|
||||
16
examples/show_track_info.py
Normal file
16
examples/show_track_info.py
Normal file
@ -0,0 +1,16 @@
|
||||
# shows track info for a URN or URL
|
||||
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
import sys
|
||||
from pprint import pprint
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
urn = sys.argv[1]
|
||||
else:
|
||||
urn = 'spotify:track:0Svkvt5I79wficMFgaqEQJ'
|
||||
|
||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
||||
|
||||
track = sp.track(urn)
|
||||
pprint(track)
|
||||
23
examples/show_tracks.py
Normal file
23
examples/show_tracks.py
Normal file
@ -0,0 +1,23 @@
|
||||
'''
|
||||
usage: show_tracks.py path_of_ids
|
||||
|
||||
given a list of track IDs show the artist and track name
|
||||
'''
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import sys
|
||||
import spotipy
|
||||
|
||||
if __name__ == '__main__':
|
||||
max_tracks_per_call = 50
|
||||
if len(sys.argv) > 1:
|
||||
file = open(sys.argv[1])
|
||||
else:
|
||||
file = sys.stdin
|
||||
tids = file.read().split()
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
for start in range(0, len(tids), max_tracks_per_call):
|
||||
results = sp.tracks(tids[start: start + max_tracks_per_call])
|
||||
for track in results['tracks']:
|
||||
print(track['name'] + ' - ' + track['artists'][0]['name'])
|
||||
17
examples/show_user.py
Normal file
17
examples/show_user.py
Normal file
@ -0,0 +1,17 @@
|
||||
# Shows artist info for a URN or URL
|
||||
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
import sys
|
||||
import pprint
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
username = sys.argv[1]
|
||||
else:
|
||||
username = 'plamere'
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
sp.trace = True
|
||||
user = sp.user(username)
|
||||
pprint.pprint(user)
|
||||
16
examples/simple_artist_albums.py
Normal file
16
examples/simple_artist_albums.py
Normal file
@ -0,0 +1,16 @@
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
|
||||
birdy_uri = 'spotify:artist:2WX2uTcsvV5OnS0inACecP'
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
|
||||
results = sp.artist_albums(birdy_uri, album_type='album')
|
||||
albums = results['items']
|
||||
while results['next']:
|
||||
results = sp.next(results)
|
||||
albums.extend(results['items'])
|
||||
|
||||
for album in albums:
|
||||
print(album['name'])
|
||||
14
examples/simple_artist_top_tracks.py
Normal file
14
examples/simple_artist_top_tracks.py
Normal file
@ -0,0 +1,14 @@
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
|
||||
lz_uri = 'spotify:artist:36QJpDe2go2KgaRleHCDTp'
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
|
||||
results = sp.artist_top_tracks(lz_uri)
|
||||
|
||||
for track in results['tracks'][:10]:
|
||||
print('track : ' + track['name'])
|
||||
print('audio : ' + track['preview_url'])
|
||||
print('cover art: ' + track['album']['images'][0]['url'])
|
||||
12
examples/simple_me.py
Normal file
12
examples/simple_me.py
Normal file
@ -0,0 +1,12 @@
|
||||
import spotipy
|
||||
from pprint import pprint
|
||||
|
||||
|
||||
def main():
|
||||
spotify = spotipy.Spotify(auth_manager=spotipy.SpotifyOAuth())
|
||||
me = spotify.me()
|
||||
pprint(me)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
9
examples/simple_search_artist.py
Normal file
9
examples/simple_search_artist.py
Normal file
@ -0,0 +1,9 @@
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
|
||||
results = sp.search(q='weezer', limit=20)
|
||||
for i, t in enumerate(results['tracks']['items']):
|
||||
print(' ', i, t['name'])
|
||||
18
examples/simple_search_artist_image_url.py
Normal file
18
examples/simple_search_artist_image_url.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Shows the name of the artist/band and their image by giving a link
|
||||
import sys
|
||||
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
|
||||
sp = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials())
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
name = ' '.join(sys.argv[1:])
|
||||
else:
|
||||
name = 'Radiohead'
|
||||
|
||||
results = sp.search(q='artist:' + name, type='artist')
|
||||
items = results['artists']['items']
|
||||
if len(items) > 0:
|
||||
artist = items[0]
|
||||
print(artist['name'], artist['images'][0]['url'])
|
||||
11
examples/test.py
Normal file
11
examples/test.py
Normal file
@ -0,0 +1,11 @@
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
scope = "user-library-read"
|
||||
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
|
||||
|
||||
results = sp.current_user_saved_tracks()
|
||||
for idx, item in enumerate(results['items']):
|
||||
track = item['track']
|
||||
print(idx, track['artists'][0]['name'], " – ", track['name'])
|
||||
69
examples/title_chain.py
Normal file
69
examples/title_chain.py
Normal file
@ -0,0 +1,69 @@
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
import random
|
||||
|
||||
'''
|
||||
generates a list of songs where the first word in each subsequent song
|
||||
matches the last word of the previous song.
|
||||
|
||||
usage: python title_chain.py [song name]
|
||||
'''
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
|
||||
|
||||
skiplist = {'dm', 'remix'}
|
||||
max_offset = 500
|
||||
seen = set()
|
||||
|
||||
|
||||
def find_songs_that_start_with_word(word):
|
||||
max_titles = 20
|
||||
max_offset = 200
|
||||
offset = 0
|
||||
|
||||
out = []
|
||||
while offset < max_offset and len(out) < max_titles:
|
||||
results = sp.search(q=word, type='track', limit=50, offset=offset)
|
||||
if len(results['tracks']['items']) == 0:
|
||||
break
|
||||
|
||||
for item in results['tracks']['items']:
|
||||
name = item['name'].lower()
|
||||
if name in seen:
|
||||
continue
|
||||
seen.add(name)
|
||||
if '(' in name:
|
||||
continue
|
||||
if '-' in name:
|
||||
continue
|
||||
if '/' in name:
|
||||
continue
|
||||
words = name.split()
|
||||
if len(words) > 1 and words[0] == word \
|
||||
and words[-1] not in skiplist:
|
||||
# print " ", name, len(out)
|
||||
out.append(item)
|
||||
offset += 50
|
||||
# print "found", len(out), "matches"
|
||||
return out
|
||||
|
||||
|
||||
def make_chain(word):
|
||||
which = 1
|
||||
while True:
|
||||
songs = find_songs_that_start_with_word(word)
|
||||
if len(songs) > 0:
|
||||
song = random.choice(songs)
|
||||
print(which, song['name'] + " by " + song['artists'][0]['name'])
|
||||
which += 1
|
||||
word = song['name'].lower().split()[-1]
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
title = ' '.join(sys.argv[1:])
|
||||
make_chain(sys.argv[1].lower())
|
||||
45
examples/track_recommendations.py
Normal file
45
examples/track_recommendations.py
Normal file
@ -0,0 +1,45 @@
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
import random
|
||||
|
||||
# Set up Spotify credentials
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(
|
||||
client_id="YOUR_APP_CLIENT_ID",
|
||||
client_secret="YOUR_APP_CLIENT_SECRET",
|
||||
redirect_uri="YOUR_APP_REDIRECT_URI",
|
||||
scope="user-top-read,user-library-read,user-read-recently-played"))
|
||||
|
||||
# Get the user's top tracks
|
||||
top_tracks = sp.current_user_top_tracks(limit=10, time_range='long_term')
|
||||
|
||||
# Get the user's liked tracks
|
||||
liked_tracks = sp.current_user_saved_tracks(limit=10)
|
||||
|
||||
# Get the user's listening history
|
||||
history = sp.current_user_recently_played(limit=10)
|
||||
|
||||
# Extract a list of the top track IDs
|
||||
top_track_ids = [track['id'] for track in top_tracks['items']]
|
||||
|
||||
# Extract a list of the liked track IDs
|
||||
liked_track_ids = [track['track']['id'] for track in liked_tracks['items']]
|
||||
|
||||
# Extract a list of the history track IDs
|
||||
history_track_ids = [track['track']['id'] for track in history['items']]
|
||||
|
||||
# Combine the three lists and shuffle them randomly
|
||||
seed_track_ids = top_track_ids + liked_track_ids + history_track_ids
|
||||
random.shuffle(seed_track_ids)
|
||||
|
||||
# Use the IDs to get some recommendations
|
||||
# Note: the seed_tracks parameter can accept up to 5 tracks
|
||||
recommendations = sp.recommendations(
|
||||
seed_tracks=seed_track_ids[0:5], limit=10, country='US')
|
||||
|
||||
# Display the recommendations
|
||||
for i, track in enumerate(recommendations['tracks']):
|
||||
print(
|
||||
"{}. {} by {}"
|
||||
.format(i+1, track['name'], ', '
|
||||
.join([artist['name'] for artist in track['artists']]))
|
||||
)
|
||||
16
examples/tracks.py
Normal file
16
examples/tracks.py
Normal file
@ -0,0 +1,16 @@
|
||||
# shows tracks for the given artist
|
||||
|
||||
# usage: python tracks.py [artist name]
|
||||
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
import sys
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
artist_name = ' '.join(sys.argv[1:])
|
||||
results = sp.search(q=artist_name, limit=20)
|
||||
for i, t in enumerate(results['tracks']['items']):
|
||||
print(' ', i, t['name'])
|
||||
31
examples/unfollow_playlist.py
Normal file
31
examples/unfollow_playlist.py
Normal file
@ -0,0 +1,31 @@
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
logger = logging.getLogger('examples.unfollow_playlist')
|
||||
logging.basicConfig(level='DEBUG')
|
||||
|
||||
'''
|
||||
Spotify doesn't have a dedicated endpoint for deleting a playlist. However,
|
||||
unfollowing a playlist has the effect of deleting it from the user's account.
|
||||
When a playlist is removed from the user's account, the system unfollows it,
|
||||
and then no longer shows it in playlist list.'''
|
||||
|
||||
|
||||
def get_args():
|
||||
parser = argparse.ArgumentParser(description='Unfollows a playlist')
|
||||
parser.add_argument('-p', '--playlist', required=True,
|
||||
help='Playlist id')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
args = get_args()
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth())
|
||||
sp.current_user_unfollow_playlist(args.playlist)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
19
examples/user_playlists.py
Normal file
19
examples/user_playlists.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Shows a user's playlists (need to be authenticated via oauth)
|
||||
|
||||
import sys
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
username = sys.argv[1]
|
||||
else:
|
||||
print("Whoops, need a username!")
|
||||
print("usage: python user_playlists.py [username]")
|
||||
sys.exit()
|
||||
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth())
|
||||
|
||||
playlists = sp.user_playlists(username)
|
||||
|
||||
for playlist in playlists['items']:
|
||||
print(playlist['name'])
|
||||
33
examples/user_playlists_contents.py
Normal file
33
examples/user_playlists_contents.py
Normal file
@ -0,0 +1,33 @@
|
||||
# Shows a user's playlists (need to be authenticated via oauth)
|
||||
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
|
||||
def show_tracks(results):
|
||||
for i, item in enumerate(results['items']):
|
||||
track = item['track']
|
||||
print(
|
||||
" %d %32.32s %s" %
|
||||
(i, track['artists'][0]['name'], track['name']))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
scope = 'playlist-read-private'
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
|
||||
|
||||
playlists = sp.current_user_playlists()
|
||||
user_id = sp.me()['id']
|
||||
|
||||
for playlist in playlists['items']:
|
||||
if playlist['owner']['id'] == user_id:
|
||||
print()
|
||||
print(playlist['name'])
|
||||
print(' total tracks', playlist['tracks']['total'])
|
||||
|
||||
tracks = sp.playlist_items(playlist['id'], fields="items,next", additional_types=('tracks', ))
|
||||
show_tracks(tracks)
|
||||
|
||||
while tracks['next']:
|
||||
tracks = sp.next(tracks)
|
||||
show_tracks(tracks)
|
||||
31
examples/user_public_playlists.py
Normal file
31
examples/user_public_playlists.py
Normal file
@ -0,0 +1,31 @@
|
||||
# Gets all the public playlists for the given
|
||||
# user. Uses Client Credentials flow
|
||||
#
|
||||
|
||||
import sys
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
|
||||
client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
|
||||
user = 'spotify'
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
user = sys.argv[1]
|
||||
|
||||
playlists = sp.user_playlists(user)
|
||||
|
||||
while playlists:
|
||||
for i, playlist in enumerate(playlists['items']):
|
||||
print(
|
||||
"%4d %s %s" %
|
||||
(i +
|
||||
1 +
|
||||
playlists['offset'],
|
||||
playlist['uri'],
|
||||
playlist['name']))
|
||||
if playlists['next']:
|
||||
playlists = sp.next(playlists)
|
||||
else:
|
||||
playlists = None
|
||||
12
examples/user_saved_albums_delete.py
Normal file
12
examples/user_saved_albums_delete.py
Normal file
@ -0,0 +1,12 @@
|
||||
# Deletes user saved album
|
||||
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
|
||||
scope = 'user-library-modify'
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
|
||||
|
||||
uris = input("input a list of album URIs, URLs or IDs: ")
|
||||
uris = list(map(str, uris.split()))
|
||||
deleted = sp.current_user_saved_albums_delete(uris)
|
||||
print("Deletion successful.")
|
||||
12
setup.py
12
setup.py
@ -8,20 +8,12 @@ memcache_cache_reqs = [
|
||||
]
|
||||
|
||||
extra_reqs = {
|
||||
'memcache': [
|
||||
'pymemcache>=3.5.2'
|
||||
],
|
||||
'test': [
|
||||
'autopep8>=2.3.2',
|
||||
'flake8>=7.3.0',
|
||||
'flake8-use-fstring>=1.4',
|
||||
'isort>=7.0.0'
|
||||
]
|
||||
'memcache': memcache_cache_reqs
|
||||
}
|
||||
|
||||
setup(
|
||||
name='spotipy',
|
||||
version='2.26.0',
|
||||
version='2.25.0',
|
||||
description='A light weight Python library for the Spotify Web API',
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
|
||||
@ -11,11 +11,10 @@ import errno
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from spotipy.util import CLIENT_CREDS_ENV_VARS
|
||||
|
||||
from redis import RedisError
|
||||
|
||||
from spotipy.util import CLIENT_CREDS_ENV_VARS
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -41,6 +40,7 @@ class CacheHandler():
|
||||
Save a token_info dictionary object to the cache and return None.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
return None
|
||||
|
||||
|
||||
class CacheFileHandler(CacheHandler):
|
||||
@ -76,30 +76,27 @@ class CacheFileHandler(CacheHandler):
|
||||
token_info = None
|
||||
|
||||
try:
|
||||
with open(self.cache_path, encoding='utf-8') as f:
|
||||
f = open(self.cache_path)
|
||||
token_info_string = f.read()
|
||||
f.close()
|
||||
token_info = json.loads(token_info_string)
|
||||
|
||||
except OSError as error:
|
||||
if error.errno == errno.ENOENT:
|
||||
logger.debug(f"cache does not exist at: {self.cache_path}")
|
||||
logger.debug("cache does not exist at: %s", self.cache_path)
|
||||
else:
|
||||
logger.warning(f"Couldn't read cache at: {self.cache_path}")
|
||||
except json.JSONDecodeError:
|
||||
logger.warning(f"Couldn't decode JSON from cache at: {self.cache_path}")
|
||||
logger.warning("Couldn't read cache at: %s", self.cache_path)
|
||||
|
||||
return token_info
|
||||
|
||||
def save_token_to_cache(self, token_info):
|
||||
try:
|
||||
with open(self.cache_path, "w", encoding='utf-8') as f:
|
||||
f = open(self.cache_path, "w")
|
||||
f.write(json.dumps(token_info, cls=self.encoder_cls))
|
||||
# https://github.com/spotipy-dev/spotipy/security/advisories/GHSA-pwhh-q4h6-w599
|
||||
os.chmod(self.cache_path, 0o600)
|
||||
f.close()
|
||||
except OSError:
|
||||
logger.warning(f"Couldn't write token to cache at: {self.cache_path}")
|
||||
except FileNotFoundError:
|
||||
logger.warning(f"Couldn't set permissions to cache file at: {self.cache_path}")
|
||||
logger.warning('Couldn\'t write token to cache at: %s',
|
||||
self.cache_path)
|
||||
|
||||
|
||||
class MemoryCacheHandler(CacheHandler):
|
||||
@ -152,7 +149,7 @@ class DjangoSessionCacheHandler(CacheHandler):
|
||||
try:
|
||||
self.request.session['token_info'] = token_info
|
||||
except Exception as e:
|
||||
logger.warning(f"Error saving token to cache: {e}")
|
||||
logger.warning("Error saving token to cache: " + str(e))
|
||||
|
||||
|
||||
class FlaskSessionCacheHandler(CacheHandler):
|
||||
@ -177,7 +174,7 @@ class FlaskSessionCacheHandler(CacheHandler):
|
||||
try:
|
||||
self.session["token_info"] = token_info
|
||||
except Exception as e:
|
||||
logger.warning(f"Error saving token to cache: {e}")
|
||||
logger.warning("Error saving token to cache: " + str(e))
|
||||
|
||||
|
||||
class RedisCacheHandler(CacheHandler):
|
||||
@ -203,7 +200,7 @@ class RedisCacheHandler(CacheHandler):
|
||||
if token_info:
|
||||
return json.loads(token_info)
|
||||
except RedisError as e:
|
||||
logger.warning(f"Error getting token from cache: {e}")
|
||||
logger.warning('Error getting token from cache: ' + str(e))
|
||||
|
||||
return token_info
|
||||
|
||||
@ -211,13 +208,12 @@ class RedisCacheHandler(CacheHandler):
|
||||
try:
|
||||
self.redis.set(self.key, json.dumps(token_info))
|
||||
except RedisError as e:
|
||||
logger.warning(f"Error saving token to cache: {e}")
|
||||
logger.warning('Error saving token to cache: ' + str(e))
|
||||
|
||||
|
||||
class MemcacheCacheHandler(CacheHandler):
|
||||
"""A Cache handler that stores the token info in Memcache using the pymemcache client
|
||||
"""
|
||||
|
||||
def __init__(self, memcache, key=None) -> None:
|
||||
"""
|
||||
Parameters:
|
||||
@ -236,11 +232,11 @@ class MemcacheCacheHandler(CacheHandler):
|
||||
if token_info:
|
||||
return json.loads(token_info.decode())
|
||||
except MemcacheError as e:
|
||||
logger.warning(f"Error getting token to cache: {e}")
|
||||
logger.warning('Error getting token from cache' + str(e))
|
||||
|
||||
def save_token_to_cache(self, token_info):
|
||||
from pymemcache import MemcacheError
|
||||
try:
|
||||
self.memcache.set(self.key, json.dumps(token_info))
|
||||
except MemcacheError as e:
|
||||
logger.warning(f"Error saving token to cache: {e}")
|
||||
logger.warning('Error saving token to cache' + str(e))
|
||||
|
||||
@ -6,12 +6,13 @@ import json
|
||||
import logging
|
||||
import re
|
||||
import warnings
|
||||
from collections import defaultdict
|
||||
|
||||
import requests
|
||||
|
||||
from spotipy.exceptions import SpotifyException
|
||||
from spotipy.util import REQUESTS_SESSION, Retry
|
||||
from spotipy.util import Retry
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -211,8 +212,11 @@ class Spotify:
|
||||
|
||||
def __del__(self):
|
||||
"""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()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def _build_session(self):
|
||||
self._session = requests.Session()
|
||||
@ -259,8 +263,8 @@ class Spotify:
|
||||
if self.language is not None:
|
||||
headers["Accept-Language"] = self.language
|
||||
|
||||
logger.debug(f"Sending {method} to {url} with Params: "
|
||||
f"{args.get('params')} Headers: {headers} and Body: {args.get('data')!r}")
|
||||
logger.debug('Sending %s to %s with Params: %s Headers: %s and Body: %r ',
|
||||
method, url, args.get("params"), headers, args.get('data'))
|
||||
|
||||
try:
|
||||
response = self._session.request(
|
||||
@ -285,8 +289,10 @@ class Spotify:
|
||||
msg = response.text or None
|
||||
reason = None
|
||||
|
||||
logger.error(f"HTTP Error for {method} to {url} with Params: "
|
||||
f"{args.get('params')} returned {response.status_code} due to {msg}")
|
||||
logger.error(
|
||||
'HTTP Error for %s to %s with Params: %s returned %s due to %s',
|
||||
method, url, args.get("params"), response.status_code, msg
|
||||
)
|
||||
|
||||
raise SpotifyException(
|
||||
response.status_code,
|
||||
@ -311,7 +317,7 @@ class Spotify:
|
||||
except ValueError:
|
||||
results = None
|
||||
|
||||
logger.debug(f'RESULTS: {results}')
|
||||
logger.debug('RESULTS: %s', results)
|
||||
return results
|
||||
|
||||
def _get(self, url, args=None, payload=None, **kwargs):
|
||||
@ -400,14 +406,10 @@ class Spotify:
|
||||
return self._get("artists/?ids=" + ",".join(tlist))
|
||||
|
||||
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
|
||||
|
||||
.. 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',
|
||||
@ -445,11 +447,6 @@ 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)
|
||||
|
||||
@ -458,9 +455,6 @@ 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
|
||||
"""
|
||||
@ -652,11 +646,6 @@ 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):
|
||||
@ -690,17 +679,13 @@ class Spotify:
|
||||
self,
|
||||
playlist_id,
|
||||
fields=None,
|
||||
limit=50,
|
||||
limit=100,
|
||||
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
|
||||
@ -722,7 +707,7 @@ class Spotify:
|
||||
self,
|
||||
playlist_id,
|
||||
fields=None,
|
||||
limit=50,
|
||||
limit=100,
|
||||
offset=0,
|
||||
market=None,
|
||||
additional_types=("track", "episode")
|
||||
@ -740,7 +725,7 @@ class Spotify:
|
||||
"""
|
||||
plid = self._get_id("playlist", playlist_id)
|
||||
return self._get(
|
||||
f"playlists/{plid}/items",
|
||||
f"playlists/{plid}/tracks",
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
fields=fields,
|
||||
@ -773,22 +758,18 @@ class Spotify:
|
||||
)
|
||||
|
||||
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::
|
||||
This method is deprecated and may be removed in a future version. Use
|
||||
`playlist(playlist_id)` instead.
|
||||
""" Gets a single playlist of a user
|
||||
|
||||
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)
|
||||
@ -802,11 +783,12 @@ class Spotify:
|
||||
offset=0,
|
||||
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::
|
||||
This method is deprecated and may be removed in a future version. Use
|
||||
`playlist_tracks(playlist_id)` instead.
|
||||
""" Get full details of the tracks of a playlist owned by a user.
|
||||
|
||||
Parameters:
|
||||
- user - the id of the user
|
||||
@ -816,10 +798,6 @@ 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,
|
||||
@ -836,12 +814,6 @@ 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
|
||||
)
|
||||
@ -856,12 +828,6 @@ 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,
|
||||
@ -871,24 +837,6 @@ 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,
|
||||
@ -902,10 +850,6 @@ 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
|
||||
@ -927,10 +871,6 @@ 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
|
||||
@ -948,10 +888,6 @@ 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
|
||||
@ -973,10 +909,6 @@ 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
|
||||
@ -996,10 +928,6 @@ 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
|
||||
@ -1024,10 +952,6 @@ 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
|
||||
@ -1053,10 +977,6 @@ 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
|
||||
@ -1077,10 +997,7 @@ class Spotify:
|
||||
):
|
||||
""" 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
|
||||
|
||||
.. deprecated::
|
||||
This endpoint has been removed by Spotify and is no longer available.
|
||||
Removes all occurrences of the given tracks from the given playlist
|
||||
|
||||
Parameters:
|
||||
- user - the id of the user
|
||||
@ -1093,8 +1010,8 @@ class Spotify:
|
||||
- snapshot_id - optional id of the playlist snapshot
|
||||
"""
|
||||
warnings.warn(
|
||||
"You're using `user_playlist_remove_specific_occurrences_of_tracks(...)`, "
|
||||
"which is marked as deprecated by Spotify.",
|
||||
"You should use `playlist_remove_specific_occurrences_of_items"
|
||||
"(playlist_id, tracks)` instead",
|
||||
DeprecationWarning,
|
||||
)
|
||||
plid = self._get_id("playlist", playlist_id)
|
||||
@ -1118,10 +1035,6 @@ 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
|
||||
@ -1139,10 +1052,6 @@ 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
|
||||
@ -1211,7 +1120,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}/items",
|
||||
f"playlists/{plid}/tracks",
|
||||
payload=ftracks,
|
||||
position=position,
|
||||
)
|
||||
@ -1227,7 +1136,7 @@ class Spotify:
|
||||
ftracks = [self._get_uri("track", tid) for tid in items]
|
||||
payload = {"uris": ftracks}
|
||||
return self._put(
|
||||
f"playlists/{plid}/items", payload=payload
|
||||
f"playlists/{plid}/tracks", payload=payload
|
||||
)
|
||||
|
||||
def playlist_reorder_items(
|
||||
@ -1258,7 +1167,7 @@ class Spotify:
|
||||
if snapshot_id:
|
||||
payload["snapshot_id"] = snapshot_id
|
||||
return self._put(
|
||||
f"playlists/{plid}/items", payload=payload
|
||||
f"playlists/{plid}/tracks", payload=payload
|
||||
)
|
||||
|
||||
def playlist_remove_all_occurrences_of_items(
|
||||
@ -1275,11 +1184,11 @@ class Spotify:
|
||||
|
||||
plid = self._get_id("playlist", playlist_id)
|
||||
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:
|
||||
payload["snapshot_id"] = snapshot_id
|
||||
return self._delete(
|
||||
f"playlists/{plid}/items", payload=payload
|
||||
f"playlists/{plid}/tracks", payload=payload
|
||||
)
|
||||
|
||||
def playlist_remove_specific_occurrences_of_items(
|
||||
@ -1306,14 +1215,14 @@ class Spotify:
|
||||
"positions": tr["positions"],
|
||||
}
|
||||
)
|
||||
payload = {"items": ftracks}
|
||||
payload = {"tracks": ftracks}
|
||||
if snapshot_id:
|
||||
payload["snapshot_id"] = snapshot_id
|
||||
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.
|
||||
|
||||
@ -1321,7 +1230,10 @@ class Spotify:
|
||||
- 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(
|
||||
self, playlist_id, user_ids
|
||||
@ -1335,27 +1247,11 @@ 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 = "playlists/{}/followers/contains?ids={}"
|
||||
return self._get(
|
||||
endpoint.format(playlist_id, ",".join(user_ids))
|
||||
)
|
||||
|
||||
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.
|
||||
@ -1368,20 +1264,10 @@ class Spotify:
|
||||
"""
|
||||
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.
|
||||
|
||||
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",
|
||||
market=market,
|
||||
additional_types=",".join(additional_types)
|
||||
)
|
||||
return self._get("me/player/currently-playing")
|
||||
|
||||
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
|
||||
@ -1402,8 +1288,8 @@ class Spotify:
|
||||
- albums - a list of album URIs, URLs or IDs
|
||||
"""
|
||||
|
||||
alist = [self._get_uri("album", a) for a in albums]
|
||||
return self._put("me/library", uris=",".join(alist))
|
||||
alist = [self._get_id("album", a) for a in albums]
|
||||
return self._put("me/albums?ids=" + ",".join(alist))
|
||||
|
||||
def current_user_saved_albums_delete(self, albums=[]):
|
||||
""" Remove one or more albums from the current user's
|
||||
@ -1412,8 +1298,8 @@ class Spotify:
|
||||
Parameters:
|
||||
- albums - a list of album URIs, URLs or IDs
|
||||
"""
|
||||
alist = [self._get_uri("album", a) for a in albums]
|
||||
return self._delete("me/library", uris=",".join(alist))
|
||||
alist = [self._get_id("album", a) for a in albums]
|
||||
return self._delete("me/albums/?ids=" + ",".join(alist))
|
||||
|
||||
def current_user_saved_albums_contains(self, albums=[]):
|
||||
""" Check if one or more albums is already saved in
|
||||
@ -1422,8 +1308,8 @@ class Spotify:
|
||||
Parameters:
|
||||
- albums - a list of album URIs, URLs or IDs
|
||||
"""
|
||||
alist = [self._get_uri("album", a) for a in albums]
|
||||
return self._get("me/library/contains", uris=",".join(alist))
|
||||
alist = [self._get_id("album", a) for a in albums]
|
||||
return self._get("me/albums/contains?ids=" + ",".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
|
||||
@ -1446,8 +1332,8 @@ class Spotify:
|
||||
"""
|
||||
tlist = []
|
||||
if tracks is not None:
|
||||
tlist = [self._get_uri("track", t) for t in tracks]
|
||||
return self._put("me/library", uris=",".join(tlist))
|
||||
tlist = [self._get_id("track", t) for t in tracks]
|
||||
return self._put("me/tracks/?ids=" + ",".join(tlist))
|
||||
|
||||
def current_user_saved_tracks_delete(self, tracks=None):
|
||||
""" Remove one or more tracks from the current user's
|
||||
@ -1458,8 +1344,8 @@ class Spotify:
|
||||
"""
|
||||
tlist = []
|
||||
if tracks is not None:
|
||||
tlist = [self._get_uri("track", t) for t in tracks]
|
||||
return self._delete("me/library", uris=",".join(tlist))
|
||||
tlist = [self._get_id("track", t) for t in tracks]
|
||||
return self._delete("me/tracks/?ids=" + ",".join(tlist))
|
||||
|
||||
def current_user_saved_tracks_contains(self, tracks=None):
|
||||
""" Check if one or more tracks is already saved in
|
||||
@ -1470,8 +1356,8 @@ class Spotify:
|
||||
"""
|
||||
tlist = []
|
||||
if tracks is not None:
|
||||
tlist = [self._get_uri("track", t) for t in tracks]
|
||||
return self._get("me/library/contains", uris=",".join(tlist))
|
||||
tlist = [self._get_id("track", t) for t in tracks]
|
||||
return self._get("me/tracks/contains?ids=" + ",".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
|
||||
@ -1494,8 +1380,8 @@ class Spotify:
|
||||
"""
|
||||
elist = []
|
||||
if episodes is not None:
|
||||
elist = [self._get_uri("episode", e) for e in episodes]
|
||||
return self._put("me/library", uris=",".join(elist))
|
||||
elist = [self._get_id("episode", e) for e in episodes]
|
||||
return self._put("me/episodes/?ids=" + ",".join(elist))
|
||||
|
||||
def current_user_saved_episodes_delete(self, episodes=None):
|
||||
""" Remove one or more episodes from the current user's
|
||||
@ -1506,8 +1392,8 @@ class Spotify:
|
||||
"""
|
||||
elist = []
|
||||
if episodes is not None:
|
||||
elist = [self._get_uri("episode", e) for e in episodes]
|
||||
return self._delete("me/library", uris=",".join(elist))
|
||||
elist = [self._get_id("episode", e) for e in episodes]
|
||||
return self._delete("me/episodes/?ids=" + ",".join(elist))
|
||||
|
||||
def current_user_saved_episodes_contains(self, episodes=None):
|
||||
""" Check if one or more episodes is already saved in
|
||||
@ -1539,8 +1425,8 @@ class Spotify:
|
||||
Parameters:
|
||||
- shows - a list of show URIs, URLs or IDs
|
||||
"""
|
||||
slist = [self._get_uri("show", s) for s in shows]
|
||||
return self._put("me/library", uris=",".join(slist))
|
||||
slist = [self._get_id("show", s) for s in shows]
|
||||
return self._put("me/shows?ids=" + ",".join(slist))
|
||||
|
||||
def current_user_saved_shows_delete(self, shows=[]):
|
||||
""" Remove one or more shows from the current user's
|
||||
@ -1549,8 +1435,8 @@ class Spotify:
|
||||
Parameters:
|
||||
- shows - a list of show URIs, URLs or IDs
|
||||
"""
|
||||
slist = [self._get_uri("show", s) for s in shows]
|
||||
return self._delete("me/library", uris=",".join(slist))
|
||||
slist = [self._get_id("show", s) for s in shows]
|
||||
return self._delete("me/shows/?ids=" + ",".join(slist))
|
||||
|
||||
def current_user_saved_shows_contains(self, shows=[]):
|
||||
""" Check if one or more shows is already saved in
|
||||
@ -1559,8 +1445,8 @@ class Spotify:
|
||||
Parameters:
|
||||
- shows - a list of show URIs, URLs or IDs
|
||||
"""
|
||||
slist = [self._get_uri("show", s) for s in shows]
|
||||
return self._get("me/library/contains", uris=",".join(slist))
|
||||
slist = [self._get_id("show", s) for s in shows]
|
||||
return self._get("me/shows/contains?ids=" + ",".join(slist))
|
||||
|
||||
def current_user_followed_artists(self, limit=20, after=None):
|
||||
""" Gets a list of the artists followed by the current authorized user
|
||||
@ -1583,11 +1469,11 @@ class Spotify:
|
||||
Parameters:
|
||||
- ids - a list of artist URIs, URLs or IDs
|
||||
"""
|
||||
ulist = []
|
||||
idlist = []
|
||||
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(
|
||||
"me/library/contains", uris=",".join(ulist)
|
||||
"me/following/contains", ids=",".join(idlist), type="artist"
|
||||
)
|
||||
|
||||
def current_user_following_users(self, ids=None):
|
||||
@ -1598,11 +1484,11 @@ class Spotify:
|
||||
Parameters:
|
||||
- ids - a list of user URIs, URLs or IDs
|
||||
"""
|
||||
ulist = []
|
||||
idlist = []
|
||||
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(
|
||||
"me/library/contains", uris=",".join(ulist)
|
||||
"me/following/contains", ids=",".join(idlist), type="user"
|
||||
)
|
||||
|
||||
def current_user_top_artists(
|
||||
@ -1611,7 +1497,7 @@ class Spotify:
|
||||
""" Get the current user's top artists
|
||||
|
||||
Parameters:
|
||||
- limit - the number of entities to return (max 50)
|
||||
- limit - the number of entities to return
|
||||
- offset - the index of the first entity to return
|
||||
- time_range - Over what time frame are the affinities computed
|
||||
Valid-values: short_term, medium_term, long_term
|
||||
@ -1659,41 +1545,34 @@ class Spotify:
|
||||
Parameters:
|
||||
- ids - a list of artist IDs
|
||||
"""
|
||||
alist = [self._get_uri("artist", a) for a in ids]
|
||||
return self._put("me/library", uris=",".join(alist))
|
||||
return self._put("me/following?type=artist&ids=" + ",".join(ids))
|
||||
|
||||
def user_follow_users(self, ids=[]):
|
||||
""" Follow one or more users
|
||||
Parameters:
|
||||
- ids - a list of user IDs
|
||||
"""
|
||||
ulist = [self._get_uri("user", a) for a in ids]
|
||||
return self._put("me/library", uris=",".join(ulist))
|
||||
return self._put("me/following?type=user&ids=" + ",".join(ids))
|
||||
|
||||
def user_unfollow_artists(self, ids=[]):
|
||||
""" Unfollow one or more artists
|
||||
Parameters:
|
||||
- ids - a list of artist IDs
|
||||
"""
|
||||
alist = [self._get_uri("artist", a) for a in ids]
|
||||
return self._delete("me/library", uris=",".join(alist))
|
||||
return self._delete("me/following?type=artist&ids=" + ",".join(ids))
|
||||
|
||||
def user_unfollow_users(self, ids=[]):
|
||||
""" Unfollow one or more users
|
||||
Parameters:
|
||||
- ids - a list of user IDs
|
||||
"""
|
||||
ulist = [self._get_uri("user", a) for a in ids]
|
||||
return self._delete("me/library", uris=",".join(ulist))
|
||||
return self._delete("me/following?type=user&ids=" + ",".join(ids))
|
||||
|
||||
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
|
||||
@ -1740,11 +1619,6 @@ 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
|
||||
)
|
||||
@ -1760,11 +1634,6 @@ 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,
|
||||
@ -1787,11 +1656,6 @@ 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,
|
||||
@ -1805,9 +1669,6 @@ 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.
|
||||
|
||||
@ -1845,9 +1706,6 @@ 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
|
||||
@ -1909,23 +1767,11 @@ class Spotify:
|
||||
|
||||
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,
|
||||
)
|
||||
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
|
||||
"""
|
||||
@ -1939,10 +1785,6 @@ 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
|
||||
"""
|
||||
@ -2224,9 +2066,11 @@ class Spotify:
|
||||
def _search_multiple_markets(self, q, limit, offset, type, markets, total):
|
||||
if total and limit > total:
|
||||
limit = total
|
||||
warnings.warn(f"limit was auto-adjusted to equal {total} "
|
||||
f"as it must not be higher than total",
|
||||
UserWarning)
|
||||
warnings.warn(
|
||||
"limit was auto-adjusted to equal {} as it must not be higher than total".format(
|
||||
total),
|
||||
UserWarning,
|
||||
)
|
||||
|
||||
results = defaultdict(dict)
|
||||
item_types = [item_type + "s" for item_type in type.split(",")]
|
||||
|
||||
@ -16,9 +16,8 @@ class SpotifyException(SpotifyBaseException):
|
||||
self.headers = headers
|
||||
|
||||
def __str__(self):
|
||||
return (f"http status: {self.http_status}, "
|
||||
f"code: {self.code} - {self.msg}, "
|
||||
f"reason: {self.reason}")
|
||||
return 'http status: {}, code:{} - {}, reason: {}'.format(
|
||||
self.http_status, self.code, self.msg, self.reason)
|
||||
|
||||
|
||||
class SpotifyOauthError(SpotifyBaseException):
|
||||
|
||||
@ -8,22 +8,20 @@ __all__ = [
|
||||
]
|
||||
|
||||
import base64
|
||||
import html
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
import urllib.parse as urllibparse
|
||||
import warnings
|
||||
import webbrowser
|
||||
|
||||
import requests
|
||||
import urllib.parse as urllibparse
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from urllib.parse import parse_qsl, urlparse
|
||||
|
||||
import requests
|
||||
|
||||
from spotipy.cache_handler import CacheFileHandler, CacheHandler
|
||||
from spotipy.exceptions import SpotifyOauthError, SpotifyStateError
|
||||
from spotipy.util import (CLIENT_CREDS_ENV_VARS, REQUESTS_SESSION,
|
||||
get_host_port, normalize_scope)
|
||||
from spotipy.util import CLIENT_CREDS_ENV_VARS, get_host_port, normalize_scope
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -124,7 +122,7 @@ class SpotifyAuthBase:
|
||||
|
||||
def __del__(self):
|
||||
"""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()
|
||||
|
||||
|
||||
@ -187,7 +185,7 @@ class SpotifyClientCredentials(SpotifyAuthBase):
|
||||
Else fetches a new token and returns it
|
||||
|
||||
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 string.
|
||||
"""
|
||||
@ -219,8 +217,10 @@ class SpotifyClientCredentials(SpotifyAuthBase):
|
||||
self.client_id, self.client_secret
|
||||
)
|
||||
|
||||
logger.debug(f"Sending POST request to {self.OAUTH_TOKEN_URL} with Headers: "
|
||||
f"{headers} and Body: {payload}")
|
||||
logger.debug(
|
||||
"sending POST request to %s with Headers: %s and Body: %r",
|
||||
self.OAUTH_TOKEN_URL, headers, payload
|
||||
)
|
||||
|
||||
try:
|
||||
response = self._session.post(
|
||||
@ -401,9 +401,9 @@ class SpotifyOAuth(SpotifyAuthBase):
|
||||
auth_url = self.get_authorize_url()
|
||||
try:
|
||||
webbrowser.open(auth_url)
|
||||
logger.info(f"Opened {auth_url} in your browser")
|
||||
logger.info("Opened %s in your browser", auth_url)
|
||||
except webbrowser.Error:
|
||||
logger.error(f"Please navigate here: {auth_url}")
|
||||
logger.error("Please navigate here: %s", auth_url)
|
||||
|
||||
def _get_auth_response_interactive(self, open_browser=False):
|
||||
if open_browser:
|
||||
@ -412,8 +412,8 @@ class SpotifyOAuth(SpotifyAuthBase):
|
||||
else:
|
||||
url = self.get_authorize_url()
|
||||
prompt = (
|
||||
f"Go to the following URL: {url}\n"
|
||||
"Enter the URL you were redirected to: "
|
||||
"Go to the following URL: {}\n"
|
||||
"Enter the URL you were redirected to: ".format(url)
|
||||
)
|
||||
response = self._get_user_input(prompt)
|
||||
state, code = SpotifyOAuth.parse_auth_response_url(response)
|
||||
@ -445,17 +445,6 @@ class SpotifyOAuth(SpotifyAuthBase):
|
||||
redirect_info = urlparse(self.redirect_uri)
|
||||
redirect_host, redirect_port = get_host_port(redirect_info.netloc)
|
||||
|
||||
if redirect_host == 'localhost':
|
||||
logger.warning(
|
||||
"Using 'localhost' as a redirect URI is being deprecated. "
|
||||
"Use a loopback IP address such as 127.0.0.1 "
|
||||
"to ensure your app remains functional.")
|
||||
|
||||
if redirect_info.scheme == "http" and redirect_host not in ("127.0.0.1", "localhost"):
|
||||
logger.warning(
|
||||
"Redirect URIs using HTTP are being deprecated. "
|
||||
"To ensure your app remains functional, use HTTPS instead.")
|
||||
|
||||
if open_browser is None:
|
||||
open_browser = self.open_browser
|
||||
|
||||
@ -468,11 +457,12 @@ class SpotifyOAuth(SpotifyAuthBase):
|
||||
if redirect_port:
|
||||
return self._get_auth_response_local_server(redirect_port)
|
||||
else:
|
||||
logger.warning(f'Using `{redirect_host}` as redirect URI without a port. '
|
||||
f'Specify a port (e.g. `{redirect_host}:8080`) to allow '
|
||||
logger.warning('Using `%s` as redirect URI without a port. '
|
||||
'Specify a port (e.g. `%s:8080`) to allow '
|
||||
'automatic retrieval of authentication code '
|
||||
'instead of having to copy and paste '
|
||||
'the URL your browser is redirected to.')
|
||||
'the URL your browser is redirected to.',
|
||||
redirect_host, redirect_host)
|
||||
|
||||
return self._get_auth_response_interactive(open_browser=open_browser)
|
||||
|
||||
@ -485,8 +475,8 @@ class SpotifyOAuth(SpotifyAuthBase):
|
||||
""" Gets the access token for the app given the code
|
||||
|
||||
Parameters:
|
||||
- code: the response code
|
||||
- as_dict: (deprecated) a boolean indicating if returning the access token
|
||||
- code - the response code
|
||||
- as_dict - a boolean indicating if returning the access token
|
||||
as a token_info dictionary, otherwise it will be returned
|
||||
as a string.
|
||||
"""
|
||||
@ -520,8 +510,10 @@ class SpotifyOAuth(SpotifyAuthBase):
|
||||
|
||||
headers = self._make_authorization_headers()
|
||||
|
||||
logger.debug(f"Sending POST request to {self.OAUTH_TOKEN_URL} with Headers: "
|
||||
f"{headers} and Body: {payload}")
|
||||
logger.debug(
|
||||
"sending POST request to %s with Headers: %s and Body: %r",
|
||||
self.OAUTH_TOKEN_URL, headers, payload
|
||||
)
|
||||
|
||||
try:
|
||||
response = self._session.post(
|
||||
@ -548,8 +540,10 @@ class SpotifyOAuth(SpotifyAuthBase):
|
||||
|
||||
headers = self._make_authorization_headers()
|
||||
|
||||
logger.debug(f"Sending POST request to {self.OAUTH_TOKEN_URL} with Headers: "
|
||||
f"{headers} and Body: {payload}")
|
||||
logger.debug(
|
||||
"sending POST request to %s with Headers: %s and Body: %r",
|
||||
self.OAUTH_TOKEN_URL, headers, payload
|
||||
)
|
||||
|
||||
try:
|
||||
response = self._session.post(
|
||||
@ -579,11 +573,6 @@ 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 " +
|
||||
@ -744,9 +733,9 @@ class SpotifyPKCE(SpotifyAuthBase):
|
||||
auth_url = self.get_authorize_url(state)
|
||||
try:
|
||||
webbrowser.open(auth_url)
|
||||
logger.info(f"Opened {auth_url} in your browser")
|
||||
logger.info("Opened %s in your browser", auth_url)
|
||||
except webbrowser.Error:
|
||||
logger.error(f"Please navigate here: {auth_url}")
|
||||
logger.error("Please navigate here: %s", auth_url)
|
||||
|
||||
def _get_auth_response(self, open_browser=None):
|
||||
logger.info('User authentication requires interaction with your '
|
||||
@ -761,17 +750,6 @@ class SpotifyPKCE(SpotifyAuthBase):
|
||||
if open_browser is None:
|
||||
open_browser = self.open_browser
|
||||
|
||||
if redirect_host == 'localhost':
|
||||
logger.warning(
|
||||
"Using 'localhost' as a redirect URI is being deprecated. "
|
||||
"Use a loopback IP address such as 127.0.0.1 "
|
||||
"to ensure your app remains functional.")
|
||||
|
||||
if redirect_info.scheme == "http" and redirect_host not in ("127.0.0.1", "localhost"):
|
||||
logger.warning(
|
||||
"Redirect URIs using HTTP are being deprecated. "
|
||||
"To ensure your app remains functional, use HTTPS instead.")
|
||||
|
||||
if (
|
||||
open_browser
|
||||
and redirect_host in ("127.0.0.1", "localhost")
|
||||
@ -781,11 +759,12 @@ class SpotifyPKCE(SpotifyAuthBase):
|
||||
if redirect_port:
|
||||
return self._get_auth_response_local_server(redirect_port)
|
||||
else:
|
||||
logger.warning(f'Using `{redirect_host}` as redirect URI without a port. '
|
||||
f'Specify a port (e.g. `{redirect_host}:8080`) to allow '
|
||||
logger.warning('Using `%s` as redirect URI without a port. '
|
||||
'Specify a port (e.g. `%s:8080`) to allow '
|
||||
'automatic retrieval of authentication code '
|
||||
'instead of having to copy and paste '
|
||||
'the URL your browser is redirected to.')
|
||||
'the URL your browser is redirected to.',
|
||||
redirect_host, redirect_host)
|
||||
return self._get_auth_response_interactive(open_browser=open_browser)
|
||||
|
||||
def _get_auth_response_local_server(self, redirect_port):
|
||||
@ -809,8 +788,10 @@ class SpotifyPKCE(SpotifyAuthBase):
|
||||
prompt = "Enter the URL you were redirected to: "
|
||||
else:
|
||||
url = self.get_authorize_url()
|
||||
prompt = (f"Go to the following URL: {url}\n"
|
||||
f"Enter the URL you were redirected to: ")
|
||||
prompt = (
|
||||
"Go to the following URL: {}\n"
|
||||
"Enter the URL you were redirected to: ".format(url)
|
||||
)
|
||||
response = self._get_user_input(prompt)
|
||||
state, code = self.parse_auth_response_url(response)
|
||||
if self.state is not None and self.state != state:
|
||||
@ -886,8 +867,10 @@ class SpotifyPKCE(SpotifyAuthBase):
|
||||
|
||||
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
||||
|
||||
logger.debug(f"Sending POST request to {self.OAUTH_TOKEN_URL} with Headers: "
|
||||
f"{headers} and Body: {payload}")
|
||||
logger.debug(
|
||||
"sending POST request to %s with Headers: %s and Body: %r",
|
||||
self.OAUTH_TOKEN_URL, headers, payload
|
||||
)
|
||||
|
||||
try:
|
||||
response = self._session.post(
|
||||
@ -915,8 +898,10 @@ class SpotifyPKCE(SpotifyAuthBase):
|
||||
|
||||
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
||||
|
||||
logger.debug(f"Sending POST request to {self.OAUTH_TOKEN_URL} with Headers: "
|
||||
f"{headers} and Body: {payload}")
|
||||
logger.debug(
|
||||
"sending POST request to %s with Headers: %s and Body: %r",
|
||||
self.OAUTH_TOKEN_URL, headers, payload
|
||||
)
|
||||
|
||||
try:
|
||||
response = self._session.post(
|
||||
@ -1038,7 +1023,7 @@ class SpotifyImplicitGrant(SpotifyAuthBase):
|
||||
(will set `cache_path` to `.cache-{username}`)
|
||||
* show_dialog: Interpreted as boolean
|
||||
"""
|
||||
logger.warning("Spotify is deprecating the Implicit "
|
||||
logger.warning("The OAuth standard no longer recommends the Implicit "
|
||||
"Grant Flow for client-side code. Use the SpotifyPKCE "
|
||||
"auth manager instead of SpotifyImplicitGrant. For "
|
||||
"more details and a guide to switching, see "
|
||||
@ -1167,9 +1152,9 @@ class SpotifyImplicitGrant(SpotifyAuthBase):
|
||||
auth_url = self.get_authorize_url(state)
|
||||
try:
|
||||
webbrowser.open(auth_url)
|
||||
logger.info(f"Opened {auth_url} in your browser")
|
||||
logger.info("Opened %s in your browser", auth_url)
|
||||
except webbrowser.Error:
|
||||
logger.error(f"Please navigate here: {auth_url}")
|
||||
logger.error("Please navigate here: %s", auth_url)
|
||||
|
||||
def get_auth_response(self, state=None):
|
||||
""" Gets a new auth **token** with user interaction """
|
||||
@ -1210,11 +1195,6 @@ 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 " +
|
||||
@ -1253,26 +1233,24 @@ class RequestHandler(BaseHTTPRequestHandler):
|
||||
if self.server.auth_code:
|
||||
status = "successful"
|
||||
elif self.server.error:
|
||||
status = f"failed ({html.escape(str(self.server.error))})"
|
||||
status = f"failed ({self.server.error})"
|
||||
else:
|
||||
self._write("<html><body><h1>Invalid request</h1></body></html>")
|
||||
return
|
||||
|
||||
self._write(f"""<html>
|
||||
self._write("""<html>
|
||||
<script>
|
||||
window.close()
|
||||
</script>
|
||||
<body>
|
||||
<h1>Authentication status: {status}</h1>
|
||||
<h1>Authentication status: {}</h1>
|
||||
This window can be closed.
|
||||
<script>
|
||||
window.close()
|
||||
</script>
|
||||
<button class="closeButton" style="cursor: pointer" onclick="window.close();">
|
||||
Close Window
|
||||
</button>
|
||||
<button class="closeButton" style="cursor: pointer" onclick="window.close();">Close Window</button>
|
||||
</body>
|
||||
</html>""")
|
||||
</html>""".format(status))
|
||||
|
||||
def _write(self, text):
|
||||
return self.wfile.write(text.encode("utf-8"))
|
||||
|
||||
@ -9,12 +9,11 @@ import os
|
||||
import warnings
|
||||
from types import TracebackType
|
||||
|
||||
import requests
|
||||
import urllib3
|
||||
|
||||
import spotipy
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
import urllib3
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CLIENT_CREDS_ENV_VARS = {
|
||||
"client_id": "SPOTIPY_CLIENT_ID",
|
||||
@ -23,9 +22,6 @@ CLIENT_CREDS_ENV_VARS = {
|
||||
"redirect_uri": "SPOTIPY_REDIRECT_URI",
|
||||
}
|
||||
|
||||
# workaround for garbage collection
|
||||
REQUESTS_SESSION = requests.Session
|
||||
|
||||
|
||||
def prompt_for_user_token(
|
||||
username=None,
|
||||
@ -37,12 +33,16 @@ def prompt_for_user_token(
|
||||
oauth_manager=None,
|
||||
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.
|
||||
|
||||
.. deprecated::
|
||||
This method is deprecated and may be removed in a future version.
|
||||
|
||||
Parameters:
|
||||
- username - the Spotify username. (optional)
|
||||
- scope - the desired scope of the request. (optional)
|
||||
@ -53,14 +53,6 @@ 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")
|
||||
@ -72,7 +64,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:
|
||||
@ -161,7 +153,6 @@ class Retry(urllib3.Retry):
|
||||
"""
|
||||
Custom class for printing a warning when a rate/request limit is reached.
|
||||
"""
|
||||
|
||||
def increment(
|
||||
self,
|
||||
method: str | None = None,
|
||||
@ -174,9 +165,8 @@ class Retry(urllib3.Retry):
|
||||
if response:
|
||||
retry_header = response.headers.get("Retry-After")
|
||||
if self.is_retry(method, response.status, bool(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")
|
||||
logging.warning("Your application has reached a rate/request limit. "
|
||||
f"Retry will occur after: {retry_header}")
|
||||
return super().increment(method,
|
||||
url,
|
||||
response=response,
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import base64
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import unittest
|
||||
|
||||
import requests
|
||||
|
||||
from spotipy import (
|
||||
Spotify,
|
||||
SpotifyClientCredentials,
|
||||
SpotifyException
|
||||
)
|
||||
import spotipy
|
||||
from spotipy import Spotify, SpotifyClientCredentials, SpotifyException
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
|
||||
class AuthTestSpotipy(unittest.TestCase):
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from spotipy import CLIENT_CREDS_ENV_VARS as CCEV
|
||||
from spotipy import (Spotify, SpotifyException, SpotifyImplicitGrant,
|
||||
SpotifyPKCE, prompt_for_user_token)
|
||||
from spotipy import (
|
||||
CLIENT_CREDS_ENV_VARS as CCEV,
|
||||
prompt_for_user_token,
|
||||
Spotify,
|
||||
SpotifyException,
|
||||
SpotifyImplicitGrant,
|
||||
SpotifyPKCE
|
||||
)
|
||||
import unittest
|
||||
from tests import helpers
|
||||
|
||||
|
||||
@ -550,7 +555,7 @@ class SpotifyQueueApiTests(unittest.TestCase):
|
||||
self.spotify.add_to_queue(test_uri)
|
||||
|
||||
# Check if the correct endpoint is called
|
||||
endpoint = f"me/player/queue?uri={test_uri}"
|
||||
endpoint = "me/player/queue?uri=%s" % test_uri
|
||||
mock_post.assert_called_with(endpoint)
|
||||
|
||||
def test_add_to_queue_with_device_id(self, mock_post):
|
||||
@ -561,5 +566,5 @@ class SpotifyQueueApiTests(unittest.TestCase):
|
||||
self.spotify.add_to_queue(test_uri, device_id=device_id)
|
||||
|
||||
# Check if the correct endpoint is called
|
||||
endpoint = f"me/player/queue?uri={test_uri}&device_id={device_id}"
|
||||
endpoint = "me/player/queue?uri=%s&device_id=%s" % (test_uri, device_id)
|
||||
mock_post.assert_called_with(endpoint)
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
import io
|
||||
import json
|
||||
import unittest
|
||||
|
||||
import unittest.mock as mock
|
||||
import urllib.parse as urllibparse
|
||||
|
||||
from spotipy import SpotifyImplicitGrant, SpotifyOAuth, SpotifyPKCE
|
||||
from spotipy import SpotifyOAuth, SpotifyImplicitGrant, SpotifyPKCE
|
||||
from spotipy.cache_handler import MemoryCacheHandler
|
||||
from spotipy.oauth2 import (SpotifyClientCredentials, SpotifyOauthError,
|
||||
SpotifyStateError)
|
||||
from spotipy.oauth2 import SpotifyClientCredentials, SpotifyOauthError
|
||||
from spotipy.oauth2 import SpotifyStateError
|
||||
|
||||
patch = mock.patch
|
||||
DEFAULT = mock.DEFAULT
|
||||
@ -52,21 +53,18 @@ class OAuthCacheTest(unittest.TestCase):
|
||||
@patch('spotipy.cache_handler.open', create=True)
|
||||
def test_gets_from_cache_path(self, opener,
|
||||
is_token_expired, refresh_access_token):
|
||||
"""Test that the token is retrieved from the cache path."""
|
||||
scope = "playlist-modify-private"
|
||||
path = ".cache-username"
|
||||
tok = _make_fake_token(1, 1, scope)
|
||||
token_file = _token_file(json.dumps(tok, ensure_ascii=False))
|
||||
opener.return_value = token_file
|
||||
opener.return_value.__enter__ = mock.Mock(return_value=token_file)
|
||||
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||
|
||||
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=False))
|
||||
is_token_expired.return_value = False
|
||||
|
||||
spot = _make_oauth(scope, path)
|
||||
cached_tok = spot.validate_token(spot.cache_handler.get_cached_token())
|
||||
cached_tok_legacy = spot.get_cached_token()
|
||||
|
||||
opener.assert_called_with(path, encoding='utf-8')
|
||||
opener.assert_called_with(path)
|
||||
self.assertIsNotNone(cached_tok)
|
||||
self.assertIsNotNone(cached_tok_legacy)
|
||||
self.assertEqual(refresh_access_token.call_count, 0)
|
||||
@ -76,15 +74,13 @@ class OAuthCacheTest(unittest.TestCase):
|
||||
@patch('spotipy.cache_handler.open', create=True)
|
||||
def test_expired_token_refreshes(self, opener,
|
||||
is_token_expired, refresh_access_token):
|
||||
"""Test that an expired token is refreshed."""
|
||||
scope = "playlist-modify-private"
|
||||
path = ".cache-username"
|
||||
expired_tok = _make_fake_token(0, None, scope)
|
||||
fresh_tok = _make_fake_token(1, 1, scope)
|
||||
|
||||
token_file = _token_file(json.dumps(expired_tok, ensure_ascii=False))
|
||||
opener.return_value.__enter__ = mock.Mock(return_value=token_file)
|
||||
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||
opener.return_value = token_file
|
||||
refresh_access_token.return_value = fresh_tok
|
||||
|
||||
spot = _make_oauth(scope, path)
|
||||
@ -92,7 +88,7 @@ class OAuthCacheTest(unittest.TestCase):
|
||||
|
||||
is_token_expired.assert_called_with(expired_tok)
|
||||
refresh_access_token.assert_called_with(expired_tok['refresh_token'])
|
||||
opener.assert_any_call(path, encoding='utf-8')
|
||||
opener.assert_any_call(path)
|
||||
|
||||
@patch.multiple(SpotifyOAuth,
|
||||
is_token_expired=DEFAULT, refresh_access_token=DEFAULT)
|
||||
@ -104,35 +100,29 @@ class OAuthCacheTest(unittest.TestCase):
|
||||
path = ".cache-username"
|
||||
tok = _make_fake_token(1, 1, token_scope)
|
||||
|
||||
token_file = _token_file(json.dumps(tok, ensure_ascii=False))
|
||||
opener.return_value = token_file
|
||||
opener.return_value.__enter__ = mock.Mock(return_value=token_file)
|
||||
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=False))
|
||||
is_token_expired.return_value = False
|
||||
|
||||
spot = _make_oauth(requested_scope, path)
|
||||
cached_tok = spot.validate_token(spot.cache_handler.get_cached_token())
|
||||
|
||||
opener.assert_called_with(path, encoding='utf-8')
|
||||
opener.assert_called_with(path)
|
||||
self.assertIsNone(cached_tok)
|
||||
self.assertEqual(refresh_access_token.call_count, 0)
|
||||
|
||||
@patch('spotipy.cache_handler.open', create=True)
|
||||
def test_saves_to_cache_path(self, opener):
|
||||
"""Test that the token is saved to the cache path."""
|
||||
scope = "playlist-modify-private"
|
||||
path = ".cache-username"
|
||||
tok = _make_fake_token(1, 1, scope)
|
||||
|
||||
fi = _fake_file()
|
||||
opener.return_value = fi
|
||||
opener.return_value.__enter__ = mock.Mock(return_value=fi)
|
||||
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||
|
||||
spot = SpotifyOAuth("CLID", "CLISEC", "REDIR", "STATE", scope, path)
|
||||
spot.cache_handler.save_token_to_cache(tok)
|
||||
|
||||
opener.assert_called_with(path, 'w', encoding='utf-8')
|
||||
opener.assert_called_with(path, 'w')
|
||||
self.assertTrue(fi.write.called)
|
||||
|
||||
@patch('spotipy.cache_handler.open', create=True)
|
||||
@ -143,13 +133,11 @@ class OAuthCacheTest(unittest.TestCase):
|
||||
|
||||
fi = _fake_file()
|
||||
opener.return_value = fi
|
||||
opener.return_value.__enter__ = mock.Mock(return_value=fi)
|
||||
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||
|
||||
spot = SpotifyOAuth("CLID", "CLISEC", "REDIR", "STATE", scope, path)
|
||||
spot._save_token_info(tok)
|
||||
|
||||
opener.assert_called_with(path, 'w', encoding='utf-8')
|
||||
opener.assert_called_with(path, 'w')
|
||||
self.assertTrue(fi.write.called)
|
||||
|
||||
def test_cache_handler(self):
|
||||
@ -265,38 +253,32 @@ class ImplicitGrantCacheTest(unittest.TestCase):
|
||||
path = ".cache-username"
|
||||
tok = _make_fake_token(1, 1, scope)
|
||||
|
||||
token_file = _token_file(json.dumps(tok, ensure_ascii=False))
|
||||
opener.return_value = token_file
|
||||
opener.return_value.__enter__ = mock.Mock(return_value=token_file)
|
||||
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=False))
|
||||
is_token_expired.return_value = False
|
||||
|
||||
spot = _make_implicitgrantauth(scope, path)
|
||||
cached_tok = spot.cache_handler.get_cached_token()
|
||||
cached_tok_legacy = spot.get_cached_token()
|
||||
|
||||
opener.assert_called_with(path, encoding='utf-8')
|
||||
opener.assert_called_with(path)
|
||||
self.assertIsNotNone(cached_tok)
|
||||
self.assertIsNotNone(cached_tok_legacy)
|
||||
|
||||
@patch.object(SpotifyImplicitGrant, "is_token_expired", DEFAULT)
|
||||
@patch('spotipy.cache_handler.open', create=True)
|
||||
def test_expired_token_returns_none(self, opener, is_token_expired):
|
||||
"""Test that an expired token returns None."""
|
||||
scope = "playlist-modify-private"
|
||||
path = ".cache-username"
|
||||
expired_tok = _make_fake_token(0, None, scope)
|
||||
|
||||
token_file = _token_file(json.dumps(expired_tok, ensure_ascii=False))
|
||||
opener.return_value = token_file
|
||||
opener.return_value.__enter__ = mock.Mock(return_value=token_file)
|
||||
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||
|
||||
spot = _make_implicitgrantauth(scope, path)
|
||||
cached_tok = spot.validate_token(spot.cache_handler.get_cached_token())
|
||||
|
||||
is_token_expired.assert_called_with(expired_tok)
|
||||
opener.assert_any_call(path, encoding='utf-8')
|
||||
opener.assert_any_call(path)
|
||||
self.assertIsNone(cached_tok)
|
||||
|
||||
@patch.object(SpotifyImplicitGrant, "is_token_expired", DEFAULT)
|
||||
@ -307,16 +289,13 @@ class ImplicitGrantCacheTest(unittest.TestCase):
|
||||
path = ".cache-username"
|
||||
tok = _make_fake_token(1, 1, token_scope)
|
||||
|
||||
token_file = _token_file(json.dumps(tok, ensure_ascii=False))
|
||||
opener.return_value = token_file
|
||||
opener.return_value.__enter__ = mock.Mock(return_value=token_file)
|
||||
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=False))
|
||||
is_token_expired.return_value = False
|
||||
|
||||
spot = _make_implicitgrantauth(requested_scope, path)
|
||||
cached_tok = spot.validate_token(spot.cache_handler.get_cached_token())
|
||||
|
||||
opener.assert_called_with(path, encoding='utf-8')
|
||||
opener.assert_called_with(path)
|
||||
self.assertIsNone(cached_tok)
|
||||
|
||||
@patch('spotipy.cache_handler.open', create=True)
|
||||
@ -328,12 +307,10 @@ class ImplicitGrantCacheTest(unittest.TestCase):
|
||||
fi = _fake_file()
|
||||
opener.return_value = fi
|
||||
|
||||
opener.return_value.__enter__ = mock.Mock(return_value=fi)
|
||||
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||
spot = SpotifyImplicitGrant("CLID", "REDIR", "STATE", scope, path)
|
||||
spot.cache_handler.save_token_to_cache(tok)
|
||||
|
||||
opener.assert_called_with(path, 'w', encoding='utf-8')
|
||||
opener.assert_called_with(path, 'w')
|
||||
self.assertTrue(fi.write.called)
|
||||
|
||||
@patch('spotipy.cache_handler.open', create=True)
|
||||
@ -344,13 +321,11 @@ class ImplicitGrantCacheTest(unittest.TestCase):
|
||||
|
||||
fi = _fake_file()
|
||||
opener.return_value = fi
|
||||
opener.return_value.__enter__ = mock.Mock(return_value=fi)
|
||||
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||
|
||||
spot = SpotifyImplicitGrant("CLID", "REDIR", "STATE", scope, path)
|
||||
spot._save_token_info(tok)
|
||||
|
||||
opener.assert_called_with(path, 'w', encoding='utf-8')
|
||||
opener.assert_called_with(path, 'w')
|
||||
self.assertTrue(fi.write.called)
|
||||
|
||||
|
||||
@ -415,17 +390,14 @@ class SpotifyPKCECacheTest(unittest.TestCase):
|
||||
path = ".cache-username"
|
||||
tok = _make_fake_token(1, 1, scope)
|
||||
|
||||
token_file = _token_file(json.dumps(tok, ensure_ascii=False))
|
||||
opener.return_value = token_file
|
||||
opener.return_value.__enter__ = mock.Mock(return_value=token_file)
|
||||
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=False))
|
||||
is_token_expired.return_value = False
|
||||
|
||||
spot = _make_pkceauth(scope, path)
|
||||
cached_tok = spot.cache_handler.get_cached_token()
|
||||
cached_tok_legacy = spot.get_cached_token()
|
||||
|
||||
opener.assert_called_with(path, encoding='utf-8')
|
||||
opener.assert_called_with(path)
|
||||
self.assertIsNotNone(cached_tok)
|
||||
self.assertIsNotNone(cached_tok_legacy)
|
||||
self.assertEqual(refresh_access_token.call_count, 0)
|
||||
@ -441,8 +413,7 @@ class SpotifyPKCECacheTest(unittest.TestCase):
|
||||
fresh_tok = _make_fake_token(1, 1, scope)
|
||||
|
||||
token_file = _token_file(json.dumps(expired_tok, ensure_ascii=False))
|
||||
opener.return_value.__enter__ = mock.Mock(return_value=token_file)
|
||||
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||
opener.return_value = token_file
|
||||
refresh_access_token.return_value = fresh_tok
|
||||
|
||||
spot = _make_pkceauth(scope, path)
|
||||
@ -450,7 +421,7 @@ class SpotifyPKCECacheTest(unittest.TestCase):
|
||||
|
||||
is_token_expired.assert_called_with(expired_tok)
|
||||
refresh_access_token.assert_called_with(expired_tok['refresh_token'])
|
||||
opener.assert_any_call(path, encoding='utf-8')
|
||||
opener.assert_any_call(path)
|
||||
|
||||
@patch.multiple(SpotifyPKCE,
|
||||
is_token_expired=DEFAULT, refresh_access_token=DEFAULT)
|
||||
@ -462,16 +433,13 @@ class SpotifyPKCECacheTest(unittest.TestCase):
|
||||
path = ".cache-username"
|
||||
tok = _make_fake_token(1, 1, token_scope)
|
||||
|
||||
token_file = _token_file(json.dumps(tok, ensure_ascii=False))
|
||||
opener.return_value = token_file
|
||||
opener.return_value.__enter__ = mock.Mock(return_value=token_file)
|
||||
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=False))
|
||||
is_token_expired.return_value = False
|
||||
|
||||
spot = _make_pkceauth(requested_scope, path)
|
||||
cached_tok = spot.validate_token(spot.cache_handler.get_cached_token())
|
||||
|
||||
opener.assert_called_with(path, encoding='utf-8')
|
||||
opener.assert_called_with(path)
|
||||
self.assertIsNone(cached_tok)
|
||||
self.assertEqual(refresh_access_token.call_count, 0)
|
||||
|
||||
@ -483,12 +451,11 @@ class SpotifyPKCECacheTest(unittest.TestCase):
|
||||
|
||||
fi = _fake_file()
|
||||
opener.return_value = fi
|
||||
opener.return_value.__enter__ = mock.Mock(return_value=fi)
|
||||
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||
|
||||
spot = SpotifyPKCE("CLID", "REDIR", "STATE", scope, path)
|
||||
spot.cache_handler.save_token_to_cache(tok)
|
||||
|
||||
opener.assert_called_with(path, 'w', encoding='utf-8')
|
||||
opener.assert_called_with(path, 'w')
|
||||
self.assertTrue(fi.write.called)
|
||||
|
||||
@patch('spotipy.cache_handler.open', create=True)
|
||||
@ -499,13 +466,11 @@ class SpotifyPKCECacheTest(unittest.TestCase):
|
||||
|
||||
fi = _fake_file()
|
||||
opener.return_value = fi
|
||||
opener.return_value.__enter__ = mock.Mock(return_value=fi)
|
||||
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||
|
||||
spot = SpotifyPKCE("CLID", "REDIR", "STATE", scope, path)
|
||||
spot._save_token_info(tok)
|
||||
|
||||
opener.assert_called_with(path, 'w', encoding='utf-8')
|
||||
opener.assert_called_with(path, 'w')
|
||||
self.assertTrue(fi.write.called)
|
||||
|
||||
|
||||
@ -522,8 +487,8 @@ class TestSpotifyPKCE(unittest.TestCase):
|
||||
self.assertTrue(auth.code_challenge)
|
||||
|
||||
def test_code_verifier_and_code_challenge_are_correct(self):
|
||||
import base64
|
||||
import hashlib
|
||||
import base64
|
||||
auth = SpotifyPKCE("CLID", "REDIR")
|
||||
auth.get_pkce_handshake_parameters()
|
||||
self.assertEqual(auth.code_challenge,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user