Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"401 error: The access token expired" when using SpotifyClientCredentials #83

Closed
rinze opened this issue Mar 6, 2016 · 17 comments
Closed

Comments

@rinze
Copy link

rinze commented Mar 6, 2016

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?

@Biggerandreas
Copy link

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.

@ritiek
Copy link
Collaborator

ritiek commented Feb 12, 2018

@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()

@Biggerandreas
Copy link

@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.

@ritiek
Copy link
Collaborator

ritiek commented Feb 12, 2018

@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)

@Biggerandreas
Copy link

Biggerandreas commented Feb 12, 2018

@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)

@ritiek
Copy link
Collaborator

ritiek commented Feb 12, 2018

@Biggerandreas Actually, there are couple of things you need to fix with your code. I'll try to explain my best. :)


  • You are calling 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.
  • As 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.

  • In your main loop, you should place main.get_song() under try block otherwise we cannot catch any exception it raises (in our case, we want to catch spotipy.client.SpotifyException).
  • You are also trying to subtract 2 different times in 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 <module>
    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:

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. :)

@Biggerandreas
Copy link

Thanks for the help, will try to see if it works!

@Biggerandreas
Copy link

@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.

@ritiek
Copy link
Collaborator

ritiek commented Feb 12, 2018

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.

@Biggerandreas
Copy link

@ritiek Thanks! Now it catches it.

@Biggerandreas
Copy link

@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 ")

@rinze
Copy link
Author

rinze commented Feb 12, 2018

@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?

@ritiek
Copy link
Collaborator

ritiek commented Feb 12, 2018

@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.

@Biggerandreas
Copy link

@ritiek Now it works, thanks for the help.

@stephanebruckert
Copy link
Member

stephanebruckert commented Jan 12, 2020

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.

@ritiek
Copy link
Collaborator

ritiek commented Jan 12, 2020

Sure, thanks! That would be very nice.

@stephanebruckert
Copy link
Member

stephanebruckert commented Jan 12, 2020

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants