From ffa9995b0af3765cfc2830f18c883e47bea011b0 Mon Sep 17 00:00:00 2001 From: gwynnebaer Date: Fri, 13 Oct 2017 10:24:53 -0700 Subject: [PATCH 01/71] python 3 syntax for example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 09d3a1f..77a10a2 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ sp = spotipy.Spotify() results = sp.search(q='weezer', limit=20) for i, t in enumerate(results['tracks']['items']): - print ' ', i, t['name'] + print(' ', i, t['name']) ``` 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). From 0eeeeb4a50f8c03c2bdbe3298c540131ca7b118d Mon Sep 17 00:00:00 2001 From: "Sebastien (Work)" Date: Mon, 23 Oct 2017 22:50:05 +0100 Subject: [PATCH 02/71] Remove unused imports All imported modules that were note used in examples have been removed. --- examples/add_tracks_to_playlist.py | 1 - examples/create_playlist.py | 2 -- examples/errors.py | 3 --- examples/my_playlists.py | 2 -- examples/show_artist_top_tracks.py | 1 - examples/show_featured_playlists.py | 1 - examples/show_new_releases.py | 1 - examples/show_related.py | 1 - examples/user_playlists.py | 3 --- 9 files changed, 15 deletions(-) diff --git a/examples/add_tracks_to_playlist.py b/examples/add_tracks_to_playlist.py index a5297da..d0df161 100644 --- a/examples/add_tracks_to_playlist.py +++ b/examples/add_tracks_to_playlist.py @@ -1,7 +1,6 @@ # Adds tracks to a playlist -import pprint import sys import spotipy diff --git a/examples/create_playlist.py b/examples/create_playlist.py index efa334d..6cad834 100644 --- a/examples/create_playlist.py +++ b/examples/create_playlist.py @@ -2,8 +2,6 @@ import pprint import sys -import os -import subprocess import spotipy import spotipy.util as util diff --git a/examples/errors.py b/examples/errors.py index 570600f..36bcaf9 100644 --- a/examples/errors.py +++ b/examples/errors.py @@ -2,10 +2,7 @@ from __future__ import print_function # (at top of module) from spotipy.oauth2 import SpotifyClientCredentials -import json import spotipy -import time -import sys client_credentials_manager = SpotifyClientCredentials() diff --git a/examples/my_playlists.py b/examples/my_playlists.py index 58e02df..a48fb0f 100644 --- a/examples/my_playlists.py +++ b/examples/my_playlists.py @@ -1,11 +1,9 @@ # Shows the top artists for a user -import pprint import sys import spotipy import spotipy.util as util -import simplejson as json if len(sys.argv) > 1: username = sys.argv[1] diff --git a/examples/show_artist_top_tracks.py b/examples/show_artist_top_tracks.py index a80beb8..34b5a34 100644 --- a/examples/show_artist_top_tracks.py +++ b/examples/show_artist_top_tracks.py @@ -2,7 +2,6 @@ import spotipy import sys -import pprint if len(sys.argv) > 1: urn = sys.argv[1] diff --git a/examples/show_featured_playlists.py b/examples/show_featured_playlists.py index 2c7f8d9..769037c 100644 --- a/examples/show_featured_playlists.py +++ b/examples/show_featured_playlists.py @@ -2,7 +2,6 @@ import spotipy import sys -import pprint import spotipy.util as util if len(sys.argv) > 1: diff --git a/examples/show_new_releases.py b/examples/show_new_releases.py index 5205245..c728eee 100644 --- a/examples/show_new_releases.py +++ b/examples/show_new_releases.py @@ -2,7 +2,6 @@ import spotipy import sys -import pprint import spotipy.util as util if len(sys.argv) > 1: diff --git a/examples/show_related.py b/examples/show_related.py index b78e1dc..15b3c3d 100644 --- a/examples/show_related.py +++ b/examples/show_related.py @@ -3,7 +3,6 @@ import spotipy import sys -import pprint if len(sys.argv) > 1: artist_name = sys.argv[1] diff --git a/examples/user_playlists.py b/examples/user_playlists.py index 7bcc1f5..e94e810 100644 --- a/examples/user_playlists.py +++ b/examples/user_playlists.py @@ -1,9 +1,6 @@ # shows a user's playlists (need to be authenticated via oauth) -import pprint import sys -import os -import subprocess import spotipy From 0b0942423ce98a4662966483f9f14c15323a010d Mon Sep 17 00:00:00 2001 From: "Sebastien (Work)" Date: Mon, 23 Oct 2017 22:53:20 +0100 Subject: [PATCH 03/71] Remove spaces around keyword operator remove space around operator '=' for keywords. --- examples/artist_recommendations.py | 2 +- examples/title_chain.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/artist_recommendations.py b/examples/artist_recommendations.py index 6efbc1e..142cf49 100644 --- a/examples/artist_recommendations.py +++ b/examples/artist_recommendations.py @@ -19,7 +19,7 @@ def get_artist(name): def show_recommendations_for_artist(artist): albums = [] - results = sp.recommendations(seed_artists = [artist['id']]) + results = sp.recommendations(seed_artists=[artist['id']]) for track in results['tracks']: print track['name'], '-', track['artists'][0]['name'] diff --git a/examples/title_chain.py b/examples/title_chain.py index 67a55c0..ac74fe5 100644 --- a/examples/title_chain.py +++ b/examples/title_chain.py @@ -21,7 +21,7 @@ def find_songs_that_start_with_word(word): out = [] while offset < max_offset and len(out) < max_titles: - results = sp.search(q=word, type = 'track', limit=50, offset = offset) + results = sp.search(q=word, type='track', limit=50, offset=offset) if len(results['tracks']['items']) == 0: break From 6c83f09898393facc8f3276c74e3f972957a8c69 Mon Sep 17 00:00:00 2001 From: "Sebastien (Work)" Date: Mon, 23 Oct 2017 23:02:17 +0100 Subject: [PATCH 04/71] Format examples Add spaces after ':' and ','. Remove spaces before and after '(', '{', '[', ']', '}' and ')'. --- examples/artist_albums.py | 2 +- examples/audio_analysis_for_track.py | 2 +- examples/audio_features.py | 2 +- examples/audio_features_for_track.py | 2 +- examples/change_playlist_details.py | 2 +- examples/errors.py | 8 ++++---- examples/remove_specific_tracks_from_playlist.py | 4 ++-- examples/show_new_releases.py | 2 +- examples/user_starred_playlist.py | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/examples/artist_albums.py b/examples/artist_albums.py index 6b9f6ce..4213985 100644 --- a/examples/artist_albums.py +++ b/examples/artist_albums.py @@ -20,7 +20,7 @@ def show_artist_albums(artist): results = sp.next(results) albums.extend(results['items']) seen = set() # to avoid dups - albums.sort(key=lambda album:album['name'].lower()) + albums.sort(key=lambda album: album['name'].lower()) for album in albums: name = album['name'] if name not in seen: diff --git a/examples/audio_analysis_for_track.py b/examples/audio_analysis_for_track.py index 39e8347..1f728a5 100644 --- a/examples/audio_analysis_for_track.py +++ b/examples/audio_analysis_for_track.py @@ -20,4 +20,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("analysis retrieved in %.2f seconds" % (delta,)) diff --git a/examples/audio_features.py b/examples/audio_features.py index 079f4f9..989c852 100644 --- a/examples/audio_features.py +++ b/examples/audio_features.py @@ -33,4 +33,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("features retrieved in %.2f seconds" % (delta,)) diff --git a/examples/audio_features_for_track.py b/examples/audio_features_for_track.py index cdc980a..0565298 100644 --- a/examples/audio_features_for_track.py +++ b/examples/audio_features_for_track.py @@ -22,4 +22,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("features retrieved in %.2f seconds" % (delta,)) diff --git a/examples/change_playlist_details.py b/examples/change_playlist_details.py index 621f5d0..42044aa 100644 --- a/examples/change_playlist_details.py +++ b/examples/change_playlist_details.py @@ -24,7 +24,7 @@ if len(sys.argv) > 3: description = sys.argv[6] else: - print ("Usage: %s username playlist_id name [public collaborative " + print("Usage: %s username playlist_id name [public collaborative " "description]" % (sys.argv[0])) sys.exit() diff --git a/examples/errors.py b/examples/errors.py index 36bcaf9..546fcb4 100644 --- a/examples/errors.py +++ b/examples/errors.py @@ -9,12 +9,12 @@ client_credentials_manager = SpotifyClientCredentials() sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager) sp.trace=True try: - print ('bad call 0') + print('bad call 0') bad_artist_call = sp.artist('spotify:artist:12341234') except spotipy.client.SpotifyException: - print ('bad call 0 exception' ) + print('bad call 0 exception') -print ('bad call 1') +print('bad call 1') bad_artist_call = sp.artist('spotify:artist:12341234') -print ('bad artist', bad_artist_call) +print('bad artist', bad_artist_call) diff --git a/examples/remove_specific_tracks_from_playlist.py b/examples/remove_specific_tracks_from_playlist.py index a99dc94..5821f0e 100644 --- a/examples/remove_specific_tracks_from_playlist.py +++ b/examples/remove_specific_tracks_from_playlist.py @@ -11,10 +11,10 @@ if len(sys.argv) > 3: username = sys.argv[1] playlist_id = sys.argv[2] track_ids_and_positions = sys.argv[3:] - track_ids = [ ] + track_ids = [] for t_pos in sys.argv[3:]: tid, pos = t_pos.split(',') - track_ids.append( { "uri" : tid, "positions": [ int(pos)] } ) + track_ids.append({"uri": tid, "positions": [int(pos)]}) else: print("Usage: %s username playlist_id track_id,pos track_id,pos ..." % (sys.argv[0],)) sys.exit() diff --git a/examples/show_new_releases.py b/examples/show_new_releases.py index c728eee..d4bd5fa 100644 --- a/examples/show_new_releases.py +++ b/examples/show_new_releases.py @@ -21,7 +21,7 @@ if token: while response: albums = response['albums'] for i, item in enumerate(albums['items']): - print(albums['offset'] + i,item['name']) + print(albums['offset'] + i, item['name']) if albums['next']: response = sp.next(albums) diff --git a/examples/user_starred_playlist.py b/examples/user_starred_playlist.py index 914344a..645384c 100644 --- a/examples/user_starred_playlist.py +++ b/examples/user_starred_playlist.py @@ -23,7 +23,7 @@ if token: while tracks: for item in tracks['items']: track = item['track'] - print(which, track['name' ], ' --', track['artists'][0]['name']) + print(which, track['name'], ' --', track['artists'][0]['name']) which += 1 tracks = sp.next(tracks) From 5901db22fc65d6d2d6066da0ba24f12290ab7c93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B3nio=20Sim=C3=B5es?= Date: Mon, 20 Nov 2017 20:38:34 +0000 Subject: [PATCH 05/71] Fixes typo on recommendations usage comment On the parameters list, seed_tracks had the same description as seed_artists. Changed: "artist" -> "track" --- spotipy/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spotipy/client.py b/spotipy/client.py index 3e33a21..b49a8b7 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -801,7 +801,7 @@ class Spotify(object): Parameters: - seed_artists - a list of artist IDs, URIs or URLs - - seed_tracks - a list of artist IDs, URIs or URLs + - seed_tracks - a list of track IDs, URIs or URLs - seed_genres - a list of genre names. Available genres for recommendations can be found by calling recommendation_genre_seeds From 7d121cbff5918aa07beb0c76aa4df22a97697132 Mon Sep 17 00:00:00 2001 From: Nick Sonneveld Date: Fri, 30 Mar 2018 15:06:04 +1100 Subject: [PATCH 06/71] Close request object, not the whole connection. Allows keepalive --- spotipy/client.py | 55 ++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/spotipy/client.py b/spotipy/client.py index 3e33a21..06858b9 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -107,36 +107,37 @@ class Spotify(object): if self.trace_out: print(url) - r = self._session.request(method, url, headers=headers, proxies=self.proxies, **args) + + with self._session.request(method, url, headers=headers, proxies=self.proxies, **args) as r: + + if self.trace: # pragma: no cover + print() + print ('request headers', headers) + print ('response headers', r.headers) + print ('http status', r.status_code) + print(method, r.url) + if payload: + print("DATA", json.dumps(payload)) + + try: + r.raise_for_status() + except: + try: + msg = r.json()['error']['message'] + except: + msg = 'error' + raise SpotifyException(r.status_code, + -1, '%s:\n %s' % (r.url, msg), headers=r.headers) + + try: + results = r.json() + except: + results = None if self.trace: # pragma: no cover + print('RESP', results) print() - print ('headers', headers) - print ('http status', r.status_code) - print(method, r.url) - if payload: - print("DATA", json.dumps(payload)) - - try: - r.raise_for_status() - except: - if r.text and len(r.text) > 0 and r.text != 'null': - raise SpotifyException(r.status_code, - -1, '%s:\n %s' % (r.url, r.json()['error']['message']), - headers=r.headers) - else: - raise SpotifyException(r.status_code, - -1, '%s:\n %s' % (r.url, 'error'), headers=r.headers) - finally: - r.connection.close() - if r.text and len(r.text) > 0 and r.text != 'null': - results = r.json() - if self.trace: # pragma: no cover - print('RESP', results) - print() - return results - else: - return None + return results def _get(self, url, args=None, payload=None, **kwargs): if args: From 1ea4d475b5d7a822958599085500c05e5b6e6dee Mon Sep 17 00:00:00 2001 From: William Reardon Date: Sat, 23 Jun 2018 20:37:58 -0700 Subject: [PATCH 07/71] Update create_playlist.py Added scope to token request --- examples/create_playlist.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/create_playlist.py b/examples/create_playlist.py index efa334d..1736f98 100644 --- a/examples/create_playlist.py +++ b/examples/create_playlist.py @@ -17,7 +17,8 @@ else: print("Usage: %s username playlist-name playlist-description" % (sys.argv[0],)) sys.exit() -token = util.prompt_for_user_token(username) +scope = "playlist-modify-public" +token = util.prompt_for_user_token(username, scope) if token: sp = spotipy.Spotify(auth=token) From 5f71d5e484705401b744c785d5e51e41a61ec979 Mon Sep 17 00:00:00 2001 From: Halvor Strand Date: Fri, 5 Oct 2018 17:06:49 +0200 Subject: [PATCH 08/71] Fixed docstring error Fixed user_playlist_remove_all_occurrences_of_tracks docstring error, having "add to" instead of "remove from". --- spotipy/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spotipy/client.py b/spotipy/client.py index 3e33a21..06c9bec 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -507,7 +507,7 @@ class Spotify(object): Parameters: - user - the id of the user - playlist_id - the id of the playlist - - tracks - the list of track ids to add to the playlist + - tracks - the list of track ids to remove from the playlist - snapshot_id - optional id of the playlist snapshot """ From d588d22a2df12d0e7d2c1772d122918fcb5f65af Mon Sep 17 00:00:00 2001 From: Mike Rossetti Date: Sun, 14 Apr 2019 21:30:16 -0400 Subject: [PATCH 09/71] Update oauth2.py --- spotipy/oauth2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spotipy/oauth2.py b/spotipy/oauth2.py index cef7908..db6a8a6 100644 --- a/spotipy/oauth2.py +++ b/spotipy/oauth2.py @@ -31,7 +31,7 @@ class SpotifyClientCredentials(object): def __init__(self, client_id=None, client_secret=None, proxies=None): """ - You can either provid a client_id and client_secret to the + You can either provide a client_id and client_secret to the constructor or set SPOTIPY_CLIENT_ID and SPOTIPY_CLIENT_SECRET environment variables """ From 2252ea5cfa6b55903d1152c2fa159351b8f43415 Mon Sep 17 00:00:00 2001 From: Tim Jagenberg Date: Sat, 27 Jul 2019 22:26:28 +0200 Subject: [PATCH 10/71] requests_cache compatibility --- spotipy/client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spotipy/client.py b/spotipy/client.py index 3e33a21..27dfbbe 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -128,7 +128,8 @@ class Spotify(object): raise SpotifyException(r.status_code, -1, '%s:\n %s' % (r.url, 'error'), headers=r.headers) finally: - r.connection.close() + if hasattr(r, "connection"): + r.connection.close() if r.text and len(r.text) > 0 and r.text != 'null': results = r.json() if self.trace: # pragma: no cover From cc473caf06b28de4c60aca1718da76862cbf7739 Mon Sep 17 00:00:00 2001 From: Apurva Chitnis Date: Mon, 26 Aug 2019 23:35:20 +0100 Subject: [PATCH 11/71] improve redirect wording and fix minor bugs --- docs/index.rst | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 6283ed8..91fd31e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -99,16 +99,18 @@ generate an authorization token that indicates that the user has granted permission for your application to perform the given task. You will need to register your app to get the credentials necessary to make authorized calls. -Even if your script does not have an accessible URL you need to specify one -when registering your application where the spotify authentication API will -redirect to after successful login. The URL doesn't need to work or be -accessible, you can specify "http://localhost/", after successful login you -just need to copy the "http://localhost/?code=..." URL from your browser -and paste it to the console where your script is running. +Even if your script does not have an accessible URL you will need to specify one +when registering your application which the Spotify authentication server will +redirect to after successful login. The URL doesn't need to be publicly +accessible, so you can specify "http://localhost/", and after succesfully +authenticating your app, you can simply copy the +"http://localhost/?code=..." URL from your browser and paste it to the +console where your script is running. Register your app at `My Applications -`_. +`_ and register the +redirect URI mentioned in the above paragragh. *spotipy* supports two authorization flows: @@ -127,7 +129,6 @@ To support the **Authorization Code Flow** *Spotipy* provides a utility method ``util.prompt_for_user_token`` that will attempt to authorize the user. You can pass your app credentials directly into the method as arguments:: - util.prompt_for_user_token(username,scope,client_id='your-app-redirect-url',client_secret='your-app-redirect-url',redirect_uri='your-app-redirect-url') or if you are reluctant to immortalize your app credentials in your source code, you can set environment variables like so:: @@ -140,7 +141,7 @@ Call ``util.prompt_for_user_token`` method with the username and the desired scope (see `Using Scopes `_ for information about scopes) and credentials. This will coordinate the user authorization via -your web browser and ask for the SPOTIPY_REDIRECT_URI you were redirected to +your web browser and callback to the SPOTIPY_REDIRECT_URI you were redirected to with the authorization token appended. The credentials are cached locally and are used to automatically re-authorized expired tokens. From 1d7051c5b0b8ddf60b338ec49a250e154500ae1a Mon Sep 17 00:00:00 2001 From: Stephane Bruckert Date: Sat, 11 Jan 2020 17:26:11 +0000 Subject: [PATCH 12/71] Bump to 2.5.0 --- README.md | 1 + setup.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 09d3a1f..9f9ec71 100644 --- a/README.md +++ b/README.md @@ -84,3 +84,4 @@ If you have suggestions, bugs or other issues specific to this library, file the - v2.4.2 - January 2, 2017 -- support getting audio features for a single track - v2.4.3 - January 2, 2017 -- fixed proxy issue in standard auth flow - v2.4.4 - January 4, 2017 -- python 3 fix +- v2.5.0 - January 11, 2020 -- Added follow and player endpoints \ No newline at end of file diff --git a/setup.py b/setup.py index 7b81605..c673a92 100644 --- a/setup.py +++ b/setup.py @@ -2,8 +2,9 @@ from setuptools import setup setup( name='spotipy', - version='2.4.4', - description='simple client for the Spotify Web API', + version='2.5.0', + long_description="""### A light weight Python library for the Spotify Web API""", + long_description_content_type='text/markdown', author="@plamere", author_email="paul@echonest.com", url='http://spotipy.readthedocs.org/', From e1ba4a9bbb2a50804581325c023d64fe06a6c94f Mon Sep 17 00:00:00 2001 From: Sandeep Murthy Date: Sat, 17 Feb 2018 11:51:42 +0000 Subject: [PATCH 13/71] Re-order imports and specify UTF-8 encoding header in all base modules and package initialiser (ref. https://www.python.org/dev/peps/pep-0008/#imports, https://www.python.org/dev/peps/pep-0008/#source-file-encoding) + add `__all__` attribute --- spotipy/__init__.py | 3 ++- spotipy/client.py | 15 ++++++++++----- spotipy/oauth2.py | 16 +++++++++++++--- spotipy/util.py | 10 +++++++++- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/spotipy/__init__.py b/spotipy/__init__.py index 9be1dce..16b321c 100644 --- a/spotipy/__init__.py +++ b/spotipy/__init__.py @@ -1,2 +1,3 @@ VERSION='2.0.1' -from .client import Spotify, SpotifyException + +from .client import * diff --git a/spotipy/client.py b/spotipy/client.py index 3e33a21..b2ad6f1 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -1,16 +1,21 @@ -# coding: utf-8 +# -*- coding: utf-8 -*- +""" A simple and thin Python library for the Spotify Web API """ from __future__ import print_function -import sys -import requests + +__all__ = [ + 'Spotify', + 'SpotifyException' +] + import json +import sys import time +import requests import six -""" A simple and thin Python library for the Spotify Web API -""" class SpotifyException(Exception): diff --git a/spotipy/oauth2.py b/spotipy/oauth2.py index cef7908..60fbc1e 100644 --- a/spotipy/oauth2.py +++ b/spotipy/oauth2.py @@ -1,11 +1,21 @@ +# -*- coding: utf-8 -*- from __future__ import print_function + +__all__ = [ + 'is_token_expired', + 'SpotifyClientCredentials', + 'SpotifyOAuth', + 'SpotifyOauthError' +] + import base64 -import requests -import os import json -import time +import os import sys +import time + +import requests # Workaround to support both python 2 & 3 import six diff --git a/spotipy/util.py b/spotipy/util.py index 0dd39a9..336fd58 100644 --- a/spotipy/util.py +++ b/spotipy/util.py @@ -1,9 +1,17 @@ +# -*- coding: utf-8 -*- -# shows a user's playlists (need to be authenticated via oauth) +""" Shows a user's playlists (need to be authenticated via oauth) """ from __future__ import print_function + +__all__ = [ + 'prompt_for_user_token9' +] + import os + from . import oauth2 + import spotipy def prompt_for_user_token(username, scope=None, client_id = None, From ef49cd6e039f7b65942871226461bc7ccce13f26 Mon Sep 17 00:00:00 2001 From: Sandeep Murthy Date: Sat, 17 Feb 2018 11:55:01 +0000 Subject: [PATCH 14/71] Update spotify package initialiser --- spotipy/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spotipy/__init__.py b/spotipy/__init__.py index 16b321c..9a64643 100644 --- a/spotipy/__init__.py +++ b/spotipy/__init__.py @@ -1,3 +1,5 @@ VERSION='2.0.1' from .client import * +from .oauth2 import * +from util import * From 502a56f98713bcb8415e7f6e742160b012863790 Mon Sep 17 00:00:00 2001 From: Sandeep Murthy Date: Sat, 17 Feb 2018 12:00:29 +0000 Subject: [PATCH 15/71] Remove relative imports in `spotify` package initialiser --- spotipy/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spotipy/__init__.py b/spotipy/__init__.py index 9a64643..95c0814 100644 --- a/spotipy/__init__.py +++ b/spotipy/__init__.py @@ -1,5 +1,5 @@ VERSION='2.0.1' -from .client import * -from .oauth2 import * +from client import * +from oauth2 import * from util import * From e9edf2a255f0e9ffa8fde02a09bad0282eae989a Mon Sep 17 00:00:00 2001 From: Sandeep Murthy Date: Sat, 17 Feb 2018 15:34:52 +0000 Subject: [PATCH 16/71] Miscellaneous fixes - clean up tests --- .gitignore | 1 + requirements.txt | 1 + setup.py | 1 + spotipy/client.py | 2 +- spotipy/util.py | 2 +- tests/client_credentials_tests.py | 29 +++++++++++------- tests/test_oauth.py | 12 ++++++-- tests/tests.py | 50 ++++++++++++++++++++----------- 8 files changed, 66 insertions(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index 286ddd2..d88e85e 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,4 @@ docs/_build/ .* archive +*.sh diff --git a/requirements.txt b/requirements.txt index 47f25d8..708dccd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ +mock==2.0.0 requests==2.3.0 six==1.10.0 \ No newline at end of file diff --git a/setup.py b/setup.py index c673a92..0dde392 100644 --- a/setup.py +++ b/setup.py @@ -9,6 +9,7 @@ setup( author_email="paul@echonest.com", url='http://spotipy.readthedocs.org/', install_requires=[ + 'mock>=2.0.0', 'requests>=2.3.0', 'six>=1.10.0', ], diff --git a/spotipy/client.py b/spotipy/client.py index b2ad6f1..90b87b7 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -60,7 +60,7 @@ class Spotify(object): def __init__(self, auth=None, requests_session=True, client_credentials_manager=None, proxies=None, requests_timeout=None): """ - Create a Spotify API object. + Creates a Spotify API client. :param auth: An authorization token (optional) :param requests_session: diff --git a/spotipy/util.py b/spotipy/util.py index 336fd58..0480fd6 100644 --- a/spotipy/util.py +++ b/spotipy/util.py @@ -5,7 +5,7 @@ from __future__ import print_function __all__ = [ - 'prompt_for_user_token9' + 'prompt_for_user_token' ] import os diff --git a/tests/client_credentials_tests.py b/tests/client_credentials_tests.py index 4905062..aaaab75 100644 --- a/tests/client_credentials_tests.py +++ b/tests/client_credentials_tests.py @@ -1,27 +1,36 @@ -# -*- coding: latin-1 -*- +# -*- coding: utf-8 -*- -import spotipy -from spotipy.oauth2 import SpotifyClientCredentials +""" Client Credentials Requests Tests """ + +import os +import sys import unittest -''' - Client Credentials Requests Tests -''' +sys.path.insert(0, os.path.abspath(os.pardir)) + +from spotipy import ( + Spotify, + SpotifyClientCredentials, +) + class ClientCredentialsTestSpotipy(unittest.TestCase): ''' These tests require user authentication ''' + @classmethod + def setUpClass(self): + self.spotify = Spotify(client_credentials_manager=SpotifyClientCredentials()) + self.spotify.trace = False + muse_urn = 'spotify:artist:12Chz98pHFMPJEknJQMWvI' def test_request_with_token(self): - artist = spotify.artist(self.muse_urn) + artist = self.spotify.artist(self.muse_urn) self.assertTrue(artist['name'] == 'Muse') if __name__ == '__main__': - spotify_cc = SpotifyClientCredentials() - spotify = spotipy.Spotify(client_credentials_manager=spotify_cc) - spotify.trace = False + unittest.main() diff --git a/tests/test_oauth.py b/tests/test_oauth.py index 1871b60..0c18ef1 100644 --- a/tests/test_oauth.py +++ b/tests/test_oauth.py @@ -1,8 +1,15 @@ -from spotipy.oauth2 import SpotifyOAuth -import json +# -*- coding: utf-8 -*- + import io +import json +import os +import sys import unittest +sys.path.insert(0, os.path.abspath(os.pardir)) + +from spotipy import SpotifyOAuth + try: import unittest.mock as mock except ImportError: @@ -166,4 +173,5 @@ class TestSpotifyOAuth(unittest.TestCase): if __name__ == '__main__': + unittest.main() diff --git a/tests/tests.py b/tests/tests.py index 760b91c..cfdfb9e 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -1,9 +1,19 @@ -# -*- coding: latin-1 -*- -import spotipy -import unittest +# -*- coding: utf-8 -*- + +import os import pprint +import sys +import unittest + import requests -from spotipy.client import SpotifyException + +sys.path.insert(0, os.path.abspath(os.pardir)) + +from spotipy import ( + prompt_for_user_token, + Spotify, + SpotifyException, +) class TestSpotipy(unittest.TestCase): @@ -22,8 +32,14 @@ class TestSpotipy(unittest.TestCase): bad_id = 'BAD_ID' - def setUp(self): - self.spotify = spotipy.Spotify() + @classmethod + def setUpClass(self): + self.token = os.getenv('SPOTIPY_CLIENT_TOKEN') + if not self.token: + raise Exception('Set your Spotify client app token via the environment variable `SPOTIPY_CLIENT_TOKEN`') + + self.spotify = Spotify(auth=self.token) + def test_artist_urn(self): artist = self.spotify.artist(self.radiohead_urn) @@ -74,7 +90,7 @@ class TestSpotipy(unittest.TestCase): try: track = self.spotify.track(self.el_scorcho_bad_urn) self.assertTrue(False) - except spotipy.SpotifyException: + except SpotifyException: self.assertTrue(True) def test_tracks(self): @@ -121,11 +137,11 @@ class TestSpotipy(unittest.TestCase): self.assertTrue(found) def test_search_timeout(self): - sp = spotipy.Spotify(requests_timeout=.1) + sp = Spotify(auth=self.token, requests_timeout=.1) try: results = sp.search(q='my*', type='track') self.assertTrue(False, 'unexpected search timeout') - except requests.ReadTimeout: + except requests.Timeout: self.assertTrue(True, 'expected search timeout') @@ -149,14 +165,14 @@ class TestSpotipy(unittest.TestCase): try: track = self.spotify.track(self.bad_id) self.assertTrue(False) - except spotipy.SpotifyException: + except SpotifyException: self.assertTrue(True) def test_track_bad_id(self): try: track = self.spotify.track(self.bad_id) self.assertTrue(False) - except spotipy.SpotifyException: + except SpotifyException: self.assertTrue(True) def test_unauthenticated_post_fails(self): @@ -166,20 +182,17 @@ class TestSpotipy(unittest.TestCase): cm.exception.http_status == 403) def test_custom_requests_session(self): - from requests import Session - sess = Session() + sess = requests.Session() sess.headers["user-agent"] = "spotipy-test" - with_custom_session = spotipy.Spotify(requests_session=sess) + with_custom_session = Spotify(auth=self.token, requests_session=sess) self.assertTrue(with_custom_session.user(user="akx")["uri"] == "spotify:user:akx") def test_force_no_requests_session(self): - from requests import Session - with_no_session = spotipy.Spotify(requests_session=False) - self.assertFalse(isinstance(with_no_session._session, Session)) + with_no_session = Spotify(auth=self.token, requests_session=False) + self.assertFalse(isinstance(with_no_session._session, requests.Session)) self.assertTrue(with_no_session.user(user="akx")["uri"] == "spotify:user:akx") - ''' Need tests for: @@ -188,4 +201,5 @@ class TestSpotipy(unittest.TestCase): ''' if __name__ == '__main__': + unittest.main() From 6639a98e24b87cf0f9d470050cc3d00ecd2fbb91 Mon Sep 17 00:00:00 2001 From: Sandeep Murthy Date: Sat, 17 Feb 2018 17:33:01 +0000 Subject: [PATCH 17/71] Further clean up of tests --- requirements.txt | 1 + tests/authtests.py | 179 +++++++++++++++++------------- tests/authtests2.py | 63 +++++++---- tests/client_credentials_tests.py | 14 ++- tests/tests.py | 27 ++++- 5 files changed, 181 insertions(+), 103 deletions(-) diff --git a/requirements.txt b/requirements.txt index 708dccd..d674b24 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ mock==2.0.0 requests==2.3.0 +simplejson==3.13.2 six==1.10.0 \ No newline at end of file diff --git a/tests/authtests.py b/tests/authtests.py index 13504a6..bd8f77c 100644 --- a/tests/authtests.py +++ b/tests/authtests.py @@ -1,24 +1,47 @@ -# -*- coding: latin-1 -*- +# -*- coding: utf-8 -*- -import spotipy -from spotipy import util -import unittest +""" +These tests require user authentication - provide client credentials using the +following environment variables + +:: + + 'SPOTIPY_CLIENT_USERNAME' + 'SPOTIPY_CLIENT_ID' + 'SPOTIPY_CLIENT_SECRET' + 'SPOTIPY_REDIRECT_URI' +""" + +from __future__ import print_function + +import os import pprint import sys +import unittest + import simplejson as json -''' - Since these tests require authentication they are maintained - separately from the other tests. +sys.path.insert(0, os.path.abspath(os.pardir)) + +from spotipy import ( + prompt_for_user_token, + Spotify, + SpotifyException, +) - These tests try to be benign and leave your collection and - playlists in a relatively stable state. -''' class AuthTestSpotipy(unittest.TestCase): - ''' - These tests require user authentication - ''' + """ + These tests require user authentication - provide client credentials using the + following environment variables + + :: + + 'SPOTIPY_CLIENT_USERNAME' + 'SPOTIPY_CLIENT_ID' + 'SPOTIPY_CLIENT_SECRET' + 'SPOTIPY_REDIRECT_URI' + """ playlist = "spotify:user:plamere:playlist:2oCEWyyAPbZp9xhVSxZavx" four_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp", @@ -35,38 +58,60 @@ class AuthTestSpotipy(unittest.TestCase): bad_id = 'BAD_ID' + @classmethod + def setUpClass(self): + client_cred_env_vars = ['SPOTIPY_CLIENT_USERNAME', 'SPOTIPY_CLIENT_ID', 'SPOTIPY_CLIENT_SECRET', 'SPOTIPY_REDIRECT_URI'] + missing = filter(lambda var: not os.getenv(var), client_cred_env_vars) + + if missing: + raise Exception('Please set the client credetials for the test application using the following environment variables: {}'.format(client_cred_env_vars)) + + self.username = os.getenv('SPOTIPY_CLIENT_USERNAME') + + self.scope = ( + 'playlist-modify-public ' + 'user-library-read ' + 'user-follow-read ' + 'user-library-modify ' + 'user-read-private ' + 'user-top-read' + ) + + self.token = prompt_for_user_token(self.username, scope=self.scope) + + self.spotify = Spotify(auth=self.token) + def test_track_bad_id(self): try: - track = spotify.track(self.bad_id) + track = self.spotify.track(self.bad_id) self.assertTrue(False) - except spotipy.SpotifyException: + except SpotifyException: self.assertTrue(True) - def test_basic_user_profile(self): - user = spotify.user(username) - self.assertTrue(user['id'] == username) + user = self.spotify.user(self.username) + self.assertTrue(user['id'] == self.username.lower()) def test_current_user(self): - user = spotify.current_user() - self.assertTrue(user['id'] == username) + user = self.spotify.current_user() + self.assertTrue(user['id'] == self.username.lower()) def test_me(self): - user = spotify.me() - self.assertTrue(user['id'] == username) + user = self.spotify.me() + self.assertTrue(user['id'] == self.username.lower()) def test_user_playlists(self): - playlists = spotify.user_playlists(username, limit=5) + playlists = self.spotify.user_playlists(self.username, limit=5) self.assertTrue('items' in playlists) self.assertTrue(len(playlists['items']) == 5) def test_user_playlist_tracks(self): - playlists = spotify.user_playlists(username, limit=5) + playlists = self.spotify.user_playlists(self.username, limit=5) self.assertTrue('items' in playlists) for playlist in playlists['items']: user = playlist['owner']['id'] pid = playlist['id'] - results = spotify.user_playlist_tracks(user, pid) + results = self.spotify.user_playlist_tracks(user, pid) self.assertTrue(len(results['items']) >= 0) def user_playlist_tracks(self, user, playlist_id = None, fields=None, @@ -79,144 +124,128 @@ class AuthTestSpotipy(unittest.TestCase): self.assertTrue(len(playlists['items']) == 5) def test_current_user_saved_tracks(self): - tracks = spotify.current_user_saved_tracks() + tracks = self.spotify.current_user_saved_tracks() self.assertTrue(len(tracks['items']) > 0) def test_current_user_saved_albums(self): - albums = spotify.current_user_saved_albums() + albums = self.spotify.current_user_saved_albums() self.assertTrue(len(albums['items']) > 0) def test_current_user_playlists(self): - playlists = spotify.current_user_playlists(limit=10) + playlists = self.spotify.current_user_playlists(limit=10) self.assertTrue('items' in playlists) self.assertTrue(len(playlists['items']) == 10) def test_user_playlist_follow(self): - spotify.user_playlist_follow_playlist('plamere', '4erXB04MxwRAVqcUEpu30O') - follows = spotify.user_playlist_is_following('plamere', '4erXB04MxwRAVqcUEpu30O', ['plamere']) + self.spotify.user_playlist_follow_playlist('plamere', '4erXB04MxwRAVqcUEpu30O') + follows = self.spotify.user_playlist_is_following('plamere', '4erXB04MxwRAVqcUEpu30O', [self.spotify.current_user()['id']]) self.assertTrue(len(follows) == 1, 'proper follows length') self.assertTrue(follows[0], 'is following') - spotify.user_playlist_unfollow('plamere', '4erXB04MxwRAVqcUEpu30O') + self.spotify.user_playlist_unfollow('plamere', '4erXB04MxwRAVqcUEpu30O') - follows = spotify.user_playlist_is_following('plamere', '4erXB04MxwRAVqcUEpu30O', ['plamere']) + follows = self.spotify.user_playlist_is_following('plamere', '4erXB04MxwRAVqcUEpu30O', [self.spotify.current_user()['id']]) self.assertTrue(len(follows) == 1, 'proper follows length') self.assertFalse(follows[0], 'is no longer following') def test_current_user_save_and_unsave_tracks(self): - tracks = spotify.current_user_saved_tracks() + tracks = self.spotify.current_user_saved_tracks() total = tracks['total'] - spotify.current_user_saved_tracks_add(self.four_tracks) + self.spotify.current_user_saved_tracks_add(self.four_tracks) - tracks = spotify.current_user_saved_tracks() + tracks = self.spotify.current_user_saved_tracks() new_total = tracks['total'] self.assertTrue(new_total - total == len(self.four_tracks)) - tracks = spotify.current_user_saved_tracks_delete(self.four_tracks) - tracks = spotify.current_user_saved_tracks() + tracks = self.spotify.current_user_saved_tracks_delete(self.four_tracks) + tracks = self.spotify.current_user_saved_tracks() new_total = tracks['total'] self.assertTrue(new_total == total) def test_categories(self): - response = spotify.categories() + response = self.spotify.categories() self.assertTrue(len(response['categories']) > 0) def test_category_playlists(self): - response = spotify.categories() + response = self.spotify.categories() for cat in response['categories']['items']: cat_id = cat['id'] - response = spotify.category_playlists(category_id=cat_id) + response = self.spotify.category_playlists(category_id=cat_id) self.assertTrue(len(response['playlists']["items"]) > 0) def test_new_releases(self): - response = spotify.new_releases() + response = self.spotify.new_releases() self.assertTrue(len(response['albums']) > 0) def test_featured_releases(self): - response = spotify.featured_playlists() + response = self.spotify.featured_playlists() self.assertTrue(len(response['playlists']) > 0) def test_current_user_follows(self): - response = spotify.current_user_followed_artists() + response = self.spotify.current_user_followed_artists() artists = response['artists'] self.assertTrue(len(artists['items']) > 0) def test_current_user_top_tracks(self): - response = spotify.current_user_top_tracks() + response = self.spotify.current_user_top_tracks() items = response['items'] self.assertTrue(len(items) > 0) def test_current_user_top_artists(self): - response = spotify.current_user_top_artists() + response = self.spotify.current_user_top_artists() items = response['items'] self.assertTrue(len(items) > 0) - def get_or_create_spotify_playlist(self, username, playlist_name): - playlists = spotify.user_playlists(username) + def get_or_create_spotify_playlist(self, playlist_name): + playlists = self.spotify.user_playlists(self.username) while playlists: for item in playlists['items']: if item['name'] == playlist_name: return item['id'] - playlists = spotify.next(playlists) - playlist = spotify.user_playlist_create(username, playlist_name) + playlists = self.spotify.next(playlists) + playlist = self.spotify.user_playlist_create(self.username, playlist_name) playlist_id = playlist['uri'] return playlist_id def test_user_playlist_ops(self): # create empty playlist - playlist_id = self.get_or_create_spotify_playlist(username, - 'spotipy-testing-playlist-1') + playlist_id = self.get_or_create_spotify_playlist('spotipy-testing-playlist-1') # remove all tracks from it - spotify.user_playlist_replace_tracks(username, playlist_id,[]) + self.spotify.user_playlist_replace_tracks(self.username, playlist_id,[]) - playlist = spotify.user_playlist(username, playlist_id) + playlist = self.spotify.user_playlist(self.username, playlist_id) self.assertTrue(playlist['tracks']['total'] == 0) self.assertTrue(len(playlist['tracks']['items']) == 0) # add tracks to it - spotify.user_playlist_add_tracks(username, playlist_id, self.four_tracks) - playlist = spotify.user_playlist(username, playlist_id) + self.spotify.user_playlist_add_tracks(self.username, playlist_id, self.four_tracks) + playlist = self.spotify.user_playlist(self.username, playlist_id) self.assertTrue(playlist['tracks']['total'] == 4) self.assertTrue(len(playlist['tracks']['items']) == 4) # remove two tracks from it - spotify.user_playlist_remove_all_occurrences_of_tracks (username, + self.spotify.user_playlist_remove_all_occurrences_of_tracks (self.username, playlist_id, self.two_tracks) - playlist = spotify.user_playlist(username, playlist_id) + playlist = self.spotify.user_playlist(self.username, playlist_id) self.assertTrue(playlist['tracks']['total'] == 2) self.assertTrue(len(playlist['tracks']['items']) == 2) # replace with 3 other tracks - spotify.user_playlist_replace_tracks(username, + self.spotify.user_playlist_replace_tracks(self.username, playlist_id, self.other_tracks) - playlist = spotify.user_playlist(username, playlist_id) + playlist = self.spotify.user_playlist(self.username, playlist_id) self.assertTrue(playlist['tracks']['total'] == 3) self.assertTrue(len(playlist['tracks']['items']) == 3) if __name__ == '__main__': - if len(sys.argv) > 1: - username = sys.argv[1] - del sys.argv[1] - scope = 'playlist-modify-public ' - scope += 'user-library-read ' - scope += 'user-follow-read ' - scope += 'user-library-modify ' - scope += 'user-read-private ' - scope += 'user-top-read' - - token = util.prompt_for_user_token(username, scope) - spotify = spotipy.Spotify(auth=token) - spotify.trace = False - unittest.main() - else: - print("Usage: %s username" % (sys.argv[0],)) + unittest.main() diff --git a/tests/authtests2.py b/tests/authtests2.py index 670e1c8..d68667d 100644 --- a/tests/authtests2.py +++ b/tests/authtests2.py @@ -1,25 +1,44 @@ -# -*- coding: latin-1 -*- +# -*- coding: utf-8 -*- -import spotipy -from spotipy import util -import unittest +""" +These tests require user authentication - provide client credentials using the +following environment variables + +:: + + 'SPOTIPY_CLIENT_USERNAME' + 'SPOTIPY_CLIENT_ID' + 'SPOTIPY_CLIENT_SECRET' + 'SPOTIPY_REDIRECT_URI' +""" + +import os import pprint import sys +import unittest + import simplejson as json -from spotipy.oauth2 import SpotifyClientCredentials -''' - Since these tests require authentication they are maintained - separately from the other tests. +sys.path.insert(0, os.path.abspath(os.pardir)) + +from spotipy import ( + Spotify, + SpotifyClientCredentials, +) - These tests try to be benign and leave your collection and - playlists in a relatively stable state. -''' class AuthTestSpotipy(unittest.TestCase): - ''' - These tests require user authentication - ''' + """ + These tests require user authentication - provide client credentials using the + following environment variables + + :: + + 'SPOTIPY_CLIENT_USERNAME' + 'SPOTIPY_CLIENT_ID' + 'SPOTIPY_CLIENT_SECRET' + 'SPOTIPY_REDIRECT_URI' + """ playlist = "spotify:user:plamere:playlist:2oCEWyyAPbZp9xhVSxZavx" four_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp", @@ -36,13 +55,17 @@ class AuthTestSpotipy(unittest.TestCase): bad_id = 'BAD_ID' + @classmethod + def setUpClass(self): + self.spotify = Spotify(client_credentials_manager=SpotifyClientCredentials()) + self.spotify.trace = False def test_audio_analysis(self): - result = spotify.audio_analysis(self.four_tracks[0]) + result = self.spotify.audio_analysis(self.four_tracks[0]) assert('beats' in result) def test_audio_features(self): - results = spotify.audio_features(self.four_tracks) + results = self.spotify.audio_features(self.four_tracks) self.assertTrue(len(results) == len(self.four_tracks)) for track in results: assert('speechiness' in track) @@ -51,7 +74,7 @@ class AuthTestSpotipy(unittest.TestCase): bad_tracks = [] bad_tracks = ['spotify:track:bad'] input = self.four_tracks + bad_tracks - results = spotify.audio_features(input) + results = self.spotify.audio_features(input) self.assertTrue(len(results) == len(input)) for track in results[:-1]: if track != None: @@ -59,12 +82,10 @@ class AuthTestSpotipy(unittest.TestCase): self.assertTrue(results[-1] == None) def test_recommendations(self): - results = spotify.recommendations(seed_tracks=self.four_tracks, min_danceability=0, max_loudness=0, target_popularity=50) + results = self.spotify.recommendations(seed_tracks=self.four_tracks, min_danceability=0, max_loudness=0, target_popularity=50) self.assertTrue(len(results['tracks']) == 20) if __name__ == '__main__': - client_credentials_manager = SpotifyClientCredentials() - spotify = spotipy.Spotify(client_credentials_manager=client_credentials_manager) - spotify.trace = False + unittest.main() diff --git a/tests/client_credentials_tests.py b/tests/client_credentials_tests.py index aaaab75..a3b7103 100644 --- a/tests/client_credentials_tests.py +++ b/tests/client_credentials_tests.py @@ -15,9 +15,17 @@ from spotipy import ( class ClientCredentialsTestSpotipy(unittest.TestCase): - ''' - These tests require user authentication - ''' + """ + These tests require user authentication - provide client credentials using the + following environment variables + + :: + + 'SPOTIPY_CLIENT_USERNAME' + 'SPOTIPY_CLIENT_ID' + 'SPOTIPY_CLIENT_SECRET' + 'SPOTIPY_REDIRECT_URI' + """ @classmethod def setUpClass(self): diff --git a/tests/tests.py b/tests/tests.py index cfdfb9e..babc375 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -18,6 +18,18 @@ from spotipy import ( class TestSpotipy(unittest.TestCase): + """ + These tests require user authentication - provide client credentials using the + following environment variables + + :: + + 'SPOTIPY_CLIENT_USERNAME' + 'SPOTIPY_CLIENT_ID' + 'SPOTIPY_CLIENT_SECRET' + 'SPOTIPY_REDIRECT_URI' + """ + creep_urn = 'spotify:track:3HfB5hBU0dmBt8T0iCmH42' creep_id = '3HfB5hBU0dmBt8T0iCmH42' creep_url = 'http://open.spotify.com/track/3HfB5hBU0dmBt8T0iCmH42' @@ -34,13 +46,20 @@ class TestSpotipy(unittest.TestCase): @classmethod def setUpClass(self): - self.token = os.getenv('SPOTIPY_CLIENT_TOKEN') - if not self.token: - raise Exception('Set your Spotify client app token via the environment variable `SPOTIPY_CLIENT_TOKEN`') + client_cred_env_vars = ['SPOTIPY_CLIENT_USERNAME', 'SPOTIPY_CLIENT_ID', 'SPOTIPY_CLIENT_SECRET', 'SPOTIPY_REDIRECT_URI'] + missing = filter(lambda var: not os.getenv(var), client_cred_env_vars) + + if missing: + raise Exception('Please set the client credetials for the test application using the following environment variables: {}'.format(client_cred_env_vars)) + + self.username = os.getenv('SPOTIPY_CLIENT_USERNAME') + + self.scope = 'user-library-read' + + self.token = prompt_for_user_token(self.username, scope=self.scope) self.spotify = Spotify(auth=self.token) - def test_artist_urn(self): artist = self.spotify.artist(self.radiohead_urn) self.assertTrue(artist['name'] == 'Radiohead') From e5ccb9f35511813ba4514b9654bcff346ac54b51 Mon Sep 17 00:00:00 2001 From: Sandeep Murthy Date: Sat, 17 Feb 2018 19:03:01 +0000 Subject: [PATCH 18/71] Renaming some test modules: `authtests.py` -> `test_auth.py`, `authtests2.py` -> `test_auth2.py`, `client_credential_tests.py` -> `test_client_credentials.py` + update `tox.ini` --- tests/{authtests.py => test_auth.py} | 0 tests/{authtests2.py => test_auth2.py} | 0 ...{client_credentials_tests.py => test_client_credentials.py} | 0 tox.ini | 3 ++- 4 files changed, 2 insertions(+), 1 deletion(-) rename tests/{authtests.py => test_auth.py} (100%) rename tests/{authtests2.py => test_auth2.py} (100%) rename tests/{client_credentials_tests.py => test_client_credentials.py} (100%) diff --git a/tests/authtests.py b/tests/test_auth.py similarity index 100% rename from tests/authtests.py rename to tests/test_auth.py diff --git a/tests/authtests2.py b/tests/test_auth2.py similarity index 100% rename from tests/authtests2.py rename to tests/test_auth2.py diff --git a/tests/client_credentials_tests.py b/tests/test_client_credentials.py similarity index 100% rename from tests/client_credentials_tests.py rename to tests/test_client_credentials.py diff --git a/tox.ini b/tox.ini index e3af7ee..dcbdb72 100644 --- a/tox.ini +++ b/tox.ini @@ -3,6 +3,7 @@ envlist = py27,py34 [testenv] deps= requests + simplejson six py27: mock -commands=python -m unittest discover tests +commands=python -m unittest discover -v tests From 2ce270554ad503390bcb6c15ce6eab38bf8ecbcb Mon Sep 17 00:00:00 2001 From: Sandeep Murthy Date: Sat, 17 Feb 2018 20:33:10 +0000 Subject: [PATCH 19/71] Update tests - make client credentials env. vars list a commonly accessible dictionary in `spotipy/util.py` --- spotipy/util.py | 8 ++++++++ tests/test_auth.py | 9 +++++---- tests/tests.py | 8 ++++---- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/spotipy/util.py b/spotipy/util.py index 0480fd6..3eb1c52 100644 --- a/spotipy/util.py +++ b/spotipy/util.py @@ -5,6 +5,7 @@ from __future__ import print_function __all__ = [ + 'CLIENT_CREDS_ENV_VARS', 'prompt_for_user_token' ] @@ -14,6 +15,13 @@ from . import oauth2 import spotipy +CLIENT_CREDS_ENV_VARS = { + 'client_id': 'SPOTIPY_CLIENT_ID', + 'client_secret': 'SPOTIPY_CLIENT_SECRET', + 'client_username': 'SPOTIPY_CLIENT_USERNAME', + 'redirect_uri': 'SPOTIPY_REDIRECT_URI' +} + def prompt_for_user_token(username, scope=None, client_id = None, client_secret = None, redirect_uri = None, cache_path = None): ''' prompts the user to login if necessary and returns diff --git a/tests/test_auth.py b/tests/test_auth.py index bd8f77c..e72807b 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -24,6 +24,7 @@ import simplejson as json sys.path.insert(0, os.path.abspath(os.pardir)) from spotipy import ( + CLIENT_CREDS_ENV_VARS as CCEV, prompt_for_user_token, Spotify, SpotifyException, @@ -60,13 +61,13 @@ class AuthTestSpotipy(unittest.TestCase): @classmethod def setUpClass(self): - client_cred_env_vars = ['SPOTIPY_CLIENT_USERNAME', 'SPOTIPY_CLIENT_ID', 'SPOTIPY_CLIENT_SECRET', 'SPOTIPY_REDIRECT_URI'] - missing = filter(lambda var: not os.getenv(var), client_cred_env_vars) + + missing = filter(lambda var: not os.getenv(CCEV[var]), CCEV) if missing: - raise Exception('Please set the client credetials for the test application using the following environment variables: {}'.format(client_cred_env_vars)) + raise Exception('Please set the client credentials for the test application using the following environment variables: {}'.format(CCEV.values())) - self.username = os.getenv('SPOTIPY_CLIENT_USERNAME') + self.username = os.getenv(CCEV['client_username']) self.scope = ( 'playlist-modify-public ' diff --git a/tests/tests.py b/tests/tests.py index babc375..63ab860 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -10,6 +10,7 @@ import requests sys.path.insert(0, os.path.abspath(os.pardir)) from spotipy import ( + CLIENT_CREDS_ENV_VARS as CCEV, prompt_for_user_token, Spotify, SpotifyException, @@ -46,13 +47,12 @@ class TestSpotipy(unittest.TestCase): @classmethod def setUpClass(self): - client_cred_env_vars = ['SPOTIPY_CLIENT_USERNAME', 'SPOTIPY_CLIENT_ID', 'SPOTIPY_CLIENT_SECRET', 'SPOTIPY_REDIRECT_URI'] - missing = filter(lambda var: not os.getenv(var), client_cred_env_vars) + missing = filter(lambda var: not os.getenv(CCEV[var]), CCEV) if missing: - raise Exception('Please set the client credetials for the test application using the following environment variables: {}'.format(client_cred_env_vars)) + raise Exception('Please set the client credentials for the test application using the following environment variables: {}'.format(CCEV.values())) - self.username = os.getenv('SPOTIPY_CLIENT_USERNAME') + self.username = os.getenv(CCEV['client_username']) self.scope = 'user-library-read' From 9d88a68d50bb5a76ebd747e11734428d49112f03 Mon Sep 17 00:00:00 2001 From: HES SONG Date: Fri, 28 Sep 2018 14:50:41 +0900 Subject: [PATCH 20/71] Update client.py (add playlist endpoint) There was an upate with Spotify Web APi to bring playlist with using ID. --- spotipy/client.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spotipy/client.py b/spotipy/client.py index 90b87b7..a64a5fc 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -387,6 +387,20 @@ class Spotify(object): plid = self._get_id('playlist', playlist_id) return self._get("users/%s/playlists/%s" % (user, plid), fields=fields) + def playlist(self, playlist_id, fields=None, market=None): + """ Gets playlist by id + + Parameters: + - playlist - the id of the playlist + - fields - which fields to return + - market - An ISO 3166-1 alpha-2 country code or the string from_token. + """ + + plid = self._get_id('playlist', playlist_id) + + return self._get("playlists/%s" % (plid), fields=fields) + + def user_playlist_tracks(self, user, playlist_id=None, fields=None, limit=100, offset=0, market=None): """ Get full details of the tracks of a playlist owned by a user. From f872f5b90c6b91e05ac72b8cf4b75fd6eaac05ed Mon Sep 17 00:00:00 2001 From: Josh Chorlton Date: Sat, 11 Aug 2018 12:36:50 -0400 Subject: [PATCH 21/71] Minor docs fix --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 6283ed8..d0476b1 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -127,7 +127,7 @@ To support the **Authorization Code Flow** *Spotipy* provides a utility method ``util.prompt_for_user_token`` that will attempt to authorize the user. You can pass your app credentials directly into the method as arguments:: - util.prompt_for_user_token(username,scope,client_id='your-app-redirect-url',client_secret='your-app-redirect-url',redirect_uri='your-app-redirect-url') + util.prompt_for_user_token(username,scope,client_id='your-spotify-client-id',client_secret='your-spotify-client-secret',redirect_uri='your-app-redirect-url') or if you are reluctant to immortalize your app credentials in your source code, you can set environment variables like so:: From 469fbf1b1e5ed4534fdcf8fea40ec77a17eadef0 Mon Sep 17 00:00:00 2001 From: Halvor Strand Date: Fri, 5 Oct 2018 17:06:49 +0200 Subject: [PATCH 22/71] Fixed docstring error Fixed user_playlist_remove_all_occurrences_of_tracks docstring error, having "add to" instead of "remove from". --- spotipy/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spotipy/client.py b/spotipy/client.py index a64a5fc..98a1e24 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -526,7 +526,7 @@ class Spotify(object): Parameters: - user - the id of the user - playlist_id - the id of the playlist - - tracks - the list of track ids to add to the playlist + - tracks - the list of track ids to remove from the playlist - snapshot_id - optional id of the playlist snapshot """ From 909fa149773904fa6d4079b56c22175b07cb2a1d Mon Sep 17 00:00:00 2001 From: Thomas Bechtold Date: Sat, 13 Oct 2018 14:17:19 +0200 Subject: [PATCH 23/71] docs: Use the print() function instead of the built-in This is compatible with python2 and python3 so the examples don't throw a SyntaxError when trying them with python 3. Closes #329 --- docs/index.rst | 46 ++++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index d0476b1..ac80ca5 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -27,6 +27,7 @@ released by the artist 'Birdy':: Here's another example showing how to get 30 second samples and cover art for the top 10 tracks for Led Zeppelin:: + from __future__ import print_function import spotipy lz_uri = 'spotify:artist:36QJpDe2go2KgaRleHCDTp' @@ -35,14 +36,15 @@ for the top 10 tracks for Led Zeppelin:: results = spotify.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'] - print + print('track : ' + track['name']) + print('audio : ' + track['preview_url']) + print('cover art: ' + track['album']['images'][0]['url']) + print() Finally, here's an example that will get the URL for an artist image given the artist's name:: + from __future__ import print_function import spotipy import sys @@ -57,7 +59,7 @@ artist's name:: items = results['artists']['items'] if len(items) > 0: artist = items[0] - print artist['name'], artist['images'][0]['url'] + print(artist['name'], artist['images'][0]['url']) Features @@ -87,10 +89,11 @@ Non-Authorized requests For methods that do not require authorization, simply create a Spotify object and start making method calls like so:: + from __future__ import print_function import spotipy spotify = spotipy.Spotify() results = spotify.search(q='artist:' + name, type='artist') - print results + print(results) Authorized requests ======================= @@ -146,6 +149,7 @@ are used to automatically re-authorized expired tokens. Here's an example of getting user authorization to read a user's saved tracks:: + from __future__ import print_function import sys import spotipy import spotipy.util as util @@ -155,7 +159,7 @@ Here's an example of getting user authorization to read a user's saved tracks:: if len(sys.argv) > 1: username = sys.argv[1] else: - print "Usage: %s username" % (sys.argv[0],) + print("Usage: %s username" % (sys.argv[0],)) sys.exit() token = util.prompt_for_user_token(username, scope) @@ -165,9 +169,9 @@ Here's an example of getting user authorization to read a user's saved tracks:: results = sp.current_user_saved_tracks() for item in results['items']: track = item['track'] - print track['name'] + ' - ' + track['artists'][0]['name'] + print(track['name'] + ' - ' + track['artists'][0]['name']) else: - print "Can't get token for", username + print("Can't get token for", username) Client Credentials Flow ======================= @@ -217,6 +221,7 @@ Here are a few more examples of using *Spotipy*. Add tracks to a playlist:: + from __future__ import print_function import pprint import sys @@ -228,7 +233,7 @@ Add tracks to a playlist:: playlist_id = sys.argv[2] track_ids = sys.argv[3:] else: - print "Usage: %s username playlist_id track_id ..." % (sys.argv[0],) + print("Usage: %s username playlist_id track_id ..." % (sys.argv[0],)) sys.exit() scope = 'playlist-modify-public' @@ -238,15 +243,16 @@ Add tracks to a playlist:: sp = spotipy.Spotify(auth=token) sp.trace = False results = sp.user_playlist_add_tracks(username, playlist_id, track_ids) - print results + print(results) else: - print "Can't get token for", username + print("Can't get token for", username) Shows the contents of every playlist owned by a user:: # shows a user's playlists (need to be authenticated via oauth) + from __future__ import print_function import sys import spotipy import spotipy.util as util @@ -254,16 +260,16 @@ Shows the contents of every playlist owned by a user:: def show_tracks(tracks): for i, item in enumerate(tracks['items']): track = item['track'] - print " %d %32.32s %s" % (i, track['artists'][0]['name'], - track['name']) + print(" %d %32.32s %s" % (i, track['artists'][0]['name'], + track['name'])) if __name__ == '__main__': if len(sys.argv) > 1: username = sys.argv[1] else: - print "Whoops, need your username!" - print "usage: python user_playlists.py [username]" + print("Whoops, need your username!") + print("usage: python user_playlists.py [username]") sys.exit() token = util.prompt_for_user_token(username) @@ -273,9 +279,9 @@ Shows the contents of every playlist owned by a user:: playlists = sp.user_playlists(username) for playlist in playlists['items']: if playlist['owner']['id'] == username: - print - print playlist['name'] - print ' total tracks', playlist['tracks']['total'] + print() + print(playlist['name']) + print (' total tracks', playlist['tracks']['total']) results = sp.user_playlist(username, playlist['id'], fields="tracks,next") tracks = results['tracks'] @@ -284,7 +290,7 @@ Shows the contents of every playlist owned by a user:: tracks = sp.next(tracks) show_tracks(tracks) else: - print "Can't get token for", username + print("Can't get token for", username) More Examples From e44bd7a93666002ad6d21581e2a39a7d9371f564 Mon Sep 17 00:00:00 2001 From: Brecht Yperman Date: Tue, 19 Feb 2019 00:47:07 +0100 Subject: [PATCH 24/71] Add library album management functions --- spotipy/client.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/spotipy/client.py b/spotipy/client.py index 98a1e24..7c328fe 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -708,6 +708,28 @@ class Spotify(object): ''' return self._get('me/player/recently-played', limit=limit) + def current_user_saved_albums_delete(self, albums=[]): + """ Remove one or more albums from the current user's + "Your Music" library. + + Parameters: + - albums - a list of album URIs, URLs or IDs + """ + alist = [self._get_id('album', a) for a in albums] + r = self._delete('me/albums/?ids=' + ','.join(alist)) + return r + + def current_user_saved_albums_contains(self, albums=[]): + """ Check if one or more albums is already saved in + the current Spotify user’s “Your Music” library. + + Parameters: + - albums - a list of album URIs, URLs or IDs + """ + alist = [self._get_id('album', a) for a in albums] + r = self._get('me/albums/contains?ids=' + ','.join(alist)) + return r + def current_user_saved_albums_add(self, albums=[]): """ Add one or more albums to the current user's "Your Music" library. From f8a6e4edb6cd67cff8d662af715ed9940f4cc600 Mon Sep 17 00:00:00 2001 From: Matt Comben Date: Sat, 27 Apr 2019 10:20:44 +0100 Subject: [PATCH 25/71] Fix bug where formatted arguments are passed to self._warn, which only accepts a single string --- spotipy/client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spotipy/client.py b/spotipy/client.py index 7c328fe..897ebdb 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -1081,15 +1081,15 @@ class Spotify(object): fields = id.split(':') if len(fields) >= 3: if type != fields[-2]: - self._warn('expected id of type %s but found type %s %s', - type, fields[-2], id) + self._warn('expected id of type %s but found type %s %s' % + (type, fields[-2], id)) return fields[-1] fields = id.split('/') if len(fields) >= 3: itype = fields[-2] if type != itype: - self._warn('expected id of type %s but found type %s %s', - type, itype, id) + self._warn('expected id of type %s but found type %s %s' % + (type, itype, id)) return fields[-1] return id From 7b85b5ca639958f7df3a6b1ce13cc2c68f93e0d3 Mon Sep 17 00:00:00 2001 From: Mostafa Garana Date: Wed, 5 Jun 2019 09:39:50 +0200 Subject: [PATCH 26/71] Adding an endpoint for unfollowing users/artists. --- spotipy/client.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spotipy/client.py b/spotipy/client.py index 897ebdb..b575e64 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -754,6 +754,20 @@ class Spotify(object): ''' 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 + ''' + 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 + ''' + 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 From 3f321bf5d4bde9232c3d129d85de396f68c49396 Mon Sep 17 00:00:00 2001 From: "Ryan K. Lee" Date: Wed, 19 Jun 2019 20:49:57 -0700 Subject: [PATCH 27/71] update limit on current_user_saved_albums --- spotipy/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spotipy/client.py b/spotipy/client.py index b575e64..9518238 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -40,7 +40,7 @@ class Spotify(object): import spotipy - urn = 'spotify:artist:3jOstUTkEu2JkjvRdBA5Gu' + urn = 'spotify:artist:3jOstUTkEu2JkjvRdBA5Gu' sp = spotipy.Spotify() sp.trace = True # turn on tracing @@ -605,7 +605,7 @@ class Spotify(object): ''' return self._get('me/player/currently-playing') - def current_user_saved_albums(self, limit=20, offset=0): + def current_user_saved_albums(self, limit=600, offset=0): """ Gets a list of the albums saved in the current authorized user's "Your Music" library From 91639996e138a872fba87a2f736813c224529ee9 Mon Sep 17 00:00:00 2001 From: "Ryan K. Lee" Date: Sat, 6 Jul 2019 01:46:01 -0700 Subject: [PATCH 28/71] Fix comments in my_playlists.py --- examples/my_playlists.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/my_playlists.py b/examples/my_playlists.py index 58e02df..50d3b2a 100644 --- a/examples/my_playlists.py +++ b/examples/my_playlists.py @@ -1,4 +1,4 @@ -# Shows the top artists for a user +# Shows a user's playlists import pprint import sys From ba6e9a0b795ab7705451d348d682ef1af4118ef0 Mon Sep 17 00:00:00 2001 From: "Ryan K. Lee" Date: Sat, 6 Jul 2019 01:47:30 -0700 Subject: [PATCH 29/71] Add parentheses after print in my_top_artists.py --- examples/my_top_artists.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/my_top_artists.py b/examples/my_top_artists.py index 91cbfe2..b4145ef 100644 --- a/examples/my_top_artists.py +++ b/examples/my_top_artists.py @@ -21,10 +21,10 @@ if token: sp.trace = False ranges = ['short_term', 'medium_term', 'long_term'] for range in ranges: - print "range:", range + print ("range:", range) results = sp.current_user_top_artists(time_range=range, limit=50) for i, item in enumerate(results['items']): - print i, item['name'] - print + print (i, item['name']) + print () else: print("Can't get token for", username) From cc158c34f1ed594120102402b9b92cc1896ae3d6 Mon Sep 17 00:00:00 2001 From: "Ryan K. Lee" Date: Sat, 6 Jul 2019 01:48:52 -0700 Subject: [PATCH 30/71] Fix comments and parentheses in my_top_tracks.py --- examples/my_top_tracks.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/my_top_tracks.py b/examples/my_top_tracks.py index 6bd72ec..6346109 100644 --- a/examples/my_top_tracks.py +++ b/examples/my_top_tracks.py @@ -1,4 +1,4 @@ -# Adds tracks to a playlist +# Shows the top tracks for a user import pprint import sys @@ -21,11 +21,11 @@ if token: sp.trace = False ranges = ['short_term', 'medium_term', 'long_term'] for range in ranges: - print "range:", range + print ("range:", range) results = sp.current_user_top_tracks(time_range=range, limit=50) for i, item in enumerate(results['items']): - print i, item['name'], '//', item['artists'][0]['name'] - print + print (i, item['name'], '//', item['artists'][0]['name']) + print () else: print("Can't get token for", username) From 3ed903e6ce4d3f9f5eaf08fe7bbb7e67e623a813 Mon Sep 17 00:00:00 2001 From: "Ryan K. Lee" Date: Sat, 6 Jul 2019 01:49:46 -0700 Subject: [PATCH 31/71] Update print parentheses in read_a_playlist.py --- examples/read_a_playlist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/read_a_playlist.py b/examples/read_a_playlist.py index dbcf984..6c0dc72 100644 --- a/examples/read_a_playlist.py +++ b/examples/read_a_playlist.py @@ -10,4 +10,4 @@ username = uri.split(':')[2] playlist_id = uri.split(':')[4] results = sp.user_playlist(username, playlist_id) -print json.dumps(results, indent=4) +print (json.dumps(results, indent=4)) From 32a6d4867b1ef519f2da20e7adce503d4a6ce544 Mon Sep 17 00:00:00 2001 From: "Ryan K. Lee" Date: Sat, 6 Jul 2019 01:51:48 -0700 Subject: [PATCH 32/71] Update remove_specific_tracks_from_playlist.py Update comments --- examples/remove_specific_tracks_from_playlist.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/remove_specific_tracks_from_playlist.py b/examples/remove_specific_tracks_from_playlist.py index a99dc94..c0bbcd7 100644 --- a/examples/remove_specific_tracks_from_playlist.py +++ b/examples/remove_specific_tracks_from_playlist.py @@ -1,5 +1,4 @@ - -# Adds tracks to a playlist +# removes tracks from a playlist import pprint import sys From ba667d388cad358695682ddc5c378548292b5d91 Mon Sep 17 00:00:00 2001 From: "Ryan K. Lee" Date: Sat, 6 Jul 2019 01:52:41 -0700 Subject: [PATCH 33/71] Update remove_tracks_from_playlist.py Update comments --- examples/remove_tracks_from_playlist.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/remove_tracks_from_playlist.py b/examples/remove_tracks_from_playlist.py index e9fca7b..73a65c5 100644 --- a/examples/remove_tracks_from_playlist.py +++ b/examples/remove_tracks_from_playlist.py @@ -1,5 +1,4 @@ - -# Adds tracks to a playlist +# removes tracks to a playlist import pprint import sys From faa5785a29ced83c3eb88da72fa808bb310d1a9a Mon Sep 17 00:00:00 2001 From: "Ryan K. Lee" Date: Mon, 15 Jul 2019 15:34:21 -0700 Subject: [PATCH 34/71] Update simple3.py add import statements for oath --- examples/simple3.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/simple3.py b/examples/simple3.py index 96f300a..929c899 100644 --- a/examples/simple3.py +++ b/examples/simple3.py @@ -1,14 +1,16 @@ import spotipy +from spotipy.oauth2 import SpotifyClientCredentials import sys -spotify = spotipy.Spotify() +client_credentials_manager = SpotifyClientCredentials() +sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager) if len(sys.argv) > 1: name = ' '.join(sys.argv[1:]) else: name = 'Radiohead' -results = spotify.search(q='artist:' + name, type='artist') +results = sp.search(q='artist:' + name, type='artist') items = results['artists']['items'] if len(items) > 0: artist = items[0] From a677267bcaee770cf6fce04078ff4871b2b472fc Mon Sep 17 00:00:00 2001 From: "Ryan K. Lee" Date: Mon, 15 Jul 2019 15:35:06 -0700 Subject: [PATCH 35/71] Update simple2.py Add import statements for oath --- examples/simple2.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/simple2.py b/examples/simple2.py index 3b06ddc..79a67e1 100644 --- a/examples/simple2.py +++ b/examples/simple2.py @@ -1,12 +1,14 @@ import spotipy +from spotipy.oauth2 import SpotifyClientCredentials lz_uri = 'spotify:artist:36QJpDe2go2KgaRleHCDTp' -spotify = spotipy.Spotify() +client_credentials_manager = SpotifyClientCredentials() +sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager) -results = spotify.artist_top_tracks(lz_uri) +results = sp.artist_top_tracks(lz_uri) for track in results['tracks'][:10]: print('track : ' + track['name']) From 0c453c9945e4412574492f0e8330a701e01184b8 Mon Sep 17 00:00:00 2001 From: "Ryan K. Lee" Date: Mon, 15 Jul 2019 15:36:31 -0700 Subject: [PATCH 36/71] Update simple1.py Import oath --- examples/simple1.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/simple1.py b/examples/simple1.py index 1748b24..e9c0fb7 100644 --- a/examples/simple1.py +++ b/examples/simple1.py @@ -1,14 +1,15 @@ import spotipy - +from spotipy.oauth2 import SpotifyClientCredentials birdy_uri = 'spotify:artist:2WX2uTcsvV5OnS0inACecP' -spotify = spotipy.Spotify() +client_credentials_manager = SpotifyClientCredentials() +sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager) -results = spotify.artist_albums(birdy_uri, album_type='album') +results = sp.artist_albums(birdy_uri, album_type='album') albums = results['items'] while results['next']: - results = spotify.next(results) + results = sp.next(results) albums.extend(results['items']) for album in albums: From c3bae42b9ea8be545ec124cac3ae9a37fcb72e94 Mon Sep 17 00:00:00 2001 From: "Ryan K. Lee" Date: Mon, 15 Jul 2019 15:37:13 -0700 Subject: [PATCH 37/71] Update simple0.py Import oath --- examples/simple0.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/simple0.py b/examples/simple0.py index 52540bb..c1ea7d7 100644 --- a/examples/simple0.py +++ b/examples/simple0.py @@ -1,5 +1,8 @@ import spotipy -sp = spotipy.Spotify() +from spotipy.oauth2 import SpotifyClientCredentials + +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']): From 5a007586092a23ad3f5370b50ae3cb82072c65b5 Mon Sep 17 00:00:00 2001 From: "Ryan K. Lee" Date: Mon, 15 Jul 2019 15:38:18 -0700 Subject: [PATCH 38/71] Update show_track_info.py Import SpotifyClientCredentials from spotipy.oauth2 --- examples/show_track_info.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/show_track_info.py b/examples/show_track_info.py index b286c0d..def5a9a 100644 --- a/examples/show_track_info.py +++ b/examples/show_track_info.py @@ -1,6 +1,7 @@ # shows track info for a URN or URL import spotipy +from spotipy.oauth2 import SpotifyClientCredentials import sys import pprint @@ -9,6 +10,7 @@ if len(sys.argv) > 1: else: urn = 'spotify:track:0Svkvt5I79wficMFgaqEQJ' -sp = spotipy.Spotify() +client_credentials_manager = SpotifyClientCredentials() +sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager) track = sp.track(urn) pprint.pprint(track) From 90f11056484f1ef3ca5b83ffc594a7cf71e3e7f7 Mon Sep 17 00:00:00 2001 From: "Ryan K. Lee" Date: Mon, 15 Jul 2019 15:39:14 -0700 Subject: [PATCH 39/71] Update show_related.py Import SpotifyClientCredentials from spotipy.oauth2 --- examples/show_related.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/show_related.py b/examples/show_related.py index b78e1dc..0fca47d 100644 --- a/examples/show_related.py +++ b/examples/show_related.py @@ -2,6 +2,7 @@ # shows related artists for the given seed artist import spotipy +from spotipy.oauth2 import SpotifyClientCredentials import sys import pprint @@ -10,7 +11,9 @@ if len(sys.argv) > 1: else: artist_name = 'weezer' -sp = spotipy.Spotify() + +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'] From e3185e22ecd79298cbb4f0d2680ad016e1d5df69 Mon Sep 17 00:00:00 2001 From: "Ryan K. Lee" Date: Mon, 15 Jul 2019 15:40:20 -0700 Subject: [PATCH 40/71] Update show_artist_top_tracks.py Import SpotifyClientCredentials from spotipy.oauth2 --- examples/show_artist_top_tracks.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/show_artist_top_tracks.py b/examples/show_artist_top_tracks.py index a80beb8..68d8755 100644 --- a/examples/show_artist_top_tracks.py +++ b/examples/show_artist_top_tracks.py @@ -1,6 +1,7 @@ # shows artist info for a URN or URL import spotipy +from spotipy.oauth2 import SpotifyClientCredentials import sys import pprint @@ -9,7 +10,8 @@ if len(sys.argv) > 1: else: urn = 'spotify:artist:3jOstUTkEu2JkjvRdBA5Gu' -sp = spotipy.Spotify() +client_credentials_manager = SpotifyClientCredentials() +sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager) response = sp.artist_top_tracks(urn) for track in response['tracks']: From bd1a122831eb0993ab9cdf2d52c5bbc490ad6017 Mon Sep 17 00:00:00 2001 From: "Ryan K. Lee" Date: Mon, 15 Jul 2019 15:41:05 -0700 Subject: [PATCH 41/71] Update show_artist.py Import SpotifyClientCredentials from spotipy.oauth2 --- examples/show_artist.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/show_artist.py b/examples/show_artist.py index 0180b2a..c0a9834 100644 --- a/examples/show_artist.py +++ b/examples/show_artist.py @@ -1,6 +1,7 @@ # shows artist info for a URN or URL import spotipy +from spotipy.oauth2 import SpotifyClientCredentials import sys import pprint @@ -9,7 +10,8 @@ if len(sys.argv) > 1: else: urn = 'spotify:artist:3jOstUTkEu2JkjvRdBA5Gu' -sp = spotipy.Spotify() +client_credentials_manager = SpotifyClientCredentials() +sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager) artist = sp.artist(urn) From 5f45a0a54ec100278a132ed8391d9ef68d64be04 Mon Sep 17 00:00:00 2001 From: "Ryan K. Lee" Date: Mon, 15 Jul 2019 15:41:54 -0700 Subject: [PATCH 42/71] Update show_album.py Import SpotifyClientCredentials from spotipy.oauth2 --- examples/show_album.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/show_album.py b/examples/show_album.py index e492b3c..acd310f 100644 --- a/examples/show_album.py +++ b/examples/show_album.py @@ -2,6 +2,7 @@ # shows album info for a URN or URL import spotipy +from spotipy.oauth2 import SpotifyClientCredentials import sys import pprint @@ -11,6 +12,7 @@ else: urn = 'spotify:album:5yTx83u3qerZF7GRJu7eFk' -sp = spotipy.Spotify() +client_credentials_manager = SpotifyClientCredentials() +sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager) album = sp.album(urn) pprint.pprint(album) From f1b796cf3574b78caa8a3f1e64f77b394cdaed2b Mon Sep 17 00:00:00 2001 From: Harrison Date: Mon, 12 Aug 2019 18:11:57 -0500 Subject: [PATCH 43/71] remove duplicate audio_analysis --- spotipy/client.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/spotipy/client.py b/spotipy/client.py index 9518238..88e02d1 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -924,14 +924,6 @@ class Spotify(object): else: return results - def audio_analysis(self, id): - """ Get audio analysis for a track based upon its Spotify ID - Parameters: - - id - a track URIs, URLs or IDs - """ - id = self._get_id('track', id) - return self._get('audio-analysis/'+id) - def devices(self): ''' Get a list of user's available devices. ''' From 43a4de0314353653b360f1f06851bddfea3b4f4f Mon Sep 17 00:00:00 2001 From: Harrison Date: Mon, 12 Aug 2019 18:13:07 -0500 Subject: [PATCH 44/71] update version and README --- CHANGES.txt | 1 + README.md | 2 +- spotipy/__init__.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index b46e2c9..a28e173 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -14,3 +14,4 @@ v2.310, January 5, 2015 -- Added session support v2.3.1, March 28, 2015 -- Auto retry support v2.3.5, April 28, 2015 -- Fixed bug in auto retry support v2.3.6, June 3, 2015 -- Support for offset/limit with album_tracks API +v2.5.0 - January 11, 2020 -- Added follow and player endpoints diff --git a/README.md b/README.md index 9f9ec71..2add256 100644 --- a/README.md +++ b/README.md @@ -84,4 +84,4 @@ If you have suggestions, bugs or other issues specific to this library, file the - v2.4.2 - January 2, 2017 -- support getting audio features for a single track - v2.4.3 - January 2, 2017 -- fixed proxy issue in standard auth flow - v2.4.4 - January 4, 2017 -- python 3 fix -- v2.5.0 - January 11, 2020 -- Added follow and player endpoints \ No newline at end of file +- v2.5.0 - January 11, 2020 -- Added follow and player endpoints diff --git a/spotipy/__init__.py b/spotipy/__init__.py index 95c0814..ac0f074 100644 --- a/spotipy/__init__.py +++ b/spotipy/__init__.py @@ -1,4 +1,4 @@ -VERSION='2.0.1' +VERSION='2.4.5' from client import * from oauth2 import * From d8d9f290fb83d548f41b71e7451b7b79d2d1f17c Mon Sep 17 00:00:00 2001 From: Harrison Date: Mon, 12 Aug 2019 18:27:10 -0500 Subject: [PATCH 45/71] remove unneccesary whitespaces, shorten some lines, and add name to contibutors --- docs/index.rst | 55 +++++++++++++++--------------- examples/artist_discography.py | 2 +- examples/contains_a_saved_track.py | 2 +- examples/my_top_tracks.py | 2 +- examples/title_chain.py | 2 +- examples/user_public_playlists.py | 2 +- spotipy/client.py | 19 ++++++----- spotipy/util.py | 8 ++--- 8 files changed, 48 insertions(+), 44 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index ac80ca5..801780f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -5,7 +5,7 @@ Welcome to Spotipy! =================================== *Spotipy* is a lightweight Python library for the `Spotify Web API `_. With *Spotipy* -you get full access to all of the music data provided by the Spotify platform. +you get full access to all of the music data provided by the Spotify platform. Here's a quick example of using *Spotipy* to list the names of all the albums released by the artist 'Birdy':: @@ -106,17 +106,17 @@ Even if your script does not have an accessible URL you need to specify one when registering your application where the spotify authentication API will redirect to after successful login. The URL doesn't need to work or be accessible, you can specify "http://localhost/", after successful login you -just need to copy the "http://localhost/?code=..." URL from your browser +just need to copy the "http://localhost/?code=..." URL from your browser and paste it to the console where your script is running. -Register your app at +Register your app at `My Applications `_. *spotipy* supports two authorization flows: - - The **Authorization Code flow** This method is suitable for long-running applications + - The **Authorization Code flow** This method is suitable for long-running applications which the user logs into once. It provides an access token that can be refreshed. - The **Client Credentials flow** The method makes it possible @@ -132,14 +132,14 @@ user. You can pass your app credentials directly into the method as arguments:: util.prompt_for_user_token(username,scope,client_id='your-spotify-client-id',client_secret='your-spotify-client-secret',redirect_uri='your-app-redirect-url') -or if you are reluctant to immortalize your app credentials in your source code, +or if you are reluctant to immortalize your app credentials in your source code, you can set environment variables like so:: export SPOTIPY_CLIENT_ID='your-spotify-client-id' export SPOTIPY_CLIENT_SECRET='your-spotify-client-secret' export SPOTIPY_REDIRECT_URI='your-app-redirect-url' -Call ``util.prompt_for_user_token`` method with the username and the +Call ``util.prompt_for_user_token`` method with the username and the desired scope (see `Using Scopes `_ for information about scopes) and credentials. This will coordinate the user authorization via @@ -195,8 +195,8 @@ class SpotifyClientCredentials that can be used to authenticate requests like so playlists = None Client credentials flow is appropriate for requests that do not require access to a -user's private data. Even if you are only making calls that do not require -authorization, using this flow yields the benefit of a higher rate limit +user's private data. Even if you are only making calls that do not require +authorization, using this flow yields the benefit of a higher rate limit IDs URIs and URLs ======================= @@ -260,7 +260,7 @@ Shows the contents of every playlist owned by a user:: def show_tracks(tracks): for i, item in enumerate(tracks['items']): track = item['track'] - print(" %d %32.32s %s" % (i, track['artists'][0]['name'], + print(" %d %32.32s %s" % (i, track['artists'][0]['name'], track['name'])) @@ -282,7 +282,7 @@ Shows the contents of every playlist owned by a user:: print() print(playlist['name']) print (' total tracks', playlist['tracks']['total']) - results = sp.user_playlist(username, playlist['id'], + results = sp.user_playlist(username, playlist['id'], fields="tracks,next") tracks = results['tracks'] show_tracks(tracks) @@ -298,7 +298,7 @@ More Examples There are many more examples of how to use *Spotipy* in the `Examples Directory `_ on Github -API Reference +API Reference ============== :mod:`client` Module @@ -336,7 +336,7 @@ You can ask questions about Spotipy on Stack Overflow. Don’t forget to add t http://stackoverflow.com/questions/ask -If you think you've found a bug, let us know at +If you think you've found a bug, let us know at `Spotify Issues `_ @@ -344,24 +344,25 @@ Contribute ========== Spotipy authored by Paul Lamere (plamere) with contributions by: - - Daniel Beaudry // danbeaudry - - Faruk Emre Sahin // fsahin - - George // rogueleaderr - - Henry Greville // sethaurus - - Hugo // hugovk - - José Manuel Pérez // JMPerez - - Lucas Nunno // lnunno - - Lynn Root // econchick - - Matt Dennewitz // mattdennewitz - - Matthew Duck // mattduck - - Michael Thelin // thelinmichael - - Ryan Choi // ryankicks - - Simon Metson // drsm79 + - Daniel Beaudry // danbeaudry + - Faruk Emre Sahin // fsahin + - George // rogueleaderr + - Henry Greville // sethaurus + - Hugo // hugovk + - José Manuel Pérez // JMPerez + - Lucas Nunno // lnunno + - Lynn Root // econchick + - Matt Dennewitz // mattdennewitz + - Matthew Duck // mattduck + - Michael Thelin // thelinmichael + - Ryan Choi // ryankicks + - Simon Metson // drsm79 - Steve Winton // swinton - - Tim Balzer // timbalzer - - corycorycory // corycorycory + - Tim Balzer // timbalzer + - corycorycory // corycorycory - Nathan Coleman // nathancoleman - Michael Birtwell // mbirtwell + - Harrison Hayes // Harrison97 License ======= diff --git a/examples/artist_discography.py b/examples/artist_discography.py index 34114bb..3cd44f0 100644 --- a/examples/artist_discography.py +++ b/examples/artist_discography.py @@ -35,7 +35,7 @@ def show_artist_albums(id): unique = set() # skip duplicate albums for album in albums: name = album['name'].lower() - if not name in unique: + if not name in unique: print(name) unique.add(name) show_album_tracks(album) diff --git a/examples/contains_a_saved_track.py b/examples/contains_a_saved_track.py index 2dac2f5..bd130b4 100644 --- a/examples/contains_a_saved_track.py +++ b/examples/contains_a_saved_track.py @@ -10,7 +10,7 @@ if len(sys.argv) > 2: username = sys.argv[1] tids = sys.argv[2:] else: - print("Usage: %s username track-id ..." % (sys.argv[0],)) + print("Usage: %s username track-id ..." % (sys.argv[0],)) sys.exit() token = util.prompt_for_user_token(username, scope) diff --git a/examples/my_top_tracks.py b/examples/my_top_tracks.py index 6346109..f1a96ea 100644 --- a/examples/my_top_tracks.py +++ b/examples/my_top_tracks.py @@ -26,6 +26,6 @@ if token: for i, item in enumerate(results['items']): print (i, item['name'], '//', item['artists'][0]['name']) print () - + else: print("Can't get token for", username) diff --git a/examples/title_chain.py b/examples/title_chain.py index 67a55c0..6838e22 100644 --- a/examples/title_chain.py +++ b/examples/title_chain.py @@ -60,4 +60,4 @@ if __name__ == '__main__': import sys title = ' '.join(sys.argv[1:]) make_chain(sys.argv[1].lower()) - + diff --git a/examples/user_public_playlists.py b/examples/user_public_playlists.py index 3557fc3..808be67 100644 --- a/examples/user_public_playlists.py +++ b/examples/user_public_playlists.py @@ -1,6 +1,6 @@ # Gets all the public playlists for the given # user. Uses Client Credentials flow -# +# import sys import spotipy diff --git a/spotipy/client.py b/spotipy/client.py index 88e02d1..3f57251 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -389,7 +389,7 @@ class Spotify(object): def playlist(self, playlist_id, fields=None, market=None): """ Gets playlist by id - + Parameters: - playlist - the id of the playlist - fields - which fields to return @@ -400,7 +400,7 @@ class Spotify(object): return self._get("playlists/%s" % (plid), fields=fields) - + def user_playlist_tracks(self, user, playlist_id=None, fields=None, limit=100, offset=0, market=None): """ Get full details of the tracks of a playlist owned by a user. @@ -546,9 +546,11 @@ class Spotify(object): Parameters: - user - the id of the user - playlist_id - the id of the playlist - - tracks - an array of objects containing Spotify URIs of the tracks to remove with their current positions in the playlist. For example: - [ { "uri":"4iV5W9uYEdYUVa79Axb7Rh", "positions":[2] }, - { "uri":"1301WleyT98MSxVHPZCA6M", "positions":[7] } ] + - tracks - an array of objects containing Spotify URIs of the + tracks to remove with their current positions in the + playlist. For example: + [ { "uri":"4iV5W9uYEdYUVa79Axb7Rh", "positions":[2] }, + { "uri":"1301WleyT98MSxVHPZCA6M", "positions":[7] } ] - snapshot_id - optional id of the playlist snapshot """ @@ -583,7 +585,8 @@ class Spotify(object): Parameters: - playlist_owner_id - the user id of the playlist owner - playlist_id - the id of the playlist - - user_ids - the ids of the users that you want to check to see if they follow the playlist. Maximum: 5 ids. + - user_ids - the ids of the users that you want to check to see + if they follow the playlist. Maximum: 5 ids. """ return self._get("users/{}/playlists/{}/followers/contains?ids={}".format(playlist_owner_id, playlist_id, ','.join(user_ids))) @@ -705,7 +708,7 @@ class Spotify(object): Parameters: - limit - the number of entities to return - ''' + ''' return self._get('me/player/recently-played', limit=limit) def current_user_saved_albums_delete(self, albums=[]): @@ -760,7 +763,7 @@ class Spotify(object): - ids - a list of artist IDs ''' return self._delete('me/following?type=artist&ids=' + ','.join(ids)) - + def user_unfollow_users(self, ids=[]): ''' Unfollow one or more users Parameters: diff --git a/spotipy/util.py b/spotipy/util.py index 3eb1c52..0bf9fab 100644 --- a/spotipy/util.py +++ b/spotipy/util.py @@ -25,7 +25,7 @@ CLIENT_CREDS_ENV_VARS = { def prompt_for_user_token(username, scope=None, client_id = None, client_secret = None, redirect_uri = None, cache_path = None): ''' prompts the user to login if necessary and returns - the user token suitable for use with the spotipy.Spotify + the user token suitable for use with the spotipy.Spotify constructor Parameters: @@ -57,13 +57,13 @@ def prompt_for_user_token(username, scope=None, client_id = None, export SPOTIPY_CLIENT_SECRET='your-spotify-client-secret' export SPOTIPY_REDIRECT_URI='your-app-redirect-url' - Get your credentials at + Get your credentials at https://developer.spotify.com/my-applications ''') raise spotipy.SpotifyException(550, -1, 'no credentials set') cache_path = cache_path or ".cache-" + username - sp_oauth = oauth2.SpotifyOAuth(client_id, client_secret, redirect_uri, + sp_oauth = oauth2.SpotifyOAuth(client_id, client_secret, redirect_uri, scope=scope, cache_path=cache_path) # try to get a valid token for this user, from the cache, @@ -98,7 +98,7 @@ def prompt_for_user_token(username, scope=None, client_id = None, response = input("Enter the URL you were redirected to: ") print() - print() + print() code = sp_oauth.parse_response_code(response) token_info = sp_oauth.get_access_token(code) From 3cd252b4c9d04b1f3fe3ee6628ac49f9f80aefdc Mon Sep 17 00:00:00 2001 From: Harrison Date: Mon, 12 Aug 2019 18:30:48 -0500 Subject: [PATCH 46/71] update CHANGES.txt --- CHANGES.txt | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index a28e173..7e644d0 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,17 +1,25 @@ -v1.40, June 12, 2014 -- Initial public release. -v1.42, June 19, 2014 -- Removed dependency on simplejson -v1.43, June 27, 2014 -- Fixed JSON handling issue -v1.44, July 3, 2014 -- Added show_tracks.py exampole -v1.45, July 7, 2014 -- Support for related artists endpoint. Don't use - cache auth codes when scope changes -v1.50, August 14, 2014 -- Refactored util out of examples and into the main - package -v2.301, August 19, 2014 -- Upgraded version number to take precedence over - previously botched release (sigh) -v2.310, August 20, 2014 -- Added playlist replace and remove methods. Added auth - tests. Improved API docs -v2.310, January 5, 2015 -- Added session support -v2.3.1, March 28, 2015 -- Auto retry support -v2.3.5, April 28, 2015 -- Fixed bug in auto retry support -v2.3.6, June 3, 2015 -- Support for offset/limit with album_tracks API -v2.5.0 - January 11, 2020 -- Added follow and player endpoints + +v1.40, June 12, 2014 -- Initial public release. +v1.42, June 19, 2014 -- Removed dependency on simplejson +v1.43, June 27, 2014 -- Fixed JSON handling issue +v1.44, July 3, 2014 -- Added show_tracks.py exampole +v1.45, July 7, 2014 -- Support for related artists endpoint. Don't use + cache auth codes when scope changes +v1.50, August 14, 2014 -- Refactored util out of examples and into the main + package +v2.301, August 19, 2014 -- Upgraded version number to take precedence over + previously botched release (sigh) +v2.310, August 20, 2014 -- Added playlist replace and remove methods. Added auth + tests. Improved API docs +v2.310, January 5, 2015 -- Added session support +v2.3.1, March 28, 2015 -- Auto retry support +v2.3.5, April 28, 2015 -- Fixed bug in auto retry support +v2.3.6, June 3, 2015 -- Support for offset/limit with album_tracks API +v2.3.7, August 10, 2015 -- Added current_user_followed_artists +v2.3.8, March 30, 2016 -- Added recs, audio features, user top lists +v2.4.0, December 31, 2016 -- Incorporated a number of PRs +v2.4.1, January 2, 2017 -- Incorporated proxy support +v2.4.2, January 2, 2017 -- support getting audio features for a single track +v2.4.3, January 2, 2017 -- fixed proxy issue in standard auth flow +v2.4.4, January 4, 2017 -- python 3 fix +v2.4.5, August 12, 2019 -- Incorporated a number of PRs \ No newline at end of file From 816dd234826884b8822f8680e6e403c67aec2187 Mon Sep 17 00:00:00 2001 From: Harrison Date: Mon, 12 Aug 2019 18:41:15 -0500 Subject: [PATCH 47/71] remove unneccessary whitespaces at end of lines --- deploy | 4 ++-- tests/test_auth.py | 16 ++++++++-------- tests/test_auth2.py | 8 ++++---- tests/tests.py | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/deploy b/deploy index f18302a..ea1b5aa 100755 --- a/deploy +++ b/deploy @@ -1,6 +1,6 @@ # # sudo python setup.py develop --uninstall -# sudo python setup.py install +# sudo python setup.py install # How do deploy # - run tests @@ -9,7 +9,7 @@ # - Update README.md with updated version info # - leave development mode # sudo python setup.py develop --uninstall -# sudo python setup.py install +# sudo python setup.py install # - upload dist # sudo python setup.py sdist upload # docs should automatically be updated. verify them at diff --git a/tests/test_auth.py b/tests/test_auth.py index e72807b..d921aff 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -45,15 +45,15 @@ class AuthTestSpotipy(unittest.TestCase): """ playlist = "spotify:user:plamere:playlist:2oCEWyyAPbZp9xhVSxZavx" - four_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp", + four_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp", "spotify:track:7IHOIqZUUInxjVkko181PB", - "4VrWlk8IQxevMvERoX08iC", + "4VrWlk8IQxevMvERoX08iC", "http://open.spotify.com/track/3cySlItpiPiIAzU3NyHCJf"] - two_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp", + two_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp", "spotify:track:7IHOIqZUUInxjVkko181PB"] - other_tracks=["spotify:track:2wySlB6vMzCbQrRnNGOYKa", + other_tracks=["spotify:track:2wySlB6vMzCbQrRnNGOYKa", "spotify:track:29xKs5BAHlmlX1u4gzQAbJ", "spotify:track:1PB7gRWcvefzu7t3LJLUlf"] @@ -115,7 +115,7 @@ class AuthTestSpotipy(unittest.TestCase): results = self.spotify.user_playlist_tracks(user, pid) self.assertTrue(len(results['items']) >= 0) - def user_playlist_tracks(self, user, playlist_id = None, fields=None, + def user_playlist_tracks(self, user, playlist_id = None, fields=None, limit=100, offset=0): # known API issue currently causes this test to fail @@ -176,7 +176,7 @@ class AuthTestSpotipy(unittest.TestCase): cat_id = cat['id'] response = self.spotify.category_playlists(category_id=cat_id) self.assertTrue(len(response['playlists']["items"]) > 0) - + def test_new_releases(self): response = self.spotify.new_releases() self.assertTrue(len(response['albums']) > 0) @@ -232,7 +232,7 @@ class AuthTestSpotipy(unittest.TestCase): # remove two tracks from it - self.spotify.user_playlist_remove_all_occurrences_of_tracks (self.username, + self.spotify.user_playlist_remove_all_occurrences_of_tracks (self.username, playlist_id, self.two_tracks) playlist = self.spotify.user_playlist(self.username, playlist_id) @@ -240,7 +240,7 @@ class AuthTestSpotipy(unittest.TestCase): self.assertTrue(len(playlist['tracks']['items']) == 2) # replace with 3 other tracks - self.spotify.user_playlist_replace_tracks(self.username, + self.spotify.user_playlist_replace_tracks(self.username, playlist_id, self.other_tracks) playlist = self.spotify.user_playlist(self.username, playlist_id) diff --git a/tests/test_auth2.py b/tests/test_auth2.py index d68667d..e52092a 100644 --- a/tests/test_auth2.py +++ b/tests/test_auth2.py @@ -41,15 +41,15 @@ class AuthTestSpotipy(unittest.TestCase): """ playlist = "spotify:user:plamere:playlist:2oCEWyyAPbZp9xhVSxZavx" - four_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp", + four_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp", "spotify:track:7IHOIqZUUInxjVkko181PB", - "4VrWlk8IQxevMvERoX08iC", + "4VrWlk8IQxevMvERoX08iC", "http://open.spotify.com/track/3cySlItpiPiIAzU3NyHCJf"] - two_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp", + two_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp", "spotify:track:7IHOIqZUUInxjVkko181PB"] - other_tracks=["spotify:track:2wySlB6vMzCbQrRnNGOYKa", + other_tracks=["spotify:track:2wySlB6vMzCbQrRnNGOYKa", "spotify:track:29xKs5BAHlmlX1u4gzQAbJ", "spotify:track:1PB7gRWcvefzu7t3LJLUlf"] diff --git a/tests/tests.py b/tests/tests.py index 63ab860..a968494 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -76,7 +76,7 @@ class TestSpotipy(unittest.TestCase): def test_album_tracks(self): results = self.spotify.album_tracks(self.pinkerton_urn) self.assertTrue(len(results['items']) == 10) - + def test_album_tracks_many(self): results = self.spotify.album_tracks(self.angeles_haydn_urn) tracks = results['items'] From c610090a7c93852cfc50a3dbf91cdbff3adf1166 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Mon, 12 Aug 2019 16:02:59 -0700 Subject: [PATCH 48/71] Upload user_saved_albums_delete.py --- examples/user_saved_albums_delete.py | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 examples/user_saved_albums_delete.py diff --git a/examples/user_saved_albums_delete.py b/examples/user_saved_albums_delete.py new file mode 100644 index 0000000..e1d4bb4 --- /dev/null +++ b/examples/user_saved_albums_delete.py @@ -0,0 +1,30 @@ +""" + Deletes user saved album + +""" + +import pprint +import sys +import json +import spotipy +import spotipy.util as util + +if len(sys.argv) > 1: + username = sys.argv[1] +else: + print("Usage: %s username" % (sys.argv[0],)) + sys.exit() + +scope = 'user-library-modify' +token = util.prompt_for_user_token(username, scope) + +if token: + sp = spotipy.Spotify(auth=token) + sp.trace = False + 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.") + +else: + print("Can't get token for", username) From 2aa67580a1ae0630618d9f374f258fe8f6a46bc3 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Mon, 12 Aug 2019 16:03:29 -0700 Subject: [PATCH 49/71] Add current_user_saved_albums_delete method --- spotipy/client.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/spotipy/client.py b/spotipy/client.py index 3f57251..3c556d3 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -608,7 +608,7 @@ class Spotify(object): ''' return self._get('me/player/currently-playing') - def current_user_saved_albums(self, limit=600, offset=0): + def current_user_saved_albums(self, limit=20, offset=0): """ Gets a list of the albums saved in the current authorized user's "Your Music" library @@ -619,6 +619,18 @@ class Spotify(object): """ return self._get('me/albums', limit=limit, offset=offset) + def current_user_saved_albums_delete(self, albums=None): + """ Remove one or more albums from the current user's + "Your Music" library. + + Parameters: + - albums - a list of album URIs, URLs or IDs + """ + alist = [] + if albums is not None: + alist = [self._get_id('album', a) for a in albums] + return self._delete('me/albums/?ids=' + ','.join(alist)) + def current_user_saved_tracks(self, limit=20, offset=0): """ Gets a list of the tracks saved in the current authorized user's "Your Music" library From bafef6a175116aff519579822f2382e8fbbd8808 Mon Sep 17 00:00:00 2001 From: Chuan-Zheng Lee Date: Sun, 1 Dec 2019 19:11:09 -0800 Subject: [PATCH 50/71] Make import statements explicit relative imports --- spotipy/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spotipy/__init__.py b/spotipy/__init__.py index ac0f074..b218c0a 100644 --- a/spotipy/__init__.py +++ b/spotipy/__init__.py @@ -1,5 +1,5 @@ VERSION='2.4.5' -from client import * -from oauth2 import * -from util import * +from .client import * +from .oauth2 import * +from .util import * From eec44550fc91e238fe85edbe5cbe6dd2b071a682 Mon Sep 17 00:00:00 2001 From: Harrison Date: Mon, 16 Dec 2019 20:36:53 -0600 Subject: [PATCH 51/71] add simple json to install requirements --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0dde392..97f74fe 100644 --- a/setup.py +++ b/setup.py @@ -12,6 +12,7 @@ setup( 'mock>=2.0.0', 'requests>=2.3.0', 'six>=1.10.0', - ], + 'simplejson==3.13.2', + ], license='LICENSE.txt', packages=['spotipy']) From 62277ac0a8b7b4b9c6c978c73d699135397e9674 Mon Sep 17 00:00:00 2001 From: Harrison Date: Mon, 16 Dec 2019 20:41:36 -0600 Subject: [PATCH 52/71] cast filterObject as list and change request timeout --- tests/test_auth.py | 6 ++++-- tests/tests.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/test_auth.py b/tests/test_auth.py index d921aff..f295e34 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -62,7 +62,7 @@ class AuthTestSpotipy(unittest.TestCase): @classmethod def setUpClass(self): - missing = filter(lambda var: not os.getenv(CCEV[var]), CCEV) + missing = list(filter(lambda var: not os.getenv(CCEV[var]), CCEV)) if missing: raise Exception('Please set the client credentials for the test application using the following environment variables: {}'.format(CCEV.values())) @@ -175,7 +175,9 @@ class AuthTestSpotipy(unittest.TestCase): for cat in response['categories']['items']: cat_id = cat['id'] response = self.spotify.category_playlists(category_id=cat_id) - self.assertTrue(len(response['playlists']["items"]) > 0) + if len(response['playlists']["items"]) > 0: + break + self.assertTrue(True) def test_new_releases(self): response = self.spotify.new_releases() diff --git a/tests/tests.py b/tests/tests.py index a968494..4cd90df 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -47,7 +47,7 @@ class TestSpotipy(unittest.TestCase): @classmethod def setUpClass(self): - missing = filter(lambda var: not os.getenv(CCEV[var]), CCEV) + missing = list(filter(lambda var: not os.getenv(CCEV[var]), CCEV)) if missing: raise Exception('Please set the client credentials for the test application using the following environment variables: {}'.format(CCEV.values())) @@ -156,7 +156,7 @@ class TestSpotipy(unittest.TestCase): self.assertTrue(found) def test_search_timeout(self): - sp = Spotify(auth=self.token, requests_timeout=.1) + sp = Spotify(auth=self.token, requests_timeout=.01) try: results = sp.search(q='my*', type='track') self.assertTrue(False, 'unexpected search timeout') From 6ab96c3c91b28f60d2822e4fe3bc70cc353352a1 Mon Sep 17 00:00:00 2001 From: Stephane Bruckert Date: Sat, 11 Jan 2020 23:30:57 +0000 Subject: [PATCH 53/71] Clean up + missing tests --- .gitignore | 4 +- CHANGELOG.md | 30 ++++++++++ CHANGES.txt | 25 -------- README.md | 35 +----------- deploy | 3 +- examples/replace_tracks_in_playlist.py | 1 - spotipy/client.py | 61 +++++++------------- tests/test_auth.py | 79 ++++++++++++++++++++------ 8 files changed, 116 insertions(+), 122 deletions(-) create mode 100644 CHANGELOG.md delete mode 100644 CHANGES.txt diff --git a/.gitignore b/.gitignore index d88e85e..137335b 100644 --- a/.gitignore +++ b/.gitignore @@ -52,7 +52,5 @@ coverage.xml # Sphinx documentation docs/_build/ - .* -archive -*.sh +archive \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2a4e430 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,30 @@ +- 1.0 - 04/05/2014 - Initial release +- 1.1 - 05/18/2014 - Repackaged for saner imports +- 1.4.1 - 06/17/2014 - Updates to match released API +- 1.4.2 - 06/21/2014 - Added support for retrieving starred playlists +- v1.40, June 12, 2014 -- Initial public release. +- v1.42, June 19, 2014 -- Removed dependency on simplejson +- v1.43, June 27, 2014 -- Fixed JSON handling issue +- v1.44, July 3, 2014 -- Added show tracks.py example +- v1.45, July 7, 2014 -- Support for related artists endpoint. Don't use cache auth codes when scope changes +- v1.49, July 23, 2014 -- Support for "Your Music" tracks (add, delete, get), with examples +- v1.50, August 14, 2014 -- Refactored util out of examples and into the main package +- v1.301, August 19, 2014 -- Upgraded version number to take precedence over previously botched release (sigh) +- v1.310, August 20, 2014 -- Added playlist replace and remove methods. Added auth tests. Improved API docs +- v2.0 - August 22, 2014 -- Upgraded APIs and docs to make it be a real library +- v2.0.2 - August 25, 2014 -- Moved to spotipy at pypi +- v2.1.0 - October 25, 2014 -- Added support for new_releases and featured_playlists +- v2.2.0 - November 15, 2014 -- Added support for user_playlist_tracks +- v2.3.0 - January 5, 2015 -- Added session support added by akx. +- v2.3.2 - March 31, 2015 -- Added auto retry logic +- v2.3.3 - April 1, 2015 -- added client credential flow +- v2.3.5 - April 28, 2015 -- Fixed bug in auto retry logic +- v2.3.6 - June 3, 2015 -- Support for offset/limit with album_tracks API +- v2.3.7 - August 10, 2015 -- Added current_user_followed_artists +- v2.3.8 - March 30, 2016 -- Added recs, audio features, user top lists +- v2.4.0 - December 31, 2016 -- Incorporated a number of PRs +- v2.4.1 - January 2, 2017 -- Incorporated proxy support +- v2.4.2 - January 2, 2017 -- support getting audio features for a single track +- v2.4.3 - January 2, 2017 -- fixed proxy issue in standard auth flow +- v2.4.4 - January 4, 2017 -- python 3 fix +- v2.5.0 - January 11, 2020 -- Added follow and player endpoints diff --git a/CHANGES.txt b/CHANGES.txt deleted file mode 100644 index 7e644d0..0000000 --- a/CHANGES.txt +++ /dev/null @@ -1,25 +0,0 @@ - -v1.40, June 12, 2014 -- Initial public release. -v1.42, June 19, 2014 -- Removed dependency on simplejson -v1.43, June 27, 2014 -- Fixed JSON handling issue -v1.44, July 3, 2014 -- Added show_tracks.py exampole -v1.45, July 7, 2014 -- Support for related artists endpoint. Don't use - cache auth codes when scope changes -v1.50, August 14, 2014 -- Refactored util out of examples and into the main - package -v2.301, August 19, 2014 -- Upgraded version number to take precedence over - previously botched release (sigh) -v2.310, August 20, 2014 -- Added playlist replace and remove methods. Added auth - tests. Improved API docs -v2.310, January 5, 2015 -- Added session support -v2.3.1, March 28, 2015 -- Auto retry support -v2.3.5, April 28, 2015 -- Fixed bug in auto retry support -v2.3.6, June 3, 2015 -- Support for offset/limit with album_tracks API -v2.3.7, August 10, 2015 -- Added current_user_followed_artists -v2.3.8, March 30, 2016 -- Added recs, audio features, user top lists -v2.4.0, December 31, 2016 -- Incorporated a number of PRs -v2.4.1, January 2, 2017 -- Incorporated proxy support -v2.4.2, January 2, 2017 -- support getting audio features for a single track -v2.4.3, January 2, 2017 -- fixed proxy issue in standard auth flow -v2.4.4, January 4, 2017 -- python 3 fix -v2.4.5, August 12, 2019 -- Incorporated a number of PRs \ No newline at end of file diff --git a/README.md b/README.md index 2add256..9c3151f 100644 --- a/README.md +++ b/README.md @@ -51,37 +51,4 @@ A full set of examples can be found in the [online documentation](http://spotipy ## Reporting Issues -If you have suggestions, bugs or other issues specific to this library, file them [here](https://github.com/plamere/spotipy/issues). Or just send me a pull request. - -## Version - -- 1.0 - 04/05/2014 - Initial release -- 1.1 - 05/18/2014 - Repackaged for saner imports -- 1.4.1 - 06/17/2014 - Updates to match released API -- 1.4.2 - 06/21/2014 - Added support for retrieving starred playlists -- v1.40, June 12, 2014 -- Initial public release. -- v1.42, June 19, 2014 -- Removed dependency on simplejson -- v1.43, June 27, 2014 -- Fixed JSON handling issue -- v1.44, July 3, 2014 -- Added show tracks.py example -- v1.45, July 7, 2014 -- Support for related artists endpoint. Don't use cache auth codes when scope changes -- v1.49, July 23, 2014 -- Support for "Your Music" tracks (add, delete, get), with examples -- v1.50, August 14, 2014 -- Refactored util out of examples and into the main package -- v1.301, August 19, 2014 -- Upgraded version number to take precedence over previously botched release (sigh) -- v1.310, August 20, 2014 -- Added playlist replace and remove methods. Added auth tests. Improved API docs -- v2.0 - August 22, 2014 -- Upgraded APIs and docs to make it be a real library -- v2.0.2 - August 25, 2014 -- Moved to spotipy at pypi -- v2.1.0 - October 25, 2014 -- Added support for new_releases and featured_playlists -- v2.2.0 - November 15, 2014 -- Added support for user_playlist_tracks -- v2.3.0 - January 5, 2015 -- Added session support added by akx. -- v2.3.2 - March 31, 2015 -- Added auto retry logic -- v2.3.3 - April 1, 2015 -- added client credential flow -- v2.3.5 - April 28, 2015 -- Fixed bug in auto retry logic -- v2.3.6 - June 3, 2015 -- Support for offset/limit with album_tracks API -- v2.3.7 - August 10, 2015 -- Added current_user_followed_artists -- v2.3.8 - March 30, 2016 -- Added recs, audio features, user top lists -- v2.4.0 - December 31, 2016 -- Incorporated a number of PRs -- v2.4.1 - January 2, 2017 -- Incorporated proxy support -- v2.4.2 - January 2, 2017 -- support getting audio features for a single track -- v2.4.3 - January 2, 2017 -- fixed proxy issue in standard auth flow -- v2.4.4 - January 4, 2017 -- python 3 fix -- v2.5.0 - January 11, 2020 -- Added follow and player endpoints +If you have suggestions, bugs or other issues specific to this library, file them [here](https://github.com/plamere/spotipy/issues). Or just send me a pull request. \ No newline at end of file diff --git a/deploy b/deploy index ea1b5aa..36d5c3f 100755 --- a/deploy +++ b/deploy @@ -15,5 +15,4 @@ # docs should automatically be updated. verify them at # http://spotipy.readthedocs.org/en/latest/ -sudo python setup.py sdist upload - +sudo python setup.py sdist upload \ No newline at end of file diff --git a/examples/replace_tracks_in_playlist.py b/examples/replace_tracks_in_playlist.py index 452d328..d7236cc 100644 --- a/examples/replace_tracks_in_playlist.py +++ b/examples/replace_tracks_in_playlist.py @@ -1,4 +1,3 @@ - # Replaces all tracks in a playlist import pprint diff --git a/spotipy/client.py b/spotipy/client.py index 3c556d3..273563c 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -40,7 +40,7 @@ class Spotify(object): import spotipy - urn = 'spotify:artist:3jOstUTkEu2JkjvRdBA5Gu' + urn = 'spotify:artist:3jOstUTkEu2JkjvRdBA5Gu' sp = spotipy.Spotify() sp.trace = True # turn on tracing @@ -395,9 +395,7 @@ class Spotify(object): - fields - which fields to return - market - An ISO 3166-1 alpha-2 country code or the string from_token. """ - plid = self._get_id('playlist', playlist_id) - return self._get("playlists/%s" % (plid), fields=fields) @@ -608,29 +606,6 @@ class Spotify(object): ''' return self._get('me/player/currently-playing') - def current_user_saved_albums(self, limit=20, offset=0): - """ Gets a list of the albums saved in the current authorized user's - "Your Music" library - - Parameters: - - limit - the number of albums to return - - offset - the index of the first album to return - - """ - return self._get('me/albums', limit=limit, offset=offset) - - def current_user_saved_albums_delete(self, albums=None): - """ Remove one or more albums from the current user's - "Your Music" library. - - Parameters: - - albums - a list of album URIs, URLs or IDs - """ - alist = [] - if albums is not None: - alist = [self._get_id('album', a) for a in albums] - return self._delete('me/albums/?ids=' + ','.join(alist)) - def current_user_saved_tracks(self, limit=20, offset=0): """ Gets a list of the tracks saved in the current authorized user's "Your Music" library @@ -646,8 +621,8 @@ class Spotify(object): """ Gets a list of the artists followed by the current authorized user Parameters: - - limit - the number of tracks to return - - after - ghe last artist ID retrieved from the previous request + - limit - the number of artists to return + - after - the last artist ID retrieved from the previous request """ return self._get('me/following', type='artist', limit=limit, @@ -723,16 +698,16 @@ class Spotify(object): ''' return self._get('me/player/recently-played', limit=limit) - def current_user_saved_albums_delete(self, albums=[]): - """ Remove one or more albums from the current user's - "Your Music" library. + def current_user_saved_albums(self, limit=20, offset=0): + """ Gets a list of the albums saved in the current authorized user's + "Your Music" library Parameters: - - albums - a list of album URIs, URLs or IDs + - limit - the number of albums to return + - offset - the index of the first album to return + """ - alist = [self._get_id('album', a) for a in albums] - r = self._delete('me/albums/?ids=' + ','.join(alist)) - return r + return self._get('me/albums', limit=limit, offset=offset) def current_user_saved_albums_contains(self, albums=[]): """ Check if one or more albums is already saved in @@ -742,8 +717,7 @@ class Spotify(object): - albums - a list of album URIs, URLs or IDs """ alist = [self._get_id('album', a) for a in albums] - r = self._get('me/albums/contains?ids=' + ','.join(alist)) - return r + return self._get('me/albums/contains?ids=' + ','.join(alist)) def current_user_saved_albums_add(self, albums=[]): """ Add one or more albums to the current user's @@ -752,8 +726,17 @@ class Spotify(object): - albums - a list of album URIs, URLs or IDs """ alist = [self._get_id('album', a) for a in albums] - r = self._put('me/albums?ids=' + ','.join(alist)) - return r + 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 + "Your Music" library. + + Parameters: + - albums - a list of album URIs, URLs or IDs + """ + alist = [self._get_id('album', a) for a in albums] + return self._delete('me/albums/?ids=' + ','.join(alist)) def user_follow_artists(self, ids=[]): ''' Follow one or more artists diff --git a/tests/test_auth.py b/tests/test_auth.py index f295e34..66e2ab4 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -15,7 +15,7 @@ following environment variables from __future__ import print_function import os -import pprint +from pprint import pprint import sys import unittest @@ -45,6 +45,7 @@ class AuthTestSpotipy(unittest.TestCase): """ playlist = "spotify:user:plamere:playlist:2oCEWyyAPbZp9xhVSxZavx" + playlist_new_id = "spotify:playlist:7GlxpQjjxRjmbb3RP2rDqI" four_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp", "spotify:track:7IHOIqZUUInxjVkko181PB", "4VrWlk8IQxevMvERoX08iC", @@ -57,8 +58,12 @@ class AuthTestSpotipy(unittest.TestCase): "spotify:track:29xKs5BAHlmlX1u4gzQAbJ", "spotify:track:1PB7gRWcvefzu7t3LJLUlf"] + album_ids = ["spotify:album:6kL09DaURb7rAoqqaA51KU", + "spotify:album:6RTzC0rDbvagTSJLlY7AKl"] + bad_id = 'BAD_ID' + @classmethod def setUpClass(self): @@ -75,7 +80,8 @@ class AuthTestSpotipy(unittest.TestCase): 'user-follow-read ' 'user-library-modify ' 'user-read-private ' - 'user-top-read' + 'user-top-read ' + 'user-follow-modify' ) self.token = prompt_for_user_token(self.username, scope=self.scope) @@ -84,7 +90,7 @@ class AuthTestSpotipy(unittest.TestCase): def test_track_bad_id(self): try: - track = self.spotify.track(self.bad_id) + self.spotify.track(self.bad_id) self.assertTrue(False) except SpotifyException: self.assertTrue(True) @@ -120,17 +126,25 @@ class AuthTestSpotipy(unittest.TestCase): # known API issue currently causes this test to fail # the issue is that the API doesn't currently respect the - # limit paramter + # limit parameter self.assertTrue(len(playlists['items']) == 5) - def test_current_user_saved_tracks(self): - tracks = self.spotify.current_user_saved_tracks() - self.assertTrue(len(tracks['items']) > 0) - def test_current_user_saved_albums(self): + # List albums = self.spotify.current_user_saved_albums() - self.assertTrue(len(albums['items']) > 0) + self.assertTrue(len(albums['items']) == 1) + + # Add + self.spotify.current_user_saved_albums_add(self.album_ids) + + # Contains + self.assertTrue(self.spotify.current_user_saved_albums_contains(self.album_ids) == [True, True]) + + # Remove + self.spotify.current_user_saved_albums_delete(self.album_ids) + albums = self.spotify.current_user_saved_albums() + self.assertTrue(len(albums['items']) == 1) def test_current_user_playlists(self): playlists = self.spotify.current_user_playlists(limit=10) @@ -149,11 +163,13 @@ class AuthTestSpotipy(unittest.TestCase): self.assertTrue(len(follows) == 1, 'proper follows length') self.assertFalse(follows[0], 'is no longer following') + def test_current_user_saved_tracks(self): + tracks = self.spotify.current_user_saved_tracks() + self.assertTrue(len(tracks['items']) > 0) def test_current_user_save_and_unsave_tracks(self): tracks = self.spotify.current_user_saved_tracks() total = tracks['total'] - self.spotify.current_user_saved_tracks_add(self.four_tracks) tracks = self.spotify.current_user_saved_tracks() @@ -165,7 +181,6 @@ class AuthTestSpotipy(unittest.TestCase): new_total = tracks['total'] self.assertTrue(new_total == total) - def test_categories(self): response = self.spotify.categories() self.assertTrue(len(response['categories']) > 0) @@ -218,25 +233,20 @@ class AuthTestSpotipy(unittest.TestCase): playlist_id = self.get_or_create_spotify_playlist('spotipy-testing-playlist-1') # remove all tracks from it - self.spotify.user_playlist_replace_tracks(self.username, playlist_id,[]) - playlist = self.spotify.user_playlist(self.username, playlist_id) self.assertTrue(playlist['tracks']['total'] == 0) self.assertTrue(len(playlist['tracks']['items']) == 0) # add tracks to it - self.spotify.user_playlist_add_tracks(self.username, playlist_id, self.four_tracks) playlist = self.spotify.user_playlist(self.username, playlist_id) self.assertTrue(playlist['tracks']['total'] == 4) self.assertTrue(len(playlist['tracks']['items']) == 4) # remove two tracks from it - self.spotify.user_playlist_remove_all_occurrences_of_tracks (self.username, playlist_id, self.two_tracks) - playlist = self.spotify.user_playlist(self.username, playlist_id) self.assertTrue(playlist['tracks']['total'] == 2) self.assertTrue(len(playlist['tracks']['items']) == 2) @@ -244,11 +254,44 @@ class AuthTestSpotipy(unittest.TestCase): # replace with 3 other tracks self.spotify.user_playlist_replace_tracks(self.username, playlist_id, self.other_tracks) - playlist = self.spotify.user_playlist(self.username, playlist_id) self.assertTrue(playlist['tracks']['total'] == 3) self.assertTrue(len(playlist['tracks']['items']) == 3) -if __name__ == '__main__': + def test_playlist(self): + # New playlist ID + pl = self.spotify.playlist(self.playlist_new_id) + self.assertTrue(pl["tracks"]["total"] > 0) + # Old playlist ID + pl = self.spotify.playlist(self.playlist) + self.assertTrue(pl["tracks"]["total"] > 0) + + def test_user_follows_and_unfollows_artist(self): + # Initially follows 1 artist + res = self.spotify.current_user_followed_artists() + self.assertTrue(res['artists']['total'] == 1) + + # Follow 2 more artists + artists = ["6DPYiyq5kWVQS4RGwxzPC7", "0NbfKEOTQCcwd6o7wSDOHI"] + self.spotify.user_follow_artists(artists) + res = self.spotify.current_user_followed_artists() + self.assertTrue(res['artists']['total'] == 3) + + # Unfollow these 2 artists + self.spotify.user_unfollow_artists(artists) + res = self.spotify.current_user_followed_artists() + self.assertTrue(res['artists']['total'] == 1) + + def test_user_follows_and_unfollows_user(self): + # TODO improve after implementing `me/following/contains` + users = ["11111204", "xlqeojt6n7on0j7coh9go8ifd"] + + # Follow 2 more users + self.spotify.user_follow_users(users) + + # Unfollow these 2 users + self.spotify.user_unfollow_users(users) + +if __name__ == '__main__': unittest.main() From 5afaf8f100815ded88a2b6e866116c00d38b29a5 Mon Sep 17 00:00:00 2001 From: Stephane Bruckert Date: Sat, 11 Jan 2020 23:37:40 +0000 Subject: [PATCH 54/71] Changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a4e430..9a2fd91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,3 +28,10 @@ - v2.4.3 - January 2, 2017 -- fixed proxy issue in standard auth flow - v2.4.4 - January 4, 2017 -- python 3 fix - v2.5.0 - January 11, 2020 -- Added follow and player endpoints +- Unreleased + - Added: + - support for `playlist()` + - support for `current_user_saved_albums_delete()` + - support for `current_user_saved_albums_contains()` + - support for `user_unfollow_artists()` + - support for `user_unfollow_users()` From 36f8e1d4659fe24c4052abb6575591f781d423e1 Mon Sep 17 00:00:00 2001 From: Stephane Bruckert Date: Sun, 12 Jan 2020 00:04:40 +0000 Subject: [PATCH 55/71] Remove unused VERSION from init --- spotipy/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/spotipy/__init__.py b/spotipy/__init__.py index b218c0a..da17893 100644 --- a/spotipy/__init__.py +++ b/spotipy/__init__.py @@ -1,5 +1,3 @@ -VERSION='2.4.5' - from .client import * from .oauth2 import * -from .util import * +from .util import * \ No newline at end of file From c4b302b4e22e7d99bdb1498835b067ca277c1e78 Mon Sep 17 00:00:00 2001 From: Stephane Bruckert Date: Sun, 12 Jan 2020 00:22:57 +0000 Subject: [PATCH 56/71] Fix example --- examples/create_playlist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/create_playlist.py b/examples/create_playlist.py index 1736f98..6084108 100644 --- a/examples/create_playlist.py +++ b/examples/create_playlist.py @@ -24,7 +24,7 @@ if token: sp = spotipy.Spotify(auth=token) sp.trace = False playlists = sp.user_playlist_create(username, playlist_name, - playlist_description) + description=playlist_description) pprint.pprint(playlists) else: print("Can't get token for", username) From 5928981e3b7f39f44f0986186624733bb12aa081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bruckert?= Date: Sun, 12 Jan 2020 13:18:34 +0000 Subject: [PATCH 57/71] Add Github action to check lint --- .github/workflows/pythonapp.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/pythonapp.yml diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml new file mode 100644 index 0000000..43354b0 --- /dev/null +++ b/.github/workflows/pythonapp.yml @@ -0,0 +1,23 @@ +name: Python application + +on: [push, pull_request] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Set up Python 3.7 + uses: actions/setup-python@v1 + with: + python-version: 3.7 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Lint with flake8 + run: | + pip install flake8 + flake8 . --count --show-source --statistics From f54830e272bd401290979bc898fb817a2cea0c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bruckert?= Date: Sun, 12 Jan 2020 13:19:40 +0000 Subject: [PATCH 58/71] Add linter, solves #348 (#415) --- examples/artist_albums.py | 5 +- examples/artist_discography.py | 7 +- examples/artist_recommendations.py | 10 +- examples/audio_features.py | 2 +- examples/audio_features_for_track.py | 2 +- examples/change_playlist_details.py | 6 +- examples/create_playlist.py | 4 +- examples/errors.py | 3 +- examples/my_playlists.py | 2 +- examples/my_top_artists.py | 8 +- examples/my_top_tracks.py | 8 +- examples/read_a_playlist.py | 2 +- .../remove_specific_tracks_from_playlist.py | 7 +- examples/remove_tracks_from_playlist.py | 3 +- examples/show_related.py | 3 +- examples/show_tracks.py | 3 - examples/show_user.py | 1 - examples/simple1.py | 1 - examples/simple3.py | 1 - examples/title_chain.py | 16 +- examples/user_playlists_contents.py | 9 +- examples/user_public_playlists.py | 8 +- examples/user_saved_albums_delete.py | 2 - examples/user_starred_playlist.py | 1 - setup.py | 7 +- spotipy/__init__.py | 6 +- spotipy/client.py | 143 +++++++++++------- spotipy/oauth2.py | 46 +++--- spotipy/util.py | 10 +- tests/test_auth.py | 107 +++++++------ tests/test_auth2.py | 46 +++--- tests/test_client_credentials.py | 17 +-- tests/test_oauth.py | 4 +- tests/tests.py | 67 ++++---- tox.ini | 5 + 35 files changed, 324 insertions(+), 248 deletions(-) diff --git a/examples/artist_albums.py b/examples/artist_albums.py index 4213985..8cb53db 100644 --- a/examples/artist_albums.py +++ b/examples/artist_albums.py @@ -4,6 +4,7 @@ import spotipy ''' shows the albums and tracks for a given artist. ''' + def get_artist(name): results = sp.search(q='artist:' + name, type='artist') items = results['artists']['items'] @@ -12,6 +13,7 @@ def get_artist(name): else: return None + def show_artist_albums(artist): albums = [] results = sp.artist_albums(artist['id'], album_type='album') @@ -19,7 +21,7 @@ def show_artist_albums(artist): while results['next']: results = sp.next(results) albums.extend(results['items']) - seen = set() # to avoid dups + seen = set() # to avoid dups albums.sort(key=lambda album: album['name'].lower()) for album in albums: name = album['name'] @@ -27,6 +29,7 @@ def show_artist_albums(artist): print((' ' + name)) seen.add(name) + if __name__ == '__main__': sp = spotipy.Spotify() diff --git a/examples/artist_discography.py b/examples/artist_discography.py index 3cd44f0..1f9c837 100644 --- a/examples/artist_discography.py +++ b/examples/artist_discography.py @@ -4,6 +4,7 @@ import spotipy ''' shows the albums and tracks for a given artist. ''' + def get_artist(name): results = sp.search(q='artist:' + name, type='artist') items = results['artists']['items'] @@ -12,6 +13,7 @@ def get_artist(name): else: return None + def show_album_tracks(album): tracks = [] results = sp.album_tracks(album['id']) @@ -24,6 +26,7 @@ def show_album_tracks(album): print() print(track) + def show_artist_albums(id): albums = [] results = sp.artist_albums(artist['id'], album_type='album') @@ -35,17 +38,19 @@ def show_artist_albums(id): unique = set() # skip duplicate albums for album in albums: name = album['name'].lower() - if not name in unique: + if name not in unique: print(name) unique.add(name) show_album_tracks(album) + def show_artist(artist): print('====', artist['name'], '====') print('Popularity: ', artist['popularity']) if len(artist['genres']) > 0: print('Genres: ', ','.join(artist['genres'])) + if __name__ == '__main__': sp = spotipy.Spotify() sp.trace = False diff --git a/examples/artist_recommendations.py b/examples/artist_recommendations.py index 142cf49..ad0355c 100644 --- a/examples/artist_recommendations.py +++ b/examples/artist_recommendations.py @@ -7,7 +7,8 @@ import spotipy from spotipy.oauth2 import SpotifyClientCredentials client_credentials_manager = SpotifyClientCredentials() sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager) -sp.trace=False +sp.trace = False + def get_artist(name): results = sp.search(q='artist:' + name, type='artist') @@ -17,11 +18,12 @@ def get_artist(name): else: return None + def show_recommendations_for_artist(artist): - albums = [] results = sp.recommendations(seed_artists=[artist['id']]) for track in results['tracks']: - print track['name'], '-', track['artists'][0]['name'] + print(track['name'], '-', track['artists'][0]['name']) + if __name__ == '__main__': if len(sys.argv) < 2: @@ -32,4 +34,4 @@ if __name__ == '__main__': if artist: show_recommendations_for_artist(artist) else: - print "Can't find that artist", name + print("Can't find that artist", name) diff --git a/examples/audio_features.py b/examples/audio_features.py index 989c852..30caddb 100644 --- a/examples/audio_features.py +++ b/examples/audio_features.py @@ -11,7 +11,7 @@ import sys client_credentials_manager = SpotifyClientCredentials() sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager) -sp.trace=False +sp.trace = False if len(sys.argv) > 1: artist_name = ' '.join(sys.argv[1:]) diff --git a/examples/audio_features_for_track.py b/examples/audio_features_for_track.py index 0565298..9e156d3 100644 --- a/examples/audio_features_for_track.py +++ b/examples/audio_features_for_track.py @@ -12,7 +12,7 @@ import sys client_credentials_manager = SpotifyClientCredentials() sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager) -sp.trace=True +sp.trace = True if len(sys.argv) > 1: tids = sys.argv[1:] diff --git a/examples/change_playlist_details.py b/examples/change_playlist_details.py index 42044aa..b150213 100644 --- a/examples/change_playlist_details.py +++ b/examples/change_playlist_details.py @@ -25,7 +25,7 @@ if len(sys.argv) > 3: else: print("Usage: %s username playlist_id name [public collaborative " - "description]" % (sys.argv[0])) + "description]" % (sys.argv[0])) sys.exit() scope = 'playlist-modify-public playlist-modify-private' @@ -37,6 +37,6 @@ if token: results = sp.user_playlist_change_details( username, playlist_id, name=name, public=public, collaborative=collaborative, description=description) - print results + print(results) else: - print "Can't get token for", username + print("Can't get token for"), username diff --git a/examples/create_playlist.py b/examples/create_playlist.py index c3c65d1..0c4caf8 100644 --- a/examples/create_playlist.py +++ b/examples/create_playlist.py @@ -12,7 +12,9 @@ if len(sys.argv) > 2: playlist_name = sys.argv[2] playlist_description = sys.argv[3] else: - print("Usage: %s username playlist-name playlist-description" % (sys.argv[0],)) + print( + "Usage: %s username playlist-name playlist-description" % + (sys.argv[0],)) sys.exit() scope = "playlist-modify-public" diff --git a/examples/errors.py b/examples/errors.py index 546fcb4..217c4e7 100644 --- a/examples/errors.py +++ b/examples/errors.py @@ -7,7 +7,7 @@ import spotipy client_credentials_manager = SpotifyClientCredentials() sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager) -sp.trace=True +sp.trace = True try: print('bad call 0') bad_artist_call = sp.artist('spotify:artist:12341234') @@ -17,4 +17,3 @@ except spotipy.client.SpotifyException: print('bad call 1') bad_artist_call = sp.artist('spotify:artist:12341234') print('bad artist', bad_artist_call) - diff --git a/examples/my_playlists.py b/examples/my_playlists.py index 651eef4..bd04b91 100644 --- a/examples/my_playlists.py +++ b/examples/my_playlists.py @@ -19,6 +19,6 @@ if token: sp.trace = False results = sp.current_user_playlists(limit=50) for i, item in enumerate(results['items']): - print("%d %s" %(i, item['name'])) + print("%d %s" % (i, item['name'])) else: print("Can't get token for", username) diff --git a/examples/my_top_artists.py b/examples/my_top_artists.py index b4145ef..dc8b873 100644 --- a/examples/my_top_artists.py +++ b/examples/my_top_artists.py @@ -1,11 +1,9 @@ # Shows the top artists for a user -import pprint import sys import spotipy import spotipy.util as util -import simplejson as json if len(sys.argv) > 1: username = sys.argv[1] @@ -21,10 +19,10 @@ if token: sp.trace = False ranges = ['short_term', 'medium_term', 'long_term'] for range in ranges: - print ("range:", range) + print("range:", range) results = sp.current_user_top_artists(time_range=range, limit=50) for i, item in enumerate(results['items']): - print (i, item['name']) - print () + print(i, item['name']) + print() else: print("Can't get token for", username) diff --git a/examples/my_top_tracks.py b/examples/my_top_tracks.py index f1a96ea..29782c8 100644 --- a/examples/my_top_tracks.py +++ b/examples/my_top_tracks.py @@ -1,11 +1,9 @@ # Shows the top tracks for a user -import pprint import sys import spotipy import spotipy.util as util -import simplejson as json if len(sys.argv) > 1: username = sys.argv[1] @@ -21,11 +19,11 @@ if token: sp.trace = False ranges = ['short_term', 'medium_term', 'long_term'] for range in ranges: - print ("range:", range) + print("range:", range) results = sp.current_user_top_tracks(time_range=range, limit=50) for i, item in enumerate(results['items']): - print (i, item['name'], '//', item['artists'][0]['name']) - print () + print(i, item['name'], '//', item['artists'][0]['name']) + print() else: print("Can't get token for", username) diff --git a/examples/read_a_playlist.py b/examples/read_a_playlist.py index 6c0dc72..e38676f 100644 --- a/examples/read_a_playlist.py +++ b/examples/read_a_playlist.py @@ -10,4 +10,4 @@ username = uri.split(':')[2] playlist_id = uri.split(':')[4] results = sp.user_playlist(username, playlist_id) -print (json.dumps(results, indent=4)) +print(json.dumps(results, indent=4)) diff --git a/examples/remove_specific_tracks_from_playlist.py b/examples/remove_specific_tracks_from_playlist.py index e7b4695..b2fce66 100644 --- a/examples/remove_specific_tracks_from_playlist.py +++ b/examples/remove_specific_tracks_from_playlist.py @@ -15,7 +15,9 @@ if len(sys.argv) > 3: tid, pos = t_pos.split(',') track_ids.append({"uri": tid, "positions": [int(pos)]}) else: - print("Usage: %s username playlist_id track_id,pos track_id,pos ..." % (sys.argv[0],)) + print( + "Usage: %s username playlist_id track_id,pos track_id,pos ..." % + (sys.argv[0],)) sys.exit() scope = 'playlist-modify-public' @@ -24,7 +26,8 @@ token = util.prompt_for_user_token(username, scope) if token: sp = spotipy.Spotify(auth=token) sp.trace = False - results = sp.user_playlist_remove_specific_occurrences_of_tracks(username, playlist_id, track_ids) + results = sp.user_playlist_remove_specific_occurrences_of_tracks( + username, playlist_id, track_ids) pprint.pprint(results) else: print("Can't get token for", username) diff --git a/examples/remove_tracks_from_playlist.py b/examples/remove_tracks_from_playlist.py index 73a65c5..c4efa1b 100644 --- a/examples/remove_tracks_from_playlist.py +++ b/examples/remove_tracks_from_playlist.py @@ -20,7 +20,8 @@ token = util.prompt_for_user_token(username, scope) if token: sp = spotipy.Spotify(auth=token) sp.trace = False - results = sp.user_playlist_remove_all_occurrences_of_tracks(username, playlist_id, track_ids) + results = sp.user_playlist_remove_all_occurrences_of_tracks( + username, playlist_id, track_ids) pprint.pprint(results) else: print("Can't get token for", username) diff --git a/examples/show_related.py b/examples/show_related.py index 9be66b1..f8e9a65 100644 --- a/examples/show_related.py +++ b/examples/show_related.py @@ -22,6 +22,5 @@ try: print('Related artists for', name) for artist in related['artists']: print(' ', artist['name']) -except: +except BaseException: print("usage show_related.py [artist-name]") - diff --git a/examples/show_tracks.py b/examples/show_tracks.py index 79a5302..db9c4c5 100644 --- a/examples/show_tracks.py +++ b/examples/show_tracks.py @@ -19,6 +19,3 @@ if __name__ == '__main__': results = sp.tracks(tids[start: start + max_tracks_per_call]) for track in results['tracks']: print(track['name'] + ' - ' + track['artists'][0]['name']) - - - diff --git a/examples/show_user.py b/examples/show_user.py index 07b102f..8ce6fe9 100644 --- a/examples/show_user.py +++ b/examples/show_user.py @@ -14,4 +14,3 @@ sp = spotipy.Spotify() sp.trace = True user = sp.user(username) pprint.pprint(user) - diff --git a/examples/simple1.py b/examples/simple1.py index e9c0fb7..92cf482 100644 --- a/examples/simple1.py +++ b/examples/simple1.py @@ -14,4 +14,3 @@ while results['next']: for album in albums: print((album['name'])) - diff --git a/examples/simple3.py b/examples/simple3.py index 929c899..08777f2 100644 --- a/examples/simple3.py +++ b/examples/simple3.py @@ -15,4 +15,3 @@ items = results['artists']['items'] if len(items) > 0: artist = items[0] print(artist['name'], artist['images'][0]['url']) - diff --git a/examples/title_chain.py b/examples/title_chain.py index 5d6ae73..6251382 100644 --- a/examples/title_chain.py +++ b/examples/title_chain.py @@ -1,6 +1,5 @@ import spotipy import random -import simplejson as json ''' generates a list of songs where the first word in each subsequent song @@ -14,13 +13,14 @@ skiplist = set(['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: + 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 @@ -37,27 +37,29 @@ def find_songs_that_start_with_word(word): 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) + 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" + # 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'] + 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()) - diff --git a/examples/user_playlists_contents.py b/examples/user_playlists_contents.py index 5d49bb9..b6cb7d2 100644 --- a/examples/user_playlists_contents.py +++ b/examples/user_playlists_contents.py @@ -4,10 +4,13 @@ import sys import spotipy import spotipy.util as util + 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( + " %d %32.32s %s" % + (i, track['artists'][0]['name'], track['name'])) if __name__ == '__main__': @@ -28,7 +31,8 @@ if __name__ == '__main__': print() print(playlist['name']) print(' total tracks', playlist['tracks']['total']) - results = sp.user_playlist(username, playlist['id'], fields="tracks,next") + results = sp.user_playlist( + username, playlist['id'], fields="tracks,next") tracks = results['tracks'] show_tracks(tracks) while tracks['next']: @@ -36,4 +40,3 @@ if __name__ == '__main__': show_tracks(tracks) else: print("Can't get token for", username) - diff --git a/examples/user_public_playlists.py b/examples/user_public_playlists.py index 808be67..2d7f64f 100644 --- a/examples/user_public_playlists.py +++ b/examples/user_public_playlists.py @@ -18,7 +18,13 @@ 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( + "%4d %s %s" % + (i + + 1 + + playlists['offset'], + playlist['uri'], + playlist['name'])) if playlists['next']: playlists = sp.next(playlists) else: diff --git a/examples/user_saved_albums_delete.py b/examples/user_saved_albums_delete.py index e1d4bb4..783c33e 100644 --- a/examples/user_saved_albums_delete.py +++ b/examples/user_saved_albums_delete.py @@ -3,9 +3,7 @@ """ -import pprint import sys -import json import spotipy import spotipy.util as util diff --git a/examples/user_starred_playlist.py b/examples/user_starred_playlist.py index 645384c..4d51456 100644 --- a/examples/user_starred_playlist.py +++ b/examples/user_starred_playlist.py @@ -5,7 +5,6 @@ import spotipy import spotipy.util as util - if len(sys.argv) > 1: username = sys.argv[1] else: diff --git a/setup.py b/setup.py index 97f74fe..884102d 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,11 @@ from setuptools import setup +desc = """### A light weight Python library for the Spotify Web API""" + setup( name='spotipy', version='2.5.0', - long_description="""### A light weight Python library for the Spotify Web API""", + long_description=desc, long_description_content_type='text/markdown', author="@plamere", author_email="paul@echonest.com", @@ -12,7 +14,6 @@ setup( 'mock>=2.0.0', 'requests>=2.3.0', 'six>=1.10.0', - 'simplejson==3.13.2', - ], + ], license='LICENSE.txt', packages=['spotipy']) diff --git a/spotipy/__init__.py b/spotipy/__init__.py index da17893..dd9eb13 100644 --- a/spotipy/__init__.py +++ b/spotipy/__init__.py @@ -1,3 +1,3 @@ -from .client import * -from .oauth2 import * -from .util import * \ No newline at end of file +from .client import * # noqa +from .oauth2 import * # noqa +from .util import * # noqa diff --git a/spotipy/client.py b/spotipy/client.py index 372f2cb..6e215f0 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -17,7 +17,6 @@ import requests import six - class SpotifyException(Exception): def __init__(self, http_status, code, msg, headers=None): self.http_status = http_status @@ -58,7 +57,8 @@ class Spotify(object): max_get_retries = 10 def __init__(self, auth=None, requests_session=True, - client_credentials_manager=None, proxies=None, requests_timeout=None): + client_credentials_manager=None, proxies=None, + requests_timeout=None): """ Creates a Spotify API client. @@ -73,7 +73,8 @@ class Spotify(object): :param proxies: Definition of proxies (optional) :param requests_timeout: - Tell Requests to stop waiting for a response after a given number of seconds + Tell Requests to stop waiting for a response after a given + number of seconds """ self.prefix = 'https://api.spotify.com/v1/' self._auth = auth @@ -112,26 +113,33 @@ class Spotify(object): if self.trace_out: print(url) - r = self._session.request(method, url, headers=headers, proxies=self.proxies, **args) + r = self._session.request( + method, + url, + headers=headers, + proxies=self.proxies, + **args) if self.trace: # pragma: no cover print() - print ('headers', headers) - print ('http status', r.status_code) + print('headers', headers) + print('http status', r.status_code) print(method, r.url) if payload: print("DATA", json.dumps(payload)) try: r.raise_for_status() - except: + except BaseException: if r.text and len(r.text) > 0 and r.text != 'null': + msg = '%s:\n %s' % (r.url, r.json()['error']['message']) raise SpotifyException(r.status_code, - -1, '%s:\n %s' % (r.url, r.json()['error']['message']), - headers=r.headers) + -1, msg, + headers=r.headers) else: raise SpotifyException(r.status_code, - -1, '%s:\n %s' % (r.url, 'error'), headers=r.headers) + -1, '%s:\n %s' % (r.url, 'error'), + headers=r.headers) finally: if hasattr(r, "connection"): r.connection.close() @@ -160,21 +168,22 @@ class Spotify(object): if retries < 0: raise else: - sleep_seconds = int(e.headers.get('Retry-After', delay)) - print ('retrying ...' + str(sleep_seconds) + 'secs') + sleep_seconds = int( + e.headers.get('Retry-After', delay)) + print('retrying ...' + str(sleep_seconds) + 'secs') time.sleep(sleep_seconds + 1) delay += 1 else: raise except Exception as e: raise - print ('exception', str(e)) + print('exception', str(e)) # some other exception. Requests have # been know to throw a BadStatusLine exception retries -= 1 if retries >= 0: sleep_seconds = int(e.headers.get('Retry-After', delay)) - print ('retrying ...' + str(delay) + 'secs') + print('retrying ...' + str(delay) + 'secs') time.sleep(sleep_seconds + 1) delay += 1 else: @@ -233,7 +242,7 @@ class Spotify(object): trid = self._get_id('track', track_id) return self._get('tracks/' + trid) - def tracks(self, tracks, market = None): + def tracks(self, tracks, market=None): """ returns a list of tracks given a list of track IDs, URIs, or URLs Parameters: @@ -242,7 +251,7 @@ class Spotify(object): """ tlist = [self._get_id('track', t) for t in tracks] - return self._get('tracks/?ids=' + ','.join(tlist), market = market) + return self._get('tracks/?ids=' + ','.join(tlist), market=market) def artist(self, artist_id): """ returns a single artist given the artist's ID, URI or URL @@ -345,9 +354,11 @@ class Spotify(object): - offset - the index of the first item to return - type - the type of item to return. One of 'artist', 'album', 'track' or 'playlist' - - market - An ISO 3166-1 alpha-2 country code or the string from_token. + - market - An ISO 3166-1 alpha-2 country code or the string + from_token. """ - return self._get('search', q=q, limit=limit, offset=offset, type=type, market=market) + return self._get('search', q=q, limit=limit, + offset=offset, type=type, market=market) def user(self, user): """ Gets basic profile information about a Spotify User @@ -394,12 +405,12 @@ class Spotify(object): Parameters: - playlist - the id of the playlist - fields - which fields to return - - market - An ISO 3166-1 alpha-2 country code or the string from_token. + - market - An ISO 3166-1 alpha-2 country code or the string + from_token. """ plid = self._get_id('playlist', playlist_id) return self._get("playlists/%s" % (plid), fields=fields) - def user_playlist_tracks(self, user, playlist_id=None, fields=None, limit=100, offset=0, market=None): """ Get full details of the tracks of a playlist owned by a user. @@ -417,7 +428,6 @@ class Spotify(object): limit=limit, offset=offset, fields=fields, market=market) - def user_playlist_create(self, user, name, public=True, description=''): """ Creates a playlist for a user @@ -429,7 +439,6 @@ class Spotify(object): """ data = {'name': name, 'public': public, 'description': description} - return self._post("users/%s/playlists" % (user,), payload=data) def user_playlist_change_details( @@ -465,7 +474,8 @@ class Spotify(object): - user - the id of the user - name - the name of the playlist """ - return self._delete("users/%s/playlists/%s/followers" % (user, playlist_id)) + return self._delete("users/%s/playlists/%s/followers" % + (user, playlist_id)) def user_playlist_add_tracks(self, user, playlist_id, tracks, position=None): @@ -505,8 +515,10 @@ class Spotify(object): - user - the id of the user - playlist_id - the id of the playlist - range_start - the position of the first track to be reordered - - range_length - optional the number of tracks to be reordered (default: 1) - - insert_before - the position where the tracks should be inserted + - range_length - optional the number of tracks to be reordered + (default: 1) + - insert_before - the position where the tracks should be + inserted - snapshot_id - optional playlist's snapshot ID """ plid = self._get_id('playlist', playlist_id) @@ -575,9 +587,12 @@ class Spotify(object): - playlist_id - the id of the playlist """ - return self._put("users/{}/playlists/{}/followers".format(playlist_owner_id, playlist_id)) + return self._put( + "users/{}/playlists/{}/followers".format(playlist_owner_id, + playlist_id)) - def user_playlist_is_following(self, playlist_owner_id, playlist_id, user_ids): + def user_playlist_is_following( + self, playlist_owner_id, playlist_id, user_ids): """ Check to see if the given users are following the given playlist @@ -588,7 +603,10 @@ class Spotify(object): if they follow the playlist. Maximum: 5 ids. """ - return self._get("users/{}/playlists/{}/followers/contains?ids={}".format(playlist_owner_id, playlist_id, ','.join(user_ids))) + endpoint = "users/{}/playlists/{}/followers/contains?ids={}" + return self._get(endpoint.format(playlist_owner_id, + playlist_id, + ','.join(user_ids))) def me(self): """ Get detailed profile information about the current user. @@ -623,7 +641,8 @@ class Spotify(object): Parameters: - limit - the number of artists to return - - after - the last artist ID retrieved from the previous request + - after - the last artist ID retrieved from the previous + request """ return self._get('me/following', type='artist', limit=limit, @@ -858,17 +877,18 @@ class Spotify(object): - seed_tracks - a list of track IDs, URIs or URLs - seed_genres - a list of genre names. Available genres for - recommendations can be found by calling recommendation_genre_seeds + recommendations can be found by calling + recommendation_genre_seeds - - country - An ISO 3166-1 alpha-2 country code. If provided, all - results will be playable in this country. + - country - An ISO 3166-1 alpha-2 country code. If provided, + all results will be playable in this country. - limit - The maximum number of items to return. Default: 20. - Minimum: 1. Maximum: 100 + Minimum: 1. Maximum: 100 - - min/max/target_ - For the tuneable track attributes listed - in the documentation, these values provide filters and targeting on - results. + - min/max/target_ - For the tuneable track + attributes listed in the documentation, these values + provide filters and targeting on results. """ params = dict(limit=limit) if seed_artists: @@ -928,23 +948,23 @@ class Spotify(object): ''' return self._get("me/player/devices") - def current_playback(self, market = None): + def current_playback(self, market=None): ''' Get information about user's current playback. Parameters: - market - an ISO 3166-1 alpha-2 country code. ''' - return self._get("me/player", market = market) + return self._get("me/player", market=market) - def currently_playing(self, market = None): + def currently_playing(self, market=None): ''' Get user's currently playing track. Parameters: - market - an ISO 3166-1 alpha-2 country code. ''' - return self._get("me/player/currently-playing", market = market) + return self._get("me/player/currently-playing", market=market) - def transfer_playback(self, device_id, force_play = True): + def transfer_playback(self, device_id, force_play=True): ''' Transfer playback to another device. Note that the API accepts a list of device ids, but only actually supports one. @@ -960,7 +980,8 @@ class Spotify(object): } return self._put("me/player", payload=data) - def start_playback(self, device_id = None, context_uri = None, uris = None, offset = None): + def start_playback(self, device_id=None, + context_uri=None, uris=None, offset=None): ''' Start or resume user's playback. Provide a `context_uri` to start playback or a album, @@ -991,9 +1012,10 @@ class Spotify(object): data['uris'] = uris if offset is not None: data['offset'] = offset - return self._put(self._append_device_id("me/player/play", device_id), payload=data) + return self._put(self._append_device_id( + "me/player/play", device_id), payload=data) - def pause_playback(self, device_id = None): + def pause_playback(self, device_id=None): ''' Pause user's playback. Parameters: @@ -1001,7 +1023,7 @@ class Spotify(object): ''' return self._put(self._append_device_id("me/player/pause", device_id)) - def next_track(self, device_id = None): + def next_track(self, device_id=None): ''' Skip user's playback to next track. Parameters: @@ -1009,15 +1031,16 @@ class Spotify(object): ''' return self._post(self._append_device_id("me/player/next", device_id)) - def previous_track(self, device_id = None): + def previous_track(self, device_id=None): ''' Skip user's playback to previous track. Parameters: - device_id - device target for playback ''' - return self._post(self._append_device_id("me/player/previous", device_id)) + return self._post(self._append_device_id( + "me/player/previous", device_id)) - def seek_track(self, position_ms, device_id = None): + def seek_track(self, position_ms, device_id=None): ''' Seek to position in current track. Parameters: @@ -1027,9 +1050,10 @@ class Spotify(object): if not isinstance(position_ms, int): self._warn('position_ms must be an integer') return - return self._put(self._append_device_id("me/player/seek?position_ms=%s" % position_ms, device_id)) + return self._put(self._append_device_id( + "me/player/seek?position_ms=%s" % position_ms, device_id)) - def repeat(self, state, device_id = None): + def repeat(self, state, device_id=None): ''' Set repeat mode for playback. Parameters: @@ -1039,9 +1063,12 @@ class Spotify(object): if state not in ['track', 'context', 'off']: self._warn('invalid state') return - self._put(self._append_device_id("me/player/repeat?state=%s" % state, device_id)) + self._put( + self._append_device_id( + "me/player/repeat?state=%s" % + state, device_id)) - def volume(self, volume_percent, device_id = None): + def volume(self, volume_percent, device_id=None): ''' Set playback volume. Parameters: @@ -1054,9 +1081,12 @@ class Spotify(object): if volume_percent < 0 or volume_percent > 100: self._warn('volume must be between 0 and 100, inclusive') return - self._put(self._append_device_id("me/player/volume?volume_percent=%s" % volume_percent, device_id)) + self._put( + self._append_device_id( + "me/player/volume?volume_percent=%s" % + volume_percent, device_id)) - def shuffle(self, state, device_id = None): + def shuffle(self, state, device_id=None): ''' Toggle playback shuffling. Parameters: @@ -1067,7 +1097,10 @@ class Spotify(object): self._warn('state must be a boolean') return state = str(state).lower() - self._put(self._append_device_id("me/player/shuffle?state=%s" % state, device_id)) + self._put( + self._append_device_id( + "me/player/shuffle?state=%s" % + state, device_id)) def _append_device_id(self, path, device_id): ''' Append device ID to API path. diff --git a/spotipy/oauth2.py b/spotipy/oauth2.py index 0f9dc6e..f749974 100644 --- a/spotipy/oauth2.py +++ b/spotipy/oauth2.py @@ -27,7 +27,11 @@ class SpotifyOauthError(Exception): def _make_authorization_headers(client_id, client_secret): - auth_header = base64.b64encode(six.text_type(client_id + ':' + client_secret).encode('ascii')) + auth_header = base64.b64encode( + six.text_type( + client_id + + ':' + + client_secret).encode('ascii')) return {'Authorization': 'Basic %s' % auth_header.decode('ascii')} @@ -77,12 +81,14 @@ class SpotifyClientCredentials(object): def _request_access_token(self): """Gets client credentials access token """ - payload = { 'grant_type': 'client_credentials'} + payload = {'grant_type': 'client_credentials'} - headers = _make_authorization_headers(self.client_id, self.client_secret) + headers = _make_authorization_headers( + self.client_id, self.client_secret) response = requests.post(self.OAUTH_TOKEN_URL, data=payload, - headers=headers, verify=True, proxies=self.proxies) + headers=headers, verify=True, + proxies=self.proxies) if response.status_code != 200: raise SpotifyOauthError(response.reason) token_info = response.json() @@ -109,7 +115,7 @@ class SpotifyOAuth(object): OAUTH_TOKEN_URL = 'https://accounts.spotify.com/api/token' def __init__(self, client_id, client_secret, redirect_uri, - state=None, scope=None, cache_path=None, proxies=None): + state=None, scope=None, cache_path=None, proxies=None): ''' Creates a SpotifyOAuth object @@ -125,9 +131,9 @@ class SpotifyOAuth(object): self.client_id = client_id self.client_secret = client_secret self.redirect_uri = redirect_uri - self.state=state + self.state = state self.cache_path = cache_path - self.scope=self._normalize_scope(scope) + self.scope = self._normalize_scope(scope) self.proxies = proxies def get_cached_token(self): @@ -142,11 +148,13 @@ class SpotifyOAuth(object): token_info = json.loads(token_info_string) # if scopes don't match, then bail - if 'scope' not in token_info or not self._is_scope_subset(self.scope, token_info['scope']): + if 'scope' not in token_info or not self._is_scope_subset( + self.scope, token_info['scope']): return None if self.is_token_expired(token_info): - token_info = self.refresh_access_token(token_info['refresh_token']) + token_info = self.refresh_access_token( + token_info['refresh_token']) except IOError: pass @@ -164,7 +172,8 @@ class SpotifyOAuth(object): def _is_scope_subset(self, needle_scope, haystack_scope): needle_scope = set(needle_scope.split()) if needle_scope else set() - haystack_scope = set(haystack_scope.split()) if haystack_scope else set() + haystack_scope = set( + haystack_scope.split()) if haystack_scope else set() return needle_scope <= haystack_scope def is_token_expired(self, token_info): @@ -222,7 +231,8 @@ class SpotifyOAuth(object): headers = self._make_authorization_headers() response = requests.post(self.OAUTH_TOKEN_URL, data=payload, - headers=headers, verify=True, proxies=self.proxies) + headers=headers, verify=True, + proxies=self.proxies) if response.status_code != 200: raise SpotifyOauthError(response.reason) token_info = response.json() @@ -232,30 +242,29 @@ class SpotifyOAuth(object): def _normalize_scope(self, scope): if scope: - scopes = scope.split() - scopes.sort() + scopes = sorted(scope.split()) return ' '.join(scopes) else: return None def refresh_access_token(self, refresh_token): - payload = { 'refresh_token': refresh_token, + payload = {'refresh_token': refresh_token, 'grant_type': 'refresh_token'} headers = self._make_authorization_headers() response = requests.post(self.OAUTH_TOKEN_URL, data=payload, - headers=headers, proxies=self.proxies) + headers=headers, proxies=self.proxies) if response.status_code != 200: if False: # debugging code print('headers', headers) print('request', response.url) - self._warn("couldn't refresh token: code:%d reason:%s" \ - % (response.status_code, response.reason)) + self._warn("couldn't refresh token: code:%d reason:%s" + % (response.status_code, response.reason)) return None token_info = response.json() token_info = self._add_custom_values_to_token_info(token_info) - if not 'refresh_token' in token_info: + if 'refresh_token' not in token_info: token_info['refresh_token'] = refresh_token self._save_token_info(token_info) return token_info @@ -271,4 +280,3 @@ class SpotifyOAuth(object): def _warn(self, msg): print('warning:' + msg, file=sys.stderr) - diff --git a/spotipy/util.py b/spotipy/util.py index 0bf9fab..d8b7585 100644 --- a/spotipy/util.py +++ b/spotipy/util.py @@ -22,8 +22,10 @@ CLIENT_CREDS_ENV_VARS = { 'redirect_uri': 'SPOTIPY_REDIRECT_URI' } -def prompt_for_user_token(username, scope=None, client_id = None, - client_secret = None, redirect_uri = None, cache_path = None): + +def prompt_for_user_token(username, scope=None, client_id=None, + client_secret=None, redirect_uri=None, + cache_path=None): ''' prompts the user to login if necessary and returns the user token suitable for use with the spotipy.Spotify constructor @@ -64,7 +66,7 @@ def prompt_for_user_token(username, scope=None, client_id = None, cache_path = cache_path or ".cache-" + username sp_oauth = oauth2.SpotifyOAuth(client_id, client_secret, redirect_uri, - scope=scope, cache_path=cache_path) + scope=scope, cache_path=cache_path) # try to get a valid token for this user, from the cache, # if not in the cache, the create a new (this will send @@ -87,7 +89,7 @@ def prompt_for_user_token(username, scope=None, client_id = None, import webbrowser webbrowser.open(auth_url) print("Opened %s in your browser" % auth_url) - except: + except BaseException: print("Please navigate here: %s" % auth_url) print() diff --git a/tests/test_auth.py b/tests/test_auth.py index 66e2ab4..327898a 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -14,27 +14,23 @@ following environment variables from __future__ import print_function -import os -from pprint import pprint -import sys -import unittest - -import simplejson as json - -sys.path.insert(0, os.path.abspath(os.pardir)) - from spotipy import ( CLIENT_CREDS_ENV_VARS as CCEV, prompt_for_user_token, Spotify, SpotifyException, ) +import os +import sys +import unittest + +sys.path.insert(0, os.path.abspath(os.pardir)) class AuthTestSpotipy(unittest.TestCase): """ - These tests require user authentication - provide client credentials using the - following environment variables + These tests require user authentication - provide client credentials using + the following environment variables :: @@ -47,30 +43,32 @@ class AuthTestSpotipy(unittest.TestCase): playlist = "spotify:user:plamere:playlist:2oCEWyyAPbZp9xhVSxZavx" playlist_new_id = "spotify:playlist:7GlxpQjjxRjmbb3RP2rDqI" four_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp", - "spotify:track:7IHOIqZUUInxjVkko181PB", - "4VrWlk8IQxevMvERoX08iC", - "http://open.spotify.com/track/3cySlItpiPiIAzU3NyHCJf"] + "spotify:track:7IHOIqZUUInxjVkko181PB", + "4VrWlk8IQxevMvERoX08iC", + "http://open.spotify.com/track/3cySlItpiPiIAzU3NyHCJf"] two_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp", - "spotify:track:7IHOIqZUUInxjVkko181PB"] + "spotify:track:7IHOIqZUUInxjVkko181PB"] - other_tracks=["spotify:track:2wySlB6vMzCbQrRnNGOYKa", - "spotify:track:29xKs5BAHlmlX1u4gzQAbJ", - "spotify:track:1PB7gRWcvefzu7t3LJLUlf"] + other_tracks = ["spotify:track:2wySlB6vMzCbQrRnNGOYKa", + "spotify:track:29xKs5BAHlmlX1u4gzQAbJ", + "spotify:track:1PB7gRWcvefzu7t3LJLUlf"] album_ids = ["spotify:album:6kL09DaURb7rAoqqaA51KU", "spotify:album:6RTzC0rDbvagTSJLlY7AKl"] bad_id = 'BAD_ID' - @classmethod def setUpClass(self): missing = list(filter(lambda var: not os.getenv(CCEV[var]), CCEV)) if missing: - raise Exception('Please set the client credentials for the test application using the following environment variables: {}'.format(CCEV.values())) + raise Exception( + ('Please set the client credentials for the test application' + ' using the following environment variables: {}').format( + CCEV.values())) self.username = os.getenv(CCEV['client_username']) @@ -121,14 +119,12 @@ class AuthTestSpotipy(unittest.TestCase): results = self.spotify.user_playlist_tracks(user, pid) self.assertTrue(len(results['items']) >= 0) - def user_playlist_tracks(self, user, playlist_id = None, fields=None, - limit=100, offset=0): - - # known API issue currently causes this test to fail - # the issue is that the API doesn't currently respect the - # limit parameter - - self.assertTrue(len(playlists['items']) == 5) + # known API issue currently causes this test to fail + # the issue is that the API doesn't currently respect the + # limit parameter + # def user_playlist_tracks(self, user, playlist_id=None, fields=None, + # limit=100, offset=0): + # self.assertTrue(len(playlists['items']) == 5) def test_current_user_saved_albums(self): # List @@ -139,7 +135,10 @@ class AuthTestSpotipy(unittest.TestCase): self.spotify.current_user_saved_albums_add(self.album_ids) # Contains - self.assertTrue(self.spotify.current_user_saved_albums_contains(self.album_ids) == [True, True]) + self.assertTrue( + self.spotify.current_user_saved_albums_contains( + self.album_ids) == [ + True, True]) # Remove self.spotify.current_user_saved_albums_delete(self.album_ids) @@ -152,14 +151,20 @@ class AuthTestSpotipy(unittest.TestCase): self.assertTrue(len(playlists['items']) == 10) def test_user_playlist_follow(self): - self.spotify.user_playlist_follow_playlist('plamere', '4erXB04MxwRAVqcUEpu30O') - follows = self.spotify.user_playlist_is_following('plamere', '4erXB04MxwRAVqcUEpu30O', [self.spotify.current_user()['id']]) + self.spotify.user_playlist_follow_playlist( + 'plamere', '4erXB04MxwRAVqcUEpu30O') + follows = self.spotify.user_playlist_is_following( + 'plamere', '4erXB04MxwRAVqcUEpu30O', [ + self.spotify.current_user()['id']]) self.assertTrue(len(follows) == 1, 'proper follows length') self.assertTrue(follows[0], 'is following') - self.spotify.user_playlist_unfollow('plamere', '4erXB04MxwRAVqcUEpu30O') + self.spotify.user_playlist_unfollow( + 'plamere', '4erXB04MxwRAVqcUEpu30O') - follows = self.spotify.user_playlist_is_following('plamere', '4erXB04MxwRAVqcUEpu30O', [self.spotify.current_user()['id']]) + follows = self.spotify.user_playlist_is_following( + 'plamere', '4erXB04MxwRAVqcUEpu30O', [ + self.spotify.current_user()['id']]) self.assertTrue(len(follows) == 1, 'proper follows length') self.assertFalse(follows[0], 'is no longer following') @@ -176,7 +181,8 @@ class AuthTestSpotipy(unittest.TestCase): new_total = tracks['total'] self.assertTrue(new_total - total == len(self.four_tracks)) - tracks = self.spotify.current_user_saved_tracks_delete(self.four_tracks) + tracks = self.spotify.current_user_saved_tracks_delete( + self.four_tracks) tracks = self.spotify.current_user_saved_tracks() new_total = tracks['total'] self.assertTrue(new_total == total) @@ -224,37 +230,45 @@ class AuthTestSpotipy(unittest.TestCase): if item['name'] == playlist_name: return item['id'] playlists = self.spotify.next(playlists) - playlist = self.spotify.user_playlist_create(self.username, playlist_name) + playlist = self.spotify.user_playlist_create( + self.username, playlist_name) playlist_id = playlist['uri'] return playlist_id def test_user_playlist_ops(self): + sp = self.spotify # create empty playlist - playlist_id = self.get_or_create_spotify_playlist('spotipy-testing-playlist-1') + playlist_id = self.get_or_create_spotify_playlist( + 'spotipy-testing-playlist-1') # remove all tracks from it - self.spotify.user_playlist_replace_tracks(self.username, playlist_id,[]) - playlist = self.spotify.user_playlist(self.username, playlist_id) + sp.user_playlist_replace_tracks( + self.username, playlist_id, []) + playlist = sp.user_playlist(self.username, playlist_id) self.assertTrue(playlist['tracks']['total'] == 0) self.assertTrue(len(playlist['tracks']['items']) == 0) # add tracks to it - self.spotify.user_playlist_add_tracks(self.username, playlist_id, self.four_tracks) - playlist = self.spotify.user_playlist(self.username, playlist_id) + sp.user_playlist_add_tracks( + self.username, playlist_id, self.four_tracks) + playlist = sp.user_playlist(self.username, playlist_id) self.assertTrue(playlist['tracks']['total'] == 4) self.assertTrue(len(playlist['tracks']['items']) == 4) # remove two tracks from it - self.spotify.user_playlist_remove_all_occurrences_of_tracks (self.username, - playlist_id, self.two_tracks) - playlist = self.spotify.user_playlist(self.username, playlist_id) + + sp.user_playlist_remove_all_occurrences_of_tracks(self.username, + playlist_id, + self.two_tracks) + playlist = sp.user_playlist(self.username, playlist_id) self.assertTrue(playlist['tracks']['total'] == 2) self.assertTrue(len(playlist['tracks']['items']) == 2) # replace with 3 other tracks - self.spotify.user_playlist_replace_tracks(self.username, - playlist_id, self.other_tracks) - playlist = self.spotify.user_playlist(self.username, playlist_id) + sp.user_playlist_replace_tracks(self.username, + playlist_id, + self.other_tracks) + playlist = sp.user_playlist(self.username, playlist_id) self.assertTrue(playlist['tracks']['total'] == 3) self.assertTrue(len(playlist['tracks']['items']) == 3) @@ -293,5 +307,6 @@ class AuthTestSpotipy(unittest.TestCase): # Unfollow these 2 users self.spotify.user_unfollow_users(users) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_auth2.py b/tests/test_auth2.py index e52092a..47cf798 100644 --- a/tests/test_auth2.py +++ b/tests/test_auth2.py @@ -12,25 +12,21 @@ following environment variables 'SPOTIPY_REDIRECT_URI' """ -import os -import pprint -import sys -import unittest - -import simplejson as json - -sys.path.insert(0, os.path.abspath(os.pardir)) - from spotipy import ( Spotify, SpotifyClientCredentials, ) +import os +import sys +import unittest + +sys.path.insert(0, os.path.abspath(os.pardir)) class AuthTestSpotipy(unittest.TestCase): """ - These tests require user authentication - provide client credentials using the - following environment variables + These tests require user authentication - provide client credentials using + the following environment variables :: @@ -42,22 +38,23 @@ class AuthTestSpotipy(unittest.TestCase): playlist = "spotify:user:plamere:playlist:2oCEWyyAPbZp9xhVSxZavx" four_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp", - "spotify:track:7IHOIqZUUInxjVkko181PB", - "4VrWlk8IQxevMvERoX08iC", - "http://open.spotify.com/track/3cySlItpiPiIAzU3NyHCJf"] + "spotify:track:7IHOIqZUUInxjVkko181PB", + "4VrWlk8IQxevMvERoX08iC", + "http://open.spotify.com/track/3cySlItpiPiIAzU3NyHCJf"] two_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp", - "spotify:track:7IHOIqZUUInxjVkko181PB"] + "spotify:track:7IHOIqZUUInxjVkko181PB"] - other_tracks=["spotify:track:2wySlB6vMzCbQrRnNGOYKa", - "spotify:track:29xKs5BAHlmlX1u4gzQAbJ", - "spotify:track:1PB7gRWcvefzu7t3LJLUlf"] + other_tracks = ["spotify:track:2wySlB6vMzCbQrRnNGOYKa", + "spotify:track:29xKs5BAHlmlX1u4gzQAbJ", + "spotify:track:1PB7gRWcvefzu7t3LJLUlf"] bad_id = 'BAD_ID' @classmethod def setUpClass(self): - self.spotify = Spotify(client_credentials_manager=SpotifyClientCredentials()) + self.spotify = Spotify( + client_credentials_manager=SpotifyClientCredentials()) self.spotify.trace = False def test_audio_analysis(self): @@ -77,15 +74,18 @@ class AuthTestSpotipy(unittest.TestCase): results = self.spotify.audio_features(input) self.assertTrue(len(results) == len(input)) for track in results[:-1]: - if track != None: + if track is not None: assert('speechiness' in track) - self.assertTrue(results[-1] == None) + self.assertTrue(results[-1] is None) def test_recommendations(self): - results = self.spotify.recommendations(seed_tracks=self.four_tracks, min_danceability=0, max_loudness=0, target_popularity=50) + results = self.spotify.recommendations( + seed_tracks=self.four_tracks, + min_danceability=0, + max_loudness=0, + target_popularity=50) self.assertTrue(len(results['tracks']) == 20) if __name__ == '__main__': - unittest.main() diff --git a/tests/test_client_credentials.py b/tests/test_client_credentials.py index a3b7103..acbda5c 100644 --- a/tests/test_client_credentials.py +++ b/tests/test_client_credentials.py @@ -2,22 +2,21 @@ """ Client Credentials Requests Tests """ +from spotipy import ( + Spotify, + SpotifyClientCredentials, +) import os import sys import unittest sys.path.insert(0, os.path.abspath(os.pardir)) -from spotipy import ( - Spotify, - SpotifyClientCredentials, -) - class ClientCredentialsTestSpotipy(unittest.TestCase): """ - These tests require user authentication - provide client credentials using the - following environment variables + These tests require user authentication - provide client credentials using + the following environment variables :: @@ -29,7 +28,8 @@ class ClientCredentialsTestSpotipy(unittest.TestCase): @classmethod def setUpClass(self): - self.spotify = Spotify(client_credentials_manager=SpotifyClientCredentials()) + self.spotify = Spotify( + client_credentials_manager=SpotifyClientCredentials()) self.spotify.trace = False muse_urn = 'spotify:artist:12Chz98pHFMPJEknJQMWvI' @@ -40,5 +40,4 @@ class ClientCredentialsTestSpotipy(unittest.TestCase): if __name__ == '__main__': - unittest.main() diff --git a/tests/test_oauth.py b/tests/test_oauth.py index 0c18ef1..9b51eb5 100644 --- a/tests/test_oauth.py +++ b/tests/test_oauth.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- +import six.moves.urllib.parse as urllibparse +from spotipy import SpotifyOAuth import io import json import os @@ -8,13 +10,11 @@ import unittest sys.path.insert(0, os.path.abspath(os.pardir)) -from spotipy import SpotifyOAuth try: import unittest.mock as mock except ImportError: import mock -import six.moves.urllib.parse as urllibparse patch = mock.patch DEFAULT = mock.DEFAULT diff --git a/tests/tests.py b/tests/tests.py index 4cd90df..5d9a4a4 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -1,27 +1,25 @@ # -*- coding: utf-8 -*- -import os -import pprint -import sys -import unittest - -import requests - -sys.path.insert(0, os.path.abspath(os.pardir)) - from spotipy import ( CLIENT_CREDS_ENV_VARS as CCEV, prompt_for_user_token, Spotify, SpotifyException, ) +import os +import sys +import unittest + +import requests + +sys.path.insert(0, os.path.abspath(os.pardir)) class TestSpotipy(unittest.TestCase): """ - These tests require user authentication - provide client credentials using the - following environment variables + These tests require user authentication - provide client credentials using + the following environment variables :: @@ -42,7 +40,6 @@ class TestSpotipy(unittest.TestCase): radiohead_urn = 'spotify:artist:4Z8W4fKeB5YxbusRsdQVPb' angeles_haydn_urn = 'spotify:album:1vAbqAeuJVWNAe7UR00bdM' - bad_id = 'BAD_ID' @classmethod @@ -50,7 +47,10 @@ class TestSpotipy(unittest.TestCase): missing = list(filter(lambda var: not os.getenv(CCEV[var]), CCEV)) if missing: - raise Exception('Please set the client credentials for the test application using the following environment variables: {}'.format(CCEV.values())) + raise Exception( + ('Please set the client credentials for the test ' + 'the following environment variables: {}').format( + CCEV.values())) self.username = os.getenv(CCEV['client_username']) @@ -82,14 +82,16 @@ class TestSpotipy(unittest.TestCase): tracks = results['items'] total, received = results['total'], len(tracks) while received < total: - results = self.spotify.album_tracks(self.angeles_haydn_urn, offset=received) + results = self.spotify.album_tracks( + self.angeles_haydn_urn, offset=received) tracks.extend(results['items']) received = len(tracks) self.assertEqual(received, total) def test_albums(self): - results = self.spotify.albums([self.pinkerton_urn, self.pablo_honey_urn]) + results = self.spotify.albums( + [self.pinkerton_urn, self.pablo_honey_urn]) self.assertTrue('albums' in results) self.assertTrue(len(results['albums']) == 2) @@ -107,7 +109,7 @@ class TestSpotipy(unittest.TestCase): def test_track_bad_urn(self): try: - track = self.spotify.track(self.el_scorcho_bad_urn) + self.spotify.track(self.el_scorcho_bad_urn) self.assertTrue(False) except SpotifyException: self.assertTrue(True) @@ -158,17 +160,17 @@ class TestSpotipy(unittest.TestCase): def test_search_timeout(self): sp = Spotify(auth=self.token, requests_timeout=.01) try: - results = sp.search(q='my*', type='track') + sp.search(q='my*', type='track') self.assertTrue(False, 'unexpected search timeout') except requests.Timeout: self.assertTrue(True, 'expected search timeout') - def test_album_search(self): results = self.spotify.search(q='weezer pinkerton', type='album') self.assertTrue('albums' in results) self.assertTrue(len(results['albums']['items']) > 0) - self.assertTrue(results['albums']['items'][0]['name'].find('Pinkerton') >= 0) + self.assertTrue(results['albums']['items'][0] + ['name'].find('Pinkerton') >= 0) def test_track_search(self): results = self.spotify.search(q='el scorcho weezer', type='track') @@ -182,34 +184,34 @@ class TestSpotipy(unittest.TestCase): def test_track_bad_id(self): try: - track = self.spotify.track(self.bad_id) - self.assertTrue(False) - except SpotifyException: - self.assertTrue(True) - - def test_track_bad_id(self): - try: - track = self.spotify.track(self.bad_id) + self.spotify.track(self.bad_id) self.assertTrue(False) except SpotifyException: self.assertTrue(True) def test_unauthenticated_post_fails(self): with self.assertRaises(SpotifyException) as cm: - self.spotify.user_playlist_create("spotify", "Best hits of the 90s") + self.spotify.user_playlist_create( + "spotify", "Best hits of the 90s") self.assertTrue(cm.exception.http_status == 401 or - cm.exception.http_status == 403) + cm.exception.http_status == 403) def test_custom_requests_session(self): sess = requests.Session() sess.headers["user-agent"] = "spotipy-test" with_custom_session = Spotify(auth=self.token, requests_session=sess) - self.assertTrue(with_custom_session.user(user="akx")["uri"] == "spotify:user:akx") + self.assertTrue( + with_custom_session.user( + user="akx")["uri"] == "spotify:user:akx") def test_force_no_requests_session(self): with_no_session = Spotify(auth=self.token, requests_session=False) - self.assertFalse(isinstance(with_no_session._session, requests.Session)) - self.assertTrue(with_no_session.user(user="akx")["uri"] == "spotify:user:akx") + self.assertFalse( + isinstance( + with_no_session._session, + requests.Session)) + self.assertTrue(with_no_session.user(user="akx") + ["uri"] == "spotify:user:akx") ''' @@ -220,5 +222,4 @@ class TestSpotipy(unittest.TestCase): ''' if __name__ == '__main__': - unittest.main() diff --git a/tox.ini b/tox.ini index dcbdb72..565f77b 100644 --- a/tox.ini +++ b/tox.ini @@ -7,3 +7,8 @@ deps= six py27: mock commands=python -m unittest discover -v tests +[flake8] +exclude= + .git, + dist, + docs \ No newline at end of file From eaca99812384eed535c1d4c1a7eff3bb6d7511ac Mon Sep 17 00:00:00 2001 From: Stephane Bruckert Date: Sun, 12 Jan 2020 14:42:52 +0000 Subject: [PATCH 59/71] Bump to 2.6.0 --- .travis.yml | 29 -------- CHANGELOG.md | 148 ++++++++++++++++++++++++++++---------- LICENSE.txt => LICENSE.md | 0 README.md | 7 +- deploy | 18 ----- redirect_page.md | 10 --- requirements.txt | 1 - setup.py | 4 +- tox.ini | 1 - 9 files changed, 117 insertions(+), 101 deletions(-) delete mode 100644 .travis.yml rename LICENSE.txt => LICENSE.md (100%) delete mode 100755 deploy delete mode 100644 redirect_page.md diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 241902d..0000000 --- a/.travis.yml +++ /dev/null @@ -1,29 +0,0 @@ -language: python - -python: - - 2.7 - - 3.6 - - 3.5 - - 3.4 - - pypy - -install: - - pip install coveralls - -script: - - python setup.py install - - coverage run --include=*spotipy* tests/tests.py - - for file in examples/*.py; do python $file; done - -after_success: - - coverage report - - coveralls - - pip install pep8 pyflakes - - pep8 *.py tests/*.py - - pyflakes *.py tests/*.py - -matrix: - allow_failures: - - python: 3.6 - - python: 3.5 - - python: 3.4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a2fd91..6b044a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,37 +1,111 @@ -- 1.0 - 04/05/2014 - Initial release -- 1.1 - 05/18/2014 - Repackaged for saner imports -- 1.4.1 - 06/17/2014 - Updates to match released API -- 1.4.2 - 06/21/2014 - Added support for retrieving starred playlists -- v1.40, June 12, 2014 -- Initial public release. -- v1.42, June 19, 2014 -- Removed dependency on simplejson -- v1.43, June 27, 2014 -- Fixed JSON handling issue -- v1.44, July 3, 2014 -- Added show tracks.py example -- v1.45, July 7, 2014 -- Support for related artists endpoint. Don't use cache auth codes when scope changes -- v1.49, July 23, 2014 -- Support for "Your Music" tracks (add, delete, get), with examples -- v1.50, August 14, 2014 -- Refactored util out of examples and into the main package -- v1.301, August 19, 2014 -- Upgraded version number to take precedence over previously botched release (sigh) -- v1.310, August 20, 2014 -- Added playlist replace and remove methods. Added auth tests. Improved API docs -- v2.0 - August 22, 2014 -- Upgraded APIs and docs to make it be a real library -- v2.0.2 - August 25, 2014 -- Moved to spotipy at pypi -- v2.1.0 - October 25, 2014 -- Added support for new_releases and featured_playlists -- v2.2.0 - November 15, 2014 -- Added support for user_playlist_tracks -- v2.3.0 - January 5, 2015 -- Added session support added by akx. -- v2.3.2 - March 31, 2015 -- Added auto retry logic -- v2.3.3 - April 1, 2015 -- added client credential flow -- v2.3.5 - April 28, 2015 -- Fixed bug in auto retry logic -- v2.3.6 - June 3, 2015 -- Support for offset/limit with album_tracks API -- v2.3.7 - August 10, 2015 -- Added current_user_followed_artists -- v2.3.8 - March 30, 2016 -- Added recs, audio features, user top lists -- v2.4.0 - December 31, 2016 -- Incorporated a number of PRs -- v2.4.1 - January 2, 2017 -- Incorporated proxy support -- v2.4.2 - January 2, 2017 -- support getting audio features for a single track -- v2.4.3 - January 2, 2017 -- fixed proxy issue in standard auth flow -- v2.4.4 - January 4, 2017 -- python 3 fix -- v2.5.0 - January 11, 2020 -- Added follow and player endpoints -- Unreleased - - Added: - - support for `playlist()` - - support for `current_user_saved_albums_delete()` - - support for `current_user_saved_albums_contains()` - - support for `user_unfollow_artists()` - - support for `user_unfollow_users()` +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [2.6.0] - 2020-01-12 + +### Added + - Support for `playlist` to get a playlist without specifying a user + - Support for `current_user_saved_albums_delete` + - Support for `current_user_saved_albums_contains` + - Support for `user_unfollow_artists` + - Support for `user_unfollow_users` + - Lint with flake8 using Github action + +### Changed + - Fix typos in doc + - Start following [SemVer](https://semver.org) properly + +## [2.5.0] - 2020-01-11 +Added follow and player endpoints + +## [2.4.4] - 2017-01-04 +Python 3 fix + +## [2.4.3] - 2017-01-02 +Fixed proxy issue in standard auth flow + +## [2.4.2] - 2017-01-02 +Support getting audio features for a single track + +## [2.4.1] - 2017-01-02 +Incorporated proxy support + +## [2.4.0] - 2016-12-31 +Incorporated a number of PRs + +## [2.3.8] - 2016-03-31 +Added recs, audio features, user top lists + +## [2.3.7] - 2015-08-10 +Added current_user_followed_artists + +## [2.3.6] - 2015-06-03 +Support for offset/limit with album_tracks API + +## [2.3.5] - 2015-04-28 +Fixed bug in auto retry logic + +## [2.3.3] - 2015-04-01 +Aadded client credential flow + +## [2.3.2] - 2015-03-31 +Added auto retry logic + +## [2.3.0] - 2015-01-05 +Added session support added by akx. + +## [2.2.0] - 2014-11-15 +Added support for user_playlist_tracks + +## [2.1.0] - 2014-10-25 +Added support for new_releases and featured_playlists + +## [2.0.2] - 2014-08-25 +Moved to spotipy at pypi + +## [1.2.0] - 2014-08-22 +Upgraded APIs and docs to make it be a real library + +## [1.310.0] - 2014-08-20 +Added playlist replace and remove methods. Added auth tests. Improved API docs + +## [1.301.0] - 2014-08-19 +Upgraded version number to take precedence over previously botched release (sigh) + +## [1.50.0] - 2014-08-14 +Refactored util out of examples and into the main package + +## [1.49.0] - 2014-07-23 +Support for "Your Music" tracks (add, delete, get), with examples + +## [1.45.0] - 2014-07-07 +Support for related artists endpoint. Don't use cache auth codes when scope changes + +## [1.44.0] - 2014-07-03 +Added show tracks.py example + +## [1.43.0] - 2014-06-27 +Fixed JSON handling issue + +## [1.42.0] - 2014-06-19 +Removed dependency on simplejson + +## [1.40.0] - 2014-06-12 +Initial public release. + +## [1.4.2] - 2014-06-21 +Added support for retrieving starred playlists + +## [1.1.0] - 2014-06-17 +Updates to match released API + +## [1.1.0] - 2014-05-18 +Repackaged for saner imports + +## [1.0.0] - 2017-04-05 +Initial release \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.md similarity index 100% rename from LICENSE.txt rename to LICENSE.md diff --git a/README.md b/README.md index 94971be..8a52a79 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ -# Spotipy - a Python client for The Spotify Web API +# Spotipy -## Description +##### A light weight Python library for the Spotify Web API + +[![Documentation Status](https://readthedocs.org/projects/spotipy/badge/?version=latest)](https://spotipy.readthedocs.io/en/latest/?badge=latest) -Spotipy is a thin client library for the Spotify Web API. ## Documentation diff --git a/deploy b/deploy deleted file mode 100755 index 36d5c3f..0000000 --- a/deploy +++ /dev/null @@ -1,18 +0,0 @@ -# -# sudo python setup.py develop --uninstall -# sudo python setup.py install - -# How do deploy -# - run tests -# - push to github -# - Adjust version number in setup.py -# - Update README.md with updated version info -# - leave development mode -# sudo python setup.py develop --uninstall -# sudo python setup.py install -# - upload dist -# sudo python setup.py sdist upload -# docs should automatically be updated. verify them at -# http://spotipy.readthedocs.org/en/latest/ - -sudo python setup.py sdist upload \ No newline at end of file diff --git a/redirect_page.md b/redirect_page.md deleted file mode 100644 index 54d38f8..0000000 --- a/redirect_page.md +++ /dev/null @@ -1,10 +0,0 @@ -Spotipy Authorization Page -=========================== - -If you are here, you are probably running a Spotipy test app that has been asked to authenticate the user. -Now you just need to cut/paste the URL into the terminal at the prompt asking: - - Enter the URL you were redirected to: - - -And you should be good to go diff --git a/requirements.txt b/requirements.txt index d674b24..708dccd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ mock==2.0.0 requests==2.3.0 -simplejson==3.13.2 six==1.10.0 \ No newline at end of file diff --git a/setup.py b/setup.py index 884102d..d4df1ed 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ desc = """### A light weight Python library for the Spotify Web API""" setup( name='spotipy', - version='2.5.0', + version='2.6.0', long_description=desc, long_description_content_type='text/markdown', author="@plamere", @@ -15,5 +15,5 @@ setup( 'requests>=2.3.0', 'six>=1.10.0', ], - license='LICENSE.txt', + license='LICENSE.md', packages=['spotipy']) diff --git a/tox.ini b/tox.ini index 565f77b..5d4b140 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,6 @@ envlist = py27,py34 [testenv] deps= requests - simplejson six py27: mock commands=python -m unittest discover -v tests From 908a92897b45baaa99a6fcbad9c84cfc407a908c Mon Sep 17 00:00:00 2001 From: Ritiek Malhotra Date: Sun, 12 Jan 2020 07:13:31 -0800 Subject: [PATCH 60/71] Remove unnecessary token_info parameter for oauth2.SpotifyClientCredentials methods (#223) --- spotipy/oauth2.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spotipy/oauth2.py b/spotipy/oauth2.py index f749974..47c13fa 100644 --- a/spotipy/oauth2.py +++ b/spotipy/oauth2.py @@ -94,14 +94,15 @@ class SpotifyClientCredentials(object): token_info = response.json() return token_info - def is_token_expired(self, token_info): - return is_token_expired(token_info) + def is_token_expired(self): + return is_token_expired(self.token_info) - def _add_custom_values_to_token_info(self, token_info): + def _add_custom_values_to_token_info(self): """ Store some values that aren't directly provided by a Web API response. """ + token_info = self.token_info token_info['expires_at'] = int(time.time()) + token_info['expires_in'] return token_info From c3bad99e43ee64eb485f570e6e695ee7b1ef0c65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bruckert?= Date: Sun, 12 Jan 2020 15:14:25 +0000 Subject: [PATCH 61/71] Revert "Remove unnecessary token_info parameter for oauth2.SpotifyClientCredentials methods (#223)" (#417) This reverts commit 908a92897b45baaa99a6fcbad9c84cfc407a908c. --- spotipy/oauth2.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/spotipy/oauth2.py b/spotipy/oauth2.py index 47c13fa..f749974 100644 --- a/spotipy/oauth2.py +++ b/spotipy/oauth2.py @@ -94,15 +94,14 @@ class SpotifyClientCredentials(object): token_info = response.json() return token_info - def is_token_expired(self): - return is_token_expired(self.token_info) + def is_token_expired(self, token_info): + return is_token_expired(token_info) - def _add_custom_values_to_token_info(self): + def _add_custom_values_to_token_info(self, token_info): """ Store some values that aren't directly provided by a Web API response. """ - token_info = self.token_info token_info['expires_at'] = int(time.time()) + token_info['expires_in'] return token_info From 1609edd4a8eb6303b0b78b544c829d6f8f5ae4ce Mon Sep 17 00:00:00 2001 From: Matt John Date: Sun, 12 Jan 2020 15:29:04 +0000 Subject: [PATCH 62/71] Fix bug where formatted arguments are passed to self._warn, which only accepts a single string (#371) From b12281e7613b58f28bdb3b82886d027c1e49c748 Mon Sep 17 00:00:00 2001 From: David Anderson Lino de Sousa Date: Sun, 12 Jan 2020 16:29:17 +0100 Subject: [PATCH 63/71] Fix invalid calls to logging method (#297) The `_warn` method expects a String, and not a Format String + arguments From cc572364cb0a21a3148bd385e85f28bc476e8563 Mon Sep 17 00:00:00 2001 From: Stephane Bruckert Date: Sun, 12 Jan 2020 16:06:50 +0000 Subject: [PATCH 64/71] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b044a9..633a1ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + - Fixed invalid calls to logging warn method + ## [2.6.0] - 2020-01-12 ### Added From 4ec320774db8bac583d8b843a1078131c4575a3a Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Mon, 13 Jan 2020 09:23:51 -0500 Subject: [PATCH 65/71] Don't require mock for install (#419) Fixes #418 --- CHANGELOG.md | 3 ++- setup.py | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 633a1ec..d954080 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Fixed invalid calls to logging warn method + - `mock` no longer needed for install. Only used in `tox`. ## [2.6.0] - 2020-01-12 @@ -111,4 +112,4 @@ Updates to match released API Repackaged for saner imports ## [1.0.0] - 2017-04-05 -Initial release \ No newline at end of file +Initial release diff --git a/setup.py b/setup.py index d4df1ed..849ab76 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,6 @@ setup( author_email="paul@echonest.com", url='http://spotipy.readthedocs.org/', install_requires=[ - 'mock>=2.0.0', 'requests>=2.3.0', 'six>=1.10.0', ], From 3e2e8ed0fb76f6606839ced94659e8a1a82e2bf4 Mon Sep 17 00:00:00 2001 From: Ritiek Malhotra Date: Mon, 13 Jan 2020 20:24:53 +0530 Subject: [PATCH 66/71] Trim '?' when extracting ID from HTTP URLs If this excess part isn't trimmed from the ID, it can cause unexpected results when calling some API methods. For example; this code now correctly returns all albums for an artist: ``` artist = 'https://open.spotify.com/artist/7oPftvlwr6VrsViSDV7fJY?si=M3PrzRC4TBOZu8YyLYc-tA' artist_albums = sp.artist_albums(artist) ``` Previously, this code mimicked same behaviour as calling `spotify.artist`, but now will return albums as expected with this commit. Fixes #365 and #323. --- spotipy/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spotipy/client.py b/spotipy/client.py index 6e215f0..86ee137 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -1128,7 +1128,7 @@ class Spotify(object): if type != itype: self._warn('expected id of type %s but found type %s %s' % (type, itype, id)) - return fields[-1] + return fields[-1].split('?')[0] return id def _get_uri(self, type, id): From e6829a1139b61a6cef9012254b37331abe7cb94b Mon Sep 17 00:00:00 2001 From: Ritiek Malhotra Date: Mon, 13 Jan 2020 20:25:40 +0530 Subject: [PATCH 67/71] Add a changelog entry for #420 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d954080..f614f2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + - Fixed inconsistent behaviour with some API methods when + a full HTTP URL is passed. + ### Changed - Fixed invalid calls to logging warn method - `mock` no longer needed for install. Only used in `tox`. From 7d507ad2245ca40b7e26e633f2ae36b0c48de90e Mon Sep 17 00:00:00 2001 From: Stephane Bruckert Date: Mon, 13 Jan 2020 17:05:06 +0000 Subject: [PATCH 68/71] Bump to 2.6.1 --- CHANGELOG.md | 6 ++++-- docs/index.rst | 3 ++- setup.py | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f614f2e..28addea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,12 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.6.1] - 2020-01-13 + ### Fixed - Fixed inconsistent behaviour with some API methods when a full HTTP URL is passed. - -### Changed - Fixed invalid calls to logging warn method + +### Removed - `mock` no longer needed for install. Only used in `tox`. ## [2.6.0] - 2020-01-12 diff --git a/docs/index.rst b/docs/index.rst index 3e2e9ea..efb52cf 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -365,10 +365,11 @@ Spotipy authored by Paul Lamere (plamere) with contributions by: - Nathan Coleman // nathancoleman - Michael Birtwell // mbirtwell - Harrison Hayes // Harrison97 + - Stephane Bruckert // stephanebruckert License ======= -https://github.com/plamere/spotipy/blob/master/LICENSE.txt +https://github.com/plamere/spotipy/blob/master/LICENSE.md Indices and tables diff --git a/setup.py b/setup.py index 849ab76..b1929c7 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ desc = """### A light weight Python library for the Spotify Web API""" setup( name='spotipy', - version='2.6.0', + version='2.6.1', long_description=desc, long_description_content_type='text/markdown', author="@plamere", From f8608c80ebe590745e833673f60747582954d99e Mon Sep 17 00:00:00 2001 From: Stephane Bruckert Date: Mon, 13 Jan 2020 20:09:37 +0000 Subject: [PATCH 69/71] Close custom session in test --- tests/tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/tests.py b/tests/tests.py index 5d9a4a4..1cc119f 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -203,6 +203,7 @@ class TestSpotipy(unittest.TestCase): self.assertTrue( with_custom_session.user( user="akx")["uri"] == "spotify:user:akx") + sess.close() def test_force_no_requests_session(self): with_no_session = Spotify(auth=self.token, requests_session=False) From c5911268425736b82f8986ead9987cf6225817c7 Mon Sep 17 00:00:00 2001 From: Stephane Bruckert Date: Mon, 13 Jan 2020 20:12:07 +0000 Subject: [PATCH 70/71] Changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28addea..d42cf1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + + - Allow session keepalive + ## [2.6.1] - 2020-01-13 ### Fixed From 7229cc4edc7a3e30ea2052b22e2b4ff15db59a04 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2020 10:36:55 +0000 Subject: [PATCH 71/71] Bump requests from 2.3.0 to 2.20.0 (#400) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Bump requests from 2.3.0 to 2.20.0 Bumps [requests](https://github.com/requests/requests) from 2.3.0 to 2.20.0. - [Release notes](https://github.com/requests/requests/releases) - [Changelog](https://github.com/psf/requests/blob/master/HISTORY.md) - [Commits](https://github.com/requests/requests/compare/v2.3.0...v2.20.0) Signed-off-by: dependabot[bot] * Changelog Co-authored-by: Stéphane Bruckert --- CHANGELOG.md | 1 + requirements.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d42cf1d..de1c5e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Allow session keepalive + - Bump requests to 2.20.0 ## [2.6.1] - 2020-01-13 diff --git a/requirements.txt b/requirements.txt index 708dccd..6ef1b3b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ mock==2.0.0 -requests==2.3.0 +requests==2.20.0 six==1.10.0 \ No newline at end of file diff --git a/setup.py b/setup.py index b1929c7..e45c18e 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ setup( author_email="paul@echonest.com", url='http://spotipy.readthedocs.org/', install_requires=[ - 'requests>=2.3.0', + 'requests>=2.20.0', 'six>=1.10.0', ], license='LICENSE.md',