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 09d3a1f..9c3151f 100644 --- a/README.md +++ b/README.md @@ -51,36 +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 +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 91fd31e..3e2e9ea 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 ======================= @@ -107,7 +110,7 @@ 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 +Register your app at `My Applications `_ and register the redirect URI mentioned in the above paragragh. @@ -115,7 +118,7 @@ redirect URI mentioned in the above paragragh. *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 @@ -129,15 +132,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-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 @@ -147,6 +151,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 @@ -156,7 +161,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) @@ -166,9 +171,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 ======================= @@ -192,8 +197,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 ======================= @@ -218,6 +223,7 @@ Here are a few more examples of using *Spotipy*. Add tracks to a playlist:: + from __future__ import print_function import pprint import sys @@ -229,7 +235,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' @@ -239,15 +245,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 @@ -255,16 +262,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) @@ -274,10 +281,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) @@ -285,7 +292,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 @@ -293,7 +300,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 @@ -331,7 +338,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 `_ @@ -339,24 +346,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 7b81605..97f74fe 100644 --- a/setup.py +++ b/setup.py @@ -2,14 +2,17 @@ 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/', 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