From 2bc8d35fc6641a8c4c514c8feec5fb9fce7a9bde Mon Sep 17 00:00:00 2001 From: Niko Date: Tue, 14 Jan 2025 07:56:47 +0100 Subject: [PATCH 1/2] Formatted strings (#1178) * use newer string formatters (https://pyformat.info) * fix typo * went through the files again, additions from #1165 are also included * replace .format() with f strings --- CHANGELOG.md | 1 + docs/index.rst | 2 +- examples/artist_albums.py | 9 ++-- examples/artist_discography.py | 17 +++--- examples/artist_recommendations.py | 8 +-- examples/follow_playlist.py | 4 +- examples/my_playlists.py | 2 +- examples/show_my_saved_tracks.py | 2 +- examples/track_recommendations.py | 7 +-- examples/user_playlists_contents.py | 4 +- examples/user_public_playlists.py | 8 +-- spotipy/cache_handler.py | 19 ++++--- spotipy/client.py | 20 +++---- spotipy/exceptions.py | 5 +- spotipy/oauth2.py | 66 ++++++++++-------------- tests/integration/user_endpoints/test.py | 4 +- 16 files changed, 71 insertions(+), 107 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e639413..24efb28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Add your changes below. ### Added - Added examples for audiobooks, shows and episodes methods to examples directory +- Use newer string formatters (https://pyformat.info) ### Fixed - Fixed scripts in examples directory that didn't run correctly diff --git a/docs/index.rst b/docs/index.rst index c7ef2bc..990205c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -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("%4d %s %s" % (i + 1 + playlists['offset'], playlist['uri'], playlist['name'])) + print(f"{i + 1 + playlists['offset']:4d} {playlist['uri']} {playlist['name']}") if playlists['next']: playlists = sp.next(playlists) else: diff --git a/examples/artist_albums.py b/examples/artist_albums.py index c5cc7a6..ce89b6e 100644 --- a/examples/artist_albums.py +++ b/examples/artist_albums.py @@ -20,10 +20,7 @@ def get_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 + return items[0] if items else None def show_artist_albums(artist): @@ -38,7 +35,7 @@ def show_artist_albums(artist): for album in albums: name = album['name'] if name not in seen: - logger.info('ALBUM: %s', name) + logger.info(f'ALBUM: {name}') seen.add(name) @@ -48,7 +45,7 @@ def main(): if artist: show_artist_albums(artist) else: - logger.error("Can't find artist: %s", artist) + logger.error(f"Can't find artist: {artist}") if __name__ == '__main__': diff --git a/examples/artist_discography.py b/examples/artist_discography.py index 84f9c26..aeb3003 100644 --- a/examples/artist_discography.py +++ b/examples/artist_discography.py @@ -20,10 +20,7 @@ def get_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 + return items[0] if items else None def show_album_tracks(album): @@ -34,7 +31,7 @@ def show_album_tracks(album): results = sp.next(results) tracks.extend(results['items']) for i, track in enumerate(tracks): - logger.info('%s. %s', i + 1, track['name']) + logger.info(f'{i + 1}. {track["name"]}') def show_artist_albums(artist): @@ -44,21 +41,21 @@ def show_artist_albums(artist): while results['next']: results = sp.next(results) albums.extend(results['items']) - logger.info('Total albums: %s', len(albums)) + logger.info(f'Total albums: {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) + logger.info(f'ALBUM: {name}') unique.add(name) show_album_tracks(album) def show_artist(artist): - logger.info('====%s====', artist['name']) - logger.info('Popularity: %s', artist['popularity']) + logger.info(f'===={artist["name"]}====') + logger.info(f'Popularity: {artist["popularity"]}') if len(artist['genres']) > 0: - logger.info('Genres: %s', ','.join(artist['genres'])) + logger.info(f"Genres: {', '.join(artist['genres'])}") def main(): diff --git a/examples/artist_recommendations.py b/examples/artist_recommendations.py index 40a95a2..d1af3ca 100644 --- a/examples/artist_recommendations.py +++ b/examples/artist_recommendations.py @@ -22,17 +22,13 @@ def get_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 + return items[0] if items else 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']) + logger.info(f'Recommendation: {track["name"]} - {track["artists"][0]["name"]}') def main(): diff --git a/examples/follow_playlist.py b/examples/follow_playlist.py index 20f4c32..59e1f5e 100644 --- a/examples/follow_playlist.py +++ b/examples/follow_playlist.py @@ -16,7 +16,9 @@ def get_args(): def main(): args = get_args() - sp.current_user_follow_playlist(args.playlist) + # Uses Lofi Girl playlist + playlist = args.playlist or '0vvXsWCC9xrXsKd4FyS8kM' + spotipy.Spotify(auth_manager=SpotifyOAuth()).current_user_follow_playlist(playlist) if __name__ == '__main__': main() \ No newline at end of file diff --git a/examples/my_playlists.py b/examples/my_playlists.py index 8c8e9be..7a5f46c 100644 --- a/examples/my_playlists.py +++ b/examples/my_playlists.py @@ -8,4 +8,4 @@ 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'])) + print(f"{i} {item['name']}") diff --git a/examples/show_my_saved_tracks.py b/examples/show_my_saved_tracks.py index ebff4fe..d606f06 100644 --- a/examples/show_my_saved_tracks.py +++ b/examples/show_my_saved_tracks.py @@ -9,7 +9,7 @@ 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'])) + print(f"{track['artists'][0]['name']:>32.32} {track['name']}") sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope)) diff --git a/examples/track_recommendations.py b/examples/track_recommendations.py index 38cf4be..6172e9b 100644 --- a/examples/track_recommendations.py +++ b/examples/track_recommendations.py @@ -38,8 +38,5 @@ recommendations = sp.recommendations( # 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']])) - ) + print(f"{i+1}. {track['name']} by " + f"{', '.join([artist['name'] for artist in track['artists']])}") diff --git a/examples/user_playlists_contents.py b/examples/user_playlists_contents.py index 9379a0b..e5a8ee1 100644 --- a/examples/user_playlists_contents.py +++ b/examples/user_playlists_contents.py @@ -7,9 +7,7 @@ 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'])) + print(f" {i} {track['artists'][0]['name']:>32.32} {track['name']}") if __name__ == '__main__': diff --git a/examples/user_public_playlists.py b/examples/user_public_playlists.py index 2d7f64f..0231f36 100644 --- a/examples/user_public_playlists.py +++ b/examples/user_public_playlists.py @@ -18,13 +18,7 @@ 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'])) + print(f"{i + 1 + playlists['offset']:4d} {playlist['uri']} {playlist['name']}") if playlists['next']: playlists = sp.next(playlists) else: diff --git a/spotipy/cache_handler.py b/spotipy/cache_handler.py index 7ae94a2..ffe539f 100644 --- a/spotipy/cache_handler.py +++ b/spotipy/cache_handler.py @@ -83,9 +83,9 @@ class CacheFileHandler(CacheHandler): except OSError as error: if error.errno == errno.ENOENT: - logger.debug("cache does not exist at: %s", self.cache_path) + logger.debug(f"cache does not exist at: {self.cache_path}") else: - logger.warning("Couldn't read cache at: %s", self.cache_path) + logger.warning(f"Couldn't read cache at: {self.cache_path}") return token_info @@ -95,8 +95,7 @@ class CacheFileHandler(CacheHandler): f.write(json.dumps(token_info, cls=self.encoder_cls)) f.close() except OSError: - logger.warning('Couldn\'t write token to cache at: %s', - self.cache_path) + logger.warning(f"Couldn't write token to cache at: {self.cache_path}") class MemoryCacheHandler(CacheHandler): @@ -149,7 +148,7 @@ class DjangoSessionCacheHandler(CacheHandler): try: self.request.session['token_info'] = token_info except Exception as e: - logger.warning("Error saving token to cache: " + str(e)) + logger.warning(f"Error saving token to cache: {e}") class FlaskSessionCacheHandler(CacheHandler): @@ -174,7 +173,7 @@ class FlaskSessionCacheHandler(CacheHandler): try: self.session["token_info"] = token_info except Exception as e: - logger.warning("Error saving token to cache: " + str(e)) + logger.warning(f"Error saving token to cache: {e}") class RedisCacheHandler(CacheHandler): @@ -200,7 +199,7 @@ class RedisCacheHandler(CacheHandler): if token_info: return json.loads(token_info) except RedisError as e: - logger.warning('Error getting token from cache: ' + str(e)) + logger.warning(f"Error getting token from cache: {e}") return token_info @@ -208,7 +207,7 @@ class RedisCacheHandler(CacheHandler): try: self.redis.set(self.key, json.dumps(token_info)) except RedisError as e: - logger.warning('Error saving token to cache: ' + str(e)) + logger.warning(f"Error saving token to cache: {e}") class MemcacheCacheHandler(CacheHandler): @@ -232,11 +231,11 @@ class MemcacheCacheHandler(CacheHandler): if token_info: return json.loads(token_info.decode()) except MemcacheError as e: - logger.warning('Error getting token from cache' + str(e)) + logger.warning(f"Error getting token to cache: {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('Error saving token to cache' + str(e)) + logger.warning(f"Error saving token to cache: {e}") diff --git a/spotipy/client.py b/spotipy/client.py index 8978058..b14f9a5 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -263,8 +263,8 @@ class Spotify: if self.language is not None: headers["Accept-Language"] = self.language - logger.debug('Sending %s to %s with Params: %s Headers: %s and Body: %r ', - method, url, args.get("params"), headers, args.get('data')) + logger.debug(f"Sending {method} to {url} with Params: " + f"{args.get('params')} Headers: {headers} and Body: {args.get('data')!r}") try: response = self._session.request( @@ -289,10 +289,8 @@ class Spotify: msg = response.text or None reason = None - 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 - ) + logger.error(f"HTTP Error for {method} to {url} with Params: " + f"{args.get('params')} returned {response.status_code} due to {msg}") raise SpotifyException( response.status_code, @@ -317,7 +315,7 @@ class Spotify: except ValueError: results = None - logger.debug('RESULTS: %s', results) + logger.debug(f'RESULTS: {results}') return results def _get(self, url, args=None, payload=None, **kwargs): @@ -2066,11 +2064,9 @@ class Spotify: def _search_multiple_markets(self, q, limit, offset, type, markets, total): if total and limit > total: limit = total - warnings.warn( - "limit was auto-adjusted to equal {} as it must not be higher than total".format( - total), - UserWarning, - ) + warnings.warn(f"limit was auto-adjusted to equal {total} " + f"as it must not be higher than total", + UserWarning) results = defaultdict(dict) item_types = [item_type + "s" for item_type in type.split(",")] diff --git a/spotipy/exceptions.py b/spotipy/exceptions.py index ba2d5f1..cacce9f 100644 --- a/spotipy/exceptions.py +++ b/spotipy/exceptions.py @@ -16,8 +16,9 @@ class SpotifyException(SpotifyBaseException): self.headers = headers def __str__(self): - return 'http status: {}, code:{} - {}, reason: {}'.format( - self.http_status, self.code, self.msg, self.reason) + return (f"http status: {self.http_status}, " + f"code: {self.code} - {self.msg}, " + f"reason: {self.reason}") class SpotifyOauthError(SpotifyBaseException): diff --git a/spotipy/oauth2.py b/spotipy/oauth2.py index 1591e77..c17b4e8 100644 --- a/spotipy/oauth2.py +++ b/spotipy/oauth2.py @@ -217,10 +217,8 @@ class SpotifyClientCredentials(SpotifyAuthBase): self.client_id, self.client_secret ) - logger.debug( - "sending POST request to %s with Headers: %s and Body: %r", - self.OAUTH_TOKEN_URL, headers, payload - ) + logger.debug(f"Sending POST request to {self.OAUTH_TOKEN_URL} with Headers: " + f"{headers} and Body: {payload}") try: response = self._session.post( @@ -401,9 +399,9 @@ class SpotifyOAuth(SpotifyAuthBase): auth_url = self.get_authorize_url() try: webbrowser.open(auth_url) - logger.info("Opened %s in your browser", auth_url) + logger.info(f"Opened {auth_url} in your browser") except webbrowser.Error: - logger.error("Please navigate here: %s", auth_url) + logger.error(f"Please navigate here: {auth_url}") def _get_auth_response_interactive(self, open_browser=False): if open_browser: @@ -412,8 +410,8 @@ class SpotifyOAuth(SpotifyAuthBase): else: url = self.get_authorize_url() prompt = ( - "Go to the following URL: {}\n" - "Enter the URL you were redirected to: ".format(url) + f"Go to the following URL: {url}\n" + "Enter the URL you were redirected to: " ) response = self._get_user_input(prompt) state, code = SpotifyOAuth.parse_auth_response_url(response) @@ -457,12 +455,11 @@ class SpotifyOAuth(SpotifyAuthBase): if redirect_port: return self._get_auth_response_local_server(redirect_port) else: - logger.warning('Using `%s` as redirect URI without a port. ' - 'Specify a port (e.g. `%s:8080`) to allow ' + logger.warning(f'Using `{redirect_host}` as redirect URI without a port. ' + f'Specify a port (e.g. `{redirect_host}:8080`) to allow ' 'automatic retrieval of authentication code ' 'instead of having to copy and paste ' - 'the URL your browser is redirected to.', - redirect_host, redirect_host) + 'the URL your browser is redirected to.') return self._get_auth_response_interactive(open_browser=open_browser) @@ -510,10 +507,8 @@ class SpotifyOAuth(SpotifyAuthBase): headers = self._make_authorization_headers() - logger.debug( - "sending POST request to %s with Headers: %s and Body: %r", - self.OAUTH_TOKEN_URL, headers, payload - ) + logger.debug(f"Sending POST request to {self.OAUTH_TOKEN_URL} with Headers: " + f"{headers} and Body: {payload}") try: response = self._session.post( @@ -540,10 +535,8 @@ class SpotifyOAuth(SpotifyAuthBase): headers = self._make_authorization_headers() - logger.debug( - "sending POST request to %s with Headers: %s and Body: %r", - self.OAUTH_TOKEN_URL, headers, payload - ) + logger.debug(f"Sending POST request to {self.OAUTH_TOKEN_URL} with Headers: " + f"{headers} and Body: {payload}") try: response = self._session.post( @@ -733,9 +726,9 @@ class SpotifyPKCE(SpotifyAuthBase): auth_url = self.get_authorize_url(state) try: webbrowser.open(auth_url) - logger.info("Opened %s in your browser", auth_url) + logger.info(f"Opened {auth_url} in your browser") except webbrowser.Error: - logger.error("Please navigate here: %s", auth_url) + logger.error(f"Please navigate here: {auth_url}") def _get_auth_response(self, open_browser=None): logger.info('User authentication requires interaction with your ' @@ -759,12 +752,11 @@ class SpotifyPKCE(SpotifyAuthBase): if redirect_port: return self._get_auth_response_local_server(redirect_port) else: - logger.warning('Using `%s` as redirect URI without a port. ' - 'Specify a port (e.g. `%s:8080`) to allow ' + logger.warning(f'Using `{redirect_host}` as redirect URI without a port. ' + f'Specify a port (e.g. `{redirect_host}:8080`) to allow ' 'automatic retrieval of authentication code ' 'instead of having to copy and paste ' - 'the URL your browser is redirected to.', - redirect_host, redirect_host) + 'the URL your browser is redirected to.') return self._get_auth_response_interactive(open_browser=open_browser) def _get_auth_response_local_server(self, redirect_port): @@ -788,10 +780,8 @@ class SpotifyPKCE(SpotifyAuthBase): prompt = "Enter the URL you were redirected to: " else: url = self.get_authorize_url() - prompt = ( - "Go to the following URL: {}\n" - "Enter the URL you were redirected to: ".format(url) - ) + prompt = (f"Go to the following URL: {url}\n" + f"Enter the URL you were redirected to: ") response = self._get_user_input(prompt) state, code = self.parse_auth_response_url(response) if self.state is not None and self.state != state: @@ -867,10 +857,8 @@ class SpotifyPKCE(SpotifyAuthBase): headers = {"Content-Type": "application/x-www-form-urlencoded"} - logger.debug( - "sending POST request to %s with Headers: %s and Body: %r", - self.OAUTH_TOKEN_URL, headers, payload - ) + logger.debug(f"Sending POST request to {self.OAUTH_TOKEN_URL} with Headers: " + f"{headers} and Body: {payload}") try: response = self._session.post( @@ -898,10 +886,8 @@ class SpotifyPKCE(SpotifyAuthBase): headers = {"Content-Type": "application/x-www-form-urlencoded"} - logger.debug( - "sending POST request to %s with Headers: %s and Body: %r", - self.OAUTH_TOKEN_URL, headers, payload - ) + logger.debug(f"Sending POST request to {self.OAUTH_TOKEN_URL} with Headers: " + f"{headers} and Body: {payload}") try: response = self._session.post( @@ -1152,9 +1138,9 @@ class SpotifyImplicitGrant(SpotifyAuthBase): auth_url = self.get_authorize_url(state) try: webbrowser.open(auth_url) - logger.info("Opened %s in your browser", auth_url) + logger.info(f"Opened {auth_url} in your browser") except webbrowser.Error: - logger.error("Please navigate here: %s", auth_url) + logger.error(f"Please navigate here: {auth_url}") def get_auth_response(self, state=None): """ Gets a new auth **token** with user interaction """ diff --git a/tests/integration/user_endpoints/test.py b/tests/integration/user_endpoints/test.py index 9e5411f..9c7c1d4 100644 --- a/tests/integration/user_endpoints/test.py +++ b/tests/integration/user_endpoints/test.py @@ -555,7 +555,7 @@ class SpotifyQueueApiTests(unittest.TestCase): self.spotify.add_to_queue(test_uri) # Check if the correct endpoint is called - endpoint = "me/player/queue?uri=%s" % test_uri + endpoint = f"me/player/queue?uri={test_uri}" mock_post.assert_called_with(endpoint) def test_add_to_queue_with_device_id(self, mock_post): @@ -566,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 = "me/player/queue?uri=%s&device_id=%s" % (test_uri, device_id) + endpoint = f"me/player/queue?uri={test_uri}&device_id={device_id}" mock_post.assert_called_with(endpoint) From 2243e135dff47d78c53bd59d7673b254bb5c0db1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 07:57:00 +0000 Subject: [PATCH 2/2] Update sphinx requirements (#1166) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update sphinx-rtd-theme requirement from ~=2.0.0 to ~=3.0.2 Updates the requirements on [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) to permit the latest version. - [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst) - [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/2.0.0...3.0.2) --- updated-dependencies: - dependency-name: sphinx-rtd-theme dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Sphinx 8.1.3 --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Stéphane Bruckert --- docs/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index ed65226..fa12ee7 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,3 @@ -Sphinx~=7.4.7 -sphinx-rtd-theme~=2.0.0 +Sphinx~=8.1.3 +sphinx-rtd-theme~=3.0.2 redis>=3.5.3