mirror of
https://github.com/spotipy-dev/spotipy.git
synced 2026-06-19 09:13:53 +00:00
Drop support for EOL Python 3.7 (#1065)
* Add python_requires to help pip * Update supported versions in tox.ini * Upgrade Python syntax with pyupgrade --py37-plus * Bump GitHub Actions * Add Python 3.11 and 3.12 to CI * Remove six dependency * Remove redundant dependencies * Remove redudant Python 3.5 code * Drop support for EOL Python 3.7 * Upgrade Python syntax with pyupgrade --py38-plus * Update CHANGELOG * More f-strings --------- Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
This commit is contained in:
parent
958ff6ad2b
commit
85c9d74dc1
4
.github/workflows/integration_tests.yml
vendored
4
.github/workflows/integration_tests.yml
vendored
@ -11,9 +11,9 @@ jobs:
|
||||
SPOTIPY_CLIENT_SECRET: ${{ secrets.SPOTIPY_CLIENT_SECRET }}
|
||||
PYTHON_VERSION: "3.10"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@v1
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: Install dependencies
|
||||
|
||||
6
.github/workflows/publish.yml
vendored
6
.github/workflows/publish.yml
vendored
@ -13,9 +13,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Install pypa/build
|
||||
@ -33,7 +33,7 @@ jobs:
|
||||
--outdir dist/
|
||||
.
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "2.x"
|
||||
- name: Install pypa/build
|
||||
|
||||
4
.github/workflows/pull_request.yml
vendored
4
.github/workflows/pull_request.yml
vendored
@ -8,8 +8,8 @@ jobs:
|
||||
changelog:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: dangoslen/changelog-enforcer@v1.1.1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dangoslen/changelog-enforcer@v3.5.1
|
||||
with:
|
||||
changeLogPath: 'CHANGELOG.md'
|
||||
skipLabel: 'skip-changelog'
|
||||
6
.github/workflows/pythonapp.yml
vendored
6
.github/workflows/pythonapp.yml
vendored
@ -8,11 +8,11 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.7", "3.8", "3.9", "3.10"]
|
||||
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v1
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
|
||||
@ -30,6 +30,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Fixed
|
||||
- Fixed unused description parameter in playlist creation example
|
||||
|
||||
### Changed
|
||||
- Drop support for EOL Python 3.7.
|
||||
|
||||
|
||||
## [2.23.0] - 2023-04-07
|
||||
|
||||
### Added
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# spotipy documentation build configuration file, created by
|
||||
# sphinx-quickstart on Thu Aug 21 11:04:39 2014.
|
||||
|
||||
@ -333,7 +333,7 @@ Export the needed Environment variables:::
|
||||
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.7 env
|
||||
$ virtualenv --python=python3.12 env
|
||||
(env) $ pip install --user -e .
|
||||
(env) $ python -m unittest discover -v tests
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
# shows audio analysis for the given track
|
||||
|
||||
from __future__ import print_function # (at top of module)
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import json
|
||||
import spotipy
|
||||
@ -20,4 +19,4 @@ start = time.time()
|
||||
analysis = sp.audio_analysis(tid)
|
||||
delta = time.time() - start
|
||||
print(json.dumps(analysis, indent=4))
|
||||
print("analysis retrieved in %.2f seconds" % (delta,))
|
||||
print(f"analysis retrieved in {delta:.2f} seconds")
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
|
||||
# shows acoustic features for tracks for the given artist
|
||||
|
||||
from __future__ import print_function # (at top of module)
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import json
|
||||
import spotipy
|
||||
@ -33,4 +31,4 @@ for feature in features:
|
||||
analysis = sp._get(feature['analysis_url'])
|
||||
print(json.dumps(analysis, indent=4))
|
||||
print()
|
||||
print("features retrieved in %.2f seconds" % (delta,))
|
||||
print(f"features retrieved in {delta:.2f} seconds")
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
|
||||
|
||||
# shows acoustic features for tracks for the given artist
|
||||
|
||||
from __future__ import print_function # (at top of module)
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import json
|
||||
import spotipy
|
||||
@ -22,4 +19,4 @@ if len(sys.argv) > 1:
|
||||
features = sp.audio_features(tids)
|
||||
delta = time.time() - start
|
||||
print(json.dumps(features, indent=4))
|
||||
print("features retrieved in %.2f seconds" % (delta,))
|
||||
print(f"features retrieved in {delta:.2f} seconds")
|
||||
|
||||
@ -11,7 +11,7 @@ scope = 'user-library-read'
|
||||
if len(sys.argv) > 1:
|
||||
tid = sys.argv[1]
|
||||
else:
|
||||
print("Usage: %s track-id ..." % (sys.argv[0],))
|
||||
print(f"Usage: {sys.argv[0]} track-id ...")
|
||||
sys.exit()
|
||||
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
|
||||
|
||||
@ -11,7 +11,7 @@ scope = 'user-library-modify'
|
||||
if len(sys.argv) > 1:
|
||||
tid = sys.argv[1]
|
||||
else:
|
||||
print("Usage: %s track-id ..." % (sys.argv[0],))
|
||||
print(f"Usage: {sys.argv[0]} track-id ...")
|
||||
sys.exit()
|
||||
|
||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
|
||||
|
||||
@ -15,8 +15,7 @@ if len(sys.argv) > 2:
|
||||
track_ids.append({"uri": tid, "positions": [int(pos)]})
|
||||
else:
|
||||
print(
|
||||
"Usage: %s playlist_id track_id,pos track_id,pos ..." %
|
||||
(sys.argv[0],))
|
||||
f"Usage: {sys.argv[0]} playlist_id track_id,pos track_id,pos ...")
|
||||
sys.exit()
|
||||
|
||||
scope = 'playlist-modify-public'
|
||||
|
||||
@ -10,7 +10,7 @@ if len(sys.argv) > 2:
|
||||
playlist_id = sys.argv[2]
|
||||
track_ids = sys.argv[3:]
|
||||
else:
|
||||
print("Usage: %s playlist_id track_id ..." % (sys.argv[0]))
|
||||
print(f"Usage: {sys.argv[0]} playlist_id track_id ...")
|
||||
sys.exit()
|
||||
|
||||
scope = 'playlist-modify-public'
|
||||
|
||||
@ -10,7 +10,7 @@ if len(sys.argv) > 3:
|
||||
playlist_id = sys.argv[1]
|
||||
track_ids = sys.argv[2:]
|
||||
else:
|
||||
print("Usage: %s playlist_id track_id ..." % (sys.argv[0],))
|
||||
print(f"Usage: {sys.argv[0]} playlist_id track_id ...")
|
||||
sys.exit()
|
||||
|
||||
scope = 'playlist-modify-public'
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
# shows album info for a URN or URL
|
||||
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
# shows related artists for the given seed artist
|
||||
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
|
||||
@ -13,4 +13,4 @@ while results['next']:
|
||||
albums.extend(results['items'])
|
||||
|
||||
for album in albums:
|
||||
print((album['name']))
|
||||
print(album['name'])
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
import spotipy
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ client_credentials_manager = SpotifyClientCredentials()
|
||||
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
|
||||
|
||||
skiplist = set(['dm', 'remix'])
|
||||
skiplist = {'dm', 'remix'}
|
||||
max_offset = 500
|
||||
seen = set()
|
||||
|
||||
|
||||
5
setup.py
5
setup.py
@ -1,6 +1,6 @@
|
||||
from setuptools import setup
|
||||
|
||||
with open("README.md", "r") as f:
|
||||
with open("README.md") as f:
|
||||
long_description = f.read()
|
||||
|
||||
test_reqs = [
|
||||
@ -28,11 +28,10 @@ setup(
|
||||
project_urls={
|
||||
'Source': 'https://github.com/plamere/spotipy',
|
||||
},
|
||||
python_requires='>3.8',
|
||||
install_requires=[
|
||||
"redis>=3.5.3",
|
||||
"redis<4.0.0;python_version<'3.4'",
|
||||
"requests>=2.25.0",
|
||||
"six>=1.15.0",
|
||||
"urllib3>=1.26.0"
|
||||
],
|
||||
tests_require=test_reqs,
|
||||
|
||||
@ -80,7 +80,7 @@ class CacheFileHandler(CacheHandler):
|
||||
f.close()
|
||||
token_info = json.loads(token_info_string)
|
||||
|
||||
except IOError as error:
|
||||
except OSError as error:
|
||||
if error.errno == errno.ENOENT:
|
||||
logger.debug("cache does not exist at: %s", self.cache_path)
|
||||
else:
|
||||
@ -93,7 +93,7 @@ class CacheFileHandler(CacheHandler):
|
||||
f = open(self.cache_path, "w")
|
||||
f.write(json.dumps(token_info, cls=self.encoder_cls))
|
||||
f.close()
|
||||
except IOError:
|
||||
except OSError:
|
||||
logger.warning('Couldn\'t write token to cache at: %s',
|
||||
self.cache_path)
|
||||
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" A simple and thin Python library for the Spotify Web API """
|
||||
|
||||
__all__ = ["Spotify", "SpotifyException"]
|
||||
@ -10,7 +8,6 @@ import re
|
||||
import warnings
|
||||
|
||||
import requests
|
||||
import six
|
||||
import urllib3
|
||||
|
||||
from spotipy.exceptions import SpotifyException
|
||||
@ -20,7 +17,7 @@ from collections import defaultdict
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Spotify(object):
|
||||
class Spotify:
|
||||
"""
|
||||
Example usage::
|
||||
|
||||
@ -234,14 +231,14 @@ class Spotify(object):
|
||||
|
||||
def _auth_headers(self):
|
||||
if self._auth:
|
||||
return {"Authorization": "Bearer {0}".format(self._auth)}
|
||||
return {"Authorization": f"Bearer {self._auth}"}
|
||||
if not self.auth_manager:
|
||||
return {}
|
||||
try:
|
||||
token = self.auth_manager.get_access_token(as_dict=False)
|
||||
except TypeError:
|
||||
token = self.auth_manager.get_access_token()
|
||||
return {"Authorization": "Bearer {0}".format(token)}
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
||||
def _internal_call(self, method, url, payload, params):
|
||||
args = dict(params=params)
|
||||
@ -296,7 +293,7 @@ class Spotify(object):
|
||||
raise SpotifyException(
|
||||
response.status_code,
|
||||
-1,
|
||||
"%s:\n %s" % (response.url, msg),
|
||||
f"{response.url}:\n {msg}",
|
||||
reason=reason,
|
||||
headers=response.headers,
|
||||
)
|
||||
@ -310,7 +307,7 @@ class Spotify(object):
|
||||
raise SpotifyException(
|
||||
429,
|
||||
-1,
|
||||
"%s:\n %s" % (request.path_url, "Max Retries"),
|
||||
f"{request.path_url}:\n Max Retries",
|
||||
reason=reason
|
||||
)
|
||||
except ValueError:
|
||||
@ -663,7 +660,7 @@ class Spotify(object):
|
||||
"""
|
||||
plid = self._get_id("playlist", playlist_id)
|
||||
return self._get(
|
||||
"playlists/%s" % (plid),
|
||||
f"playlists/{plid}",
|
||||
fields=fields,
|
||||
market=market,
|
||||
additional_types=",".join(additional_types),
|
||||
@ -719,7 +716,7 @@ class Spotify(object):
|
||||
"""
|
||||
plid = self._get_id("playlist", playlist_id)
|
||||
return self._get(
|
||||
"playlists/%s/tracks" % (plid),
|
||||
f"playlists/{plid}/tracks",
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
fields=fields,
|
||||
@ -734,7 +731,7 @@ class Spotify(object):
|
||||
- playlist_id - the playlist ID, URI or URL
|
||||
"""
|
||||
plid = self._get_id("playlist", playlist_id)
|
||||
return self._get("playlists/%s/images" % (plid))
|
||||
return self._get(f"playlists/{plid}/images")
|
||||
|
||||
def playlist_upload_cover_image(self, playlist_id, image_b64):
|
||||
""" Replace the image used to represent a specific playlist
|
||||
@ -746,7 +743,7 @@ class Spotify(object):
|
||||
"""
|
||||
plid = self._get_id("playlist", playlist_id)
|
||||
return self._put(
|
||||
"playlists/{}/images".format(plid),
|
||||
f"playlists/{plid}/images",
|
||||
payload=image_b64,
|
||||
content_type="image/jpeg",
|
||||
)
|
||||
@ -765,7 +762,7 @@ class Spotify(object):
|
||||
- fields - which fields to return
|
||||
"""
|
||||
if playlist_id is None:
|
||||
return self._get("users/%s/starred" % user)
|
||||
return self._get(f"users/{user}/starred")
|
||||
return self.playlist(playlist_id, fields=fields, market=market)
|
||||
|
||||
def user_playlist_tracks(
|
||||
@ -809,7 +806,7 @@ class Spotify(object):
|
||||
- offset - the index of the first item to return
|
||||
"""
|
||||
return self._get(
|
||||
"users/%s/playlists" % user, limit=limit, offset=offset
|
||||
f"users/{user}/playlists", limit=limit, offset=offset
|
||||
)
|
||||
|
||||
def user_playlist_create(self, user, name, public=True, collaborative=False, description=""):
|
||||
@ -829,7 +826,7 @@ class Spotify(object):
|
||||
"description": description
|
||||
}
|
||||
|
||||
return self._post("users/%s/playlists" % (user,), payload=data)
|
||||
return self._post(f"users/{user}/playlists", payload=data)
|
||||
|
||||
def user_playlist_change_details(
|
||||
self,
|
||||
@ -1004,7 +1001,7 @@ class Spotify(object):
|
||||
if snapshot_id:
|
||||
payload["snapshot_id"] = snapshot_id
|
||||
return self._delete(
|
||||
"users/%s/playlists/%s/tracks" % (user, plid), payload=payload
|
||||
f"users/{user}/playlists/{plid}/tracks", payload=payload
|
||||
)
|
||||
|
||||
def user_playlist_follow_playlist(self, playlist_owner_id, playlist_id):
|
||||
@ -1061,16 +1058,16 @@ class Spotify(object):
|
||||
"""
|
||||
|
||||
data = {}
|
||||
if isinstance(name, six.string_types):
|
||||
if isinstance(name, str):
|
||||
data["name"] = name
|
||||
if isinstance(public, bool):
|
||||
data["public"] = public
|
||||
if isinstance(collaborative, bool):
|
||||
data["collaborative"] = collaborative
|
||||
if isinstance(description, six.string_types):
|
||||
if isinstance(description, str):
|
||||
data["description"] = description
|
||||
return self._put(
|
||||
"playlists/%s" % (self._get_id("playlist", playlist_id)), payload=data
|
||||
f"playlists/{self._get_id('playlist', playlist_id)}", payload=data
|
||||
)
|
||||
|
||||
def current_user_unfollow_playlist(self, playlist_id):
|
||||
@ -1081,7 +1078,7 @@ class Spotify(object):
|
||||
- name - the name of the playlist
|
||||
"""
|
||||
return self._delete(
|
||||
"playlists/%s/followers" % (playlist_id)
|
||||
f"playlists/{playlist_id}/followers"
|
||||
)
|
||||
|
||||
def playlist_add_items(
|
||||
@ -1097,7 +1094,7 @@ class Spotify(object):
|
||||
plid = self._get_id("playlist", playlist_id)
|
||||
ftracks = [self._get_uri("track", tid) for tid in items]
|
||||
return self._post(
|
||||
"playlists/%s/tracks" % (plid),
|
||||
f"playlists/{plid}/tracks",
|
||||
payload=ftracks,
|
||||
position=position,
|
||||
)
|
||||
@ -1113,7 +1110,7 @@ class Spotify(object):
|
||||
ftracks = [self._get_uri("track", tid) for tid in items]
|
||||
payload = {"uris": ftracks}
|
||||
return self._put(
|
||||
"playlists/%s/tracks" % (plid), payload=payload
|
||||
f"playlists/{plid}/tracks", payload=payload
|
||||
)
|
||||
|
||||
def playlist_reorder_items(
|
||||
@ -1144,7 +1141,7 @@ class Spotify(object):
|
||||
if snapshot_id:
|
||||
payload["snapshot_id"] = snapshot_id
|
||||
return self._put(
|
||||
"playlists/%s/tracks" % (plid), payload=payload
|
||||
f"playlists/{plid}/tracks", payload=payload
|
||||
)
|
||||
|
||||
def playlist_remove_all_occurrences_of_items(
|
||||
@ -1165,7 +1162,7 @@ class Spotify(object):
|
||||
if snapshot_id:
|
||||
payload["snapshot_id"] = snapshot_id
|
||||
return self._delete(
|
||||
"playlists/%s/tracks" % (plid), payload=payload
|
||||
f"playlists/{plid}/tracks", payload=payload
|
||||
)
|
||||
|
||||
def playlist_remove_specific_occurrences_of_items(
|
||||
@ -1196,7 +1193,7 @@ class Spotify(object):
|
||||
if snapshot_id:
|
||||
payload["snapshot_id"] = snapshot_id
|
||||
return self._delete(
|
||||
"playlists/%s/tracks" % (plid), payload=payload
|
||||
f"playlists/{plid}/tracks", payload=payload
|
||||
)
|
||||
|
||||
def current_user_follow_playlist(self, playlist_id):
|
||||
@ -1208,7 +1205,7 @@ class Spotify(object):
|
||||
|
||||
"""
|
||||
return self._put(
|
||||
"playlists/{}/followers".format(playlist_id)
|
||||
f"playlists/{playlist_id}/followers"
|
||||
)
|
||||
|
||||
def playlist_is_following(
|
||||
@ -1874,7 +1871,7 @@ class Spotify(object):
|
||||
return
|
||||
return self._put(
|
||||
self._append_device_id(
|
||||
"me/player/seek?position_ms=%s" % position_ms, device_id
|
||||
f"me/player/seek?position_ms={position_ms}", device_id
|
||||
)
|
||||
)
|
||||
|
||||
@ -1890,7 +1887,7 @@ class Spotify(object):
|
||||
return
|
||||
self._put(
|
||||
self._append_device_id(
|
||||
"me/player/repeat?state=%s" % state, device_id
|
||||
f"me/player/repeat?state={state}", device_id
|
||||
)
|
||||
)
|
||||
|
||||
@ -1909,7 +1906,7 @@ class Spotify(object):
|
||||
return
|
||||
self._put(
|
||||
self._append_device_id(
|
||||
"me/player/volume?volume_percent=%s" % volume_percent,
|
||||
f"me/player/volume?volume_percent={volume_percent}",
|
||||
device_id,
|
||||
)
|
||||
)
|
||||
@ -1927,7 +1924,7 @@ class Spotify(object):
|
||||
state = str(state).lower()
|
||||
self._put(
|
||||
self._append_device_id(
|
||||
"me/player/shuffle?state=%s" % state, device_id
|
||||
f"me/player/shuffle?state={state}", device_id
|
||||
)
|
||||
)
|
||||
|
||||
@ -1952,10 +1949,10 @@ class Spotify(object):
|
||||
|
||||
uri = self._get_uri("track", uri)
|
||||
|
||||
endpoint = "me/player/queue?uri=%s" % uri
|
||||
endpoint = f"me/player/queue?uri={uri}"
|
||||
|
||||
if device_id is not None:
|
||||
endpoint += "&device_id=%s" % device_id
|
||||
endpoint += f"&device_id={device_id}"
|
||||
|
||||
return self._post(endpoint)
|
||||
|
||||
@ -1974,9 +1971,9 @@ class Spotify(object):
|
||||
"""
|
||||
if device_id:
|
||||
if "?" in path:
|
||||
path += "&device_id=%s" % device_id
|
||||
path += f"&device_id={device_id}"
|
||||
else:
|
||||
path += "?device_id=%s" % device_id
|
||||
path += f"?device_id={device_id}"
|
||||
return path
|
||||
|
||||
def _get_id(self, type, id):
|
||||
|
||||
@ -12,5 +12,5 @@ class SpotifyException(Exception):
|
||||
self.headers = headers
|
||||
|
||||
def __str__(self):
|
||||
return 'http status: {0}, code:{1} - {2}, reason: {3}'.format(
|
||||
return 'http status: {}, code:{} - {}, reason: {}'.format(
|
||||
self.http_status, self.code, self.msg, self.reason)
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__all__ = [
|
||||
"SpotifyClientCredentials",
|
||||
"SpotifyOAuth",
|
||||
@ -17,11 +15,9 @@ import warnings
|
||||
import webbrowser
|
||||
|
||||
import requests
|
||||
# Workaround to support both python 2 & 3
|
||||
import six
|
||||
import six.moves.urllib.parse as urllibparse
|
||||
from six.moves.BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
||||
from six.moves.urllib_parse import parse_qsl, urlparse
|
||||
import urllib.parse as urllibparse
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from urllib.parse import parse_qsl, urlparse
|
||||
|
||||
from spotipy.cache_handler import CacheFileHandler, CacheHandler
|
||||
from spotipy.util import CLIENT_CREDS_ENV_VARS, get_host_port, normalize_scope
|
||||
@ -36,7 +32,7 @@ class SpotifyOauthError(Exception):
|
||||
self.error = error
|
||||
self.error_description = error_description
|
||||
self.__dict__.update(kwargs)
|
||||
super(SpotifyOauthError, self).__init__(message, *args, **kwargs)
|
||||
super().__init__(message, *args, **kwargs)
|
||||
|
||||
|
||||
class SpotifyStateError(SpotifyOauthError):
|
||||
@ -54,24 +50,21 @@ class SpotifyStateError(SpotifyOauthError):
|
||||
|
||||
def _make_authorization_headers(client_id, client_secret):
|
||||
auth_header = base64.b64encode(
|
||||
six.text_type(client_id + ":" + client_secret).encode("ascii")
|
||||
str(client_id + ":" + client_secret).encode("ascii")
|
||||
)
|
||||
return {"Authorization": "Basic %s" % auth_header.decode("ascii")}
|
||||
return {"Authorization": f"Basic {auth_header.decode('ascii')}"}
|
||||
|
||||
|
||||
def _ensure_value(value, env_key):
|
||||
env_val = CLIENT_CREDS_ENV_VARS[env_key]
|
||||
_val = value or os.getenv(env_val)
|
||||
if _val is None:
|
||||
msg = "No %s. Pass it or set a %s environment variable." % (
|
||||
env_key,
|
||||
env_val,
|
||||
)
|
||||
msg = f"No {env_key}. Pass it or set a {env_val} environment variable."
|
||||
raise SpotifyOauthError(msg)
|
||||
return _val
|
||||
|
||||
|
||||
class SpotifyAuthBase(object):
|
||||
class SpotifyAuthBase:
|
||||
def __init__(self, requests_session):
|
||||
if isinstance(requests_session, requests.Session):
|
||||
self._session = requests_session
|
||||
@ -144,9 +137,7 @@ class SpotifyAuthBase(object):
|
||||
error_description = None
|
||||
|
||||
raise SpotifyOauthError(
|
||||
'error: {0}, error_description: {1}'.format(
|
||||
error, error_description
|
||||
),
|
||||
f'error: {error}, error_description: {error_description}',
|
||||
error=error,
|
||||
error_description=error_description
|
||||
)
|
||||
@ -196,7 +187,7 @@ class SpotifyClientCredentials(SpotifyAuthBase):
|
||||
|
||||
"""
|
||||
|
||||
super(SpotifyClientCredentials, self).__init__(requests_session)
|
||||
super().__init__(requests_session)
|
||||
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
@ -327,7 +318,7 @@ class SpotifyOAuth(SpotifyAuthBase):
|
||||
(takes precedence over `cache_path` and `username`)
|
||||
"""
|
||||
|
||||
super(SpotifyOAuth, self).__init__(requests_session)
|
||||
super().__init__(requests_session)
|
||||
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
@ -402,7 +393,7 @@ class SpotifyOAuth(SpotifyAuthBase):
|
||||
|
||||
urlparams = urllibparse.urlencode(payload)
|
||||
|
||||
return "%s?%s" % (self.OAUTH_AUTHORIZE_URL, urlparams)
|
||||
return f"{self.OAUTH_AUTHORIZE_URL}?{urlparams}"
|
||||
|
||||
def parse_response_code(self, url):
|
||||
""" Parse the response code in the given response url
|
||||
@ -421,8 +412,7 @@ class SpotifyOAuth(SpotifyAuthBase):
|
||||
query_s = urlparse(url).query
|
||||
form = dict(parse_qsl(query_s))
|
||||
if "error" in form:
|
||||
raise SpotifyOauthError("Received error from auth server: "
|
||||
"{}".format(form["error"]),
|
||||
raise SpotifyOauthError(f"Received error from auth server: {form['error']}",
|
||||
error=form["error"])
|
||||
return tuple(form.get(param) for param in ["state", "code"])
|
||||
|
||||
@ -677,7 +667,7 @@ class SpotifyPKCE(SpotifyAuthBase):
|
||||
(takes precedence over `cache_path` and `username`)
|
||||
"""
|
||||
|
||||
super(SpotifyPKCE, self).__init__(requests_session)
|
||||
super().__init__(requests_session)
|
||||
self.client_id = client_id
|
||||
self.redirect_uri = redirect_uri
|
||||
self.state = state
|
||||
@ -727,15 +717,8 @@ class SpotifyPKCE(SpotifyAuthBase):
|
||||
length = random.randint(33, 96)
|
||||
|
||||
# The seeded length generates between a 44 and 128 base64 characters encoded string
|
||||
try:
|
||||
import secrets
|
||||
verifier = secrets.token_urlsafe(length)
|
||||
except ImportError: # For python 3.5 support
|
||||
import base64
|
||||
import os
|
||||
rand_bytes = os.urandom(length)
|
||||
verifier = base64.urlsafe_b64encode(rand_bytes).decode('utf-8').replace('=', '')
|
||||
return verifier
|
||||
import secrets
|
||||
return secrets.token_urlsafe(length)
|
||||
|
||||
def _get_code_challenge(self):
|
||||
""" Spotify PCKE code challenge - See step 1 of the reference guide below
|
||||
@ -766,7 +749,7 @@ class SpotifyPKCE(SpotifyAuthBase):
|
||||
if state is not None:
|
||||
payload["state"] = state
|
||||
urlparams = urllibparse.urlencode(payload)
|
||||
return "%s?%s" % (self.OAUTH_AUTHORIZE_URL, urlparams)
|
||||
return f"{self.OAUTH_AUTHORIZE_URL}?{urlparams}"
|
||||
|
||||
def _open_auth_url(self, state=None):
|
||||
auth_url = self.get_authorize_url(state)
|
||||
@ -817,7 +800,7 @@ class SpotifyPKCE(SpotifyAuthBase):
|
||||
if server.auth_code is not None:
|
||||
return server.auth_code
|
||||
elif server.error is not None:
|
||||
raise SpotifyOauthError("Received error from OAuth server: {}".format(server.error))
|
||||
raise SpotifyOauthError(f"Received error from OAuth server: {server.error}")
|
||||
else:
|
||||
raise SpotifyOauthError("Server listening on localhost has not been accessed")
|
||||
|
||||
@ -1160,7 +1143,7 @@ class SpotifyImplicitGrant(SpotifyAuthBase):
|
||||
|
||||
urlparams = urllibparse.urlencode(payload)
|
||||
|
||||
return "%s?%s" % (self.OAUTH_AUTHORIZE_URL, urlparams)
|
||||
return f"{self.OAUTH_AUTHORIZE_URL}?{urlparams}"
|
||||
|
||||
def parse_response_token(self, url, state=None):
|
||||
""" Parse the response code in the given response url """
|
||||
@ -1180,8 +1163,7 @@ class SpotifyImplicitGrant(SpotifyAuthBase):
|
||||
form = dict(i.split('=') for i
|
||||
in (fragment_s or query_s or url).split('&'))
|
||||
if "error" in form:
|
||||
raise SpotifyOauthError("Received error from auth server: "
|
||||
"{}".format(form["error"]),
|
||||
raise SpotifyOauthError(f"Received error from auth server: {form['error']}",
|
||||
state=form["state"])
|
||||
if "expires_in" in form:
|
||||
form["expires_in"] = int(form["expires_in"])
|
||||
@ -1273,7 +1255,7 @@ class RequestHandler(BaseHTTPRequestHandler):
|
||||
if self.server.auth_code:
|
||||
status = "successful"
|
||||
elif self.server.error:
|
||||
status = "failed ({})".format(self.server.error)
|
||||
status = f"failed ({self.server.error})"
|
||||
else:
|
||||
self._write("<html><body><h1>Invalid request</h1></body></html>")
|
||||
return
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Shows a user's playlists (need to be authenticated via oauth) """
|
||||
|
||||
__all__ = ["CLIENT_CREDS_ENV_VARS", "prompt_for_user_token"]
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from spotipy import (
|
||||
Spotify,
|
||||
SpotifyClientCredentials,
|
||||
|
||||
@ -1,20 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import io
|
||||
import json
|
||||
import unittest
|
||||
|
||||
import six.moves.urllib.parse as urllibparse
|
||||
import unittest.mock as mock
|
||||
import urllib.parse as urllibparse
|
||||
|
||||
from spotipy import SpotifyOAuth, SpotifyImplicitGrant, SpotifyPKCE
|
||||
from spotipy.cache_handler import MemoryCacheHandler
|
||||
from spotipy.oauth2 import SpotifyClientCredentials, SpotifyOauthError
|
||||
from spotipy.oauth2 import SpotifyStateError
|
||||
|
||||
try:
|
||||
import unittest.mock as mock
|
||||
except ImportError:
|
||||
import mock
|
||||
|
||||
patch = mock.patch
|
||||
DEFAULT = mock.DEFAULT
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user