diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index c5450c2..f6b6eac 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -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 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2c1fe1f..fe9b7fa 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -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 diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index d7871ef..32f92a6 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -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' \ No newline at end of file diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 7c83ab3..5f2ffc8 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index bf144e0..8362463 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/docs/conf.py b/docs/conf.py index 3da5998..5f74164 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # spotipy documentation build configuration file, created by # sphinx-quickstart on Thu Aug 21 11:04:39 2014. diff --git a/docs/index.rst b/docs/index.rst index 46252f9..ef6fd2d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -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 diff --git a/examples/audio_analysis_for_track.py b/examples/audio_analysis_for_track.py index 1f728a5..1bef5e9 100644 --- a/examples/audio_analysis_for_track.py +++ b/examples/audio_analysis_for_track.py @@ -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") diff --git a/examples/audio_features.py b/examples/audio_features.py index 30caddb..4657a97 100644 --- a/examples/audio_features.py +++ b/examples/audio_features.py @@ -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") diff --git a/examples/audio_features_for_track.py b/examples/audio_features_for_track.py index 9e156d3..e345ca6 100644 --- a/examples/audio_features_for_track.py +++ b/examples/audio_features_for_track.py @@ -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") diff --git a/examples/contains_a_saved_track.py b/examples/contains_a_saved_track.py index 41da4fd..fb6175d 100644 --- a/examples/contains_a_saved_track.py +++ b/examples/contains_a_saved_track.py @@ -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)) diff --git a/examples/delete_a_saved_track.py b/examples/delete_a_saved_track.py index 3952549..2f46153 100644 --- a/examples/delete_a_saved_track.py +++ b/examples/delete_a_saved_track.py @@ -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)) diff --git a/examples/remove_specific_tracks_from_playlist.py b/examples/remove_specific_tracks_from_playlist.py index 963eaef..340f379 100644 --- a/examples/remove_specific_tracks_from_playlist.py +++ b/examples/remove_specific_tracks_from_playlist.py @@ -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' diff --git a/examples/remove_tracks_from_playlist.py b/examples/remove_tracks_from_playlist.py index 8a51c56..4e011eb 100644 --- a/examples/remove_tracks_from_playlist.py +++ b/examples/remove_tracks_from_playlist.py @@ -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' diff --git a/examples/replace_tracks_in_playlist.py b/examples/replace_tracks_in_playlist.py index 6d1c46f..6c76b05 100644 --- a/examples/replace_tracks_in_playlist.py +++ b/examples/replace_tracks_in_playlist.py @@ -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' diff --git a/examples/show_album.py b/examples/show_album.py index 248e305..8f5e617 100644 --- a/examples/show_album.py +++ b/examples/show_album.py @@ -1,4 +1,3 @@ - # shows album info for a URN or URL from spotipy.oauth2 import SpotifyClientCredentials diff --git a/examples/show_related.py b/examples/show_related.py index 6fed03f..8791404 100644 --- a/examples/show_related.py +++ b/examples/show_related.py @@ -1,4 +1,3 @@ - # shows related artists for the given seed artist from spotipy.oauth2 import SpotifyClientCredentials diff --git a/examples/simple_artist_albums.py b/examples/simple_artist_albums.py index c4cc562..3f8323d 100644 --- a/examples/simple_artist_albums.py +++ b/examples/simple_artist_albums.py @@ -13,4 +13,4 @@ while results['next']: albums.extend(results['items']) for album in albums: - print((album['name'])) + print(album['name']) diff --git a/examples/simple_artist_top_tracks.py b/examples/simple_artist_top_tracks.py index 1a207b2..caf5fed 100644 --- a/examples/simple_artist_top_tracks.py +++ b/examples/simple_artist_top_tracks.py @@ -1,4 +1,3 @@ - from spotipy.oauth2 import SpotifyClientCredentials import spotipy diff --git a/examples/title_chain.py b/examples/title_chain.py index f3bc321..6cf3b8e 100644 --- a/examples/title_chain.py +++ b/examples/title_chain.py @@ -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() diff --git a/setup.py b/setup.py index dd1ab17..069511a 100644 --- a/setup.py +++ b/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, diff --git a/spotipy/cache_handler.py b/spotipy/cache_handler.py index 9a6d703..0ab98e9 100644 --- a/spotipy/cache_handler.py +++ b/spotipy/cache_handler.py @@ -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) diff --git a/spotipy/client.py b/spotipy/client.py index 8a4d72f..00850e5 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -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): diff --git a/spotipy/exceptions.py b/spotipy/exceptions.py index df503f1..28b9141 100644 --- a/spotipy/exceptions.py +++ b/spotipy/exceptions.py @@ -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) diff --git a/spotipy/oauth2.py b/spotipy/oauth2.py index 125c87c..126f861 100644 --- a/spotipy/oauth2.py +++ b/spotipy/oauth2.py @@ -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("

Invalid request

") return diff --git a/spotipy/util.py b/spotipy/util.py index b949a61..7e58673 100644 --- a/spotipy/util.py +++ b/spotipy/util.py @@ -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"] diff --git a/tests/integration/non_user_endpoints/test.py b/tests/integration/non_user_endpoints/test.py index ca2faac..583cc3d 100644 --- a/tests/integration/non_user_endpoints/test.py +++ b/tests/integration/non_user_endpoints/test.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from spotipy import ( Spotify, SpotifyClientCredentials, diff --git a/tests/unit/test_oauth.py b/tests/unit/test_oauth.py index fa58a16..10e1062 100644 --- a/tests/unit/test_oauth.py +++ b/tests/unit/test_oauth.py @@ -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 diff --git a/tox.ini b/tox.ini index b0f5bff..bbf780f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,8 @@ [tox] -envlist = py27,py34 +envlist = py3{8,9,10,11,12} [testenv] deps= requests - six - py27: mock commands=python -m unittest discover -v tests [flake8] max-line-length = 99