I am creating my Spotipy object like this, after reading this code snippet:
scc = SpotifyClientCredentials(client_id = SPOTIPY_CLIENT_ID,
client_secret = SPOTIPY_CLIENT_SECRET)
sp = spotipy.Spotify(client_credentials_manager = scc)
However, after a number of queries, I obtain the following error:
http status:401
Traceback (most recent call last):
File "retriever2.py", line 114, in <module>
store_songs(terms, dbfile, total_number)
File "retriever2.py", line 97, in store_songs
songs = get_songs(sp, playlist)
File "retriever2.py", line 36, in get_songs
track_results = sp.next(track_results)
File "/home/chema/.local/lib/python2.7/site-packages/spotipy/client.py", line 172, in next
return self._get(result['next'])
File "/home/chema/.local/lib/python2.7/site-packages/spotipy/client.py", line 123, in _get
return self._internal_call('GET', url, payload, kwargs)
File "/home/chema/.local/lib/python2.7/site-packages/spotipy/client.py", line 104, in _internal_call
-1, '%s:\n %s' % (r.url, r.json()['error']['message']))
spotipy.client.SpotifyException: http status: 401, code:-1 - https://api.spotify.com/v1/users/1248788010/playlists/4wEVX1M28mBIpiHttM6PrP/tracks?offset=100&limit=100:
The access token expired
I was under the impression that SpotifyClientCredentials was used to update the access token accordingly. Right now, I have my queries inside a try - except block and I refresh it manually (I basically recreate the sp object every time I get an exception). Am I using this correctly or is there any other way?
Hi, I am having the same problem as you and I was wondering how you got the try - except to work? Right now it just seems to not catch the error for me.
@Biggerandreas You can use something like this to catch exception raised when the token expires:
try:
do_something()
except spotipy.client.SpotifyException:
# re-authenticate when token expires
token = ...
sp = spotipy.Spotify(auth=token)
do_something()
@ritiek I have already tried that but it still gives me this error:
Traceback (most recent call last):
File "/usr/local/lib/python3.5/dist-packages/spotipy-2.4.4-py3.5.egg/spotipy/client.py", line 121, in _internal_call
File "/usr/local/lib/python3.5/dist-packages/requests/models.py", line 935, in raise_for_status
raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 401 Client Error: Unauthorized for url: https://api.spotify.com/v1/me/player/currently-playing
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/pi/python/musicplayer3.py", line 101, in
run_loop()
File "/home/pi/python/musicplayer3.py", line 87, in run_loop
new_track = main.get_song()
File "/home/pi/python/musicplayer3.py", line 41, in get_song
info = self.sp.currently_playing()
File "/usr/local/lib/python3.5/dist-packages/spotipy-2.4.4-py3.5.egg/spotipy/client.py", line 899, in currently_playing
File "/usr/local/lib/python3.5/dist-packages/spotipy-2.4.4-py3.5.egg/spotipy/client.py", line 148, in _get
File "/usr/local/lib/python3.5/dist-packages/spotipy-2.4.4-py3.5.egg/spotipy/client.py", line 126, in _internal_call
spotipy.client.SpotifyException: http status: 401, code:-1 - https://api.spotify.com/v1/me/player/currently-playing:
The access token expired
Thanks in advance.
@Biggerandreas That should not be happening, I've been using something similar without problems. Do you mind sharing relevant code from your script here? (removing any credentials of course)
@ritiek Sorry for the mess of a code but here it is:
import spotipy
import spotipy.oauth2 as oauth
import re
import time
from RPLCD.i2c import CharLCD
lcd = CharLCD(
i2c_expander='PCF8574', address=0x27, port=1,
cols=20, rows=4, dotsize=8,
charmap='A02',
auto_linebreaks=True,
backlight_enabled=True)
lcd.clear()
username = ''
scope = 'playlist-read-private,' \
' playlist-read-collaborative,' \
' user-read-playback-state,' \
' user-modify-playback-state,' \
' user-read-currently-playing'
redirect_uri = 'http://localhost/'
client_id = ''
client_secret = ''
_re_compiled = re.compile("'name':\s'(.*?)'|'name':\s\"(.*?)\"")
_auth_finder = re.compile("code=(.*?)$", re.MULTILINE)
class Main:
def __init__(self):
self.spo = oauth.SpotifyOAuth(
client_id=client_id,
client_secret=client_secret,
redirect_uri=redirect_uri,
scope=scope,
cache_path=".cache-{}".format(username))
self.sp = spotipy.Spotify(auth=self.get_token())
self.get_song()
def spot(self, token):
self.token = token
self.sp = spotipy.Spotify(auth=token)
return self.sp
def refresh_token(self):
cached_token = self.spo.get_cached_token()
is_it_expired = self.spo.is_token_expired(cached_token)
if is_it_expired is True:
refreshed_token = cached_token['refresh_token']
new_token = self.spo.refresh_access_token(refreshed_token)
self.sp = spotipy.Spotify(auth=new_token)
return new_token
def get_token(self):
token_info = self.spo.get_cached_token()
if token_info:
access_token = token_info['access_token']
return access_token
else:
auth = self.spo.get_authorize_url()
print(auth)
auth_url = input('Click the link above and copy and paste the url here: ')
_re_auth = re.findall(_auth_finder, auth_url)
access_token = self.spo.get_access_token(_re_auth[0])
return access_token
def get_song(self):
info = self.sp.currently_playing()
self.finder = re.findall(_re_compiled, str(info))
self.song = info['item']['name']
_re_artist = re.findall(_re_compiled, str(info['item']['artists']))
if not _re_artist[0][0]:
self.artist = _re_artist[0][1]
else:
self.artist = _re_artist[0][0]
return "Song: {} Artist: {}".format(self.song, self.artist)
time_started = time.strftime("%Y-%m-%d %H:%M:%S")
main = Main()
main.refresh_token()
def run_loop():
lcd.clear()
lcd.cursor_pos = (0, 0)
lcd.write_string(main.song)
lcd.cursor_pos = (3, 0)
lcd.write_string(main.artist)
track_count = 0
old_track = main.get_song()
while True:
new_track = main.get_song()
time.sleep(0.1)
if new_track != old_track:
print(main.finder)
track_count += 1
print(track_count)
lcd.clear()
lcd.cursor_pos = (0, 0)
lcd.write_string(main.song)
lcd.cursor_pos = (3, 0)
lcd.write_string(main.artist)
old_track = new_track
lcd.clear()
lcd.cursor_pos = (0, 0)
lcd.write_string(main.song)
lcd.cursor_pos = (3, 0)
lcd.write_string(main.artist)
track_count = 0
old_track = main.get_song()
while True:
new_track = main.get_song()
time.sleep(0.1)
try:
if new_track != old_track:
print(main.finder)
track_count += 1
print(track_count)
lcd.clear()
lcd.cursor_pos = (0, 0)
lcd.write_string(main.song)
lcd.cursor_pos = (3, 0)
lcd.write_string(main.artist)
old_track = new_track
main.refresh_token()
except spotipy.client.SpotifyException:
#main = Main
token = main.refresh_token()
main.spot(token)
current_time = time.strftime("%Y-%m-%d %H:%M:%S")
print(time_started - current_time)
print(current_time)
print(time_started)
print("Got an exception ")
with open('logs.txt', 'a') as the_file:
the_file.write(current_time)
@Biggerandreas Actually, there are couple of things you need to fix with your code. I'll try to explain my best. :)
Main.refresh_token() unnecessarily at multiple places, which would slow down your script. We only need to call it when a spotipy.client.SpotifyException is raised. You should remove calling it from any other place.spotipy.client.SpotifyException is raised when the token expires, we don't need to check it again in Main.refresh_token(). So, we can reduce Main.refresh_token() to something below: def refresh_token(self):
cached_token = self.spo.get_cached_token()
refreshed_token = cached_token['refresh_token']
new_token = self.spo.refresh_access_token(refreshed_token)
# also we need to specifically pass `auth=new_token['access_token']`
self.sp = spotipy.Spotify(auth=new_token['access_token'])
return new_token
You could then just call main.refresh_token() in your except block which should refresh token.
main.get_song() under try block otherwise we cannot catch any exception it raises (in our case, we want to catch spotipy.client.SpotifyException).str which is not possible. Consider:import time
time_started = time.strftime("%Y-%m-%d %H:%M:%S")
current_time = time.strftime("%Y-%m-%d %H:%M:%S")
print(time_started - current_time)
```
Traceback (most recent call last):
File "timi.py", line 4, in
print(time_started - current_time)
TypeError: unsupported operand type(s) for -: 'str' and 'str'
As a workaround, we can convert them to `datetime.datetime` objects and then subtract them:
```python
from datetime import datetime
import time
fmt = "%Y-%m-d %H:%M:%S"
time_started = time.strftime(fmt)
current_time = time.strftime(fmt)
# also i think you meant `current_time - time_started` and not other way round
print(datetime.strptime(current_time, fmt) - datetime.strptime(time_started, fmt))
That should clear most stuff. Let me know if anything is still not right. :)
Thanks for the help, will try to see if it works!
@ritiek Sadly it did not work, maybe I am just doing it wrong.
You said I should put main.get_song() in my main loop with a try block, but how would I do that?
Is there a way to shorten the timeout? So I don't have to wait an hour for the error?
Thanks in advance.
My bad, that wasn't clear enough. You have this code:
while True:
new_track = main.get_song()
time.sleep(0.1)
try:
if new_track != old_track:
print(main.finder)
...
You should put Main.get_song() under try block like this so we could catch exceptions:
while True:
time.sleep(0.1)
try:
new_track = main.get_song()
if new_track != old_track:
print(main.finder)
...
Is there a way to shorten the timeout?
Yep, kind of. For testing purposes, you could add raise spotipy.client.SpotifyError(401, 401, 'ouch!') under try block as well:
while True:
time.sleep(0.1)
try:
raise spotipy.client.SpotifyError(401, 401, 'ouch!')
new_track = main.get_song()
if new_track != old_track:
print(main.finder)
Of course, this won't let rest of the code in try block (after raise ...) run, you could just check out if it is generating a new token correctly or not by adding a simple print in Main.refresh_token():
def refresh_token(self):
cached_token = self.spo.get_cached_token()
refreshed_token = cached_token['refresh_token']
new_token = self.spo.refresh_access_token(refreshed_token)
print(new_token['access_token']) # <--
# also we need to specifically pass `auth=new_token['access_token']`
self.sp = spotipy.Spotify(auth=new_token['access_token'])
return new_token
If it gives you a new token every time then you're probably good to go.
I can't actually test your complete code as I don't have any LCD display for my Pi.
@ritiek Thanks! Now it catches it.
@ritiek Now it catches the error but It does not seem to refresh the token because it just keeps printing, should I do something else in the try block?
while True:
time.sleep(0.1)
try:
new_track = main.get_song()
if new_track != old_track:
print(main.finder)
track_count += 1
print(track_count)
lcd.clear()
lcd.cursor_pos = (0, 0)
lcd.write_string(main.song)
lcd.cursor_pos = (3, 0)
lcd.write_string(main.artist)
old_track = new_track
except spotipy.client.SpotifyException:
token = main.refresh_token()
main.spot(token)
print("Got an exception ")
@Biggerandreas I went back to the code I was writing when I reported this issue. In the end it seems that what I did was simply to authenticate at the beginning of each loop I was doing (which retrieved some songs, but not so many that the auth would expire). This: https://github.com/rinze/spotify-moods/blob/847cfcbcf1b463e50705b772821bf48acbb309fd/retriever.py#L78
while n_playlists < total_playlists:
sp = spotipy.Spotify(client_credentials_manager = scc)
[...]
In any case, shouldn't spotipy handle this internally?
@Biggerandreas You don't need to feed your refreshed token to Main.spot(). Main.refresh_token() automatically take cares of that with self.sp = spotipy.Spotify(auth=new_token['access_token']).
You could just reduce your except block to:
except spotipy.client.SpotifyException:
main.refresh_token()
print("Got an exception ")
When your token expires (after 1 hour), your code will raise an exception spotipy.client.SpotifyException which will cause Main.refresh_token() to execute which should refresh your instance of spotipy.Spotify() with your newly refreshed token, so the script should keep working fine.
In any case, shouldn't spotipy handle this internally?
@rinze Yep, it would nice to have an optional argument to spotipy.Spotify(auto_refresh=True) which would automatically refresh token once it expires. I don't see any active development on spotipy, so can't say how long before anything like this happens.
@ritiek Now it works, thanks for the help.
Thanks @ritiek. You've been very helpful and active on this repo over the years. I'm currently the only maintainer on the repo, would you be interested to join me? If yes I will tweet your name to @plamere and see if he's ok. I see no reason why not, because Paul initially invited 2 more people but they aren't as available as expected.
Sure, thanks! That would be very nice.
Great, thank you! You should have received the invite from @plamere
The goal is to automate RTD + PyPi deployments from a single account, but if needed we can still request it later
Most helpful comment
@ritiek Now it works, thanks for the help.