spotifywebapiPython 1.0.178__py3-none-any.whl → 1.0.180__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.178.dist-info → spotifywebapiPython-1.0.180.dist-info}/METADATA +1 -1
  2. {spotifywebapiPython-1.0.178.dist-info → spotifywebapiPython-1.0.180.dist-info}/RECORD +23 -23
  3. spotifywebapipython/const.py +1 -1
  4. spotifywebapipython/docs/__init__.html +16 -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 +54 -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 +7 -6
  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/saappmessages.py +15 -0
  19. spotifywebapipython/spotifyclient.py +92 -43
  20. spotifywebapipython/spotifywebplayertoken.py +59 -42
  21. {spotifywebapiPython-1.0.178.dist-info → spotifywebapiPython-1.0.180.dist-info}/LICENSE +0 -0
  22. {spotifywebapiPython-1.0.178.dist-info → spotifywebapiPython-1.0.180.dist-info}/WHEEL +0 -0
  23. {spotifywebapiPython-1.0.178.dist-info → spotifywebapiPython-1.0.180.dist-info}/top_level.txt +0 -0
@@ -1164,6 +1164,7 @@ class SpotifyClient:
1164
1164
  - 500 Internal Server Error. You should never receive this error because our clever coders catch them all ... but if you are unlucky enough to get one, please report it to us through a comment at the bottom of this page.
1165
1165
  - 502 Bad Gateway - The server was acting as a gateway or proxy and received an invalid response from the upstream server.
1166
1166
  - 503 Service Unavailable - The server is currently unable to handle the request due to a temporary condition which will be alleviated after some delay. You can choose to resend the request again.
1167
+ - 504 Gateway Timeout - The server is currently unable to handle the request due to a temporary condition which will be alleviated after some delay. You can choose to resend the request again.
1167
1168
  """
1168
1169
  apiMethodName:str = 'MakeRequest'
1169
1170
  apiMethodParms:SIMethodParmListContext = None
@@ -1228,49 +1229,86 @@ class SpotifyClient:
1228
1229
  # in the logic below, ensure that ALL urllib3.request method calls conform to version 1.26.18.
1229
1230
  # urllib3 version 2.0 is not supported! see internal developer notes for more details.
1230
1231
 
1231
- # call the appropriate poolmanager request method.
1232
- if msg.HasUrlParameters:
1232
+ # request retry loop for failed requests that are temporary in nature (504 Gateway Timeout, etc).
1233
+ loopTotalDelay:float = 0
1234
+ LOOP_DELAY:float = 0.200
1235
+ LOOP_TIMEOUT:float = 1.000
1236
+ while True:
1237
+
1238
+ # call the appropriate poolmanager request method.
1239
+ if msg.HasUrlParameters:
1233
1240
 
1234
- # add querystring parameters to url; if url already has a partial parm
1235
- # string (e.g. has a '?xxx=...') then use the append separator (e.g. '...&xxx=...').
1236
- urlQS:str = urlencode(msg.UrlParameters)
1237
- urlParmSep:str = '?'
1238
- if (url.find('?') > 0):
1239
- urlParmSep = '&'
1240
- url = url + urlParmSep + urlQS
1241
+ # add querystring parameters to url; if url already has a partial parm
1242
+ # string (e.g. has a '?xxx=...') then use the append separator (e.g. '...&xxx=...').
1243
+ urlQS:str = urlencode(msg.UrlParameters)
1244
+ urlParmSep:str = '?'
1245
+ if (url.find('?') > 0):
1246
+ urlParmSep = '&'
1247
+ url = url + urlParmSep + urlQS
1241
1248
 
1242
- _logsi.LogDictionary(SILevel.Verbose, "SpotifyClient http request: '%s' (with urlparms)" % (url), msg.UrlParameters, prettyPrint=True)
1243
- response = self._Manager.request_encode_url(method, url, headers=msg.RequestHeaders)
1249
+ _logsi.LogDictionary(SILevel.Verbose, "SpotifyClient http request: '%s' (with urlparms)" % (url), msg.UrlParameters, prettyPrint=True)
1250
+ response = self._Manager.request_encode_url(method, url, headers=msg.RequestHeaders)
1244
1251
 
1245
- elif msg.HasRequestData:
1252
+ elif msg.HasRequestData:
1246
1253
 
1247
- if msg.IsRequestDataEncoded:
1254
+ if msg.IsRequestDataEncoded:
1248
1255
 
1249
- _logsi.LogText(SILevel.Verbose, "SpotifyClient http request: '%s' (with body encoded)" % (url), msg.RequestData)
1250
- response = self._Manager.request(method, url, body=msg.RequestData, headers=msg.RequestHeaders)
1256
+ _logsi.LogText(SILevel.Verbose, "SpotifyClient http request: '%s' (with body encoded)" % (url), msg.RequestData)
1257
+ response = self._Manager.request(method, url, body=msg.RequestData, headers=msg.RequestHeaders)
1251
1258
 
1252
- else:
1259
+ else:
1253
1260
 
1254
- _logsi.LogDictionary(SILevel.Verbose, "SpotifyClient http request: '%s' (with body)" % (url), msg.RequestData, prettyPrint=True)
1255
- response = self._Manager.request_encode_body(method, url, fields=msg.RequestData, headers=msg.RequestHeaders, encode_multipart=False)
1261
+ _logsi.LogDictionary(SILevel.Verbose, "SpotifyClient http request: '%s' (with body)" % (url), msg.RequestData, prettyPrint=True)
1262
+ response = self._Manager.request_encode_body(method, url, fields=msg.RequestData, headers=msg.RequestHeaders, encode_multipart=False)
1256
1263
 
1257
- elif msg.HasRequestJson:
1264
+ elif msg.HasRequestJson:
1258
1265
 
1259
- _logsi.LogDictionary(SILevel.Verbose, "SpotifyClient http request: '%s' (with json body)" % (url), msg.RequestJson, prettyPrint=True)
1266
+ _logsi.LogDictionary(SILevel.Verbose, "SpotifyClient http request: '%s' (with json body)" % (url), msg.RequestJson, prettyPrint=True)
1260
1267
 
1261
- # add content-type=json header and convert the dictionary to json format.
1262
- if not ("content-type" in map(str.lower, msg.RequestHeaders.keys())):
1263
- #headers = HTTPHeaderDict(headers)
1264
- msg.RequestHeaders["Content-Type"] = "application/json"
1265
- reqBody:str = json.dumps(msg.RequestJson, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
1266
- _logsi.LogBinary(SILevel.Debug, "SpotifyClient http request JSON body", reqBody)
1267
- response = self._Manager.request(method, url, body=reqBody, headers=msg.RequestHeaders)
1268
+ # add content-type=json header and convert the dictionary to json format.
1269
+ if not ("content-type" in map(str.lower, msg.RequestHeaders.keys())):
1270
+ #headers = HTTPHeaderDict(headers)
1271
+ msg.RequestHeaders["Content-Type"] = "application/json"
1272
+ reqBody:str = json.dumps(msg.RequestJson, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
1273
+ _logsi.LogBinary(SILevel.Debug, "SpotifyClient http request JSON body", reqBody)
1274
+ response = self._Manager.request(method, url, body=reqBody, headers=msg.RequestHeaders)
1268
1275
 
1269
- else:
1276
+ else:
1277
+
1278
+ _logsi.LogDictionary(SILevel.Verbose, "SpotifyClient http request: '%s' (no body)" % (url), msg.RequestData, prettyPrint=True)
1279
+ response = self._Manager.request(method, url, headers=msg.RequestHeaders)
1280
+
1281
+ # TEST TODO - for testing retry logic.
1282
+ # if (response.status_code == 200) and (loopTotalDelay <= 0.200):
1283
+ # _logsi.LogWarning("TEST TODO - Testing Spotify Web API 504 status (Gateway timeout) condition ...", colorValue=SIColors.Red)
1284
+ # response.status_code = 404
1285
+ # response.reason = "URL Not Found"
1286
+ # if (response.status_code == 200) and (loopTotalDelay <= 0.200):
1287
+ # _logsi.LogWarning("TEST TODO - Testing Spotify Web API 504 status (Gateway timeout) condition ...", colorValue=SIColors.Red)
1288
+ # response.status_code = 504
1289
+ # response.reason = "Gateway Timeout"
1290
+
1291
+ # check for errors that are temporary in nature; for these errors, we will retry the
1292
+ # request for a specified number of tries with a small wait period in between.
1293
+ if (response.status == 504):
1294
+
1295
+ # only retry so many times before we give up;
1296
+ if (loopTotalDelay >= LOOP_TIMEOUT):
1297
+ raise SpotifyApiError(SAAppMessages.MSG_SPOTIFY_WEB_API_RETRY_TIMEOUT % (loopTotalDelay), None, logsi=_logsi)
1298
+
1299
+ # trace.
1300
+ _logsi.LogVerbose(SAAppMessages.MSG_SPOTIFY_WEB_API_RETRY_RESPONSE_STATUS % (response.status, response.reason), colorValue=SIColors.Red)
1301
+
1302
+ # wait just a bit between requests.
1303
+ _logsi.LogVerbose(SAAppMessages.MSG_SPOTIFY_WEB_API_RETRY_REQUEST_DELAY % (LOOP_DELAY))
1304
+ time.sleep(LOOP_DELAY)
1305
+ loopTotalDelay = loopTotalDelay + LOOP_DELAY
1306
+
1307
+ else:
1308
+
1309
+ # otherwise, break out of retry loop and process response.
1310
+ break
1270
1311
 
1271
- _logsi.LogDictionary(SILevel.Verbose, "SpotifyClient http request: '%s' (no body)" % (url), msg.RequestData, prettyPrint=True)
1272
- response = self._Manager.request(method, url, headers=msg.RequestHeaders)
1273
-
1274
1312
  # process based upon response status code; some requests will not return response data.
1275
1313
  # I know this could have been simplified, but I broke it down into possible return code ranges.
1276
1314
  if response.status >= 200 and response.status <= 299:
@@ -6320,9 +6358,9 @@ class SpotifyClient:
6320
6358
  deviceId:str|SpotifyConnectDevice|None=None,
6321
6359
  ) -> PlayerPlayState:
6322
6360
  """
6323
- Get information about the current playback state of the device, including track or episode,
6324
- and progress. For Sonos devices, this will return the playstate of the device instead of the
6325
- active Spotify Web API player playstate.
6361
+ Get information about the current playback state, including track or episode, and progress.
6362
+ If the Spotify Web API reports nothing as playing, then the device-specific playback state
6363
+ is returned (if one exists).
6326
6364
 
6327
6365
  This method requires the `user-read-playback-state` scope.
6328
6366
 
@@ -6365,7 +6403,7 @@ class SpotifyClient:
6365
6403
  # trace.
6366
6404
  apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
6367
6405
  apiMethodParms.AppendKeyValue("deviceId", deviceId)
6368
- _logsi.LogMethodParmList(SILevel.Verbose, "Get the current playback state of the device", apiMethodParms)
6406
+ _logsi.LogMethodParmList(SILevel.Verbose, "Get the current Spotify playback state, with device fallback", apiMethodParms)
6369
6407
 
6370
6408
  # resolve the device object; no need to activate it for what we want here.
6371
6409
  # this will also set the currently active device in the devices cache, as well as
@@ -6375,15 +6413,20 @@ class SpotifyClient:
6375
6413
  # get cached Spotify Web API playerstate (_ResolveDeviceObject method just refreshed it).
6376
6414
  result = self.GetPlayerPlaybackState(refresh=False)
6377
6415
 
6378
- # is this a Sonos device?
6379
- if (scDevice is not None) and (scDevice.IsSonos):
6416
+ # did Spotify return a playstate?
6417
+ if (result.IsEmpty):
6418
+
6419
+ # no; we will now check for a device-specific playstate.
6420
+
6421
+ # is this a Sonos device?
6422
+ if (scDevice is not None) and (scDevice.IsSonos):
6380
6423
 
6381
- # get Sonos playback state via Sonos Controller instance.
6382
- playerStateSonos:PlayerPlayState = self.GetPlayerPlaybackStateSonos(scDevice)
6424
+ # get Sonos playback state via Sonos Controller instance.
6425
+ playerStateSonos:PlayerPlayState = self.GetPlayerPlaybackStateSonos(scDevice)
6383
6426
 
6384
- # return the device playstate.
6385
- _logsi.LogVerbose("Sonos device %s playstate will be returned" % (scDevice.Title))
6386
- result = playerStateSonos
6427
+ # return the device playstate.
6428
+ _logsi.LogVerbose("Sonos device %s playstate will be returned" % (scDevice.Title))
6429
+ result = playerStateSonos
6387
6430
 
6388
6431
  # trace.
6389
6432
  _logsi.LogObject(SILevel.Verbose, TRACE_METHOD_RESULT_TYPE % (apiMethodName, type(result).__name__), result, excludeNonPublic=True)
@@ -13467,7 +13510,13 @@ class SpotifyClient:
13467
13510
  # note that for other brands, the Spotify Web API transfer playback takes care of
13468
13511
  # pausing the player since everything is under the Spotify players control.
13469
13512
  if (scDeviceFrom is not None) and (scDeviceFrom.IsSonos):
13470
- self.PlayerMediaPause(scDeviceFrom)
13513
+ #self.PlayerMediaPause(scDeviceFrom)
13514
+ try:
13515
+ self.PlayerMediaPause(scDeviceFrom)
13516
+ except:
13517
+ # ignore exceptions, since this is a convenience call to pause the from player.
13518
+ _logsi.LogVerbose("Ignoring previous exception, since this is a convenience call to pause the from player")
13519
+ pass
13471
13520
 
13472
13521
  # resolve the device object from the device id; activate if it's dormant.
13473
13522
  # for transfer playback, we will always activate the device.
@@ -7,6 +7,7 @@ import requests
7
7
  import time
8
8
 
9
9
  # our package imports.
10
+ from .saappmessages import SAAppMessages
10
11
  from .spotifyapierror import SpotifyApiError
11
12
  from .const import (
12
13
  SPOTIFY_WEBUI_URL_BASE,
@@ -14,7 +15,7 @@ from .const import (
14
15
  )
15
16
 
16
17
  # get smartinspect logger reference; create a new session for this module name.
17
- from smartinspectpython.siauto import SIAuto, SILevel, SISession, SIMethodParmListContext
18
+ from smartinspectpython.siauto import SIAuto, SILevel, SISession, SIMethodParmListContext, SIColors
18
19
  import logging
19
20
  _logsi:SISession = SIAuto.Si.GetSession(__name__)
20
21
  if (_logsi == None):
@@ -276,17 +277,17 @@ class SpotifyWebPlayerToken:
276
277
  "productType": "web_player",
277
278
  }
278
279
 
279
- # retry if gateway timeout error is returned.
280
+ # trace.
281
+ _logsi.LogVerbose("Exchanging Spotify Web Player cookie credentials for OAuth2 authorization access token")
282
+ _logsi.LogDictionary(SILevel.Verbose, "%s http request: \"%s\" (cookies)" % (tracePrefix, SPOTIFY_WEBUI_URL_GET_ACCESS_TOKEN), reqCookies)
283
+ _logsi.LogDictionary(SILevel.Debug, "%s http request: \"%s\" (headers)" % (tracePrefix, SPOTIFY_WEBUI_URL_GET_ACCESS_TOKEN), reqHeaders)
284
+ _logsi.LogDictionary(SILevel.Verbose, "%s http request: \"%s\" (parms)" % (tracePrefix, SPOTIFY_WEBUI_URL_GET_ACCESS_TOKEN), reqParms)
285
+
286
+ # request retry loop for failed requests that are temporary in nature (504 Gateway Timeout, etc).
280
287
  loopTotalDelay:float = 0
281
- LOOP_DELAY:float = 0.100
282
- LOOP_TIMEOUT:float = 0.500
288
+ LOOP_DELAY:float = 0.200
289
+ LOOP_TIMEOUT:float = 1.000
283
290
  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
291
 
291
292
  # convert the spotify web player cookie credentials to an access token.
292
293
  response = session.get(
@@ -297,42 +298,58 @@ class SpotifyWebPlayerToken:
297
298
  allow_redirects=False,
298
299
  )
299
300
 
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
301
+ # TEST TODO - for testing retry logic.
302
+ # if (response.status_code == 200) and (loopTotalDelay <= 0.200):
303
+ # _logsi.LogWarning("TEST TODO - Testing Spotify Web API 504 status (Gateway timeout) condition ...", colorValue=SIColors.Red)
304
+ # response.status_code = 404
305
+ # response.reason = "URL Not Found"
306
+ # if (response.status_code == 200) and (loopTotalDelay <= 0.200):
307
+ # _logsi.LogWarning("TEST TODO - Testing Spotify Web API 504 status (Gateway timeout) condition ...", colorValue=SIColors.Red)
308
+ # response.status_code = 504
309
+ # response.reason = "Gateway Timeout"
310
+
311
+ # check for errors that are temporary in nature; for these errors, we will retry the
312
+ # request for a specified number of tries with a small wait period in between.
313
+ if (response.status_code == 504):
314
+
315
+ # only retry so many times before we give up.
316
+ if (loopTotalDelay >= LOOP_TIMEOUT):
317
+ raise SpotifyApiError(SAAppMessages.MSG_SPOTIFY_WEB_API_RETRY_TIMEOUT % (loopTotalDelay), None, logsi=_logsi)
320
318
 
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)
319
+ # trace.
320
+ _logsi.LogVerbose(SAAppMessages.MSG_SPOTIFY_WEB_API_RETRY_RESPONSE_STATUS % (response.status_code, response.reason), colorValue=SIColors.Red)
324
321
 
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)
322
+ # wait just a bit between requests.
323
+ _logsi.LogVerbose(SAAppMessages.MSG_SPOTIFY_WEB_API_RETRY_REQUEST_DELAY % (LOOP_DELAY))
324
+ time.sleep(LOOP_DELAY)
325
+ loopTotalDelay = loopTotalDelay + LOOP_DELAY
328
326
 
329
- # trace.
330
- _logsi.LogVerbose("Spotify Web Player cookie credentials token exchange request returned with a 504 status (Gateway timeout); request will be retried")
327
+ else:
328
+
329
+ # otherwise, break out of retry loop and process response.
330
+ break
331
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
332
+ # trace.
333
+ if _logsi.IsOn(SILevel.Debug):
334
+ _logsi.LogObject(SILevel.Debug, "%s http response object - type=\"%s\", module=\"%s\"" % (tracePrefix, type(response).__name__, type(response).__module__), response)
335
+
336
+ # trace.
337
+ if _logsi.IsOn(SILevel.Debug):
338
+ _logsi.LogObject(SILevel.Debug, "%s http response [%s-%s] (response)" % (tracePrefix, response.status_code, response.reason), response)
339
+ if (response.headers):
340
+ _logsi.LogCollection(SILevel.Debug, "%s http response [%s-%s] (headers)" % (tracePrefix, response.status_code, response.reason), response.headers.items())
341
+
342
+ # raise exception if we could not get the access token, or if a redirect occured.
343
+ # the redirect usually indicates the sp_dc and sp_key are expired, and it's redirecting to a login page.
344
+ respHdrLocation:str = response.headers.get("Location", None)
345
+ if (response.status_code == 302 and respHdrLocation == "/get_access_token?reason=transport&productType=web_player&_authfailed=1"):
346
+ 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)
347
+
348
+ # if successful request, then break out of the retry loop.
349
+ if (response.status_code == 200):
350
+ _logsi.LogVerbose("Token exchange request was successful within %f seconds from initial request; processing results" % (loopTotalDelay))
351
+ else:
352
+ raise SpotifyApiError("Token exchange request failed: %s - %s" % (response.status_code, response.reason), None, logsi=_logsi)
336
353
 
337
354
  # load request response.
338
355
  data = response.content.decode('utf-8')