spotifywebapiPython 1.0.175__py3-none-any.whl → 1.0.177__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of spotifywebapiPython might be problematic. Click here for more details.

Files changed (23) hide show
  1. {spotifywebapiPython-1.0.175.dist-info → spotifywebapiPython-1.0.177.dist-info}/METADATA +1 -1
  2. {spotifywebapiPython-1.0.175.dist-info → spotifywebapiPython-1.0.177.dist-info}/RECORD +23 -23
  3. spotifywebapipython/const.py +1 -1
  4. spotifywebapipython/docs/__init__.html +14 -2
  5. spotifywebapipython/docs/index.html +2 -2
  6. spotifywebapipython/docs/search.js +1 -1
  7. spotifywebapipython/docs/spotifywebapipython/const.html +3 -3
  8. spotifywebapipython/docs/spotifywebapipython/saappmessages.html +2 -2
  9. spotifywebapipython/docs/spotifywebapipython/sautils.html +2 -2
  10. spotifywebapipython/docs/spotifywebapipython/spotifyapierror.html +2 -2
  11. spotifywebapipython/docs/spotifywebapipython/spotifyapimessage.html +2 -2
  12. spotifywebapipython/docs/spotifywebapipython/spotifyauthtoken.html +2 -2
  13. spotifywebapipython/docs/spotifywebapipython/spotifyclient.html +4 -4
  14. spotifywebapipython/docs/spotifywebapipython/spotifydiscovery.html +2 -2
  15. spotifywebapipython/docs/spotifywebapipython/spotifywebapiauthenticationerror.html +2 -2
  16. spotifywebapipython/docs/spotifywebapipython/spotifywebapierror.html +2 -2
  17. spotifywebapipython/docs/spotifywebapipython/spotifywebplayertoken.html +2 -2
  18. spotifywebapipython/spotifyclient.py +17 -33
  19. spotifywebapipython/spotifyconnect/spotifyconnectdirectorytask.py +50 -25
  20. spotifywebapipython/spotifywebplayertoken.py +70 -51
  21. {spotifywebapiPython-1.0.175.dist-info → spotifywebapiPython-1.0.177.dist-info}/LICENSE +0 -0
  22. {spotifywebapiPython-1.0.175.dist-info → spotifywebapiPython-1.0.177.dist-info}/WHEEL +0 -0
  23. {spotifywebapiPython-1.0.175.dist-info → spotifywebapiPython-1.0.177.dist-info}/top_level.txt +0 -0
@@ -1048,37 +1048,45 @@ class SpotifyConnectDirectoryTask(threading.Thread):
1048
1048
  if (sonosPlayer is None):
1049
1049
  raise SpotifyApiError("Could not find Sonos Controller instance for device: %s" % (device.Title), None, logsi=_logsi)
1050
1050
 
1051
+ # trace.
1052
+ _logsi.LogDictionary(SILevel.Verbose, "Sonos Controller instance for device: %s (speaker_info)" % (device.Title), sonosPlayer.speaker_info)
1053
+
1051
1054
  # does caller want the group coordinator?
1052
1055
  if (returnCoordinator):
1053
1056
 
1054
1057
  # TEST TODO - use to test Sonos coordinator functionality.
1055
1058
  # _logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s" % (device.Title), sonosPlayer, colorValue=SIColors.Red)
1056
- # sonosPlayer._is_coordinator = False
1059
+ #sonosPlayer._is_coordinator = False
1057
1060
 
1058
- # if Sonos Controller instance is the group coordinator then we are done.
1061
+ # is Sonos Controller instance the group coordinator?
1059
1062
  if (sonosPlayer.is_coordinator):
1060
- if _logsi.IsOn(SILevel.Verbose):
1061
- _logsi.LogVerbose("Sonos Controller instance is a group coordinator for device: %s" % (device.Title))
1062
- _logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s" % (device.Title), sonosPlayer)
1063
- _logsi.LogDictionary(SILevel.Verbose, "Sonos Controller instance for device: %s (speaker_info)" % (device.Title), sonosPlayer.speaker_info)
1064
- _logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s (group)" % (device.Title), sonosPlayer.group)
1065
- _logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s (zone_group_state)" % (device.Title), sonosPlayer.zone_group_state)
1066
- return sonosPlayer
1067
1063
 
1068
- # trace.
1069
- if _logsi.IsOn(SILevel.Verbose):
1070
- _logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s" % (device.Title), sonosPlayer)
1071
- _logsi.LogDictionary(SILevel.Verbose, "Sonos Controller instance for device: %s (speaker_info)" % (device.Title), sonosPlayer.speaker_info)
1072
- _logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s (group)" % (device.Title), sonosPlayer.group)
1073
- _logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s (zone_group_state)" % (device.Title), sonosPlayer.zone_group_state)
1074
-
1075
- # is the device part of a group? if so, then use the group coordinator.
1076
- if (sonosPlayer.group is not None):
1077
- if (sonosPlayer.group.coordinator is not None):
1078
- sonosPlayerCoordinator:SoCo = sonosPlayer.group.coordinator
1079
- _logsi.LogVerbose("Sonos Controller group coordinator \"%s\" (%s) will be used for device: %s" % (sonosPlayerCoordinator.player_name, sonosPlayerCoordinator.ip_address, device.Title))
1080
- sonosPlayer = sonosPlayerCoordinator
1064
+ # yes - nothing else to do since it's already a group coordinator.
1065
+ _logsi.LogVerbose("Sonos Controller instance is a group coordinator")
1066
+
1067
+ else:
1081
1068
 
1069
+ # no - try to determine the group coordinator.
1070
+ _logsi.LogVerbose("Sonos Controller instance is NOT a group coordinator for device: %s" % (device.Title))
1071
+
1072
+ # is the device part of a group? if so, then use the `group.coordinator` value (if present).
1073
+ sonosPlayerCoordinator:SoCo = None
1074
+ if (sonosPlayer.group is not None):
1075
+ if (sonosPlayer.group.coordinator is not None):
1076
+ sonosPlayerCoordinator = sonosPlayer.group.coordinator
1077
+ sonosPlayer = sonosPlayerCoordinator
1078
+ _logsi.LogVerbose("Sonos Controller group coordinator \"%s\" (%s) will be used for device: %s" % (sonosPlayerCoordinator.player_name, sonosPlayerCoordinator.ip_address, device.Title))
1079
+ _logsi.LogDictionary(SILevel.Verbose, "Sonos Controller instance for device: %s (speaker_info)" % (device.Title), sonosPlayerCoordinator.speaker_info)
1080
+
1081
+ # if group coordinator could not be found, then log a trace message indicating
1082
+ # this and that subsequent Sonos Controller operations will probably fail.
1083
+ # this only seems to happen for "orphaned" Sonos devices (e.g. uid="")
1084
+ # zone_group_state.groups = [ZoneGroup(uid='RINCON_5CAAFDF4E8DE01400:orphan', coordinator=None, members={SoCo("192.168.50.79")}), etc
1085
+ if (sonosPlayerCoordinator is None):
1086
+ _logsi.LogVerbose("Sonos Controller group coordinator could not be determined for device: %s; subsequent Sonos Controller API functions will probably fail!" % (device.Title), colorValue=SIColors.Red)
1087
+ _logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s (group)" % (device.Title), sonosPlayer.group)
1088
+
1089
+ # return Sonos Controller instance to caller.
1082
1090
  return sonosPlayer
1083
1091
 
1084
1092
 
@@ -1882,11 +1890,28 @@ class SpotifyConnectDirectoryTask(threading.Thread):
1882
1890
 
1883
1891
  try:
1884
1892
 
1885
- # if new device is a Sonos, then create a Sonos Controller instance for the device.
1886
- # use device ip address as the key, as Spotify Web API reports id=null for restricted devices in playerstate!
1893
+ # is this a Sonos device?
1887
1894
  if (scDevice.IsSonos):
1895
+
1896
+ # create a Sonos Controller instance for the device, retrieve the Sonos speaker information,
1897
+ # and add it to the Sonos players collection. use device ip address as the key, as the
1898
+ # Spotify Web API reports "id=null" for restricted devices in playerstate!
1888
1899
  _logsi.LogVerbose("Sonos device detected; creating Sonos Controller instance for device: %s (ip=%s)" % (scDevice.Title, zeroconfDiscoveryResult.HostIpAddress))
1889
- self._SonosPlayers[zeroconfDiscoveryResult.HostIpAddress] = SoCo(zeroconfDiscoveryResult.HostIpAddress)
1900
+ sonosPlayer:SoCo = SoCo(zeroconfDiscoveryResult.HostIpAddress)
1901
+ self._SonosPlayers[zeroconfDiscoveryResult.HostIpAddress] = sonosPlayer
1902
+
1903
+ try:
1904
+ sonosPlayer.get_speaker_info()
1905
+ except Exception as ex:
1906
+ _logsi.LogException("Could not get speaker info for Sonos Controller instance: %s" % (str(ex)), ex, logToSystemLogger=False)
1907
+
1908
+ # trace.
1909
+ if (_logsi.IsOn(SILevel.Verbose)):
1910
+ _logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s" % (scDevice.Title), sonosPlayer)
1911
+ _logsi.LogDictionary(SILevel.Verbose, "Sonos Controller instance for device: %s (speaker_info)" % (scDevice.Title), sonosPlayer.speaker_info)
1912
+ _logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s (group)" % (scDevice.Title), sonosPlayer.group)
1913
+ _logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s (zone_group_state)" % (scDevice.Title), sonosPlayer.zone_group_state)
1914
+ _logsi.LogEnumerable(SILevel.Verbose, "Sonos Controller instance for device: %s (all_zones)" % (scDevice.Title), sonosPlayer.all_zones)
1890
1915
 
1891
1916
  except:
1892
1917
 
@@ -1,13 +1,13 @@
1
1
  # external package imports.
2
- from datetime import datetime, timedelta, timezone
2
+ from datetime import datetime
3
3
  import json
4
4
  import os.path
5
5
  import platformdirs
6
6
  import requests
7
7
  import time
8
- from tokenize import TokenError
9
8
 
10
9
  # our package imports.
10
+ from .spotifyapierror import SpotifyApiError
11
11
  from .const import (
12
12
  SPOTIFY_WEBUI_URL_BASE,
13
13
  SPOTIFYWEBAPIPYTHON_TOKEN_CACHE_FILE,
@@ -224,28 +224,22 @@ class SpotifyWebPlayerToken:
224
224
 
225
225
  # validation.
226
226
  if (self._sp_dc is None):
227
- raise TokenError("Cookie credentials storage key \"%s\" did not contain an \"sp_dc\" key value" % (tokenKey))
227
+ raise SpotifyApiError("Cookie credentials storage key \"%s\" did not contain an \"sp_dc\" key value" % (tokenKey), None, logsi=_logsi)
228
228
  if (self._sp_key is None):
229
- raise TokenError("Cookie credentials storage key \"%s\" did not contain an \"sp_key\" key value" % (tokenKey))
229
+ raise SpotifyApiError("Cookie credentials storage key \"%s\" did not contain an \"sp_key\" key value" % (tokenKey), None, logsi=_logsi)
230
230
 
231
231
  # return token to caller.
232
232
  return tokens[tokenKey]
233
233
 
234
234
  # if we make it here, then it denotes that a token was not found
235
235
  # for the specified key.
236
- raise TokenError("Cookie credentials storage key not found: \"%s\"" % (tokenKey))
236
+ raise SpotifyApiError("Cookie credentials storage key not found: \"%s\"" % (tokenKey), None, logsi=_logsi)
237
237
 
238
- except TokenError as ex:
239
-
240
- # trace.
241
- _logsi.LogException(str(ex), ex, logToSystemLogger=False)
242
- raise
243
-
238
+ except SpotifyApiError: raise # pass handled exceptions on thru
244
239
  except Exception as ex:
245
240
 
246
241
  # trace.
247
- _logsi.LogException("Could not load Spotify Web Player cookie credentials", ex, logToSystemLogger=False)
248
- raise TokenError(str(ex))
242
+ raise SpotifyApiError("Could not load Spotify Web Player cookie credentials", ex, logsi=_logsi)
249
243
 
250
244
  finally:
251
245
 
@@ -282,39 +276,63 @@ class SpotifyWebPlayerToken:
282
276
  "productType": "web_player",
283
277
  }
284
278
 
285
- # trace.
286
- _logsi.LogVerbose("Exchanging Spotify Web Player cookie credentials for OAuth2 authorization access token")
287
- _logsi.LogDictionary(SILevel.Verbose, "%s http request: \"%s\" (cookies)" % (tracePrefix, SPOTIFY_WEBUI_URL_GET_ACCESS_TOKEN), reqCookies)
288
- _logsi.LogDictionary(SILevel.Debug, "%s http request: \"%s\" (headers)" % (tracePrefix, SPOTIFY_WEBUI_URL_GET_ACCESS_TOKEN), reqHeaders)
289
- _logsi.LogDictionary(SILevel.Verbose, "%s http request: \"%s\" (parms)" % (tracePrefix, SPOTIFY_WEBUI_URL_GET_ACCESS_TOKEN), reqParms)
279
+ # retry if gateway timeout error is returned.
280
+ loopTotalDelay:float = 0
281
+ LOOP_DELAY:float = 0.100
282
+ LOOP_TIMEOUT:float = 0.500
283
+ while True:
284
+
285
+ # trace.
286
+ _logsi.LogVerbose("Exchanging Spotify Web Player cookie credentials for OAuth2 authorization access token")
287
+ _logsi.LogDictionary(SILevel.Verbose, "%s http request: \"%s\" (cookies)" % (tracePrefix, SPOTIFY_WEBUI_URL_GET_ACCESS_TOKEN), reqCookies)
288
+ _logsi.LogDictionary(SILevel.Debug, "%s http request: \"%s\" (headers)" % (tracePrefix, SPOTIFY_WEBUI_URL_GET_ACCESS_TOKEN), reqHeaders)
289
+ _logsi.LogDictionary(SILevel.Verbose, "%s http request: \"%s\" (parms)" % (tracePrefix, SPOTIFY_WEBUI_URL_GET_ACCESS_TOKEN), reqParms)
290
290
 
291
- # convert the spotify web player cookie credentials to an access token.
292
- response = session.get(
293
- SPOTIFY_WEBUI_URL_GET_ACCESS_TOKEN,
294
- cookies=reqCookies,
295
- headers=reqHeaders,
296
- params=reqParms,
297
- allow_redirects=False,
298
- )
299
-
300
- # trace.
301
- if _logsi.IsOn(SILevel.Debug):
302
- _logsi.LogObject(SILevel.Debug, "%s http response object - type=\"%s\", module=\"%s\"" % (tracePrefix, type(response).__name__, type(response).__module__), response)
303
-
304
- # trace.
305
- if _logsi.IsOn(SILevel.Debug):
306
- _logsi.LogObject(SILevel.Debug, "%s http response [%s-%s] (response)" % (tracePrefix, response.status_code, response.reason), response)
307
- if (response.headers):
308
- _logsi.LogCollection(SILevel.Debug, "%s http response [%s-%s] (headers)" % (tracePrefix, response.status_code, response.reason), response.headers.items())
309
-
310
- # raise exception if we could not get the access token, or if a redirect occured.
311
- # the redirect usually indicates the sp_dc and sp_key are expired, and it's redirecting to a login page.
312
- if (response.status_code == 302 and response.headers["Location"] == "/get_access_token?reason=transport&productType=web_player&_authfailed=1"):
313
- _logsi.LogError("Unsuccessful token request, received code 302 and Location header %s. sp_dc and sp_key could be expired. Please update values in token storage file.", response.headers["Location"])
314
- raise TokenError("Expired sp_dc, sp_key; please update values in token storage file")
315
- if response.status_code != 200:
316
- _logsi.LogVerbose("Unsuccessful token request, received code %i", response.status_code)
317
- raise TokenError("Token exchange request failed: %s - %s" % (response.status_code, response.reason))
291
+ # convert the spotify web player cookie credentials to an access token.
292
+ response = session.get(
293
+ SPOTIFY_WEBUI_URL_GET_ACCESS_TOKEN,
294
+ cookies=reqCookies,
295
+ headers=reqHeaders,
296
+ params=reqParms,
297
+ allow_redirects=False,
298
+ )
299
+
300
+ # trace.
301
+ if _logsi.IsOn(SILevel.Debug):
302
+ _logsi.LogObject(SILevel.Debug, "%s http response object - type=\"%s\", module=\"%s\"" % (tracePrefix, type(response).__name__, type(response).__module__), response)
303
+
304
+ # trace.
305
+ if _logsi.IsOn(SILevel.Debug):
306
+ _logsi.LogObject(SILevel.Debug, "%s http response [%s-%s] (response)" % (tracePrefix, response.status_code, response.reason), response)
307
+ if (response.headers):
308
+ _logsi.LogCollection(SILevel.Debug, "%s http response [%s-%s] (headers)" % (tracePrefix, response.status_code, response.reason), response.headers.items())
309
+
310
+ # raise exception if we could not get the access token, or if a redirect occured.
311
+ # the redirect usually indicates the sp_dc and sp_key are expired, and it's redirecting to a login page.
312
+ respHdrLocation:str = response.headers.get("Location", None)
313
+ if (response.status_code == 302 and respHdrLocation == "/get_access_token?reason=transport&productType=web_player&_authfailed=1"):
314
+ raise SpotifyApiError("Unsuccessful token request, received status 302 and Location header \"%s\". sp_dc and sp_key could be expired. Please update values in token storage file." % (respHdrLocation), None, logsi=_logsi)
315
+
316
+ # if successful request, then break out of the retry loop.
317
+ if response.status_code == 200:
318
+ _logsi.LogVerbose("Token exchange request was successful within %f seconds from initial request; processing results" % (loopTotalDelay))
319
+ break
320
+
321
+ # any other status code besides 504 (gateway timeout) is an exception event!
322
+ if response.status_code != 504:
323
+ raise SpotifyApiError("Token exchange request failed: %s - %s" % (response.status_code, response.reason), None, logsi=_logsi)
324
+
325
+ # only retry so many times before we give up;
326
+ if (loopTotalDelay > LOOP_TIMEOUT):
327
+ raise SpotifyApiError("Timed out waiting for Spotify Gateway to become available; gave up after %f seconds from initial request" % (loopTotalDelay), None, logsi=_logsi)
328
+
329
+ # trace.
330
+ _logsi.LogVerbose("Spotify Web Player cookie credentials token exchange request returned with a 504 status (Gateway timeout); request will be retried")
331
+
332
+ # wait just a bit between requests.
333
+ _logsi.LogVerbose("Delaying for %s seconds to allow Spotify Gateway to become available" % (LOOP_DELAY))
334
+ time.sleep(LOOP_DELAY)
335
+ loopTotalDelay = loopTotalDelay + LOOP_DELAY
318
336
 
319
337
  # load request response.
320
338
  data = response.content.decode('utf-8')
@@ -347,15 +365,16 @@ class SpotifyWebPlayerToken:
347
365
  # trace.
348
366
  _logsi.LogObject(SILevel.Verbose, "Spotify Web Player access token object", self, excludeNonPublic=True)
349
367
 
368
+ except SpotifyApiError: raise # pass handled exceptions on thru
350
369
  except requests.TooManyRedirects as ex:
351
370
 
352
- _logsi.LogException("Could not get Spotify Web Player access token; sp_dc and sp_key could be expired; Please update values in token storage file", ex, logToSystemLogger=False)
353
- raise TokenError("Expired sp_dc, sp_key")
371
+ # trace.
372
+ raise SpotifyApiError("Could not get Spotify Web Player access token; sp_dc and sp_key could be expired; Please update values in token storage file", ex, logsi=_logsi)
354
373
 
355
- except Exception as ex:
356
-
357
- _logsi.LogException("Could not get Spotify Web Player access token", ex, logToSystemLogger=False)
358
- raise TokenError("Could not get Spotify Web Player access token")
374
+ except Exception as ex:
375
+
376
+ # trace.
377
+ raise SpotifyApiError("Could not get Spotify Web Player access token", ex, logsi=_logsi)
359
378
 
360
379
  finally:
361
380