Retries For All Endpoints, fixes #347 (#457)

* test_improvements - Add __init__.py files to tests dirs so you can run all tests

* test_improvements - added helpers file, restructured tests to work without previous data and to be grouped with api type

* http_retries - Implement Retry for all requests

* Readme - Update README with contributing info

* PR Feedback - Added CONTRIBUTING.md, fixed README, fixed test
This commit is contained in:
Dj 2020-03-29 09:18:23 -07:00 committed by GitHub
parent 8b84300597
commit 024a6c96d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 382 additions and 328 deletions

18
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,18 @@
## Contributing
If you would like to contribute to spotipy follow these steps:
### Export the needed environment variables
```bash
export SPOTIPY_CLIENT_ID=client_id_here
export SPOTIPY_CLIENT_SECRET=client_secret_here
export SPOTIPY_CLIENT_USERNAME=client_username_here # This is actually an id not spotify display name
export SPOTIPY_REDIRECT_URI=http://localhost/ # Make url is set in app you created to get your ID and SECRET
```
### Create virtual enevironment, install dependencies, run tests:
```bash
$ virtualenv --python=python3.7 env
(env) $ pip install requirements.txt
(env) $ python -m unittest discover -v tests
```

View File

@ -8,10 +8,10 @@ __all__ = ["Spotify", "SpotifyException"]
import json import json
import sys import sys
import time
import warnings import warnings
import requests import requests
import urllib3
import six import six
@ -53,7 +53,8 @@ class Spotify(object):
trace = False # Enable tracing? trace = False # Enable tracing?
trace_out = False trace_out = False
max_get_retries = 10 max_retries = 3
default_retry_codes = (429, 500, 502, 503, 504)
def __init__( def __init__(
self, self,
@ -63,7 +64,11 @@ class Spotify(object):
oauth_manager=None, oauth_manager=None,
auth_manager=None, auth_manager=None,
proxies=None, proxies=None,
requests_timeout=None, requests_timeout=5,
status_forcelist=None,
retries=max_retries,
status_retries=max_retries,
backoff_factor=0.3,
): ):
""" """
Creates a Spotify API client. Creates a Spotify API client.
@ -86,6 +91,15 @@ class Spotify(object):
:param requests_timeout: :param requests_timeout:
Tell Requests to stop waiting for a response after a given Tell Requests to stop waiting for a response after a given
number of seconds number of seconds
:param status_forcelist:
Tell requests what type of status codes retries should occur on
:param retries:
Total number of retries to allow
:param status_retries:
Number of times to retry on bad status codes
:param backoff_factor:
A backoff factor to apply between attempts after the second try
See urllib3 https://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html
""" """
self.prefix = "https://api.spotify.com/v1/" self.prefix = "https://api.spotify.com/v1/"
self._auth = auth self._auth = auth
@ -94,16 +108,18 @@ class Spotify(object):
self.auth_manager = auth_manager self.auth_manager = auth_manager
self.proxies = proxies self.proxies = proxies
self.requests_timeout = requests_timeout self.requests_timeout = requests_timeout
self.status_forcelist = status_forcelist or self.default_retry_codes
self.backoff_factor = backoff_factor
self.retries = retries
self.status_retries = status_retries
if isinstance(requests_session, requests.Session): if isinstance(requests_session, requests.Session):
self._session = requests_session self._session = requests_session
else: else:
if requests_session: # Build a new session. if requests_session: # Build a new session.
self._session = requests.Session() self._build_session()
else: # Use the Requests API module as a "session". else: # Use the Requests API module as a "session".
from requests import api self._session = requests.api
self._session = api
@property @property
def auth_manager(self): def auth_manager(self):
@ -118,6 +134,20 @@ class Spotify(object):
self.client_credentials_manager or self.oauth_manager self.client_credentials_manager or self.oauth_manager
) )
def _build_session(self):
self._session = requests.Session()
retry = urllib3.Retry(
total=self.retries,
connect=None,
read=False,
status=self.status_retries,
backoff_factor=self.backoff_factor,
status_forcelist=self.status_forcelist)
adapter = requests.adapters.HTTPAdapter(max_retries=retry)
self._session.mount('http://', adapter)
self._session.mount('https://', adapter)
def _auth_headers(self): def _auth_headers(self):
if self._auth: if self._auth:
return {"Authorization": "Bearer {0}".format(self._auth)} return {"Authorization": "Bearer {0}".format(self._auth)}
@ -129,7 +159,6 @@ 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()
@ -147,36 +176,43 @@ class Spotify(object):
if self.trace_out: if self.trace_out:
print(url) print(url)
with self._session.request( try:
method, url, headers=headers, proxies=self.proxies, **args response = self._session.request(
) as r: method, url, headers=headers, proxies=self.proxies,
timeout=self.requests_timeout, **args
)
if self.trace: # pragma: no cover if self.trace: # pragma: no cover
print() print()
print("Request headers:", headers) print("Request headers:", headers)
print("Response headers:", r.headers) print("Response headers:", response.headers)
print("HTTP status", r.status_code) print("HTTP status", response.status_code)
print(method, r.url) print(method, response.url)
if payload: if payload:
print("Data", json.dumps(payload)) print("Data", json.dumps(payload))
response.raise_for_status()
results = response.json()
except requests.exceptions.HTTPError:
try: try:
r.raise_for_status() msg = response.json()["error"]["message"]
except BaseException: except (ValueError, KeyError):
try:
msg = r.json()["error"]["message"]
except BaseException:
msg = "error" msg = "error"
raise SpotifyException(
r.status_code,
-1,
"%s:\n %s" % (r.url, msg),
headers=r.headers,
)
try: raise SpotifyException(
results = r.json() response.status_code,
except BaseException: -1,
"%s:\n %s" % (response.url, msg),
headers=response.headers,
)
except requests.exceptions.RetryError:
raise SpotifyException(
599,
-1,
"%s:\n %s" % (response.url, "Max Retries"),
headers=response.headers,
)
except ValueError:
results = None results = None
if self.trace: # pragma: no cover if self.trace: # pragma: no cover
@ -187,23 +223,8 @@ class Spotify(object):
def _get(self, url, args=None, payload=None, **kwargs): def _get(self, url, args=None, payload=None, **kwargs):
if args: if args:
kwargs.update(args) kwargs.update(args)
retries = self.max_get_retries
delay = 0
while retries > 0:
try:
return self._internal_call("GET", url, payload, kwargs) return self._internal_call("GET", url, payload, kwargs)
except SpotifyException as e:
retries -= 1
delay += 1
status = e.http_status
# 429 means we hit a rate limit, back-off
if not (status == 429 or status >= 500 and status < 600):
raise
sleep_seconds = int(
e.headers.get("Retry-After", delay)
)
print("retrying after..." + str(sleep_seconds) + "secs")
time.sleep(sleep_seconds + 1)
def _post(self, url, args=None, payload=None, **kwargs): def _post(self, url, args=None, payload=None, **kwargs):
if args: if args:

0
tests/__init__.py Normal file
View File

19
tests/helpers.py Normal file
View File

@ -0,0 +1,19 @@
import base64
import requests
def get_spotify_playlist(spotify_object, playlist_name, username):
playlists = spotify_object.user_playlists(username)
while playlists:
for item in playlists['items']:
if item['name'] == playlist_name:
return item
playlists = spotify_object.next(playlists)
def create_spotify_playlist(spotify_object, playlist_name, username):
return spotify_object.user_playlist_create(username, playlist_name)
def get_as_base64(url):
return base64.b64encode(requests.get(url).content).decode("utf-8")

View File

View File

@ -189,15 +189,11 @@ class AuthTestSpotipy(unittest.TestCase):
def test_search_timeout(self): def test_search_timeout(self):
client_credentials_manager = SpotifyClientCredentials() client_credentials_manager = SpotifyClientCredentials()
sp = spotipy.Spotify( sp = spotipy.Spotify(requests_timeout=0.01,
client_credentials_manager=client_credentials_manager, client_credentials_manager=client_credentials_manager)
requests_timeout=.01)
try: with self.assertRaises(requests.exceptions.Timeout):
sp.search(q='my*', type='track') sp.search(q='my*', type='track')
self.assertTrue(False, 'unexpected search timeout')
except requests.exceptions.Timeout:
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')
@ -302,10 +298,9 @@ class AuthTestSpotipy(unittest.TestCase):
sess.close() sess.close()
def test_force_no_requests_session(self): def test_force_no_requests_session(self):
from requests import Session
with_no_session = spotipy.Spotify( with_no_session = spotipy.Spotify(
client_credentials_manager=SpotifyClientCredentials(), client_credentials_manager=SpotifyClientCredentials(),
requests_session=False) requests_session=False)
self.assertFalse(isinstance(with_no_session._session, Session)) self.assertNotIsInstance(with_no_session._session, requests.Session)
self.assertTrue(with_no_session.user(user="akx") user = with_no_session.user(user="akx")
["uri"] == "spotify:user:akx") self.assertEqual(user["uri"], "spotify:user:akx")

View File

@ -1,20 +1,4 @@
# -*- 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 import os
import sys
from spotipy import ( from spotipy import (
CLIENT_CREDS_ENV_VARS as CCEV, CLIENT_CREDS_ENV_VARS as CCEV,
@ -23,63 +7,23 @@ from spotipy import (
SpotifyException, SpotifyException,
) )
import unittest import unittest
import warnings
import requests import requests
from pprint import pprint # noqa from tests import helpers
class AuthTestSpotipy(unittest.TestCase): class SpotipyPlaylistApiTest(unittest.TestCase):
""" @classmethod
These tests require user authentication - provide client credentials using def setUpClass(cls):
the following environment variables cls.four_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp",
::
'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", "spotify:track:7IHOIqZUUInxjVkko181PB",
"4VrWlk8IQxevMvERoX08iC", "4VrWlk8IQxevMvERoX08iC",
"http://open.spotify.com/track/3cySlItpiPiIAzU3NyHCJf"] "http://open.spotify.com/track/3cySlItpiPiIAzU3NyHCJf"]
cls.other_tracks = ["spotify:track:2wySlB6vMzCbQrRnNGOYKa",
two_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp",
"spotify:track:7IHOIqZUUInxjVkko181PB"]
other_tracks = ["spotify:track:2wySlB6vMzCbQrRnNGOYKa",
"spotify:track:29xKs5BAHlmlX1u4gzQAbJ", "spotify:track:29xKs5BAHlmlX1u4gzQAbJ",
"spotify:track:1PB7gRWcvefzu7t3LJLUlf"] "spotify:track:1PB7gRWcvefzu7t3LJLUlf"]
cls.username = os.getenv(CCEV['client_username'])
album_ids = ["spotify:album:6kL09DaURb7rAoqqaA51KU", scope = (
"spotify:album:6RTzC0rDbvagTSJLlY7AKl"]
bad_id = 'BAD_ID'
@classmethod
def setUpClass(self):
if sys.version_info >= (3, 2):
# >= Python3.2 only
warnings.filterwarnings(
"ignore",
category=ResourceWarning, # noqa
message="unclosed.*<ssl.SSLSocket.*>")
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 ' 'playlist-modify-public '
'user-library-read ' 'user-library-read '
'user-follow-read ' 'user-follow-read '
@ -88,52 +32,25 @@ class AuthTestSpotipy(unittest.TestCase):
'user-top-read ' 'user-top-read '
'user-follow-modify ' 'user-follow-modify '
'user-read-recently-played ' 'user-read-recently-played '
'ugc-image-upload' 'ugc-image-upload '
'user-read-playback-state'
) )
self.token = prompt_for_user_token(self.username, scope=self.scope) token = prompt_for_user_token(cls.username, scope=scope)
self.spotify = Spotify(auth=self.token) cls.spotify = Spotify(auth=token)
# Helper cls.new_playlist_name = 'spotipy-playlist-test'
def get_or_create_spotify_playlist(self, playlist_name): cls.new_playlist = helpers.get_spotify_playlist(
playlists = self.spotify.user_playlists(self.username) cls.spotify, cls.new_playlist_name, cls.username) or \
while playlists: helpers.create_spotify_playlist(
for item in playlists['items']: cls.spotify, cls.new_playlist_name, cls.username)
if item['name'] == playlist_name: cls.new_playlist_uri = cls.new_playlist['uri']
return item
playlists = self.spotify.next(playlists)
return self.spotify.user_playlist_create(
self.username, playlist_name)
# Helper
def get_as_base64(self, url):
import base64
return base64.b64encode(requests.get(url).content).decode("utf-8")
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): def test_user_playlists(self):
playlists = self.spotify.user_playlists(self.username, limit=5) playlists = self.spotify.user_playlists(self.username, limit=5)
self.assertTrue('items' in playlists) self.assertTrue('items' in playlists)
self.assertTrue(len(playlists['items']) == 5) self.assertGreaterEqual(len(playlists['items']), 1)
def test_user_playlist_tracks(self): def test_user_playlist_tracks(self):
playlists = self.spotify.user_playlists(self.username, limit=5) playlists = self.spotify.user_playlists(self.username, limit=5)
@ -142,53 +59,135 @@ class AuthTestSpotipy(unittest.TestCase):
user = playlist['owner']['id'] user = playlist['owner']['id']
pid = playlist['id'] pid = playlist['id']
results = self.spotify.user_playlist_tracks(user, pid) results = self.spotify.user_playlist_tracks(user, pid)
self.assertTrue(len(results['items']) >= 0) self.assertEquals(len(results['items']), 0)
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): def test_current_user_playlists(self):
playlists = self.spotify.current_user_playlists(limit=10) playlists = self.spotify.current_user_playlists(limit=10)
self.assertTrue('items' in playlists) self.assertTrue('items' in playlists)
self.assertTrue(len(playlists['items']) == 10) self.assertGreaterEqual(len(playlists['items']), 1)
self.assertLessEqual(len(playlists['items']), 10)
def test_user_playlist_follow(self): def test_user_playlist_follow(self):
user_to_follow = 'plamere'
user_to_follow_id = '4erXB04MxwRAVqcUEpu30O'
self.spotify.user_playlist_follow_playlist( self.spotify.user_playlist_follow_playlist(
'plamere', '4erXB04MxwRAVqcUEpu30O') user_to_follow, user_to_follow_id)
follows = self.spotify.user_playlist_is_following( follows = self.spotify.user_playlist_is_following(
'plamere', '4erXB04MxwRAVqcUEpu30O', [ user_to_follow, user_to_follow_id, [self.username])
self.spotify.current_user()['id']])
self.assertTrue(len(follows) == 1, 'proper follows length') self.assertTrue(len(follows) == 1, 'proper follows length')
self.assertTrue(follows[0], 'is following') self.assertTrue(follows[0], 'is following')
self.spotify.user_playlist_unfollow( self.spotify.user_playlist_unfollow(
'plamere', '4erXB04MxwRAVqcUEpu30O') user_to_follow, user_to_follow_id)
follows = self.spotify.user_playlist_is_following( follows = self.spotify.user_playlist_is_following(
'plamere', '4erXB04MxwRAVqcUEpu30O', [ user_to_follow, user_to_follow_id, [self.username])
self.spotify.current_user()['id']])
self.assertTrue(len(follows) == 1, 'proper follows length') self.assertTrue(len(follows) == 1, 'proper follows length')
self.assertFalse(follows[0], 'is no longer following') self.assertFalse(follows[0], 'is no longer following')
def test_user_playlist_replace_tracks(self):
# add tracks to playlist
self.spotify.user_playlist_add_tracks(
self.username, self.new_playlist['id'], self.four_tracks)
playlist = self.spotify.user_playlist(self.username, self.new_playlist['id'])
self.assertEqual(playlist['tracks']['total'], 4)
self.assertEqual(len(playlist['tracks']['items']), 4)
# replace with 3 other tracks
self.spotify.user_playlist_replace_tracks(self.username,
self.new_playlist['id'],
self.other_tracks)
playlist = self.spotify.user_playlist(self.username,
self.new_playlist['id'])
self.assertEqual(playlist['tracks']['total'], 3)
self.assertEqual(len(playlist['tracks']['items']), 3)
self.spotify.user_playlist_remove_all_occurrences_of_tracks(
self.username, playlist['id'], self.other_tracks)
playlist = self.spotify.user_playlist(self.username, self.new_playlist['id'])
self.assertEqual(playlist["tracks"]["total"], 0)
def test_get_playlist_by_id(self):
pl = self.spotify.playlist(self.new_playlist['id'])
self.assertEqual(pl["tracks"]["total"], 0)
def test_playlist_add_tracks(self):
# add tracks to playlist
self.spotify.user_playlist_add_tracks(
self.username, self.new_playlist['id'], self.other_tracks)
playlist = self.spotify.user_playlist(self.username, self.new_playlist['id'])
self.assertEqual(playlist['tracks']['total'], 3)
self.assertEqual(len(playlist['tracks']['items']), 3)
pl = self.spotify.playlist_tracks(self.new_playlist['id'], limit=2)
self.assertEqual(len(pl["items"]), 2)
self.spotify.user_playlist_remove_all_occurrences_of_tracks(
self.username, playlist['id'], self.other_tracks)
playlist = self.spotify.user_playlist(self.username, self.new_playlist['id'])
self.assertEqual(playlist["tracks"]["total"], 0)
def test_playlist_cover_image(self):
# Upload random dog image
r = requests.get('https://dog.ceo/api/breeds/image/random')
dog_base64 = helpers.get_as_base64(r.json()['message'])
self.spotify.playlist_upload_cover_image(self.new_playlist_uri, dog_base64)
res = self.spotify.playlist_cover_image(self.new_playlist_uri)
self.assertEquals(len(res), 1)
first_image = res[0]
self.assertIn('width', first_image)
self.assertIn('height', first_image)
self.assertIn('url', first_image)
def test_deprecated_starred(self):
pl = self.spotify.user_playlist(self.username)
self.assertTrue(pl["tracks"] is None)
self.assertTrue(pl["owner"] is None)
def test_deprecated_user_playlist(self):
# Test without user due to change from
# https://developer.spotify.com/community/news/2018/06/12/changes-to-playlist-uris/
pl = self.spotify.user_playlist(None, self.new_playlist['id'])
self.assertEqual(pl["tracks"]["total"], 0)
class SpotipyLibraryApiTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.four_tracks = ["spotify:track:6RtPijgfPKROxEzTHNRiDp",
"spotify:track:7IHOIqZUUInxjVkko181PB",
"4VrWlk8IQxevMvERoX08iC",
"http://open.spotify.com/track/3cySlItpiPiIAzU3NyHCJf"]
cls.album_ids = ["spotify:album:6kL09DaURb7rAoqqaA51KU",
"spotify:album:6RTzC0rDbvagTSJLlY7AKl"]
cls.username = os.getenv(CCEV['client_username'])
scope = (
'playlist-modify-public '
'user-library-read '
'user-follow-read '
'user-library-modify '
'user-read-private '
'user-top-read '
'user-follow-modify '
'user-read-recently-played '
'ugc-image-upload '
'user-read-playback-state'
)
token = prompt_for_user_token(cls.username, scope=scope)
cls.spotify = Spotify(auth=token)
def test_track_bad_id(self):
with self.assertRaises(SpotifyException):
self.spotify.track('BadID123')
def test_current_user_saved_tracks(self): def test_current_user_saved_tracks(self):
# TODO make this not fail if someone doesnthave saved tracks
tracks = self.spotify.current_user_saved_tracks() tracks = self.spotify.current_user_saved_tracks()
self.assertTrue(len(tracks['items']) > 0) self.assertGreater(len(tracks['items']), 0)
def test_current_user_save_and_unsave_tracks(self): def test_current_user_save_and_unsave_tracks(self):
tracks = self.spotify.current_user_saved_tracks() tracks = self.spotify.current_user_saved_tracks()
@ -197,168 +196,146 @@ class AuthTestSpotipy(unittest.TestCase):
tracks = self.spotify.current_user_saved_tracks() tracks = self.spotify.current_user_saved_tracks()
new_total = tracks['total'] new_total = tracks['total']
self.assertTrue(new_total - total == len(self.four_tracks)) self.assertEquals(new_total - total, len(self.four_tracks))
tracks = self.spotify.current_user_saved_tracks_delete( tracks = self.spotify.current_user_saved_tracks_delete(
self.four_tracks) self.four_tracks)
tracks = self.spotify.current_user_saved_tracks() tracks = self.spotify.current_user_saved_tracks()
new_total = tracks['total'] new_total = tracks['total']
self.assertTrue(new_total == total) self.assertEquals(new_total, total)
def test_categories(self): def test_current_user_saved_albums(self):
response = self.spotify.categories() # Add
self.assertTrue(len(response['categories']) > 0) self.spotify.current_user_saved_albums_add(self.album_ids)
albums = self.spotify.current_user_saved_albums()
self.assertGreaterEqual(len(albums['items']), 2)
def test_category_playlists(self): # Contains
response = self.spotify.categories() resp = self.spotify.current_user_saved_albums_contains(self.album_ids)
for cat in response['categories']['items']: self.assertEquals(resp, [True, True])
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): # Remove
response = self.spotify.new_releases() self.spotify.current_user_saved_albums_delete(self.album_ids)
self.assertTrue(len(response['albums']) > 0) resp = self.spotify.current_user_saved_albums_contains(self.album_ids)
self.assertEquals(resp, [False, False])
def test_featured_releases(self):
response = self.spotify.featured_playlists()
self.assertTrue(len(response['playlists']) > 0)
def test_current_user_follows(self): class SpotipyUserApiTests(unittest.TestCase):
response = self.spotify.current_user_followed_artists() @classmethod
artists = response['artists'] def setUpClass(cls):
self.assertTrue(len(artists['items']) > 0) cls.username = os.getenv(CCEV['client_username'])
scope = (
'playlist-modify-public '
'user-library-read '
'user-follow-read '
'user-library-modify '
'user-read-private '
'user-top-read '
'user-follow-modify '
'user-read-recently-played '
'ugc-image-upload '
'user-read-playback-state'
)
token = prompt_for_user_token(cls.username, scope=scope)
cls.spotify = Spotify(auth=token)
def test_basic_user_profile(self):
user = self.spotify.user(self.username)
self.assertEquals(user['id'], self.username.lower())
def test_current_user(self):
user = self.spotify.current_user()
self.assertEquals(user['id'], self.username.lower())
def test_me(self):
user = self.spotify.me()
self.assertTrue(user['id'] == self.username.lower())
def test_current_user_top_tracks(self): def test_current_user_top_tracks(self):
response = self.spotify.current_user_top_tracks() response = self.spotify.current_user_top_tracks()
items = response['items'] items = response['items']
self.assertTrue(len(items) > 0) self.assertGreater(len(items), 0)
def test_current_user_top_artists(self): def test_current_user_top_artists(self):
response = self.spotify.current_user_top_artists() response = self.spotify.current_user_top_artists()
items = response['items'] items = response['items']
self.assertTrue(len(items) > 0) self.assertGreater(len(items), 0)
def test_current_user_recently_played(self):
# No cursor
res = self.spotify.current_user_recently_played()
self.assertTrue(len(res['items']) <= 50)
played_at = res['items'][0]['played_at']
# Using `before` gives tracks played before class SpotipyBrowseApiTests(unittest.TestCase):
res = self.spotify.current_user_recently_played( @classmethod
before=res['cursors']['after']) def setUpClass(cls):
self.assertTrue(len(res['items']) <= 50) username = os.getenv(CCEV['client_username'])
self.assertTrue(res['items'][0]['played_at'] < played_at) token = prompt_for_user_token(username)
played_at = res['items'][0]['played_at'] cls.spotify = Spotify(auth=token)
# Using `after` gives tracks played after def test_categories(self):
res = self.spotify.current_user_recently_played( response = self.spotify.categories()
after=res['cursors']['before']) self.assertGreater(len(response['categories']), 0)
self.assertTrue(len(res['items']) <= 50)
self.assertTrue(res['items'][0]['played_at'] > played_at)
def test_user_playlist_ops(self): def test_category_playlists(self):
sp = self.spotify response = self.spotify.categories()
# create empty playlist category = 'rock'
playlist = self.get_or_create_spotify_playlist( for cat in response['categories']['items']:
'spotipy-testing-playlist-1') cat_id = cat['id']
playlist_id = playlist['id'] if cat_id == category:
response = self.spotify.category_playlists(category_id=cat_id)
self.assertGreater(len(response['playlists']["items"]), 0)
# remove all tracks from it def test_new_releases(self):
sp.user_playlist_replace_tracks( response = self.spotify.new_releases()
self.username, playlist_id, []) self.assertGreater(len(response['albums']), 0)
playlist = sp.user_playlist(self.username, playlist_id)
self.assertTrue(playlist['tracks']['total'] == 0)
self.assertTrue(len(playlist['tracks']['items']) == 0)
# add tracks to it def test_featured_releases(self):
sp.user_playlist_add_tracks( response = self.spotify.featured_playlists()
self.username, playlist_id, self.four_tracks) self.assertGreater(len(response['playlists']), 0)
playlist = sp.user_playlist(self.username, playlist_id)
self.assertTrue(playlist['tracks']['total'] == 4)
self.assertTrue(len(playlist['tracks']['items']) == 4)
# remove two tracks from it
sp.user_playlist_remove_all_occurrences_of_tracks(self.username, class SpotipyFollowApiTests(unittest.TestCase):
playlist_id, @classmethod
self.two_tracks) def setUpClass(cls):
playlist = sp.user_playlist(self.username, playlist_id) cls.username = os.getenv(CCEV['client_username'])
self.assertTrue(playlist['tracks']['total'] == 2)
self.assertTrue(len(playlist['tracks']['items']) == 2)
# replace with 3 other tracks scope = (
sp.user_playlist_replace_tracks(self.username, 'playlist-modify-public '
playlist_id, 'user-library-read '
self.other_tracks) 'user-follow-read '
playlist = sp.user_playlist(self.username, playlist_id) 'user-library-modify '
self.assertTrue(playlist['tracks']['total'] == 3) 'user-read-private '
self.assertTrue(len(playlist['tracks']['items']) == 3) 'user-top-read '
'user-follow-modify '
'user-read-recently-played '
'ugc-image-upload '
'user-read-playback-state'
)
def test_playlist(self): token = prompt_for_user_token(cls.username, scope=scope)
# New playlist ID
pl = self.spotify.playlist(self.playlist_new_id)
self.assertTrue(pl["tracks"]["total"] > 0)
# Old playlist ID cls.spotify = Spotify(auth=token)
pl = self.spotify.playlist(self.playlist)
self.assertTrue(pl["tracks"]["total"] > 0)
def test_playlist_tracks(self): def test_current_user_follows(self):
# New playlist ID response = self.spotify.current_user_followed_artists()
pl = self.spotify.playlist_tracks(self.playlist_new_id, limit=2) artists = response['artists']
self.assertTrue(len(pl["items"]) == 2) self.assertGreater(len(artists['items']), 0)
self.assertTrue(pl["total"] > 0)
# Old playlist ID
pl = self.spotify.playlist_tracks(self.playlist, limit=2)
self.assertTrue(len(pl["items"]) == 2)
self.assertTrue(pl["total"] > 0)
def test_playlist_upload_cover_image(self):
pl1 = self.get_or_create_spotify_playlist('spotipy-testing-playlist-1')
plid = pl1['uri']
old_b64 = pl1['images'][0]['url']
# Upload random dog image
r = requests.get('https://dog.ceo/api/breeds/image/random')
dog_base64 = self.get_as_base64(r.json()['message'])
self.spotify.playlist_upload_cover_image(plid, dog_base64)
# Image must be different
pl1 = self.spotify.playlist(plid)
new_b64 = self.get_as_base64(pl1['images'][0]['url'])
self.assertTrue(old_b64 != new_b64)
def test_playlist_cover_image(self):
pl = self.get_or_create_spotify_playlist('spotipy-testing-playlist-1')
plid = pl['uri']
res = self.spotify.playlist_cover_image(plid)
self.assertTrue(len(res) > 0)
first_image = res[0]
self.assertTrue('width' in first_image)
self.assertTrue('height' in first_image)
self.assertTrue('url' in first_image)
def test_user_follows_and_unfollows_artist(self): def test_user_follows_and_unfollows_artist(self):
# Initially follows 1 artist # Initially follows 1 artist
res = self.spotify.current_user_followed_artists() res = self.spotify.current_user_followed_artists()
self.assertTrue(res['artists']['total'] == 1) self.assertEqual(res['artists']['total'], 1)
# Follow 2 more artists # Follow 2 more artists
artists = ["6DPYiyq5kWVQS4RGwxzPC7", "0NbfKEOTQCcwd6o7wSDOHI"] artists = ["6DPYiyq5kWVQS4RGwxzPC7", "0NbfKEOTQCcwd6o7wSDOHI"]
self.spotify.user_follow_artists(artists) self.spotify.user_follow_artists(artists)
res = self.spotify.current_user_followed_artists() res = self.spotify.current_user_followed_artists()
self.assertTrue(res['artists']['total'] == 3) self.assertEqual(res['artists']['total'], 3)
# Unfollow these 2 artists # Unfollow these 2 artists
self.spotify.user_unfollow_artists(artists) self.spotify.user_unfollow_artists(artists)
res = self.spotify.current_user_followed_artists() res = self.spotify.current_user_followed_artists()
self.assertTrue(res['artists']['total'] == 1) self.assertEqual(res['artists']['total'], 1)
def test_user_follows_and_unfollows_user(self): def test_user_follows_and_unfollows_user(self):
# TODO improve after implementing `me/following/contains` # TODO improve after implementing `me/following/contains`
@ -370,25 +347,49 @@ class AuthTestSpotipy(unittest.TestCase):
# Unfollow these 2 users # Unfollow these 2 users
self.spotify.user_unfollow_users(users) self.spotify.user_unfollow_users(users)
def test_deprecated_starred(self):
pl = self.spotify.user_playlist(self.username)
self.assertTrue(pl["tracks"] is None)
self.assertTrue(pl["owner"] is None)
def test_deprecated_user_playlist(self): class SpotipyPlayerApiTests(unittest.TestCase):
# Test without user due to change from @classmethod
# https://developer.spotify.com/community/news/2018/06/12/changes-to-playlist-uris/ def setUpClass(cls):
pl = self.spotify.user_playlist(None, self.playlist) cls.username = os.getenv(CCEV['client_username'])
self.assertTrue(pl["tracks"]["total"] > 0)
def test_deprecated_user_playlis(self): scope = (
# Test without user due to change from 'playlist-modify-public '
# https://developer.spotify.com/community/news/2018/06/12/changes-to-playlist-uris/ 'user-library-read '
pl = self.spotify.user_playlist_tracks(None, self.playlist, limit=2) 'user-follow-read '
self.assertTrue(len(pl["items"]) == 2) 'user-library-modify '
self.assertTrue(pl["total"] > 0) 'user-read-private '
'user-top-read '
'user-follow-modify '
'user-read-recently-played '
'ugc-image-upload '
'user-read-playback-state'
)
token = prompt_for_user_token(cls.username, scope=scope)
cls.spotify = Spotify(auth=token)
def test_devices(self): def test_devices(self):
# No devices playing by default # No devices playing by default
res = self.spotify.devices() res = self.spotify.devices()
self.assertEqual(len(res["devices"]), 0) self.assertEquals(len(res["devices"]), 0)
def test_current_user_recently_played(self):
# No cursor
res = self.spotify.current_user_recently_played()
self.assertLessEqual(len(res['items']), 50)
played_at = res['items'][0]['played_at']
# Using `before` gives tracks played before
res = self.spotify.current_user_recently_played(
before=res['cursors']['after'])
self.assertLessEqual(len(res['items']), 50)
self.assertTrue(res['items'][0]['played_at'] < played_at)
played_at = res['items'][0]['played_at']
# Using `after` gives tracks played after
res = self.spotify.current_user_recently_played(
after=res['cursors']['before'])
self.assertLessEqual(len(res['items']), 50)
self.assertGreater(res['items'][0]['played_at'], played_at)

0
tests/unit/__init__.py Normal file
View File