diff --git a/CHANGES.txt b/CHANGES.txt index 2d48728..b46e2c9 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,14 +2,15 @@ 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 used +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 +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 +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 05, 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.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 ff452be..27524a6 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ If you have suggestions, bugs or other issues specific to this library, file the - 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 used cache auth codes when scope changes +- 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) @@ -68,3 +68,4 @@ If you have suggestions, bugs or other issues specific to this library, file the - 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 diff --git a/setup.py b/setup.py index 6da71f9..857baa4 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup setup( name='spotipy', - version='2.3.5', + version='2.3.6', description='simple client for the Spotify Web API', author="@plamere", author_email="paul@echonest.com", diff --git a/spotipy/client.py b/spotipy/client.py index 16f0b2d..5c2ff21 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -100,7 +100,7 @@ class Spotify(object): try: r.raise_for_status() except: - raise SpotifyException(r.status_code, + raise SpotifyException(r.status_code, -1, u'%s:\n %s' % (r.url, r.json()['error']['message'])) if len(r.text) > 0: results = r.json() @@ -153,7 +153,7 @@ class Spotify(object): ''' returns the next result given a paged result Parameters: - - result - a previously returned paged result + - result - a previously returned paged result ''' if result['next']: return self._get(result['next']) @@ -164,13 +164,13 @@ class Spotify(object): ''' returns the previous result given a paged result Parameters: - - result - a previously returned paged result + - result - a previously returned paged result ''' if result['previous']: return self._get(result['previous']) else: return None - + def _warn(self, msg): print('warning:' + msg, file=sys.stderr) @@ -215,7 +215,7 @@ class Spotify(object): tlist = [self._get_id('artist', a) for a in artists] return self._get('artists/?ids=' + ','.join(tlist)) - def artist_albums(self, artist_id, album_type=None, country=None, + def artist_albums(self, artist_id, album_type=None, country=None, limit=20, offset=0): ''' Get Spotify catalog information about an artist's albums @@ -228,11 +228,11 @@ class Spotify(object): ''' trid = self._get_id('artist', artist_id) - return self._get('artists/' + trid + '/albums', album_type=album_type, + return self._get('artists/' + trid + '/albums', album_type=album_type, country=country, limit=limit, offset=offset) def artist_top_tracks(self, artist_id, country='US'): - ''' Get Spotify catalog information about an artist's top 10 tracks + ''' Get Spotify catalog information about an artist's top 10 tracks by country. Parameters: @@ -244,8 +244,8 @@ class Spotify(object): return self._get('artists/' + trid + '/top-tracks', country=country) def artist_related_artists(self, artist_id): - ''' Get Spotify catalog information about artists similar to an - identified artist. Similarity is based on analysis of the + ''' Get Spotify catalog information about artists similar to an + identified artist. Similarity is based on analysis of the Spotify community's listening history. Parameters: @@ -264,15 +264,17 @@ class Spotify(object): trid = self._get_id('album', album_id) return self._get('albums/' + trid) - def album_tracks(self, album_id): + def album_tracks(self, album_id, limit=50, offset=0): ''' Get Spotify catalog information about an album's tracks Parameters: - album_id - the album ID, URI or URL + - limit - the number of items to return + - offset - the index of the first item to return ''' trid = self._get_id('album', album_id) - return self._get('albums/' + trid + '/tracks/') + return self._get('albums/' + trid + '/tracks/', limit=limit, offset=offset) def albums(self, albums): ''' returns a list of albums given the album IDs, URIs, or URLs @@ -303,7 +305,7 @@ class Spotify(object): - user - the id of the usr ''' return self._get('users/' + user) - + def user_playlists(self, user, limit=50, offset=0): ''' Gets playlists of a user @@ -326,7 +328,7 @@ class Spotify(object): plid = self._get_id('playlist', playlist_id) return self._get("users/%s/playlists/%s" % (user, plid), fields=fields) - def user_playlist_tracks(self, user, playlist_id = None, fields=None, + def user_playlist_tracks(self, user, playlist_id = None, fields=None, limit=100, offset=0): ''' Get full details of the tracks of a playlist owned by a user. @@ -338,7 +340,7 @@ class Spotify(object): - offset - the index of the first track to return ''' plid = self._get_id('playlist', playlist_id) - return self._get("users/%s/playlists/%s/tracks" % (user, plid), + return self._get("users/%s/playlists/%s/tracks" % (user, plid), limit=limit, offset=offset, fields=fields) def user_playlist_create(self, user, name, public=True): @@ -352,7 +354,7 @@ class Spotify(object): data = {'name':name, 'public':public } return self._post("users/%s/playlists" % (user,), payload = data) - def user_playlist_add_tracks(self, user, playlist_id, tracks, + def user_playlist_add_tracks(self, user, playlist_id, tracks, position=None): ''' Adds tracks to a playlist @@ -364,7 +366,7 @@ class Spotify(object): ''' plid = self._get_id('playlist', playlist_id) ftracks = [ self._get_uri('track', tid) for tid in tracks] - return self._post("users/%s/playlists/%s/tracks" % (user,plid), + return self._post("users/%s/playlists/%s/tracks" % (user,plid), payload = ftracks, position=position) def user_playlist_replace_tracks(self, user, playlist_id, tracks): @@ -378,7 +380,7 @@ class Spotify(object): plid = self._get_id('playlist', playlist_id) ftracks = [ self._get_uri('track', tid) for tid in tracks] payload = { "uris": ftracks } - return self._put("users/%s/playlists/%s/tracks" % (user,plid), + return self._put("users/%s/playlists/%s/tracks" % (user,plid), payload = payload) def user_playlist_reorder_tracks(self, user, playlist_id, range_start, insert_before, @@ -402,7 +404,7 @@ class Spotify(object): return self._put("users/%s/playlists/%s/tracks" % (user,plid), payload = payload) - def user_playlist_remove_all_occurrences_of_tracks(self, user, playlist_id, + def user_playlist_remove_all_occurrences_of_tracks(self, user, playlist_id, tracks, snapshot_id=None): ''' Removes all occurrences of the given tracks from the given playlist @@ -419,19 +421,19 @@ class Spotify(object): payload = { "tracks": [ {"uri": track} for track in ftracks] } if snapshot_id: payload["snapshot_id"] = snapshot_id - return self._delete("users/%s/playlists/%s/tracks" % (user, plid), + return self._delete("users/%s/playlists/%s/tracks" % (user, plid), payload = payload) - def user_playlist_remove_specific_occurrences_of_tracks(self, user, + def user_playlist_remove_specific_occurrences_of_tracks(self, user, playlist_id, tracks, snapshot_id=None): ''' Removes all occurrences of the given tracks from the given playlist 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: + - 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] } ] + { "uri":"1301WleyT98MSxVHPZCA6M", "positions":[7] } ] - snapshot_id - optional id of the playlist snapshot ''' @@ -445,7 +447,7 @@ class Spotify(object): payload = { "tracks": ftracks } if snapshot_id: payload["snapshot_id"] = snapshot_id - return self._delete("users/%s/playlists/%s/tracks" % (user, plid), + return self._delete("users/%s/playlists/%s/tracks" % (user, plid), payload = payload) def me(self): @@ -455,7 +457,7 @@ class Spotify(object): return self._get('me/') def current_user(self): - ''' Get detailed profile information about the current user. + ''' Get detailed profile information about the current user. An alias for the 'me' method. ''' return self.me() @@ -472,7 +474,7 @@ class Spotify(object): return self._get('me/tracks', limit=limit, offset=offset) def current_user_saved_tracks_delete(self, tracks=[]): - ''' Remove one or more tracks from the current user's + ''' Remove one or more tracks from the current user's "Your Music" library. Parameters: @@ -493,7 +495,7 @@ class Spotify(object): def current_user_saved_tracks_add(self, tracks=[]): - ''' Add one or more tracks to the current user's + ''' Add one or more tracks to the current user's "Your Music" library. Parameters: @@ -503,16 +505,16 @@ class Spotify(object): return self._put('me/tracks/?ids=' + ','.join(tlist)) - def featured_playlists(self, locale=None, country=None, + def featured_playlists(self, locale=None, country=None, timestamp=None, limit=20, offset = 0): ''' Get a list of Spotify featured playlists - + Parameters: - locale - The desired language, consisting of a lowercase ISO 639 language code and an uppercase ISO 3166-1 alpha-2 country - code, joined by an underscore. + code, joined by an underscore. - - country - An ISO 3166-1 alpha-2 country code. + - country - An ISO 3166-1 alpha-2 country code. - timestamp - A timestamp in ISO 8601 format: yyyy-MM-ddTHH:mm:ss. Use this parameter to specify the user's @@ -524,25 +526,25 @@ class Spotify(object): - offset - The index of the first item to return. Default: 0 (the first object). Use with limit to get the next set of - items. + items. ''' return self._get('browse/featured-playlists', locale=locale, country=country, timestamp=timestamp, limit=limit, offset=offset) def new_releases(self, country=None, limit=20, offset = 0): ''' Get a list of new album releases featured in Spotify - + Parameters: - - country - An ISO 3166-1 alpha-2 country code. + - country - An ISO 3166-1 alpha-2 country code. - limit - The maximum number of items to return. Default: 20. Minimum: 1. Maximum: 50 - offset - The index of the first item to return. Default: 0 (the first object). Use with limit to get the next set of - items. + items. ''' - return self._get('browse/new-releases', country=country, + return self._get('browse/new-releases', country=country, limit=limit, offset=offset) def _get_id(self, type, id): diff --git a/tests/tests.py b/tests/tests.py index 3f244fe..7308e1a 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -15,6 +15,8 @@ class TestSpotipy(unittest.TestCase): weezer_urn = 'spotify:artist:3jOstUTkEu2JkjvRdBA5Gu' pablo_honey_urn = 'spotify:album:6AZv3m27uyRxi8KyJSfUxL' radiohead_urn = 'spotify:artist:4Z8W4fKeB5YxbusRsdQVPb' + angeles_haydn_urn = 'spotify:album:1vAbqAeuJVWNAe7UR00bdM' + bad_id = 'BAD_ID' @@ -38,6 +40,17 @@ class TestSpotipy(unittest.TestCase): 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'] + total, received = results['total'], len(tracks) + while received < total: + results = self.spotify.album_tracks(self.angeles_haydn_urn, offset=received) + tracks.extend(results['items']) + received = len(tracks) + + self.assertEqual(received, total) + def test_albums(self): results = self.spotify.albums([self.pinkerton_urn, self.pablo_honey_urn]) self.assertTrue('albums' in results)