spotifywebapiPython 1.0.39__py3-none-any.whl → 1.0.41__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 (24) hide show
  1. {spotifywebapiPython-1.0.39.dist-info → spotifywebapiPython-1.0.41.dist-info}/METADATA +1 -1
  2. {spotifywebapiPython-1.0.39.dist-info → spotifywebapiPython-1.0.41.dist-info}/RECORD +24 -22
  3. spotifywebapipython/const.py +6 -1
  4. spotifywebapipython/docs/__init__.html +19 -2
  5. spotifywebapipython/docs/index.html +2 -2
  6. spotifywebapipython/docs/search.js +1 -1
  7. spotifywebapipython/docs/spotifywebapiPython/const.html +20 -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 +319 -17
  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/models/__init__.py +2 -0
  18. spotifywebapipython/models/artistinfo.py +263 -0
  19. spotifywebapipython/models/artistinfotourevent.py +107 -0
  20. spotifywebapipython/sautils.py +29 -0
  21. spotifywebapipython/spotifyclient.py +644 -26
  22. {spotifywebapiPython-1.0.39.dist-info → spotifywebapiPython-1.0.41.dist-info}/LICENSE +0 -0
  23. {spotifywebapiPython-1.0.39.dist-info → spotifywebapiPython-1.0.41.dist-info}/WHEEL +0 -0
  24. {spotifywebapiPython-1.0.39.dist-info → spotifywebapiPython-1.0.41.dist-info}/top_level.txt +0 -0
@@ -1,17 +1,20 @@
1
1
  # external package imports.
2
2
  import base64
3
+ from datetime import datetime
3
4
  import json
4
5
  from io import BytesIO
5
6
  from oauthlib.oauth2 import BackendApplicationClient, WebApplicationClient
7
+ import time
6
8
  from typing import Tuple, Callable
7
9
  from urllib3 import PoolManager, Timeout, HTTPResponse
8
10
  from urllib.parse import urlencode
11
+ from lxml.etree import fromstring, Element
9
12
 
10
13
  # our package imports.
11
14
  from .oauthcli import AuthClient
12
15
  from .models import *
13
16
  from .models import UserProfile as UserProfileCurrentUser
14
- from .sautils import export, GetUnixTimestampMSFromUtcNow
17
+ from .sautils import export, GetUnixTimestampMSFromUtcNow, _xmlGetInnerText
15
18
  from .saappmessages import SAAppMessages
16
19
  from .spotifyapierror import SpotifyApiError
17
20
  from .spotifyapimessage import SpotifyApiMessage
@@ -29,6 +32,7 @@ from .const import (
29
32
  TRACE_METHOD_RESULT_TYPE_PAGE,
30
33
  TRACE_MSG_AUTHTOKEN_CREATE,
31
34
  TRACE_MSG_AUTOPAGING_NEXT,
35
+ TRACE_MSG_DELAY_DEVICE,
32
36
  TRACE_MSG_USERPROFILE,
33
37
  TRACE_WARN_SPOTIFY_SEARCH_NO_MARKET,
34
38
  )
@@ -37,7 +41,7 @@ CACHE_SOURCE_CACHED:str = "cached"
37
41
  CACHE_SOURCE_CURRENT:str = "current"
38
42
 
39
43
  # get smartinspect logger reference; create a new session for this module name.
40
- from smartinspectpython.siauto import SIAuto, SILevel, SISession, SIMethodParmListContext
44
+ from smartinspectpython.siauto import SIAuto, SILevel, SISession, SIMethodParmListContext, SISourceId
41
45
  import logging
42
46
  _logsi:SISession = SIAuto.Si.GetSession(__name__)
43
47
  if (_logsi == None):
@@ -409,10 +413,7 @@ class SpotifyClient:
409
413
  # no - treat it as utf-8 encoded data.
410
414
  responseUTF8 = response.data.decode('utf-8')
411
415
  _logsi.LogText(SILevel.Error, "SpotifyClient http response [%s-%s]: '%s' (utf-8)" % (response.status, response.reason, responseUrl), responseUTF8)
412
-
413
- # at this point we don't know what Spotify Web Api returned, so let's
414
- # just raise a new exception with the non-JSON response data.
415
- #raise SpotifyWebApiError(response.status, responseUTF8, msg.MethodName, response.reason, _logsi)
416
+ responseData = responseUTF8
416
417
 
417
418
  except SpotifyWebApiError: raise # pass handled exceptions on thru
418
419
  except Exception as ex:
@@ -548,6 +549,32 @@ class SpotifyClient:
548
549
  return result
549
550
 
550
551
 
552
+ def _ValidateDelay(self, delay:float, default:float=0.5, maxDelay:float=10) -> float:
553
+ """
554
+ Validates a delay value.
555
+
556
+ Args:
557
+ delay (int):
558
+ The delay value to validate.
559
+ default (int):
560
+ The default delay value to set if the user-input delay is not valid.
561
+ maxDelay (int):
562
+ The maximum delay value allowed.
563
+ Default is 10.
564
+ """
565
+ if (isinstance(delay,int)):
566
+ delay = float(delay)
567
+
568
+ if (not isinstance(delay,float)) or (delay < 0):
569
+ result = default
570
+ elif (delay > maxDelay):
571
+ result = maxDelay
572
+ else:
573
+ result = delay
574
+
575
+ return result
576
+
577
+
551
578
  def _ValidateMarket(self,
552
579
  market:str,
553
580
  ) -> str:
@@ -640,7 +667,9 @@ class SpotifyClient:
640
667
  # formulate the request url.
641
668
  url:str = None
642
669
  uri:str = msg.Uri
643
- if (uri == self.SpotifyApiTokenUrl) or (uri == self.SpotifyApiAuthorizeUrl):
670
+ if (uri == self.SpotifyApiTokenUrl) \
671
+ or (uri == self.SpotifyApiAuthorizeUrl) \
672
+ or (uri.startswith('https:')):
644
673
  url = uri
645
674
  else:
646
675
  url = f'{self.SpotifyWebApiUrlBase}{uri}'
@@ -3168,6 +3197,254 @@ class SpotifyClient:
3168
3197
  _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
3169
3198
 
3170
3199
 
3200
+ def GetArtistInfo(self,
3201
+ artistId:str,
3202
+ ) -> ArtistInfo:
3203
+ """
3204
+ Get artist about information from the Spotify Artist Biography page for the
3205
+ specified Spotify artist ID.
3206
+
3207
+ Args:
3208
+ artistId (str):
3209
+ The Spotify ID of the artist.
3210
+ Example: `6APm8EjxOHSYM5B4i3vT3q`
3211
+
3212
+ Returns:
3213
+ An `ArtistInfo` object that contains the artist info details.
3214
+
3215
+ Raises:
3216
+ SpotifyWebApiError:
3217
+ If the Spotify Web API request was for a non-authorization service
3218
+ and the response contains error information.
3219
+ SpotifApiError:
3220
+ If the method fails for any other reason.
3221
+
3222
+ <details>
3223
+ <summary>Sample Code</summary>
3224
+ ```python
3225
+ .. include:: ../docs/include/samplecode/SpotifyClient/GetArtistInfo.py
3226
+ ```
3227
+ </details>
3228
+ """
3229
+ apiMethodName:str = 'GetArtistInfo'
3230
+ apiMethodParms:SIMethodParmListContext = None
3231
+ result:ArtistInfo = None
3232
+
3233
+ ABOUT_MONTHLY_LISTENERS:str = ' monthly listeners'
3234
+
3235
+ try:
3236
+
3237
+ # trace.
3238
+ apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
3239
+ apiMethodParms.AppendKeyValue("artistId", artistId)
3240
+ _logsi.LogMethodParmList(SILevel.Verbose, "Get Spotify catalog information for a single artist", apiMethodParms)
3241
+
3242
+ # execute spotify web api request.
3243
+ msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/artists/{id}'.format(id=artistId))
3244
+ msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
3245
+ self.MakeRequest('GET', msg)
3246
+
3247
+ # process results.
3248
+ artist = Artist(root=msg.ResponseData)
3249
+ _logsi.LogObject(SILevel.Verbose, TRACE_METHOD_RESULT_TYPE % (apiMethodName, type(artist).__name__), artist, excludeNonPublic=True)
3250
+
3251
+ # create base result.
3252
+ result = ArtistInfo(artist.Id, artist.Name, artist.Type, artist.Uri, artist.ImageUrl)
3253
+
3254
+ # does artist have a bio information url? if not then we are done.
3255
+ if (artist.Href is None) or (len(artist.Href.strip()) == 0):
3256
+ _logsi.LogVerbose("No html returned for artist details page: '%s'" % (artist.ExternalUrls.Spotify))
3257
+ return result
3258
+
3259
+ # was a Spotify external url specified for the artist? if not then we are done.
3260
+ if artist.ExternalUrls is None or artist.ExternalUrls.Spotify is None:
3261
+ return artist
3262
+
3263
+ # retrieve artist details page.
3264
+ msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, artist.ExternalUrls.Spotify)
3265
+ msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
3266
+ self.MakeRequest('GET', msg)
3267
+
3268
+ # log html response.
3269
+ #_logsi.LogHtml(SILevel.Verbose, "GetArtistInfo response HTML", msg.ResponseData)
3270
+ _logsi.LogSource(SILevel.Verbose, "GetArtistInfo response Text", msg.ResponseData, SISourceId.Html)
3271
+
3272
+ # if no response data then we are done.
3273
+ if not msg.HasResponseData:
3274
+ _logsi.LogVerbose("No html returned for artist details page: '%s'" % (artist.ExternalUrls.Spotify))
3275
+ return result
3276
+
3277
+ # find the starting <html> tag.
3278
+ # ElementTree fromstring method will fail if initial tag is "<!doctype html>".
3279
+ html:str = msg.ResponseData
3280
+ if html is None:
3281
+ return result
3282
+ if not html.startswith('<html'):
3283
+ idx:int = html.find('<html')
3284
+ if idx == -1:
3285
+ _logsi.LogVerbose("<html> tag could not be found for artist details page: '%s'" % (artist.ExternalUrls.Spotify))
3286
+ return result
3287
+ html = html[idx:]
3288
+ _logsi.LogSource(SILevel.Verbose, "GetArtistInfo response Text (starting from '<html')", html, SISourceId.Html)
3289
+
3290
+ # log formatted html response.
3291
+ _logsi.LogXml(SILevel.Verbose, "GetArtistInfo response Text (formatted)", html, prettyPrint=True)
3292
+
3293
+ # load html element tree so we can parse it.
3294
+ doc:Element = fromstring(html)
3295
+ _logsi.LogObject(SILevel.Verbose, "doc Element object", doc)
3296
+
3297
+ attrXPath:str = None
3298
+ elm:Element = None
3299
+ elmChild:Element = None
3300
+ innerText:str = None
3301
+
3302
+ # about information - image (button / bio page):
3303
+ attrXPath = './/button[@type="button"]/descendant-or-self::img'
3304
+ _logsi.LogVerbose("Element search 'image button': '%s'" % attrXPath)
3305
+ attrElements:list[Element] = doc.xpath(attrXPath)
3306
+ for elm in attrElements:
3307
+ _logsi.LogObject(SILevel.Verbose, "Element match: '%s'" % attrXPath, elm)
3308
+ attrValue:str = elm.get('src', None)
3309
+ if attrValue is not None:
3310
+ result.ImageUrl = attrValue
3311
+ break
3312
+
3313
+ # about information - image (entity-id):
3314
+ # use the internal `_ImageUrl` attribute value to check, as the `ImageUrl` property
3315
+ # will return the `ImageUrlDefault` if `_ImageUrl` attribute was not set.
3316
+ # <img data-testid="artist-entity-image" src="https://i.scdn.co/image/ab6761610000517446196125b56397cd4e0d9c4b" />
3317
+ if result._ImageUrl is None:
3318
+ attrXPath = './/attribute::data-testid[contains(., "artist-entity-image")]/../@src'
3319
+ _logsi.LogVerbose("Element search 'image entity': '%s'" % attrXPath)
3320
+ elmChild = doc.xpath(attrXPath)
3321
+ if elmChild is not None:
3322
+ _logsi.LogVerbose("Element match: '%s'" % elmChild[0])
3323
+ result.ImageUrl = elmChild[0]
3324
+
3325
+ # about information - monthly listeners:
3326
+ # <div data-testid="monthly-listeners-label">2,707,252 monthly listeners</div>
3327
+ attrXPath = './/attribute::data-testid[contains(., "monthly-listeners-label")]/../text()'
3328
+ _logsi.LogVerbose("Element search 'monthly listeners': '%s'" % attrXPath)
3329
+ elmChild = doc.xpath(attrXPath)
3330
+ if elmChild is not None:
3331
+ _logsi.LogVerbose("Element match: '%s'" % elmChild[0])
3332
+ attrValue = elmChild[0].replace(ABOUT_MONTHLY_LISTENERS,'')
3333
+ attrValue = attrValue.replace(',','')
3334
+ attrValue = attrValue.strip()
3335
+ if attrValue.isnumeric:
3336
+ result.MonthlyListeners = int(attrValue)
3337
+
3338
+ # about information - bio:
3339
+ # <div data-testid="expandable-description">
3340
+ # <div>
3341
+ # <div>
3342
+ # <span>MercyMe is a contemporary Christian music band ...</span>
3343
+ # </div>
3344
+ # </div>
3345
+ # </div>
3346
+ attrXPath = './/attribute::data-testid[contains(., "expandable-description")]/..'
3347
+ _logsi.LogVerbose("Element search 'bio': '%s'" % attrXPath)
3348
+ attrElements:list[Element] = doc.xpath(attrXPath)
3349
+ for elm in attrElements:
3350
+ innerText = _xmlGetInnerText(elm)
3351
+ _logsi.LogObject(SILevel.Verbose, "Element match: '%s' (innerText=%s)" % (attrXPath, innerText), elm)
3352
+ result.Bio = innerText
3353
+ break
3354
+
3355
+ # about information - on tour dates:
3356
+ # <h2>On tour</h2>
3357
+ # <div><ul><li>
3358
+ # <a draggable="false" class="x" href="/concert/1plBKlFD04tlsD1ky081qL">
3359
+ # <time class="x" dateTime="2024-04-04T19:00-07:00">
3360
+ # <h5 class="x" data-encore-id="type">Apr</h5>
3361
+ # <h1 class="x" data-encore-id="type">5</h1>
3362
+ # </time>
3363
+ # <div class="x">
3364
+ # <h3 class="x" data-encore-id="type">MercyMe with David Leonard and Newsboys</h3>
3365
+ # <p class="x" data-encore-id="type"><time class="cR3tL5CgXmgwfJDDKQ2A">Fri, Apr 5</time>accesso ShoWare Center, Kent</p>
3366
+ # </div>
3367
+ # </a>
3368
+ # </li></ul></div>
3369
+ attrXPath = './/h2[text()="On tour"]/following-sibling::*/descendant-or-self::a'
3370
+ _logsi.LogVerbose("Element search 'On tour': '%s'" % attrXPath)
3371
+ attrElements:list[Element] = doc.xpath(attrXPath)
3372
+ for elm in attrElements:
3373
+ innerText = _xmlGetInnerText(elm)
3374
+ if innerText is not None:
3375
+ if innerText.lower() == 'see all':
3376
+ break
3377
+ _logsi.LogObject(SILevel.Verbose, "Element match: '%s' (innerText=%s)" % (attrXPath, innerText), elm)
3378
+ event:ArtistInfoTourEvent = ArtistInfoTourEvent()
3379
+ elmChild = elm.xpath('.//time/@dateTime')
3380
+ if elmChild is not None:
3381
+ try:
3382
+ attrValue = str(elmChild[0])
3383
+ idx:int = attrValue.rfind('-')
3384
+ if idx > 15: # drop timezone portion of the datetime.
3385
+ event.EventDateTime = datetime.fromisoformat(attrValue[:idx])
3386
+ except Exception as ex:
3387
+ _logsi.LogException("Element value '%s' (%s) could not be parsed to a datetime object" % (elmChild[0], attrValue), ex, logToSystemLogger=False)
3388
+ event.EventDateTime = None
3389
+ elmChild = elm.xpath('.//h3/text()')
3390
+ if elmChild is not None:
3391
+ event.Title = elmChild[0]
3392
+ elmChild = elm.xpath('.//p/text()')
3393
+ if elmChild is not None:
3394
+ event.VenueName = elmChild[0]
3395
+ result.TourEvents.append(event)
3396
+ _logsi.LogObject(SILevel.Verbose, '%s: "%s" (%s)' % (type(event).__name__, event.Title, event.EventDateTime), event, excludeNonPublic=True)
3397
+
3398
+ # about information - links.
3399
+ result.AboutUrlFacebook = self._GetArtistInfoAboutLink(doc,"Facebook")
3400
+ result.AboutUrlInstagram = self._GetArtistInfoAboutLink(doc,"Instagram")
3401
+ result.AboutUrlTwitter = self._GetArtistInfoAboutLink(doc,"Twitter")
3402
+ result.AboutUrlWikipedia = self._GetArtistInfoAboutLink(doc,"Wikipedia")
3403
+
3404
+ # trace.
3405
+ _logsi.LogObject(SILevel.Verbose, TRACE_METHOD_RESULT_TYPE % (apiMethodName, type(result).__name__), result, excludeNonPublic=True)
3406
+ return result
3407
+
3408
+ except SpotifyWebApiError: raise # pass handled exceptions on thru
3409
+ except SpotifyWebApiAuthenticationError: raise # pass handled exceptions on thru
3410
+ except Exception as ex:
3411
+
3412
+ # format unhandled exception.
3413
+ raise SpotifyApiError(SAAppMessages.UNHANDLED_EXCEPTION.format(apiMethodName, str(ex)), ex, logsi=_logsi)
3414
+
3415
+ finally:
3416
+
3417
+ # trace.
3418
+ _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
3419
+
3420
+
3421
+ def _GetArtistInfoAboutLink(self,
3422
+ doc:Element,
3423
+ linkTitle:str,
3424
+ ) -> str:
3425
+ """
3426
+ Searches artist about page for the specified link title and returns the HREF
3427
+ attribute value if found; otherwise, null is returned.
3428
+ """
3429
+ result:str = None
3430
+
3431
+ # about information - link.
3432
+ # <a href="https://facebook.com/mercyme">
3433
+ # <div>Facebook</div>
3434
+ # </a>
3435
+ attrXPath:str = './/div[text()="%s"]/parent::a' % linkTitle
3436
+ _logsi.LogVerbose("Element search '%s link': '%s'" % (linkTitle, attrXPath))
3437
+ attrElements:list[Element] = doc.xpath(attrXPath)
3438
+ for elm in attrElements:
3439
+ _logsi.LogObject(SILevel.Verbose, "Element match: '%s'" % attrXPath, elm)
3440
+ attrValue:str = elm.get('href', None)
3441
+ if attrValue is not None:
3442
+ result = attrValue
3443
+ break
3444
+
3445
+ return result
3446
+
3447
+
3171
3448
  def GetArtistRelatedArtists(self,
3172
3449
  artistId:str,
3173
3450
  ) -> list[Artist]:
@@ -5689,7 +5966,8 @@ class SpotifyClient:
5689
5966
  properly handles cases of new types in the future by checking against the type field of each object.
5690
5967
 
5691
5968
  Returns:
5692
- A `PlayerPlayState` object that contains the currently playing media, or null if nothing is playing.
5969
+ A `PlayerPlayState` object that contains player state details as well as
5970
+ currently playing content.
5693
5971
 
5694
5972
  Raises:
5695
5973
  SpotifyWebApiError:
@@ -5780,7 +6058,8 @@ class SpotifyClient:
5780
6058
  properly handles cases of new types in the future by checking against the type field of each object.
5781
6059
 
5782
6060
  Returns:
5783
- A `PlayerPlayState` object that contains the currently playing media state.
6061
+ A `PlayerPlayState` object that contains player state details as well as
6062
+ currently playing content.
5784
6063
 
5785
6064
  Raises:
5786
6065
  SpotifyWebApiError:
@@ -8579,7 +8858,8 @@ class SpotifyClient:
8579
8858
 
8580
8859
 
8581
8860
  def PlayerMediaPause(self,
8582
- deviceId:str=None
8861
+ deviceId:str=None,
8862
+ delay:float=0.50
8583
8863
  ) -> None:
8584
8864
  """
8585
8865
  Pause media play for the specified Spotify Connect device.
@@ -8591,7 +8871,12 @@ class SpotifyClient:
8591
8871
  The id of the device this command is targeting.
8592
8872
  If not supplied, the user's currently active device is the target.
8593
8873
  Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
8594
-
8874
+ delay (float):
8875
+ Time delay (in seconds) to wait AFTER issuing the command to the player.
8876
+ This delay will give the spotify web api time to process the change before
8877
+ another command is issued.
8878
+ Default is 0.50; value range is 0 - 10.
8879
+
8595
8880
  Raises:
8596
8881
  SpotifyWebApiError:
8597
8882
  If the Spotify Web API request was for a non-authorization service
@@ -8618,8 +8903,12 @@ class SpotifyClient:
8618
8903
  # trace.
8619
8904
  apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
8620
8905
  apiMethodParms.AppendKeyValue("deviceId", deviceId)
8906
+ apiMethodParms.AppendKeyValue("delay", delay)
8621
8907
  _logsi.LogMethodParmList(SILevel.Verbose, "Spotify Connect device pause playback", apiMethodParms)
8622
8908
 
8909
+ # validations.
8910
+ delay = self._ValidateDelay(delay, 0.50, 10)
8911
+
8623
8912
  # build spotify web api request parameters.
8624
8913
  urlParms:dict = {}
8625
8914
  if deviceId is not None:
@@ -8631,6 +8920,11 @@ class SpotifyClient:
8631
8920
  msg.UrlParameters = urlParms
8632
8921
  self.MakeRequest('PUT', msg)
8633
8922
 
8923
+ # give spotify web api time to process the change.
8924
+ if delay > 0:
8925
+ _logsi.LogVerbose(TRACE_MSG_DELAY_DEVICE % delay)
8926
+ time.sleep(delay)
8927
+
8634
8928
  # process results.
8635
8929
  # no results to process - this is pass or fail.
8636
8930
  return
@@ -8653,7 +8947,8 @@ class SpotifyClient:
8653
8947
  offsetUri:str=None,
8654
8948
  offsetPosition:int=None,
8655
8949
  positionMS:int=0,
8656
- deviceId:str=None
8950
+ deviceId:str=None,
8951
+ delay:float=0.50
8657
8952
  ) -> None:
8658
8953
  """
8659
8954
  Start playing one or more tracks of the specified context on a Spotify Connect device.
@@ -8687,6 +8982,11 @@ class SpotifyClient:
8687
8982
  The id of the device this command is targeting.
8688
8983
  If not supplied, the user's currently active device is the target.
8689
8984
  Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
8985
+ delay (float):
8986
+ Time delay (in seconds) to wait AFTER issuing the command to the player.
8987
+ This delay will give the spotify web api time to process the change before
8988
+ another command is issued.
8989
+ Default is 0.50; value range is 0 - 10.
8690
8990
 
8691
8991
  Raises:
8692
8992
  SpotifyWebApiError:
@@ -8732,8 +9032,12 @@ class SpotifyClient:
8732
9032
  apiMethodParms.AppendKeyValue("offsetPosition", offsetPosition)
8733
9033
  apiMethodParms.AppendKeyValue("positionMS", positionMS)
8734
9034
  apiMethodParms.AppendKeyValue("deviceId", deviceId)
9035
+ apiMethodParms.AppendKeyValue("delay", delay)
8735
9036
  _logsi.LogMethodParmList(SILevel.Verbose, "Spotify Connect device play context", apiMethodParms)
8736
9037
 
9038
+ # validations.
9039
+ delay = self._ValidateDelay(delay, 0.50, 10)
9040
+
8737
9041
  # build spotify web api request parameters.
8738
9042
  reqData:dict = \
8739
9043
  {
@@ -8756,6 +9060,102 @@ class SpotifyClient:
8756
9060
  msg.RequestJson = reqData
8757
9061
  self.MakeRequest('PUT', msg)
8758
9062
 
9063
+ # give spotify web api time to process the change.
9064
+ if delay > 0:
9065
+ _logsi.LogVerbose(TRACE_MSG_DELAY_DEVICE % delay)
9066
+ time.sleep(delay)
9067
+
9068
+ # process results.
9069
+ # no results to process - this is pass or fail.
9070
+ return
9071
+
9072
+ except SpotifyWebApiError: raise # pass handled exceptions on thru
9073
+ except SpotifyWebApiAuthenticationError: raise # pass handled exceptions on thru
9074
+ except Exception as ex:
9075
+
9076
+ # format unhandled exception.
9077
+ raise SpotifyApiError(SAAppMessages.UNHANDLED_EXCEPTION.format(apiMethodName, str(ex)), ex, logsi=_logsi)
9078
+
9079
+ finally:
9080
+
9081
+ # trace.
9082
+ _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
9083
+
9084
+
9085
+ def PlayerMediaPlayTrackFavorites(self,
9086
+ deviceId:str=None,
9087
+ shuffle:bool=True,
9088
+ delay:float=0.50
9089
+ ) -> None:
9090
+ """
9091
+ Get a list of the tracks saved in the current Spotify user's 'Your Library'
9092
+ and starts playing them.
9093
+
9094
+ Args:
9095
+ deviceId (str):
9096
+ The id of the device this command is targeting.
9097
+ If not supplied, the user's currently active device is the target.
9098
+ Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
9099
+ shuffle (bool):
9100
+ True to set player shuffle mode to on; otherwise, False for no shuffle.
9101
+ delay (float):
9102
+ Time delay (in seconds) to wait AFTER issuing the command to the player.
9103
+ This delay will give the spotify web api time to process the change before
9104
+ another command is issued.
9105
+ Default is 0.50; value range is 0 - 10.
9106
+
9107
+ Raises:
9108
+ SpotifyWebApiError:
9109
+ If the Spotify Web API request was for a non-authorization service
9110
+ and the response contains error information.
9111
+ SpotifApiError:
9112
+ If the method fails for any other reason.
9113
+
9114
+ This API only works for users who have Spotify Premium.
9115
+
9116
+ This method simply calls the `GetTrackFavorites` method to retrieve the current users
9117
+ favorite tracks (200 max), then calls the `PlayerMediaPlayTracks` method to play them.
9118
+
9119
+ <details>
9120
+ <summary>Sample Code</summary>
9121
+ ```python
9122
+ .. include:: ../docs/include/samplecode/SpotifyClient/PlayerMediaPlayTrackFavorites.py
9123
+ ```
9124
+ </details>
9125
+ """
9126
+ apiMethodName:str = 'PlayerMediaPlayTrackFavorites'
9127
+ apiMethodParms:SIMethodParmListContext = None
9128
+
9129
+ try:
9130
+
9131
+ # trace.
9132
+ apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
9133
+ apiMethodParms.AppendKeyValue("deviceId", deviceId)
9134
+ apiMethodParms.AppendKeyValue("shuffle", shuffle)
9135
+ apiMethodParms.AppendKeyValue("delay", delay)
9136
+ _logsi.LogMethodParmList(SILevel.Verbose, "Spotify Connect device play track favorites", apiMethodParms)
9137
+
9138
+ # validations.
9139
+ delay = self._ValidateDelay(delay, 0.50, 10)
9140
+
9141
+ # get current users favorite tracks.
9142
+ tracks:TrackPageSaved = self.GetTrackFavorites(limitTotal=200)
9143
+ if (tracks.ItemsCount == 0):
9144
+ _logsi.LogVerbose("Current user has no favorite tracks; nothing to do")
9145
+ return
9146
+
9147
+ # build a list of all item uri's.
9148
+ arrUris:list[str] = []
9149
+ trackSaved:TrackSaved
9150
+ for trackSaved in tracks.Items:
9151
+ arrUris.append(trackSaved.Track.Uri)
9152
+
9153
+ # play the tracks.
9154
+ self.PlayerMediaPlayTracks(arrUris, deviceId=deviceId, delay=delay)
9155
+
9156
+ # set desired shuffle mode.
9157
+ self.PlayerSetShuffleMode(shuffle, deviceId)
9158
+
8759
9159
  # process results.
8760
9160
  # no results to process - this is pass or fail.
8761
9161
  return
@@ -8776,7 +9176,8 @@ class SpotifyClient:
8776
9176
  def PlayerMediaPlayTracks(self,
8777
9177
  uris:list[str],
8778
9178
  positionMS:int=0,
8779
- deviceId:str=None
9179
+ deviceId:str=None,
9180
+ delay:float=0.50
8780
9181
  ) -> None:
8781
9182
  """
8782
9183
  Start playing one or more tracks on the specified Spotify Connect device.
@@ -8800,6 +9201,11 @@ class SpotifyClient:
8800
9201
  The id of the device this command is targeting.
8801
9202
  If not supplied, the user's currently active device is the target.
8802
9203
  Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
9204
+ delay (float):
9205
+ Time delay (in seconds) to wait AFTER issuing the command to the player.
9206
+ This delay will give the spotify web api time to process the change before
9207
+ another command is issued.
9208
+ Default is 0.50; value range is 0 - 10.
8803
9209
 
8804
9210
  Raises:
8805
9211
  SpotifyWebApiError:
@@ -8829,8 +9235,12 @@ class SpotifyClient:
8829
9235
  apiMethodParms.AppendKeyValue("uris", uris)
8830
9236
  apiMethodParms.AppendKeyValue("positionMS", positionMS)
8831
9237
  apiMethodParms.AppendKeyValue("deviceId", deviceId)
9238
+ apiMethodParms.AppendKeyValue("delay", delay)
8832
9239
  _logsi.LogMethodParmList(SILevel.Verbose, "Spotify Connect device play tracks", apiMethodParms)
8833
9240
 
9241
+ # validations.
9242
+ delay = self._ValidateDelay(delay, 0.50, 10)
9243
+
8834
9244
  # build a list of all item uri's.
8835
9245
  # remove any leading / trailing spaces in case user put a space between the items.
8836
9246
  arrUris:list[str] = None
@@ -8860,6 +9270,11 @@ class SpotifyClient:
8860
9270
  msg.RequestJson = reqData
8861
9271
  self.MakeRequest('PUT', msg)
8862
9272
 
9273
+ # give spotify web api time to process the change.
9274
+ if delay > 0:
9275
+ _logsi.LogVerbose(TRACE_MSG_DELAY_DEVICE % delay)
9276
+ time.sleep(delay)
9277
+
8863
9278
  # process results.
8864
9279
  # no results to process - this is pass or fail.
8865
9280
  return
@@ -8878,7 +9293,8 @@ class SpotifyClient:
8878
9293
 
8879
9294
 
8880
9295
  def PlayerMediaResume(self,
8881
- deviceId:str=None
9296
+ deviceId:str=None,
9297
+ delay:float=0.50
8882
9298
  ) -> None:
8883
9299
  """
8884
9300
  Resume media play for the specified Spotify Connect device.
@@ -8890,6 +9306,11 @@ class SpotifyClient:
8890
9306
  The id of the device this command is targeting.
8891
9307
  If not supplied, the user's currently active device is the target.
8892
9308
  Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
9309
+ delay (float):
9310
+ Time delay (in seconds) to wait AFTER issuing the command to the player.
9311
+ This delay will give the spotify web api time to process the change before
9312
+ another command is issued.
9313
+ Default is 0.50; value range is 0 - 10.
8893
9314
 
8894
9315
  Raises:
8895
9316
  SpotifyWebApiError:
@@ -8917,8 +9338,13 @@ class SpotifyClient:
8917
9338
  # trace.
8918
9339
  apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
8919
9340
  apiMethodParms.AppendKeyValue("deviceId", deviceId)
9341
+ apiMethodParms.AppendKeyValue("delay", delay)
8920
9342
  _logsi.LogMethodParmList(SILevel.Verbose, "Spotify Connect device resume playback", apiMethodParms)
8921
9343
 
9344
+ # validations.
9345
+ delay = self._ValidateDelay(delay, 0.50, 10)
9346
+
9347
+ # build spotify web api request parameters.
8922
9348
  urlParms:dict = {}
8923
9349
  if deviceId is not None:
8924
9350
  urlParms['device_id'] = deviceId
@@ -8929,6 +9355,11 @@ class SpotifyClient:
8929
9355
  msg.UrlParameters = urlParms
8930
9356
  self.MakeRequest('PUT', msg)
8931
9357
 
9358
+ # give spotify web api time to process the change.
9359
+ if delay > 0:
9360
+ _logsi.LogVerbose(TRACE_MSG_DELAY_DEVICE % delay)
9361
+ time.sleep(delay)
9362
+
8932
9363
  # process results.
8933
9364
  # no results to process - this is pass or fail.
8934
9365
  return
@@ -8948,7 +9379,8 @@ class SpotifyClient:
8948
9379
 
8949
9380
  def PlayerMediaSeek(self,
8950
9381
  positionMS:int=0,
8951
- deviceId:str=None
9382
+ deviceId:str=None,
9383
+ delay:float=0.50
8952
9384
  ) -> None:
8953
9385
  """
8954
9386
  Seeks to the given position in the user's currently playing track for the specified
@@ -8966,6 +9398,11 @@ class SpotifyClient:
8966
9398
  The id of the device this command is targeting.
8967
9399
  If not supplied, the user's currently active device is the target.
8968
9400
  Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
9401
+ delay (float):
9402
+ Time delay (in seconds) to wait AFTER issuing the command to the player.
9403
+ This delay will give the spotify web api time to process the change before
9404
+ another command is issued.
9405
+ Default is 0.50; value range is 0 - 10.
8969
9406
 
8970
9407
  Raises:
8971
9408
  SpotifyWebApiError:
@@ -8994,8 +9431,13 @@ class SpotifyClient:
8994
9431
  apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
8995
9432
  apiMethodParms.AppendKeyValue("positionMS", positionMS)
8996
9433
  apiMethodParms.AppendKeyValue("deviceId", deviceId)
9434
+ apiMethodParms.AppendKeyValue("delay", delay)
8997
9435
  _logsi.LogMethodParmList(SILevel.Verbose, "Spotify Connect device seek position", apiMethodParms)
8998
9436
 
9437
+ # validations.
9438
+ delay = self._ValidateDelay(delay, 0.50, 10)
9439
+
9440
+ # build spotify web api request parameters.
8999
9441
  urlParms:dict = \
9000
9442
  {
9001
9443
  'position_ms': positionMS
@@ -9009,6 +9451,11 @@ class SpotifyClient:
9009
9451
  msg.UrlParameters = urlParms
9010
9452
  self.MakeRequest('PUT', msg)
9011
9453
 
9454
+ # give spotify web api time to process the change.
9455
+ if delay > 0:
9456
+ _logsi.LogVerbose(TRACE_MSG_DELAY_DEVICE % delay)
9457
+ time.sleep(delay)
9458
+
9012
9459
  # process results.
9013
9460
  # no results to process - this is pass or fail.
9014
9461
  return
@@ -9027,7 +9474,8 @@ class SpotifyClient:
9027
9474
 
9028
9475
 
9029
9476
  def PlayerMediaSkipNext(self,
9030
- deviceId:str=None
9477
+ deviceId:str=None,
9478
+ delay:float=0.50
9031
9479
  ) -> None:
9032
9480
  """
9033
9481
  Skips to next track in the user's queue for the specified Spotify Connect device.
@@ -9039,6 +9487,11 @@ class SpotifyClient:
9039
9487
  The id of the device this command is targeting.
9040
9488
  If not supplied, the user's currently active device is the target.
9041
9489
  Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
9490
+ delay (float):
9491
+ Time delay (in seconds) to wait AFTER issuing the command to the player.
9492
+ This delay will give the spotify web api time to process the change before
9493
+ another command is issued.
9494
+ Default is 0.50; value range is 0 - 10.
9042
9495
 
9043
9496
  Raises:
9044
9497
  SpotifyWebApiError:
@@ -9066,8 +9519,13 @@ class SpotifyClient:
9066
9519
  # trace.
9067
9520
  apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
9068
9521
  apiMethodParms.AppendKeyValue("deviceId", deviceId)
9522
+ apiMethodParms.AppendKeyValue("delay", delay)
9069
9523
  _logsi.LogMethodParmList(SILevel.Verbose, "Spotify Connect device skip next", apiMethodParms)
9070
9524
 
9525
+ # validations.
9526
+ delay = self._ValidateDelay(delay, 0.50, 10)
9527
+
9528
+ # build spotify web api request parameters.
9071
9529
  urlParms:dict = {}
9072
9530
  if deviceId is not None:
9073
9531
  urlParms['device_id'] = deviceId
@@ -9078,6 +9536,11 @@ class SpotifyClient:
9078
9536
  msg.UrlParameters = urlParms
9079
9537
  self.MakeRequest('POST', msg)
9080
9538
 
9539
+ # give spotify web api time to process the change.
9540
+ if delay > 0:
9541
+ _logsi.LogVerbose(TRACE_MSG_DELAY_DEVICE % delay)
9542
+ time.sleep(delay)
9543
+
9081
9544
  # process results.
9082
9545
  # no results to process - this is pass or fail.
9083
9546
  return
@@ -9096,7 +9559,8 @@ class SpotifyClient:
9096
9559
 
9097
9560
 
9098
9561
  def PlayerMediaSkipPrevious(self,
9099
- deviceId:str=None
9562
+ deviceId:str=None,
9563
+ delay:float=0.50
9100
9564
  ) -> None:
9101
9565
  """
9102
9566
  Skips to previous track in the user's queue for the specified Spotify Connect device.
@@ -9108,6 +9572,11 @@ class SpotifyClient:
9108
9572
  The id of the device this command is targeting.
9109
9573
  If not supplied, the user's currently active device is the target.
9110
9574
  Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
9575
+ delay (float):
9576
+ Time delay (in seconds) to wait AFTER issuing the command to the player.
9577
+ This delay will give the spotify web api time to process the change before
9578
+ another command is issued.
9579
+ Default is 0.50; value range is 0 - 10.
9111
9580
 
9112
9581
  Raises:
9113
9582
  SpotifyWebApiError:
@@ -9135,8 +9604,13 @@ class SpotifyClient:
9135
9604
  # trace.
9136
9605
  apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
9137
9606
  apiMethodParms.AppendKeyValue("deviceId", deviceId)
9607
+ apiMethodParms.AppendKeyValue("delay", delay)
9138
9608
  _logsi.LogMethodParmList(SILevel.Verbose, "Spotify Connect device skip previous", apiMethodParms)
9139
9609
 
9610
+ # validations.
9611
+ delay = self._ValidateDelay(delay, 0.50, 10)
9612
+
9613
+ # build spotify web api request parameters.
9140
9614
  urlParms:dict = {}
9141
9615
  if deviceId is not None:
9142
9616
  urlParms['device_id'] = deviceId
@@ -9147,6 +9621,11 @@ class SpotifyClient:
9147
9621
  msg.UrlParameters = urlParms
9148
9622
  self.MakeRequest('POST', msg)
9149
9623
 
9624
+ # give spotify web api time to process the change.
9625
+ if delay > 0:
9626
+ _logsi.LogVerbose(TRACE_MSG_DELAY_DEVICE % delay)
9627
+ time.sleep(delay)
9628
+
9150
9629
  # process results.
9151
9630
  # no results to process - this is pass or fail.
9152
9631
  return
@@ -9166,7 +9645,8 @@ class SpotifyClient:
9166
9645
 
9167
9646
  def PlayerSetRepeatMode(self,
9168
9647
  state:str='off',
9169
- deviceId:str=None
9648
+ deviceId:str=None,
9649
+ delay:float=0.50
9170
9650
  ) -> None:
9171
9651
  """
9172
9652
  Set repeat mode for the specified Spotify Connect device.
@@ -9184,6 +9664,11 @@ class SpotifyClient:
9184
9664
  The id of the device this command is targeting.
9185
9665
  If not supplied, the user's currently active device is the target.
9186
9666
  Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
9667
+ delay (float):
9668
+ Time delay (in seconds) to wait AFTER issuing the command to the player.
9669
+ This delay will give the spotify web api time to process the change before
9670
+ another command is issued.
9671
+ Default is 0.50; value range is 0 - 10.
9187
9672
 
9188
9673
  Raises:
9189
9674
  SpotifyWebApiError:
@@ -9212,8 +9697,12 @@ class SpotifyClient:
9212
9697
  apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
9213
9698
  apiMethodParms.AppendKeyValue("state", state)
9214
9699
  apiMethodParms.AppendKeyValue("deviceId", deviceId)
9700
+ apiMethodParms.AppendKeyValue("delay", delay)
9215
9701
  _logsi.LogMethodParmList(SILevel.Verbose, "Set Spotify Connect device set repeat mode", apiMethodParms)
9216
9702
 
9703
+ # validations.
9704
+ delay = self._ValidateDelay(delay, 0.50, 10)
9705
+
9217
9706
  # build spotify web api request parameters.
9218
9707
  urlParms:dict = \
9219
9708
  {
@@ -9228,6 +9717,11 @@ class SpotifyClient:
9228
9717
  msg.UrlParameters = urlParms
9229
9718
  self.MakeRequest('PUT', msg)
9230
9719
 
9720
+ # give spotify web api time to process the change.
9721
+ if delay > 0:
9722
+ _logsi.LogVerbose(TRACE_MSG_DELAY_DEVICE % delay)
9723
+ time.sleep(delay)
9724
+
9231
9725
  # process results.
9232
9726
  # no results to process - this is pass or fail.
9233
9727
  return
@@ -9247,7 +9741,8 @@ class SpotifyClient:
9247
9741
 
9248
9742
  def PlayerSetShuffleMode(self,
9249
9743
  state:bool=False,
9250
- deviceId:str=None
9744
+ deviceId:str=None,
9745
+ delay:float=0.50
9251
9746
  ) -> None:
9252
9747
  """
9253
9748
  Set shuffle mode for the specified Spotify Connect device.
@@ -9264,6 +9759,11 @@ class SpotifyClient:
9264
9759
  The id of the device this command is targeting.
9265
9760
  If not supplied, the user's currently active device is the target.
9266
9761
  Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
9762
+ delay (float):
9763
+ Time delay (in seconds) to wait AFTER issuing the command to the player.
9764
+ This delay will give the spotify web api time to process the change before
9765
+ another command is issued.
9766
+ Default is 0.50; value range is 0 - 10.
9267
9767
 
9268
9768
  Raises:
9269
9769
  SpotifyWebApiError:
@@ -9292,8 +9792,12 @@ class SpotifyClient:
9292
9792
  apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
9293
9793
  apiMethodParms.AppendKeyValue("state", state)
9294
9794
  apiMethodParms.AppendKeyValue("deviceId", deviceId)
9795
+ apiMethodParms.AppendKeyValue("delay", delay)
9295
9796
  _logsi.LogMethodParmList(SILevel.Verbose, "Set Spotify Connect device set shuffle mode", apiMethodParms)
9296
9797
 
9798
+ # validations.
9799
+ delay = self._ValidateDelay(delay, 0.50, 10)
9800
+
9297
9801
  # build spotify web api request parameters.
9298
9802
  urlParms:dict = \
9299
9803
  {
@@ -9308,6 +9812,11 @@ class SpotifyClient:
9308
9812
  msg.UrlParameters = urlParms
9309
9813
  self.MakeRequest('PUT', msg)
9310
9814
 
9815
+ # give spotify web api time to process the change.
9816
+ if delay > 0:
9817
+ _logsi.LogVerbose(TRACE_MSG_DELAY_DEVICE % delay)
9818
+ time.sleep(delay)
9819
+
9311
9820
  # process results.
9312
9821
  # no results to process - this is pass or fail.
9313
9822
  return
@@ -9327,7 +9836,8 @@ class SpotifyClient:
9327
9836
 
9328
9837
  def PlayerSetVolume(self,
9329
9838
  volumePercent:int,
9330
- deviceId:str=None
9839
+ deviceId:str=None,
9840
+ delay:float=0.50
9331
9841
  ) -> None:
9332
9842
  """
9333
9843
  Set volume level for the specified Spotify Connect device.
@@ -9343,6 +9853,11 @@ class SpotifyClient:
9343
9853
  The id of the device this command is targeting.
9344
9854
  If not supplied, the user's currently active device is the target.
9345
9855
  Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
9856
+ delay (float):
9857
+ Time delay (in seconds) to wait AFTER issuing the command to the player.
9858
+ This delay will give the spotify web api time to process the change before
9859
+ another command is issued.
9860
+ Default is 0.50; value range is 0 - 10.
9346
9861
 
9347
9862
  Raises:
9348
9863
  SpotifyWebApiError:
@@ -9371,8 +9886,12 @@ class SpotifyClient:
9371
9886
  apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
9372
9887
  apiMethodParms.AppendKeyValue("volumePercent", volumePercent)
9373
9888
  apiMethodParms.AppendKeyValue("deviceId", deviceId)
9889
+ apiMethodParms.AppendKeyValue("delay", delay)
9374
9890
  _logsi.LogMethodParmList(SILevel.Verbose, "Set Spotify Connect device set volume level", apiMethodParms)
9375
9891
 
9892
+ # validations.
9893
+ delay = self._ValidateDelay(delay, 0.50, 10)
9894
+
9376
9895
  # build spotify web api request parameters.
9377
9896
  urlParms:dict = \
9378
9897
  {
@@ -9387,6 +9906,11 @@ class SpotifyClient:
9387
9906
  msg.UrlParameters = urlParms
9388
9907
  self.MakeRequest('PUT', msg)
9389
9908
 
9909
+ # give spotify web api time to process the change.
9910
+ if delay > 0:
9911
+ _logsi.LogVerbose(TRACE_MSG_DELAY_DEVICE % delay)
9912
+ time.sleep(delay)
9913
+
9390
9914
  # process results.
9391
9915
  # no results to process - this is pass or fail.
9392
9916
  return
@@ -9407,6 +9931,7 @@ class SpotifyClient:
9407
9931
  def PlayerTransferPlayback(self,
9408
9932
  deviceId:str=None,
9409
9933
  play:bool=True,
9934
+ delay:float=0.50
9410
9935
  ) -> None:
9411
9936
  """
9412
9937
  Transfer playback to a new Spotify Connect device and optionally begin playback.
@@ -9422,6 +9947,11 @@ class SpotifyClient:
9422
9947
  - `True` - ensure playback happens on new device.
9423
9948
  - `False` - keep the current playback state.
9424
9949
  Default: `True`
9950
+ delay (float):
9951
+ Time delay (in seconds) to wait AFTER issuing the command to the player.
9952
+ This delay will give the spotify web api time to process the change before
9953
+ another command is issued.
9954
+ Default is 0.50; value range is 0 - 10.
9425
9955
 
9426
9956
  Raises:
9427
9957
  SpotifyWebApiError:
@@ -9450,8 +9980,12 @@ class SpotifyClient:
9450
9980
  apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
9451
9981
  apiMethodParms.AppendKeyValue("deviceId", deviceId)
9452
9982
  apiMethodParms.AppendKeyValue("play", play)
9983
+ apiMethodParms.AppendKeyValue("delay", delay)
9453
9984
  _logsi.LogMethodParmList(SILevel.Verbose, "Transfer playback to a new Spotify Connect device", apiMethodParms)
9454
-
9985
+
9986
+ # validations.
9987
+ delay = self._ValidateDelay(delay, 0.50, 10)
9988
+
9455
9989
  # build spotify web api request parameters.
9456
9990
  reqData:dict = \
9457
9991
  {
@@ -9466,6 +10000,11 @@ class SpotifyClient:
9466
10000
  msg.RequestJson = reqData
9467
10001
  self.MakeRequest('PUT', msg)
9468
10002
 
10003
+ # give spotify web api time to process the change.
10004
+ if delay > 0:
10005
+ _logsi.LogVerbose(TRACE_MSG_DELAY_DEVICE % delay)
10006
+ time.sleep(delay)
10007
+
9469
10008
  # process results.
9470
10009
  # no results to process - this is pass or fail.
9471
10010
  return
@@ -9486,6 +10025,7 @@ class SpotifyClient:
9486
10025
  def PlayerVerifyDeviceDefault(self,
9487
10026
  defaultDeviceId:str=None,
9488
10027
  play:bool=False,
10028
+ delay:float=0.50
9489
10029
  ) -> PlayerPlayState:
9490
10030
  """
9491
10031
  Checks to see if there is an active Spotify Connect device, and if not to make one active.
@@ -9505,9 +10045,15 @@ class SpotifyClient:
9505
10045
  - `True` - ensure playback happens on new device.
9506
10046
  - `False` - keep the current playback state.
9507
10047
  Default: `False`
10048
+ delay (float):
10049
+ Time delay (in seconds) to wait AFTER issuing the command to the player.
10050
+ This delay will give the spotify web api time to process the change before
10051
+ another command is issued.
10052
+ Default is 0.50; value range is 0 - 10.
9508
10053
 
9509
10054
  Returns:
9510
- A `PlayerPlayState` object that contains the currently playing media, or null if nothing is playing.
10055
+ A `PlayerPlayState` object that contains player state details as well as
10056
+ currently playing content.
9511
10057
 
9512
10058
  Raises:
9513
10059
  SpotifyWebApiError:
@@ -9533,20 +10079,24 @@ class SpotifyClient:
9533
10079
  apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
9534
10080
  apiMethodParms.AppendKeyValue("defaultDeviceId", defaultDeviceId)
9535
10081
  apiMethodParms.AppendKeyValue("play", play)
10082
+ apiMethodParms.AppendKeyValue("delay", delay)
9536
10083
  _logsi.LogMethodParmList(SILevel.Verbose, "Checks if a Spotify Connect device is active, and activate one if not", apiMethodParms)
9537
10084
 
10085
+ # validations.
10086
+ delay = self._ValidateDelay(delay, 0.50, 10)
10087
+
9538
10088
  # get current Spotify Connect player state.
9539
10089
  result = self.GetPlayerPlaybackState()
9540
10090
 
9541
10091
  # is there an active device set?
9542
- if (result is None) \
9543
- or (result.CurrentlyPlayingType is None and result.Context is None) \
9544
- or (result.Device is None):
10092
+ if (result.CurrentlyPlayingType is None and result.Context is None) \
10093
+ or (result.Device is None) \
10094
+ or (result.Device.Id is None):
9545
10095
 
9546
10096
  _logsi.LogVerbose('No active Spotify Connect device was found - activating device "%s"' % defaultDeviceId)
9547
10097
 
9548
10098
  # no - transfer control to the default device.
9549
- self.PlayerTransferPlayback(defaultDeviceId, play)
10099
+ self.PlayerTransferPlayback(defaultDeviceId, play, delay)
9550
10100
 
9551
10101
  # get current Spotify Connect player state.
9552
10102
  result = self.GetPlayerPlaybackState()
@@ -9814,6 +10364,72 @@ class SpotifyClient:
9814
10364
  _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
9815
10365
 
9816
10366
 
10367
+ def RemovePlaylist(self,
10368
+ playlistId:str,
10369
+ ) -> None:
10370
+ """
10371
+ Remove a user's playlist (calls the `UnfollowPlaylist` method).
10372
+
10373
+ This method requires the `playlist-modify-public` and `playlist-modify-private` scope.
10374
+
10375
+ Args:
10376
+
10377
+ playlistId (str):
10378
+ The Spotify ID of the playlist.
10379
+ Example: `5AC9ZXA7nJ7oGWO911FuDG`
10380
+
10381
+ Raises:
10382
+ SpotifyWebApiError:
10383
+ If the Spotify Web API request was for a non-authorization service
10384
+ and the response contains error information.
10385
+ SpotifApiError:
10386
+ If the method fails for any other reason.
10387
+
10388
+ There is no Spotify Web API endpoint for deleting a playlist. The notion of deleting a playlist is
10389
+ not relevant within the Spotify playlist system. Even if you are the playlist owner and you choose
10390
+ to manually remove it from your own list of playlists, you are simply unfollowing it. Although this
10391
+ behavior may sound strange, it means that other users who are already following the playlist can keep
10392
+ enjoying it.
10393
+
10394
+ [Manually restoring a deleted playlist](https://www.spotify.com/us/account/recover-playlists/) through
10395
+ the Spotify Accounts Service is the same thing as following one of your own playlists that you have
10396
+ previously unfollowed.
10397
+
10398
+ <details>
10399
+ <summary>Sample Code</summary>
10400
+ ```python
10401
+ .. include:: ../docs/include/samplecode/SpotifyClient/RemovePlaylist.py
10402
+ ```
10403
+ </details>
10404
+ """
10405
+ apiMethodName:str = 'RemovePlaylist'
10406
+ apiMethodParms:SIMethodParmListContext = None
10407
+ result:str = None
10408
+
10409
+ try:
10410
+
10411
+ # trace.
10412
+ apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
10413
+ apiMethodParms.AppendKeyValue("playlistId", playlistId)
10414
+ _logsi.LogMethodParmList(SILevel.Verbose, "Remove (by unfollowing) a user's playlist", apiMethodParms)
10415
+
10416
+ # unfollow the playlist for the specified user.
10417
+ return self.UnfollowPlaylist(playlistId)
10418
+
10419
+ except SpotifyApiError: raise # pass handled exceptions on thru
10420
+ except SpotifyWebApiError: raise # pass handled exceptions on thru
10421
+ except SpotifyWebApiAuthenticationError: raise # pass handled exceptions on thru
10422
+ except Exception as ex:
10423
+
10424
+ # format unhandled exception.
10425
+ raise SpotifyApiError(SAAppMessages.UNHANDLED_EXCEPTION.format(apiMethodName, str(ex)), ex, logsi=_logsi)
10426
+
10427
+ finally:
10428
+
10429
+ # trace.
10430
+ _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
10431
+
10432
+
9817
10433
  def RemovePlaylistItems(self,
9818
10434
  playlistId:str,
9819
10435
  uris:str=None,
@@ -9839,6 +10455,7 @@ class SpotifyClient:
9839
10455
  The API will validate that the specified items exist and in the specified positions and
9840
10456
  make the changes, even if more recent changes have been made to the playlist.
9841
10457
  If null, the current playlist is updated.
10458
+ Example: `MTk3LGEzMjUwZGYwODljNmI5ZjAxZTRjZThiOGI4NzZhM2U5M2IxOWUyMDQ`
9842
10459
  Default is null.
9843
10460
 
9844
10461
  Returns:
@@ -10126,6 +10743,7 @@ class SpotifyClient:
10126
10743
  snapshotId (str):
10127
10744
  The playlist's snapshot ID against which you want to make the changes.
10128
10745
  If null, the current playlist is updated.
10746
+ Example: `MTk3LGEzMjUwZGYwODljNmI5ZjAxZTRjZThiOGI4NzZhM2U5M2IxOWUyMDQ`
10129
10747
  Default is null.
10130
10748
 
10131
10749
  Returns: