Merge branch 'master' into proxy

This commit is contained in:
Sebastian Bischoff 2017-01-02 14:43:40 +01:00 committed by GitHub
commit 1ef89cfd2c
20 changed files with 516 additions and 133 deletions

View File

@ -6,7 +6,7 @@ Spotipy is a thin client library for the Spotify Web API.
## Documentation ## Documentation
Spotipy's full documentation is online at [Spotipy Documentation](http://spotipy.readthedocs.org/) Spotipy's full documentation is online at [Spotipy Documentation](http://spotipy.readthedocs.org/)
## Installation ## Installation
@ -14,7 +14,7 @@ If you already have [Python](http://www.python.org/) on your system you can inst
python setup.py install python setup.py install
You can also install it using a popular package manager with You can also install it using a popular package manager with
`pip install spotipy` `pip install spotipy`
@ -29,7 +29,7 @@ or
## Quick Start ## Quick Start
To get started, simply install spotipy, reate a Spotify object and call methods: To get started, simply install spotipy, create a Spotify object and call methods:
import spotipy import spotipy
sp = spotipy.Spotify() sp = spotipy.Spotify()
@ -39,7 +39,7 @@ To get started, simply install spotipy, reate a Spotify object and call methods:
print ' ', i, t['name'] print ' ', i, t['name']
A full set of examples can be found in the [online documentation](http://spotipy.readthedocs.org/) and in the [Spotipy examples directory](https://github.com/plamere/spotipy/tree/master/examples). A full set of examples can be found in the [online documentation](http://spotipy.readthedocs.org/) and in the [Spotipy examples directory](https://github.com/plamere/spotipy/tree/master/examples).
## Reporting Issues ## Reporting Issues
@ -64,10 +64,11 @@ If you have suggestions, bugs or other issues specific to this library, file the
- v2.0.2 - August 25, 2014 -- Moved to spotipy at pypi - 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.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.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.0 - January 5, 2015 -- Added session support added by akx.
- v2.3.2 - March 31, 2015 -- Added auto retry logic - v2.3.2 - March 31, 2015 -- Added auto retry logic
- v2.3.3 - April 1, 2015 -- added client credential flow - v2.3.3 - April 1, 2015 -- added client credential flow
- v2.3.5 - April 28, 2015 -- Fixed bug in auto retry logic - 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.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.7 - August 10, 2015 -- Added current_user_followed_artists
- v2.3.8 - March 30, 2016 -- Added recs, audio features, user top lists - v2.3.8 - March 30, 2016 -- Added recs, audio features, user top lists
- v2.4.0 - December 31, 2016 -- Incorporated a number of PRs

View File

@ -98,12 +98,32 @@ Many methods require user authentication. For these requests you will need to
generate an authorization token that indicates that the user has granted generate an authorization token that indicates that the user has granted
permission for your application to perform the given task. You will need to permission for your application to perform the given task. You will need to
register your app to get the credentials necessary to make authorized calls. register your app to get the credentials necessary to make authorized calls.
Even if your script does not have an accessible URL you need to specify one
when registering your application where the spotify authentication API will
redirect to after successful login. The URL doesn't need to work or be
accessible, you can specify "http://localhost/", after successful login you
just need to copy the "http://localhost/?code=..." URL from your browser
and paste it to the console where your script is running.
Register your app at Register your app at
`My Applications `My Applications
<https://developer.spotify.com/my-applications/#!/applications>`_. <https://developer.spotify.com/my-applications/#!/applications>`_.
*Spotipy* provides a *spotipy* supports two authorization flows:
- 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
to authenticate your requests to the Spotify Web API and to obtain
a higher rate limit than you would
Authorization Code Flow
=======================
To support the **Authorization Code Flow** *Spotipy* provides a
utility method ``util.prompt_for_user_token`` that will attempt to authorize the 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, user. You can pass your app credentials directly into the method as arguments,
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,
@ -117,8 +137,9 @@ Call ``util.prompt_for_user_token`` method with the username and the
desired scope (see `Using desired scope (see `Using
Scopes <https://developer.spotify.com/web-api/using-scopes/>`_ for information Scopes <https://developer.spotify.com/web-api/using-scopes/>`_ for information
about scopes) and credentials. This will coordinate the user authorization via about scopes) and credentials. This will coordinate the user authorization via
your web browser. The credentials are cached locally and are used to automatically your web browser and ask for the SPOTIPY_REDIRECT_URI you were redirected to
re-authorized expired tokens. with the authorization token appended. The credentials are cached locally and
are used to automatically re-authorized expired tokens.
Here's an example of getting user authorization to read a user's saved tracks:: Here's an example of getting user authorization to read a user's saved tracks::
@ -145,6 +166,31 @@ Here's an example of getting user authorization to read a user's saved tracks::
else: else:
print "Can't get token for", username print "Can't get token for", username
Client Credentials Flow
=======================
To support the **Client Credentials Flow** *Spotipy* provides a
class SpotifyClientCredentials that can be used to authenticate requests like so::
import spotipy
from spotipy.oauth2 import SpotifyClientCredentials
client_credentials_manager = SpotifyClientCredentials()
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
playlists = sp.user_playlists('spotify')
while playlists:
for i, playlist in enumerate(playlists['items']):
print("%4d %s %s" % (i + 1 + playlists['offset'], playlist['uri'], playlist['name']))
if playlists['next']:
playlists = sp.next(playlists)
else:
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
IDs URIs and URLs IDs URIs and URLs
======================= =======================
*Spotipy* supports a number of different ID types: *Spotipy* supports a number of different ID types:
@ -305,6 +351,7 @@ Spotipy authored by Paul Lamere (plamere) with contributions by:
- Steve Winton // swinton - Steve Winton // swinton
- Tim Balzer // timbalzer - Tim Balzer // timbalzer
- corycorycory // corycorycory - corycorycory // corycorycory
- Nathan Coleman // nathancoleman
License License
======= =======

View File

@ -0,0 +1,27 @@
# Add tracks to 'Your Collection' of saved tracks
import pprint
import sys
import spotipy
import spotipy.util as util
scope = 'user-library-modify'
if len(sys.argv) > 2:
username = sys.argv[1]
aids = sys.argv[2:]
else:
print("Usage: %s username album-id ..." % (sys.argv[0],))
sys.exit()
token = util.prompt_for_user_token(username, scope)
if token:
sp = spotipy.Spotify(auth=token)
sp.trace = False
results = sp.current_user_saved_albums_add(albums=aids)
pprint.pprint(results)
else:
print("Can't get token for", username)

View File

@ -34,7 +34,7 @@ def show_artist_albums(id):
print('Total albums:', len(albums)) print('Total albums:', len(albums))
unique = set() # skip duplicate albums unique = set() # skip duplicate albums
for album in albums: for album in albums:
name = album['name'] name = album['name'].lower()
if not name in unique: if not name in unique:
print(name) print(name)
unique.add(name) unique.add(name)

View File

@ -0,0 +1,23 @@
# shows audio analysis for the given track
from __future__ import print_function # (at top of module)
from spotipy.oauth2 import SpotifyClientCredentials
import json
import spotipy
import time
import sys
client_credentials_manager = SpotifyClientCredentials()
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
if len(sys.argv) > 1:
tid = sys.argv[1]
else:
tid = 'spotify:track:4TTV7EcfroSLWzXRY6gLv6'
start = time.time()
analysis = sp.audio_analysis(tid)
delta = time.time() - start
print(json.dumps(analysis, indent=4))
print ("analysis retrieved in %.2f seconds" % (delta,))

View File

@ -15,14 +15,22 @@ sp.trace=False
if len(sys.argv) > 1: if len(sys.argv) > 1:
artist_name = ' '.join(sys.argv[1:]) artist_name = ' '.join(sys.argv[1:])
results = sp.search(q=artist_name, limit=50) else:
tids = [] artist_name = 'weezer'
for i, t in enumerate(results['tracks']['items']):
print(' ', i, t['name'])
tids.append(t['uri'])
start = time.time() results = sp.search(q=artist_name, limit=50)
features = sp.audio_features(tids) tids = []
delta = time.time() - start for i, t in enumerate(results['tracks']['items']):
print(json.dumps(features, indent=4)) print(' ', i, t['name'])
print ("features retrieved in %.2f seconds" % (delta,)) tids.append(t['uri'])
start = time.time()
features = sp.audio_features(tids)
delta = time.time() - start
for feature in features:
print(json.dumps(feature, indent=4))
print()
analysis = sp._get(feature['analysis_url'])
print(json.dumps(analysis, indent=4))
print()
print ("features retrieved in %.2f seconds" % (delta,))

View File

@ -0,0 +1,38 @@
# Modify the details of a playlist (name, public, collaborative)
import sys
import spotipy
import spotipy.util as util
if len(sys.argv) > 3:
username = sys.argv[1]
playlist_id = sys.argv[2]
name = sys.argv[3]
public = None
if len(sys.argv) > 4:
public = sys.argv[4].lower() == 'true'
collaborative = None
if len(sys.argv) > 5:
collaborative = sys.argv[5].lower() == 'true'
else:
print ("Usage: %s username playlist_id name [public collaborative]" %
(sys.argv[0]))
sys.exit()
scope = 'playlist-modify-public playlist-modify-private'
token = util.prompt_for_user_token(username, scope)
if token:
sp = spotipy.Spotify(auth=token)
sp.trace = False
results = sp.user_playlist_change_details(
username, playlist_id, name=name, public=public,
collaborative=collaborative)
print results
else:
print "Can't get token for", username

26
examples/my_playlists.py Normal file
View File

@ -0,0 +1,26 @@
# Shows the top artists for a user
import pprint
import sys
import spotipy
import spotipy.util as util
import simplejson as json
if len(sys.argv) > 1:
username = sys.argv[1]
else:
print("Usage: %s username" % (sys.argv[0],))
sys.exit()
scope = ''
token = util.prompt_for_user_token(username, scope)
if token:
sp = spotipy.Spotify(auth=token)
sp.trace = False
results = sp.current_user_playlists(limit=50)
for i, item in enumerate(results['items']):
print("%d %s" %(i, item['name']))
else:
print("Can't get token for", username)

View File

@ -1,5 +1,5 @@
# shows artist info for a URN or URL # shows album info for a URN or URL
import spotipy import spotipy
import sys import sys
@ -8,10 +8,9 @@ import pprint
if len(sys.argv) > 1: if len(sys.argv) > 1:
urn = sys.argv[1] urn = sys.argv[1]
else: else:
urn = 'spotify:artist:3jOstUTkEu2JkjvRdBA5Gu' urn = 'spotify:album:5yTx83u3qerZF7GRJu7eFk'
sp = spotipy.Spotify() sp = spotipy.Spotify()
artist = sp.artist(urn) album = sp.album(urn)
pprint.pprint(artist) pprint.pprint(album)

View File

@ -1,5 +1,4 @@
# shows a user's saved tracks (need to be authenticated via oauth)
# Adds tracks to a playlist
import sys import sys
import spotipy import spotipy

View File

@ -0,0 +1,14 @@
# shows artist info for a URN or URL
import spotipy
import sys
import pprint
if len(sys.argv) > 1:
search_str = sys.argv[1]
else:
search_str = 'Radiohead'
sp = spotipy.Spotify(requests_timeout=.1)
result = sp.search(search_str)
pprint.pprint(result)

View File

@ -16,7 +16,7 @@ if __name__ == '__main__':
username = sys.argv[1] username = sys.argv[1]
else: else:
print("Whoops, need your username!") print("Whoops, need your username!")
print("usage: python user_playlists.py [username]") print("usage: python user_playlists_contents.py [username]")
sys.exit() sys.exit()
token = util.prompt_for_user_token(username) token = util.prompt_for_user_token(username)

View File

@ -0,0 +1,25 @@
# Gets all the public playlists for the given
# user. Uses Client Credentials flow
#
import sys
import spotipy
from spotipy.oauth2 import SpotifyClientCredentials
client_credentials_manager = SpotifyClientCredentials()
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
user = 'spotify'
if len(sys.argv) > 1:
user = sys.argv[1]
playlists = sp.user_playlists(user)
while playlists:
for i, playlist in enumerate(playlists['items']):
print("%4d %s %s" % (i + 1 + playlists['offset'], playlist['uri'], playlist['name']))
if playlists['next']:
playlists = sp.next(playlists)
else:
playlists = None

View File

@ -2,7 +2,7 @@ from setuptools import setup
setup( setup(
name='spotipy', name='spotipy',
version='2.3.8', version='2.4.0',
description='simple client for the Spotify Web API', description='simple client for the Spotify Web API',
author="@plamere", author="@plamere",
author_email="paul@echonest.com", author_email="paul@echonest.com",

View File

@ -3,7 +3,6 @@
from __future__ import print_function from __future__ import print_function
import sys import sys
import base64
import requests import requests
import json import json
import time import time
@ -11,16 +10,23 @@ import time
''' A simple and thin Python library for the Spotify Web API ''' A simple and thin Python library for the Spotify Web API
''' '''
class SpotifyException(Exception): class SpotifyException(Exception):
def __init__(self, http_status, code, msg): def __init__(self, http_status, code, msg, headers=None):
self.http_status = http_status self.http_status = http_status
self.code = code self.code = code
self.msg = msg self.msg = msg
# `headers` is used to support `Retry-After` in the event of a
# 429 status code.
if headers is None:
headers = {}
self.headers = headers
def __str__(self): def __str__(self):
return 'http status: {0}, code:{1} - {2}'.format( return 'http status: {0}, code:{1} - {2}'.format(
self.http_status, self.code, self.msg) self.http_status, self.code, self.msg)
class Spotify(object): class Spotify(object):
''' '''
Example usage:: Example usage::
@ -45,7 +51,7 @@ class Spotify(object):
max_get_retries = 10 max_get_retries = 10
def __init__(self, auth=None, requests_session=True, def __init__(self, auth=None, requests_session=True,
client_credentials_manager=None, proxies=None): client_credentials_manager=None, proxies=None, requests_timeout=None):
''' '''
Create a Spotify API object. Create a Spotify API object.
@ -59,12 +65,14 @@ class Spotify(object):
SpotifyClientCredentials object SpotifyClientCredentials object
:param proxies: :param proxies:
Definition of proxies (optional) Definition of proxies (optional)
:param requests_timeout:
Tell Requests to stop waiting for a response after a given number of seconds
''' '''
self.prefix = 'https://api.spotify.com/v1/' self.prefix = 'https://api.spotify.com/v1/'
self._auth = auth self._auth = auth
self.client_credentials_manager = client_credentials_manager self.client_credentials_manager = client_credentials_manager
self.proxies = proxies self.proxies = proxies
self.requests_timeout = requests_timeout
if isinstance(requests_session, requests.Session): if isinstance(requests_session, requests.Session):
self._session = requests_session self._session = requests_session
@ -86,6 +94,7 @@ class Spotify(object):
def _internal_call(self, method, url, payload, params): def _internal_call(self, method, url, payload, params):
args = dict(params=params) args = dict(params=params)
args["timeout"] = self.requests_timeout
if not url.startswith('http'): if not url.startswith('http'):
url = self.prefix + url url = self.prefix + url
headers = self._auth_headers() headers = self._auth_headers()
@ -111,10 +120,11 @@ class Spotify(object):
except: except:
if r.text and len(r.text) > 0 and r.text != 'null': if r.text and len(r.text) > 0 and r.text != 'null':
raise SpotifyException(r.status_code, raise SpotifyException(r.status_code,
-1, '%s:\n %s' % (r.url, r.json()['error']['message'])) -1, '%s:\n %s' % (r.url, r.json()['error']['message']),
headers=r.headers)
else: else:
raise SpotifyException(r.status_code, raise SpotifyException(r.status_code,
-1, '%s:\n %s' % (r.url, 'error')) -1, '%s:\n %s' % (r.url, 'error'), headers=r.headers)
finally: finally:
r.connection.close() r.connection.close()
if r.text and len(r.text) > 0 and r.text != 'null': if r.text and len(r.text) > 0 and r.text != 'null':
@ -142,25 +152,26 @@ class Spotify(object):
if retries < 0: if retries < 0:
raise raise
else: else:
print ('retrying ...' + str(delay) + 'secs') sleep_seconds = int(e.headers.get('Retry-After', delay))
time.sleep(delay) print ('retrying ...' + str(sleep_seconds) + 'secs')
time.sleep(sleep_seconds)
delay += 1 delay += 1
else: else:
raise raise
except Exception as e: except Exception as e:
raise raise
print ('exception', str(e)) print ('exception', str(e))
# some other exception. Requests have # some other exception. Requests have
# been know to throw a BadStatusLine exception # been know to throw a BadStatusLine exception
retries -= 1 retries -= 1
if retries >= 0: if retries >= 0:
sleep_seconds = int(e.headers.get('Retry-After', delay))
print ('retrying ...' + str(delay) + 'secs') print ('retrying ...' + str(delay) + 'secs')
time.sleep(delay) time.sleep(sleep_seconds)
delay += 1 delay += 1
else: else:
raise raise
def _post(self, url, args=None, payload=None, **kwargs): def _post(self, url, args=None, payload=None, **kwargs):
if args: if args:
kwargs.update(args) kwargs.update(args)
@ -211,15 +222,16 @@ class Spotify(object):
trid = self._get_id('track', track_id) trid = self._get_id('track', track_id)
return self._get('tracks/' + trid) return self._get('tracks/' + trid)
def tracks(self, tracks): def tracks(self, tracks, market = None):
''' returns a list of tracks given a list of track IDs, URIs, or URLs ''' returns a list of tracks given a list of track IDs, URIs, or URLs
Parameters: Parameters:
- tracks - a list of spotify URIs, URLs or IDs - tracks - a list of spotify URIs, URLs or IDs
- market - an ISO 3166-1 alpha-2 country code.
''' '''
tlist = [self._get_id('track', t) for t in tracks] tlist = [self._get_id('track', t) for t in tracks]
return self._get('tracks/?ids=' + ','.join(tlist)) return self._get('tracks/?ids=' + ','.join(tlist), market = market)
def artist(self, artist_id): def artist(self, artist_id):
''' returns a single artist given the artist's ID, URI or URL ''' returns a single artist given the artist's ID, URI or URL
@ -231,7 +243,6 @@ class Spotify(object):
trid = self._get_id('artist', artist_id) trid = self._get_id('artist', artist_id)
return self._get('artists/' + trid) return self._get('artists/' + trid)
def artists(self, artists): def artists(self, artists):
''' returns a list of artists given the artist IDs, URIs, or URLs ''' returns a list of artists given the artist IDs, URIs, or URLs
@ -242,8 +253,8 @@ class Spotify(object):
tlist = [self._get_id('artist', a) for a in artists] tlist = [self._get_id('artist', a) for a in artists]
return self._get('artists/?ids=' + ','.join(tlist)) 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,
limit=20, offset=0): offset=0):
''' Get Spotify catalog information about an artist's albums ''' Get Spotify catalog information about an artist's albums
Parameters: Parameters:
@ -256,7 +267,7 @@ class Spotify(object):
trid = self._get_id('artist', artist_id) 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) country=country, limit=limit, offset=offset)
def artist_top_tracks(self, artist_id, country='US'): 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
@ -301,7 +312,8 @@ class Spotify(object):
''' '''
trid = self._get_id('album', album_id) trid = self._get_id('album', album_id)
return self._get('albums/' + trid + '/tracks/', limit=limit, offset=offset) return self._get('albums/' + trid + '/tracks/', limit=limit,
offset=offset)
def albums(self, albums): def albums(self, albums):
''' returns a list of albums given the album IDs, URIs, or URLs ''' returns a list of albums given the album IDs, URIs, or URLs
@ -313,7 +325,7 @@ class Spotify(object):
tlist = [self._get_id('album', a) for a in albums] tlist = [self._get_id('album', a) for a in albums]
return self._get('albums/?ids=' + ','.join(tlist)) return self._get('albums/?ids=' + ','.join(tlist))
def search(self, q, limit=10, offset=0, type='track'): def search(self, q, limit=10, offset=0, type='track', market=None):
''' searches for an item ''' searches for an item
Parameters: Parameters:
@ -322,8 +334,9 @@ class Spotify(object):
- offset - the index of the first item to return - offset - the index of the first item to return
- type - the type of item to return. One of 'artist', 'album', - type - the type of item to return. One of 'artist', 'album',
'track' or 'playlist' 'track' or 'playlist'
- market - An ISO 3166-1 alpha-2 country code or the string from_token.
''' '''
return self._get('search', q=q, limit=limit, offset=offset, type=type) return self._get('search', q=q, limit=limit, offset=offset, type=type, market=market)
def user(self, user): def user(self, user):
''' Gets basic profile information about a Spotify User ''' Gets basic profile information about a Spotify User
@ -333,6 +346,14 @@ class Spotify(object):
''' '''
return self._get('users/' + user) return self._get('users/' + user)
def current_user_playlists(self, limit=50, offset=0):
""" Get current user playlists without required getting his profile
Parameters:
- limit - the number of items to return
- offset - the index of the first item to return
"""
return self._get("me/playlists", limit=limit, offset=offset)
def user_playlists(self, user, limit=50, offset=0): def user_playlists(self, user, limit=50, offset=0):
''' Gets playlists of a user ''' Gets playlists of a user
@ -341,22 +362,23 @@ class Spotify(object):
- limit - the number of items to return - limit - the number of items to return
- offset - the index of the first item to return - offset - the index of the first item to return
''' '''
return self._get("users/%s/playlists" % user, limit=limit, offset=offset) return self._get("users/%s/playlists" % user, limit=limit,
offset=offset)
def user_playlist(self, user, playlist_id = None, fields=None): def user_playlist(self, user, playlist_id=None, fields=None):
''' Gets playlist of a user ''' Gets playlist of a user
Parameters: Parameters:
- user - the id of the user - user - the id of the user
- playlist_id - the id of the playlist - playlist_id - the id of the playlist
- fields - which fields to return - fields - which fields to return
''' '''
if playlist_id == None: if playlist_id is None:
return self._get("users/%s/starred" % (user), fields=fields) return self._get("users/%s/starred" % (user), fields=fields)
plid = self._get_id('playlist', playlist_id) plid = self._get_id('playlist', playlist_id)
return self._get("users/%s/playlists/%s" % (user, plid), fields=fields) 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, market=None): limit=100, offset=0, market=None):
''' Get full details of the tracks of a playlist owned by a user. ''' Get full details of the tracks of a playlist owned by a user.
Parameters: Parameters:
@ -369,7 +391,8 @@ class Spotify(object):
''' '''
plid = self._get_id('playlist', playlist_id) 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, market=market) limit=limit, offset=offset, fields=fields,
market=market)
def user_playlist_create(self, user, name, public=True): def user_playlist_create(self, user, name, public=True):
''' Creates a playlist for a user ''' Creates a playlist for a user
@ -379,11 +402,42 @@ class Spotify(object):
- name - the name of the playlist - name - the name of the playlist
- public - is the created playlist public - public - is the created playlist public
''' '''
data = {'name':name, 'public':public } data = {'name': name, 'public': public}
return self._post("users/%s/playlists" % (user,), payload = data) return self._post("users/%s/playlists" % (user,), payload=data)
def user_playlist_change_details(
self, user, playlist_id, name=None, public=None,
collaborative=None):
''' Changes a playlist's name and/or public/private state
Parameters:
- user - the id of the user
- playlist_id - the id of the playlist
- name - optional name of the playlist
- public - optional is the playlist public
- collaborative - optional is the playlist collaborative
'''
data = {}
if isinstance(name, basestring):
data['name'] = name
if isinstance(public, bool):
data['public'] = public
if isinstance(collaborative, bool):
data['collaborative'] = collaborative
return self._put("users/%s/playlists/%s" % (user, playlist_id),
payload=data)
def user_playlist_unfollow(self, user, playlist_id):
''' Unfollows (deletes) a playlist for a user
Parameters:
- user - the id of the user
- name - the name of the playlist
'''
return self._delete("users/%s/playlists/%s/followers" % (user, playlist_id))
def user_playlist_add_tracks(self, user, playlist_id, tracks, def user_playlist_add_tracks(self, user, playlist_id, tracks,
position=None): position=None):
''' Adds tracks to a playlist ''' Adds tracks to a playlist
Parameters: Parameters:
@ -393,9 +447,9 @@ class Spotify(object):
- position - the position to add the tracks - position - the position to add the tracks
''' '''
plid = self._get_id('playlist', playlist_id) plid = self._get_id('playlist', playlist_id)
ftracks = [ self._get_uri('track', tid) for tid in tracks] 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) payload=ftracks, position=position)
def user_playlist_replace_tracks(self, user, playlist_id, tracks): def user_playlist_replace_tracks(self, user, playlist_id, tracks):
''' Replace all tracks in a playlist ''' Replace all tracks in a playlist
@ -406,13 +460,14 @@ class Spotify(object):
- tracks - the list of track ids to add to the playlist - tracks - the list of track ids to add to the playlist
''' '''
plid = self._get_id('playlist', playlist_id) plid = self._get_id('playlist', playlist_id)
ftracks = [ self._get_uri('track', tid) for tid in tracks] ftracks = [self._get_uri('track', tid) for tid in tracks]
payload = { "uris": ftracks } 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) payload=payload)
def user_playlist_reorder_tracks(self, user, playlist_id, range_start, insert_before, def user_playlist_reorder_tracks(
range_length=1, snapshot_id=None): self, user, playlist_id, range_start, insert_before,
range_length=1, snapshot_id=None):
''' Reorder tracks in a playlist ''' Reorder tracks in a playlist
Parameters: Parameters:
@ -424,16 +479,16 @@ class Spotify(object):
- snapshot_id - optional playlist's snapshot ID - snapshot_id - optional playlist's snapshot ID
''' '''
plid = self._get_id('playlist', playlist_id) plid = self._get_id('playlist', playlist_id)
payload = { "range_start": range_start, payload = {"range_start": range_start,
"range_length": range_length, "range_length": range_length,
"insert_before": insert_before } "insert_before": insert_before}
if snapshot_id: if snapshot_id:
payload["snapshot_id"] = snapshot_id payload["snapshot_id"] = snapshot_id
return self._put("users/%s/playlists/%s/tracks" % (user,plid), return self._put("users/%s/playlists/%s/tracks" % (user, plid),
payload = payload) payload=payload)
def user_playlist_remove_all_occurrences_of_tracks(self, user, playlist_id, def user_playlist_remove_all_occurrences_of_tracks(
tracks, snapshot_id=None): self, user, playlist_id, tracks, snapshot_id=None):
''' Removes all occurrences of the given tracks from the given playlist ''' Removes all occurrences of the given tracks from the given playlist
Parameters: Parameters:
@ -445,15 +500,15 @@ class Spotify(object):
''' '''
plid = self._get_id('playlist', playlist_id) plid = self._get_id('playlist', playlist_id)
ftracks = [ self._get_uri('track', tid) for tid in tracks] ftracks = [self._get_uri('track', tid) for tid in tracks]
payload = { "tracks": [ {"uri": track} for track in ftracks] } payload = {"tracks": [{"uri": track} for track in ftracks]}
if snapshot_id: if snapshot_id:
payload["snapshot_id"] = 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) payload=payload)
def user_playlist_remove_specific_occurrences_of_tracks(self, user, def user_playlist_remove_specific_occurrences_of_tracks(
playlist_id, tracks, snapshot_id=None): self, user, playlist_id, tracks, snapshot_id=None):
''' Removes all occurrences of the given tracks from the given playlist ''' Removes all occurrences of the given tracks from the given playlist
Parameters: Parameters:
@ -472,11 +527,34 @@ class Spotify(object):
"uri": self._get_uri("track", tr["uri"]), "uri": self._get_uri("track", tr["uri"]),
"positions": tr["positions"], "positions": tr["positions"],
}) })
payload = { "tracks": ftracks } payload = {"tracks": ftracks}
if snapshot_id: if snapshot_id:
payload["snapshot_id"] = 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) payload=payload)
def user_playlist_follow_playlist(self, playlist_owner_id, playlist_id):
'''
Add the current authenticated user as a follower of a playlist.
Parameters:
- playlist_owner_id - the user id of the playlist owner
- playlist_id - the id of the playlist
'''
return self._put("users/{}/playlists/{}/followers".format(playlist_owner_id, playlist_id))
def user_playlist_is_following(self, playlist_owner_id, playlist_id, user_ids):
'''
Check to see if the given users are following the given playlist
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.
'''
return self._get("users/{}/playlists/{}/followers/contains?ids={}".format(playlist_owner_id, playlist_id, ','.join(user_ids)))
def me(self): def me(self):
''' Get detailed profile information about the current user. ''' Get detailed profile information about the current user.
@ -511,7 +589,7 @@ class Spotify(object):
''' '''
return self._get('me/tracks', limit=limit, offset=offset) return self._get('me/tracks', limit=limit, offset=offset)
def current_user_followed_artists(self, limit=20, after=None): def current_user_followed_artists(self, limit=20, after=None):
''' Gets a list of the artists followed by the current authorized user ''' Gets a list of the artists followed by the current authorized user
@ -520,64 +598,83 @@ class Spotify(object):
- after - ghe last artist ID retrieved from the previous request - after - ghe last artist ID retrieved from the previous request
''' '''
return self._get('me/following', type='artist', limit=limit, after=after) return self._get('me/following', type='artist', limit=limit,
after=after)
def current_user_saved_tracks_delete(self, tracks=[]): def current_user_saved_tracks_delete(self, tracks=None):
''' Remove one or more tracks from the current user's ''' Remove one or more tracks from the current user's
"Your Music" library. "Your Music" library.
Parameters: Parameters:
- tracks - a list of track URIs, URLs or IDs - tracks - a list of track URIs, URLs or IDs
''' '''
tlist = [self._get_id('track', t) for t in tracks] tlist = []
if tracks is not None:
tlist = [self._get_id('track', t) for t in tracks]
return self._delete('me/tracks/?ids=' + ','.join(tlist)) return self._delete('me/tracks/?ids=' + ','.join(tlist))
def current_user_saved_tracks_contains(self, tracks=[]): def current_user_saved_tracks_contains(self, tracks=None):
''' Check if one or more tracks is already saved in ''' Check if one or more tracks is already saved in
the current Spotify users Your Music library. the current Spotify users Your Music library.
Parameters: Parameters:
- tracks - a list of track URIs, URLs or IDs - tracks - a list of track URIs, URLs or IDs
''' '''
tlist = [self._get_id('track', t) for t in tracks] tlist = []
if tracks is not None:
tlist = [self._get_id('track', t) for t in tracks]
return self._get('me/tracks/contains?ids=' + ','.join(tlist)) return self._get('me/tracks/contains?ids=' + ','.join(tlist))
def current_user_saved_tracks_add(self, tracks=None):
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. "Your Music" library.
Parameters: Parameters:
- tracks - a list of track URIs, URLs or IDs - tracks - a list of track URIs, URLs or IDs
''' '''
tlist = [self._get_id('track', t) for t in tracks] tlist = []
if tracks is not None:
tlist = [self._get_id('track', t) for t in tracks]
return self._put('me/tracks/?ids=' + ','.join(tlist)) return self._put('me/tracks/?ids=' + ','.join(tlist))
def current_user_top_artists(self, limit=20, offset=0, time_range='medium_term'): def current_user_top_artists(self, limit=20, offset=0,
time_range='medium_term'):
''' Get the current user's top artists ''' Get the current user's top artists
Parameters: Parameters:
- limit - the number of entities to return - limit - the number of entities to return
- offset - the index of the first entity to return - offset - the index of the first entity to return
- time_range - Over what time frame are the affinities computed. - time_range - Over what time frame are the affinities computed
Valid-values: short_term, medium_term, long_term Valid-values: short_term, medium_term, long_term
''' '''
return self._get('me/top/artists', time_range=time_range, limit=limit,offset=offset) return self._get('me/top/artists', time_range=time_range, limit=limit,
offset=offset)
def current_user_top_tracks(self, limit=20, offset=0, time_range='medium_term'): def current_user_top_tracks(self, limit=20, offset=0,
time_range='medium_term'):
''' Get the current user's top tracks ''' Get the current user's top tracks
Parameters: Parameters:
- limit - the number of entities to return - limit - the number of entities to return
- offset - the index of the first entity to return - offset - the index of the first entity to return
- time_range - Over what time frame are the affinities computed. - time_range - Over what time frame are the affinities computed
Valid-values: short_term, medium_term, long_term Valid-values: short_term, medium_term, long_term
''' '''
return self._get('me/top/tracks', time_range=time_range, limit=limit,offset=offset) return self._get('me/top/tracks', time_range=time_range, limit=limit,
offset=offset)
def current_user_saved_albums_add(self, albums=[]):
''' Add one or more albums to the current user's
"Your Music" library.
Parameters:
- albums - a list of album URIs, URLs or IDs
'''
alist = [self._get_id('album', a) for a in albums]
r = self._put('me/albums?ids=' + ','.join(alist))
return r
def featured_playlists(self, locale=None, country=None, def featured_playlists(self, locale=None, country=None, timestamp=None,
timestamp=None, limit=20, offset = 0): limit=20, offset=0):
''' Get a list of Spotify featured playlists ''' Get a list of Spotify featured playlists
Parameters: Parameters:
@ -600,9 +697,10 @@ class Spotify(object):
items. items.
''' '''
return self._get('browse/featured-playlists', locale=locale, return self._get('browse/featured-playlists', locale=locale,
country=country, timestamp=timestamp, limit=limit, offset=offset) country=country, timestamp=timestamp, limit=limit,
offset=offset)
def new_releases(self, country=None, limit=20, offset = 0): def new_releases(self, country=None, limit=20, offset=0):
''' Get a list of new album releases featured in Spotify ''' Get a list of new album releases featured in Spotify
Parameters: Parameters:
@ -615,10 +713,10 @@ class Spotify(object):
(the first object). Use with limit to get the next set of (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,
limit=limit, offset=offset) offset=offset)
def categories(self, country=None, locale=None, limit=20, offset = 0): def categories(self, country=None, locale=None, limit=20, offset=0):
''' Get a list of new album releases featured in Spotify ''' Get a list of new album releases featured in Spotify
Parameters: Parameters:
@ -635,9 +733,10 @@ class Spotify(object):
items. items.
''' '''
return self._get('browse/categories', country=country, locale=locale, return self._get('browse/categories', country=country, locale=locale,
limit=limit, offset=offset) limit=limit, offset=offset)
def category_playlists(self, category_id=None, country=None, limit=20, offset = 0): def category_playlists(self, category_id=None, country=None, limit=20,
offset=0):
''' Get a list of new album releases featured in Spotify ''' Get a list of new album releases featured in Spotify
Parameters: Parameters:
@ -652,11 +751,11 @@ class Spotify(object):
(the first object). Use with limit to get the next set of (the first object). Use with limit to get the next set of
items. items.
''' '''
return self._get('browse/categories/' + category_id + '/playlists', country=country, return self._get('browse/categories/' + category_id + '/playlists',
limit=limit, offset=offset) country=country, limit=limit, offset=offset)
def recommendations(self, seed_artists=[], seed_genres=[], seed_tracks=[], def recommendations(self, seed_artists=None, seed_genres=None,
limit = 20, country=None, **kwargs): seed_tracks=None, limit=20, country=None, **kwargs):
''' Get a list of recommended tracks for one to five seeds. ''' Get a list of recommended tracks for one to five seeds.
Parameters: Parameters:
@ -664,50 +763,61 @@ class Spotify(object):
- seed_tracks - a list of artist IDs, URIs or URLs - seed_tracks - a list of artist IDs, URIs or URLs
- seed_genres - a list of genre names. Available genres for - seed_genres - a list of genre names. Available genres for
recommendations can be found by calling recommendation_genre_seeds recommendations can be found by calling recommendation_genre_seeds
- country - An ISO 3166-1 alpha-2 country code. If provided, all - country - An ISO 3166-1 alpha-2 country code. If provided, all
results will be playable in this country. results will be playable in this country.
- limit - The maximum number of items to return. Default: 20. - limit - The maximum number of items to return. Default: 20.
Minimum: 1. Maximum: 100 Minimum: 1. Maximum: 100
- min/max/target_<attribute> - For the tuneable track attributes listed - min/max/target_<attribute> - For the tuneable track attributes listed
in the documentation, these values provide filters and targeting on in the documentation, these values provide filters and targeting on
results. results.
''' '''
params = dict(limit=limit) params = dict(limit=limit)
if seed_artists: if seed_artists:
params['seed_artists'] = [self._get_id('artist', a) for a in seed_artists] params['seed_artists'] = ','.join(
[self._get_id('artist', a) for a in seed_artists])
if seed_genres: if seed_genres:
params['seed_genres'] = seed_genres params['seed_genres'] = ','.join(seed_genres)
if seed_tracks: if seed_tracks:
params['seed_tracks'] = [self._get_id('track', t) for t in seed_tracks] params['seed_tracks'] = ','.join(
[self._get_id('track', t) for t in seed_tracks])
if country: if country:
params['market'] = country params['market'] = country
for attribute in ["acousticness", "danceability", "duration_ms", "energy", for attribute in ["acousticness", "danceability", "duration_ms",
"instrumentalness", "key", "liveness", "loudness", "mode", "popularity", "energy", "instrumentalness", "key", "liveness",
"speechiness", "tempo", "time_signature", "valence"]: "loudness", "mode", "popularity", "speechiness",
"tempo", "time_signature", "valence"]:
for prefix in ["min_", "max_", "target_"]: for prefix in ["min_", "max_", "target_"]:
param = prefix + attribute param = prefix + attribute
if param in kwargs: if param in kwargs:
params[param] = kwargs[param] params[param] = kwargs[param]
return self._get('recommendations', **params) return self._get('recommendations', **params)
def recommendation_genre_seeds(self): def recommendation_genre_seeds(self):
''' Get a list of genres available for the recommendations function. ''' Get a list of genres available for the recommendations function.
''' '''
return self._get('recommendations/available-genre-seeds') return self._get('recommendations/available-genre-seeds')
def audio_analysis(self, track_id):
''' Get audio analysis for a track based upon its Spotify ID
Parameters:
- track_id - a track URI, URL or ID
'''
trid = self._get_id('track', track_id)
return self._get('audio-analysis/' + trid)
def audio_features(self, tracks=[]): def audio_features(self, tracks=[]):
''' Get audio features for multiple tracks based upon their Spotify IDs ''' Get audio features for multiple tracks based upon their Spotify IDs
Parameters: Parameters:
- tracks - a list of track URIs, URLs or IDs, maximum: 50 ids - tracks - a list of track URIs, URLs or IDs, maximum: 50 ids
''' '''
tlist = [self._get_id('track', t) for t in tracks] tlist = [self._get_id('track', t) for t in tracks]
results = self._get('audio-features?ids=' + ','.join(tlist)) results = self._get('audio-features?ids=' + ','.join(tlist))
# the response has changed, look for the new style first, and if # the response has changed, look for the new style first, and if
# its not there, fallback on the old style # its not there, fallback on the old style
if 'audio_features' in results: if 'audio_features' in results:
@ -715,19 +825,27 @@ class Spotify(object):
else: else:
return results 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 _get_id(self, type, id): def _get_id(self, type, id):
fields = id.split(':') fields = id.split(':')
if len(fields) >= 3: if len(fields) >= 3:
if type != fields[-2]: if type != fields[-2]:
self._warn('expected id of type ' + type + ' but found type ' \ self._warn('expected id of type %s but found type %s %s',
+ fields[2] + " " + id) type, fields[-2], id)
return fields[-1] return fields[-1]
fields = id.split('/') fields = id.split('/')
if len(fields) >= 3: if len(fields) >= 3:
itype = fields[-2] itype = fields[-2]
if type != itype: if type != itype:
self._warn('expected id of type ' + type + ' but found type ' \ self._warn('expected id of type %s but found type %s %s',
+ itype + " " + id) type, itype, id)
return fields[-1] return fields[-1]
return id return id

View File

@ -79,7 +79,7 @@ class SpotifyClientCredentials(object):
def _is_token_expired(self, token_info): def _is_token_expired(self, token_info):
now = int(time.time()) now = int(time.time())
return token_info['expires_at'] < now return token_info['expires_at'] - now < 60
def _add_custom_values_to_token_info(self, token_info): def _add_custom_values_to_token_info(self, token_info):
""" """
@ -131,11 +131,11 @@ class SpotifyOAuth(object):
token_info = json.loads(token_info_string) token_info = json.loads(token_info_string)
# if scopes don't match, then bail # if scopes don't match, then bail
if 'scope' not in token_info or self.scope != token_info['scope']: if 'scope' not in token_info or not self._is_scope_subset(self.scope, token_info['scope']):
return None return None
if self._is_token_expired(token_info): if self._is_token_expired(token_info):
token_info = self._refresh_access_token(token_info['refresh_token']) token_info = self.refresh_access_token(token_info['refresh_token'])
except IOError: except IOError:
pass pass
@ -151,6 +151,11 @@ class SpotifyOAuth(object):
self._warn("couldn't write token cache to " + self.cache_path) self._warn("couldn't write token cache to " + self.cache_path)
pass pass
def _is_scope_subset(self, needle_scope, haystack_scope):
needle_scope = set(needle_scope.split())
haystack_scope = set(haystack_scope.split())
return needle_scope <= haystack_scope
def _is_token_expired(self, token_info): def _is_token_expired(self, token_info):
now = int(time.time()) now = int(time.time())
@ -222,7 +227,7 @@ class SpotifyOAuth(object):
else: else:
return None return None
def _refresh_access_token(self, refresh_token): def refresh_access_token(self, refresh_token):
payload = { 'refresh_token': refresh_token, payload = { 'refresh_token': refresh_token,
'grant_type': 'refresh_token'} 'grant_type': 'refresh_token'}

View File

@ -3,9 +3,9 @@
from __future__ import print_function from __future__ import print_function
import os import os
import subprocess
from . import oauth2 from . import oauth2
import spotipy import spotipy
import webbrowser
def prompt_for_user_token(username, scope=None, client_id = None, def prompt_for_user_token(username, scope=None, client_id = None,
client_secret = None, redirect_uri = None): client_secret = None, redirect_uri = None):
@ -67,8 +67,8 @@ def prompt_for_user_token(username, scope=None, client_id = None,
''') ''')
auth_url = sp_oauth.get_authorize_url() auth_url = sp_oauth.get_authorize_url()
try: try:
subprocess.call(["open", auth_url]) webbrowser.open(auth_url)
print("Opening %s in your browser" % auth_url) print("Opened %s in your browser" % auth_url)
except: except:
print("Please navigate here: %s" % auth_url) print("Please navigate here: %s" % auth_url)

View File

@ -86,6 +86,24 @@ class AuthTestSpotipy(unittest.TestCase):
albums = spotify.current_user_saved_albums() albums = spotify.current_user_saved_albums()
self.assertTrue(len(albums['items']) > 0) 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): def test_current_user_save_and_unsave_tracks(self):
tracks = spotify.current_user_saved_tracks() tracks = spotify.current_user_saved_tracks()
total = tracks['total'] total = tracks['total']

View File

@ -37,6 +37,10 @@ class AuthTestSpotipy(unittest.TestCase):
bad_id = 'BAD_ID' 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): def test_audio_features(self):
results = spotify.audio_features(self.four_tracks) results = spotify.audio_features(self.four_tracks)
self.assertTrue(len(results) == len(self.four_tracks)) self.assertTrue(len(results) == len(self.four_tracks))

View File

@ -2,6 +2,7 @@
import spotipy import spotipy
import unittest import unittest
import pprint import pprint
import requests
from spotipy.client import SpotifyException from spotipy.client import SpotifyException
@ -11,6 +12,7 @@ class TestSpotipy(unittest.TestCase):
creep_id = '3HfB5hBU0dmBt8T0iCmH42' creep_id = '3HfB5hBU0dmBt8T0iCmH42'
creep_url = 'http://open.spotify.com/track/3HfB5hBU0dmBt8T0iCmH42' creep_url = 'http://open.spotify.com/track/3HfB5hBU0dmBt8T0iCmH42'
el_scorcho_urn = 'spotify:track:0Svkvt5I79wficMFgaqEQJ' el_scorcho_urn = 'spotify:track:0Svkvt5I79wficMFgaqEQJ'
el_scorcho_bad_urn = 'spotify:track:0Svkvt5I79wficMFgaqEQK'
pinkerton_urn = 'spotify:album:04xe676vyiTeYNXw15o9jT' pinkerton_urn = 'spotify:album:04xe676vyiTeYNXw15o9jT'
weezer_urn = 'spotify:artist:3jOstUTkEu2JkjvRdBA5Gu' weezer_urn = 'spotify:artist:3jOstUTkEu2JkjvRdBA5Gu'
pablo_honey_urn = 'spotify:album:6AZv3m27uyRxi8KyJSfUxL' pablo_honey_urn = 'spotify:album:6AZv3m27uyRxi8KyJSfUxL'
@ -39,7 +41,7 @@ class TestSpotipy(unittest.TestCase):
def test_album_tracks(self): def test_album_tracks(self):
results = self.spotify.album_tracks(self.pinkerton_urn) results = self.spotify.album_tracks(self.pinkerton_urn)
self.assertTrue(len(results['items']) == 10) self.assertTrue(len(results['items']) == 10)
def test_album_tracks_many(self): def test_album_tracks_many(self):
results = self.spotify.album_tracks(self.angeles_haydn_urn) results = self.spotify.album_tracks(self.angeles_haydn_urn)
tracks = results['items'] tracks = results['items']
@ -68,6 +70,13 @@ class TestSpotipy(unittest.TestCase):
track = self.spotify.track(self.creep_url) track = self.spotify.track(self.creep_url)
self.assertTrue(track['name'] == 'Creep') self.assertTrue(track['name'] == 'Creep')
def test_track_bad_urn(self):
try:
track = self.spotify.track(self.el_scorcho_bad_urn)
self.assertTrue(False)
except spotipy.SpotifyException:
self.assertTrue(True)
def test_tracks(self): def test_tracks(self):
results = self.spotify.tracks([self.creep_url, self.el_scorcho_urn]) results = self.spotify.tracks([self.creep_url, self.el_scorcho_urn])
self.assertTrue('tracks' in results) self.assertTrue('tracks' in results)
@ -83,7 +92,7 @@ class TestSpotipy(unittest.TestCase):
self.assertTrue('artists' in results) self.assertTrue('artists' in results)
self.assertTrue(len(results['artists']) == 20) self.assertTrue(len(results['artists']) == 20)
for artist in results['artists']: for artist in results['artists']:
if artist['name'] == 'Rivers Cuomo': if artist['name'] == 'Jimmy Eat World':
found = True found = True
self.assertTrue(found) self.assertTrue(found)
@ -93,6 +102,12 @@ class TestSpotipy(unittest.TestCase):
self.assertTrue(len(results['artists']['items']) > 0) self.assertTrue(len(results['artists']['items']) > 0)
self.assertTrue(results['artists']['items'][0]['name'] == 'Weezer') self.assertTrue(results['artists']['items'][0]['name'] == 'Weezer')
def test_artist_search_with_market(self):
results = self.spotify.search(q='weezer', type='artist', market='GB')
self.assertTrue('artists' in results)
self.assertTrue(len(results['artists']['items']) > 0)
self.assertTrue(results['artists']['items'][0]['name'] == 'Weezer')
def test_artist_albums(self): def test_artist_albums(self):
results = self.spotify.artist_albums(self.weezer_urn) results = self.spotify.artist_albums(self.weezer_urn)
self.assertTrue('items' in results) self.assertTrue('items' in results)
@ -105,6 +120,15 @@ class TestSpotipy(unittest.TestCase):
self.assertTrue(found) self.assertTrue(found)
def test_search_timeout(self):
sp = spotipy.Spotify(requests_timeout=.1)
try:
results = sp.search(q='my*', type='track')
self.assertTrue(False, 'unexpected search timeout')
except requests.ReadTimeout:
self.assertTrue(True, 'expected search timeout')
def test_album_search(self): def test_album_search(self):
results = self.spotify.search(q='weezer pinkerton', type='album') results = self.spotify.search(q='weezer pinkerton', type='album')
self.assertTrue('albums' in results) self.assertTrue('albums' in results)
@ -128,6 +152,13 @@ class TestSpotipy(unittest.TestCase):
except spotipy.SpotifyException: except spotipy.SpotifyException:
self.assertTrue(True) self.assertTrue(True)
def test_track_bad_id(self):
try:
track = self.spotify.track(self.bad_id)
self.assertTrue(False)
except spotipy.SpotifyException:
self.assertTrue(True)
def test_unauthenticated_post_fails(self): def test_unauthenticated_post_fails(self):
with self.assertRaises(SpotifyException) as cm: with self.assertRaises(SpotifyException) as cm:
self.spotify.user_playlist_create("spotify", "Best hits of the 90s") self.spotify.user_playlist_create("spotify", "Best hits of the 90s")