diff --git a/.gitignore b/.gitignore index 286ddd2..137335b 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,5 @@ coverage.xml # Sphinx documentation docs/_build/ - .* -archive +archive \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9a2fd91 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,37 @@ +- 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()` diff --git a/CHANGES.txt b/CHANGES.txt deleted file mode 100644 index b46e2c9..0000000 --- a/CHANGES.txt +++ /dev/null @@ -1,16 +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 diff --git a/README.md b/README.md index 9f9ec71..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 \ No newline at end of file +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 f18302a..36d5c3f 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,11 +9,10 @@ # - 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 # 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/docs/index.rst b/docs/index.rst index 6283ed8..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':: @@ -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 ======================= @@ -103,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 @@ -127,16 +130,16 @@ 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, +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 @@ -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 ======================= @@ -191,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 ======================= @@ -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,10 +279,10 @@ 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'] - results = sp.user_playlist(username, playlist['id'], + print() + print(playlist['name']) + print (' total tracks', playlist['tracks']['total']) + results = sp.user_playlist(username, playlist['id'], fields="tracks,next") tracks = results['tracks'] show_tracks(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 @@ -292,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 @@ -330,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 `_ @@ -338,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_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 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) diff --git a/examples/my_top_tracks.py b/examples/my_top_tracks.py index 6bd72ec..f1a96ea 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) 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)) 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 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 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/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) 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) 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']: 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'] 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) 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']): 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: 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']) 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] 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/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) diff --git a/requirements.txt b/requirements.txt index 47f25d8..d674b24 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +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/setup.py b/setup.py index c673a92..97f74fe 100644 --- a/setup.py +++ b/setup.py @@ -9,8 +9,10 @@ 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', - ], + 'simplejson==3.13.2', + ], license='LICENSE.txt', packages=['spotipy']) diff --git a/spotipy/__init__.py b/spotipy/__init__.py index 9be1dce..b218c0a 100644 --- a/spotipy/__init__.py +++ b/spotipy/__init__.py @@ -1,2 +1,5 @@ -VERSION='2.0.1' -from .client import Spotify, SpotifyException +VERSION='2.4.5' + +from .client import * +from .oauth2 import * +from .util import * diff --git a/spotipy/client.py b/spotipy/client.py index 3e33a21..273563c 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): @@ -55,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: @@ -382,6 +387,18 @@ 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. @@ -507,7 +524,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 """ @@ -527,9 +544,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 """ @@ -564,7 +583,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))) @@ -586,17 +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_tracks(self, limit=20, offset=0): """ Gets a list of the tracks saved in the current authorized user's "Your Music" library @@ -612,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, @@ -686,9 +695,30 @@ 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(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_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] + 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 "Your Music" library. @@ -696,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 @@ -713,6 +752,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 @@ -869,14 +922,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. ''' @@ -1040,15 +1085,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 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..0bf9fab 100644 --- a/spotipy/util.py +++ b/spotipy/util.py @@ -1,15 +1,31 @@ +# -*- 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__ = [ + 'CLIENT_CREDS_ENV_VARS', + 'prompt_for_user_token' +] + import os + 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 - the user token suitable for use with the spotipy.Spotify + the user token suitable for use with the spotipy.Spotify constructor Parameters: @@ -41,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, @@ -82,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) diff --git a/tests/authtests.py b/tests/authtests.py deleted file mode 100644 index 13504a6..0000000 --- a/tests/authtests.py +++ /dev/null @@ -1,222 +0,0 @@ -# -*- coding: latin-1 -*- - -import spotipy -from spotipy import util -import unittest -import pprint -import sys -import simplejson as json - -''' - Since these tests require authentication they are maintained - separately from the other tests. - - 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 - ''' - - playlist = "spotify:user:plamere:playlist:2oCEWyyAPbZp9xhVSxZavx" - four_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp", - "spotify:track:7IHOIqZUUInxjVkko181PB", - "4VrWlk8IQxevMvERoX08iC", - "http://open.spotify.com/track/3cySlItpiPiIAzU3NyHCJf"] - - two_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp", - "spotify:track:7IHOIqZUUInxjVkko181PB"] - - other_tracks=["spotify:track:2wySlB6vMzCbQrRnNGOYKa", - "spotify:track:29xKs5BAHlmlX1u4gzQAbJ", - "spotify:track:1PB7gRWcvefzu7t3LJLUlf"] - - bad_id = 'BAD_ID' - - def test_track_bad_id(self): - try: - track = spotify.track(self.bad_id) - self.assertTrue(False) - except spotipy.SpotifyException: - self.assertTrue(True) - - - def test_basic_user_profile(self): - user = spotify.user(username) - self.assertTrue(user['id'] == username) - - def test_current_user(self): - user = spotify.current_user() - self.assertTrue(user['id'] == username) - - def test_me(self): - user = spotify.me() - self.assertTrue(user['id'] == username) - - def test_user_playlists(self): - playlists = spotify.user_playlists(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) - self.assertTrue('items' in playlists) - for playlist in playlists['items']: - user = playlist['owner']['id'] - pid = playlist['id'] - results = 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 paramter - - self.assertTrue(len(playlists['items']) == 5) - - def test_current_user_saved_tracks(self): - tracks = spotify.current_user_saved_tracks() - self.assertTrue(len(tracks['items']) > 0) - - def test_current_user_saved_albums(self): - albums = spotify.current_user_saved_albums() - self.assertTrue(len(albums['items']) > 0) - - def test_current_user_playlists(self): - playlists = 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.assertTrue(len(follows) == 1, 'proper follows length') - self.assertTrue(follows[0], 'is following') - spotify.user_playlist_unfollow('plamere', '4erXB04MxwRAVqcUEpu30O') - - follows = spotify.user_playlist_is_following('plamere', '4erXB04MxwRAVqcUEpu30O', ['plamere']) - 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() - total = tracks['total'] - - spotify.current_user_saved_tracks_add(self.four_tracks) - - tracks = 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() - new_total = tracks['total'] - self.assertTrue(new_total == total) - - - def test_categories(self): - response = spotify.categories() - self.assertTrue(len(response['categories']) > 0) - - def test_category_playlists(self): - response = spotify.categories() - for cat in response['categories']['items']: - cat_id = cat['id'] - response = spotify.category_playlists(category_id=cat_id) - self.assertTrue(len(response['playlists']["items"]) > 0) - - def test_new_releases(self): - response = spotify.new_releases() - self.assertTrue(len(response['albums']) > 0) - - def test_featured_releases(self): - response = spotify.featured_playlists() - self.assertTrue(len(response['playlists']) > 0) - - def test_current_user_follows(self): - response = 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() - items = response['items'] - self.assertTrue(len(items) > 0) - - def test_current_user_top_artists(self): - response = 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) - 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) - 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') - - # remove all tracks from it - - spotify.user_playlist_replace_tracks(username, playlist_id,[]) - - playlist = spotify.user_playlist(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.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, - playlist_id, self.two_tracks) - - playlist = spotify.user_playlist(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, - playlist_id, self.other_tracks) - - playlist = spotify.user_playlist(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],)) diff --git a/tests/authtests2.py b/tests/authtests2.py deleted file mode 100644 index 670e1c8..0000000 --- a/tests/authtests2.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: latin-1 -*- - -import spotipy -from spotipy import util -import unittest -import pprint -import sys -import simplejson as json -from spotipy.oauth2 import SpotifyClientCredentials - -''' - Since these tests require authentication they are maintained - separately from the other tests. - - 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 - ''' - - playlist = "spotify:user:plamere:playlist:2oCEWyyAPbZp9xhVSxZavx" - four_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp", - "spotify:track:7IHOIqZUUInxjVkko181PB", - "4VrWlk8IQxevMvERoX08iC", - "http://open.spotify.com/track/3cySlItpiPiIAzU3NyHCJf"] - - two_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp", - "spotify:track:7IHOIqZUUInxjVkko181PB"] - - other_tracks=["spotify:track:2wySlB6vMzCbQrRnNGOYKa", - "spotify:track:29xKs5BAHlmlX1u4gzQAbJ", - "spotify:track:1PB7gRWcvefzu7t3LJLUlf"] - - bad_id = 'BAD_ID' - - - def test_audio_analysis(self): - result = spotify.audio_analysis(self.four_tracks[0]) - assert('beats' in result) - - def test_audio_features(self): - results = spotify.audio_features(self.four_tracks) - self.assertTrue(len(results) == len(self.four_tracks)) - for track in results: - assert('speechiness' in track) - - def test_audio_features_with_bad_track(self): - bad_tracks = [] - bad_tracks = ['spotify:track:bad'] - input = self.four_tracks + bad_tracks - results = spotify.audio_features(input) - self.assertTrue(len(results) == len(input)) - for track in results[:-1]: - if track != None: - assert('speechiness' in track) - 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) - 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 deleted file mode 100644 index 4905062..0000000 --- a/tests/client_credentials_tests.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: latin-1 -*- - -import spotipy -from spotipy.oauth2 import SpotifyClientCredentials -import unittest - -''' - Client Credentials Requests Tests -''' - -class ClientCredentialsTestSpotipy(unittest.TestCase): - ''' - These tests require user authentication - ''' - - muse_urn = 'spotify:artist:12Chz98pHFMPJEknJQMWvI' - - def test_request_with_token(self): - artist = 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_auth.py b/tests/test_auth.py new file mode 100644 index 0000000..66e2ab4 --- /dev/null +++ b/tests/test_auth.py @@ -0,0 +1,297 @@ +# -*- coding: utf-8 -*- + +""" +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 +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, +) + + +class AuthTestSpotipy(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' + """ + + 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"] + + two_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp", + "spotify:track:7IHOIqZUUInxjVkko181PB"] + + 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())) + + self.username = os.getenv(CCEV['client_username']) + + self.scope = ( + 'playlist-modify-public ' + 'user-library-read ' + 'user-follow-read ' + 'user-library-modify ' + 'user-read-private ' + 'user-top-read ' + 'user-follow-modify' + ) + + self.token = prompt_for_user_token(self.username, scope=self.scope) + + self.spotify = Spotify(auth=self.token) + + def test_track_bad_id(self): + try: + self.spotify.track(self.bad_id) + self.assertTrue(False) + except SpotifyException: + self.assertTrue(True) + + def test_basic_user_profile(self): + user = self.spotify.user(self.username) + self.assertTrue(user['id'] == self.username.lower()) + + def test_current_user(self): + user = self.spotify.current_user() + self.assertTrue(user['id'] == self.username.lower()) + + def test_me(self): + user = self.spotify.me() + self.assertTrue(user['id'] == self.username.lower()) + + def test_user_playlists(self): + 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 = 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 = 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) + + def test_current_user_saved_albums(self): + # List + albums = self.spotify.current_user_saved_albums() + 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) + self.assertTrue('items' in playlists) + 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.assertTrue(len(follows) == 1, 'proper follows length') + self.assertTrue(follows[0], 'is following') + self.spotify.user_playlist_unfollow('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.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() + 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() + new_total = tracks['total'] + self.assertTrue(new_total == total) + + def test_categories(self): + response = self.spotify.categories() + self.assertTrue(len(response['categories']) > 0) + + def test_category_playlists(self): + response = self.spotify.categories() + for cat in response['categories']['items']: + cat_id = cat['id'] + response = self.spotify.category_playlists(category_id=cat_id) + if len(response['playlists']["items"]) > 0: + break + self.assertTrue(True) + + def test_new_releases(self): + response = self.spotify.new_releases() + self.assertTrue(len(response['albums']) > 0) + + def test_featured_releases(self): + response = self.spotify.featured_playlists() + self.assertTrue(len(response['playlists']) > 0) + + def test_current_user_follows(self): + response = self.spotify.current_user_followed_artists() + artists = response['artists'] + self.assertTrue(len(artists['items']) > 0) + + def test_current_user_top_tracks(self): + response = self.spotify.current_user_top_tracks() + items = response['items'] + self.assertTrue(len(items) > 0) + + def test_current_user_top_artists(self): + response = self.spotify.current_user_top_artists() + items = response['items'] + self.assertTrue(len(items) > 0) + + 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 = 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('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) + + # 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) + + 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() diff --git a/tests/test_auth2.py b/tests/test_auth2.py new file mode 100644 index 0000000..e52092a --- /dev/null +++ b/tests/test_auth2.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- + +""" +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 + +sys.path.insert(0, os.path.abspath(os.pardir)) + +from spotipy import ( + Spotify, + SpotifyClientCredentials, +) + + +class AuthTestSpotipy(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' + """ + + playlist = "spotify:user:plamere:playlist:2oCEWyyAPbZp9xhVSxZavx" + four_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp", + "spotify:track:7IHOIqZUUInxjVkko181PB", + "4VrWlk8IQxevMvERoX08iC", + "http://open.spotify.com/track/3cySlItpiPiIAzU3NyHCJf"] + + two_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp", + "spotify:track:7IHOIqZUUInxjVkko181PB"] + + 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.trace = False + + def test_audio_analysis(self): + result = self.spotify.audio_analysis(self.four_tracks[0]) + assert('beats' in result) + + def test_audio_features(self): + results = self.spotify.audio_features(self.four_tracks) + self.assertTrue(len(results) == len(self.four_tracks)) + for track in results: + assert('speechiness' in track) + + def test_audio_features_with_bad_track(self): + bad_tracks = [] + bad_tracks = ['spotify:track:bad'] + input = self.four_tracks + bad_tracks + results = self.spotify.audio_features(input) + self.assertTrue(len(results) == len(input)) + for track in results[:-1]: + if track != None: + assert('speechiness' in track) + self.assertTrue(results[-1] == None) + + def test_recommendations(self): + 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 new file mode 100644 index 0000000..a3b7103 --- /dev/null +++ b/tests/test_client_credentials.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- + +""" Client Credentials Requests Tests """ + +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 + + :: + + 'SPOTIPY_CLIENT_USERNAME' + 'SPOTIPY_CLIENT_ID' + 'SPOTIPY_CLIENT_SECRET' + 'SPOTIPY_REDIRECT_URI' + """ + + @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 = self.spotify.artist(self.muse_urn) + self.assertTrue(artist['name'] == 'Muse') + + +if __name__ == '__main__': + + 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..4cd90df 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -1,13 +1,36 @@ -# -*- 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 ( + CLIENT_CREDS_ENV_VARS as CCEV, + prompt_for_user_token, + Spotify, + SpotifyException, +) 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' @@ -22,8 +45,20 @@ class TestSpotipy(unittest.TestCase): bad_id = 'BAD_ID' - def setUp(self): - self.spotify = spotipy.Spotify() + @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())) + + self.username = os.getenv(CCEV['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) @@ -41,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'] @@ -74,7 +109,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 +156,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=.01) 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 +184,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 +201,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 +220,5 @@ class TestSpotipy(unittest.TestCase): ''' if __name__ == '__main__': + unittest.main() 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