spotifywebapiPython 1.0.40__py3-none-any.whl → 1.0.42__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 (26) hide show
  1. {spotifywebapiPython-1.0.40.dist-info → spotifywebapiPython-1.0.42.dist-info}/METADATA +1 -1
  2. {spotifywebapiPython-1.0.40.dist-info → spotifywebapiPython-1.0.42.dist-info}/RECORD +26 -24
  3. spotifywebapipython/const.py +1 -1
  4. spotifywebapipython/docs/__init__.html +20 -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 +253 -2
  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/models/device.py +10 -0
  21. spotifywebapipython/models/playerplaystate.py +13 -0
  22. spotifywebapipython/sautils.py +29 -0
  23. spotifywebapipython/spotifyclient.py +415 -7
  24. {spotifywebapiPython-1.0.40.dist-info → spotifywebapiPython-1.0.42.dist-info}/LICENSE +0 -0
  25. {spotifywebapiPython-1.0.40.dist-info → spotifywebapiPython-1.0.42.dist-info}/WHEEL +0 -0
  26. {spotifywebapiPython-1.0.40.dist-info → spotifywebapiPython-1.0.42.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,6 @@
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,12 +8,13 @@ import time
7
8
  from typing import Tuple, Callable
8
9
  from urllib3 import PoolManager, Timeout, HTTPResponse
9
10
  from urllib.parse import urlencode
11
+ from lxml.etree import fromstring, Element
10
12
 
11
13
  # our package imports.
12
14
  from .oauthcli import AuthClient
13
15
  from .models import *
14
16
  from .models import UserProfile as UserProfileCurrentUser
15
- from .sautils import export, GetUnixTimestampMSFromUtcNow
17
+ from .sautils import export, GetUnixTimestampMSFromUtcNow, _xmlGetInnerText
16
18
  from .saappmessages import SAAppMessages
17
19
  from .spotifyapierror import SpotifyApiError
18
20
  from .spotifyapimessage import SpotifyApiMessage
@@ -39,7 +41,7 @@ CACHE_SOURCE_CACHED:str = "cached"
39
41
  CACHE_SOURCE_CURRENT:str = "current"
40
42
 
41
43
  # get smartinspect logger reference; create a new session for this module name.
42
- from smartinspectpython.siauto import SIAuto, SILevel, SISession, SIMethodParmListContext
44
+ from smartinspectpython.siauto import SIAuto, SILevel, SISession, SIMethodParmListContext, SISourceId
43
45
  import logging
44
46
  _logsi:SISession = SIAuto.Si.GetSession(__name__)
45
47
  if (_logsi == None):
@@ -411,10 +413,7 @@ class SpotifyClient:
411
413
  # no - treat it as utf-8 encoded data.
412
414
  responseUTF8 = response.data.decode('utf-8')
413
415
  _logsi.LogText(SILevel.Error, "SpotifyClient http response [%s-%s]: '%s' (utf-8)" % (response.status, response.reason, responseUrl), responseUTF8)
414
-
415
- # at this point we don't know what Spotify Web Api returned, so let's
416
- # just raise a new exception with the non-JSON response data.
417
- #raise SpotifyWebApiError(response.status, responseUTF8, msg.MethodName, response.reason, _logsi)
416
+ responseData = responseUTF8
418
417
 
419
418
  except SpotifyWebApiError: raise # pass handled exceptions on thru
420
419
  except Exception as ex:
@@ -668,7 +667,9 @@ class SpotifyClient:
668
667
  # formulate the request url.
669
668
  url:str = None
670
669
  uri:str = msg.Uri
671
- if (uri == self.SpotifyApiTokenUrl) or (uri == self.SpotifyApiAuthorizeUrl):
670
+ if (uri == self.SpotifyApiTokenUrl) \
671
+ or (uri == self.SpotifyApiAuthorizeUrl) \
672
+ or (uri.startswith('https:')):
672
673
  url = uri
673
674
  else:
674
675
  url = f'{self.SpotifyWebApiUrlBase}{uri}'
@@ -3196,6 +3197,254 @@ class SpotifyClient:
3196
3197
  _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
3197
3198
 
3198
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
+
3199
3448
  def GetArtistRelatedArtists(self,
3200
3449
  artistId:str,
3201
3450
  ) -> list[Artist]:
@@ -8833,6 +9082,97 @@ class SpotifyClient:
8833
9082
  _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
8834
9083
 
8835
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
+ # set desired shuffle mode.
9154
+ self.PlayerSetShuffleMode(shuffle, deviceId, delay)
9155
+
9156
+ # play the tracks.
9157
+ self.PlayerMediaPlayTracks(arrUris, deviceId=deviceId, delay=delay)
9158
+
9159
+ # process results.
9160
+ # no results to process - this is pass or fail.
9161
+ return
9162
+
9163
+ except SpotifyWebApiError: raise # pass handled exceptions on thru
9164
+ except SpotifyWebApiAuthenticationError: raise # pass handled exceptions on thru
9165
+ except Exception as ex:
9166
+
9167
+ # format unhandled exception.
9168
+ raise SpotifyApiError(SAAppMessages.UNHANDLED_EXCEPTION.format(apiMethodName, str(ex)), ex, logsi=_logsi)
9169
+
9170
+ finally:
9171
+
9172
+ # trace.
9173
+ _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
9174
+
9175
+
8836
9176
  def PlayerMediaPlayTracks(self,
8837
9177
  uris:list[str],
8838
9178
  positionMS:int=0,
@@ -10024,6 +10364,72 @@ class SpotifyClient:
10024
10364
  _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
10025
10365
 
10026
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
+
10027
10433
  def RemovePlaylistItems(self,
10028
10434
  playlistId:str,
10029
10435
  uris:str=None,
@@ -10049,6 +10455,7 @@ class SpotifyClient:
10049
10455
  The API will validate that the specified items exist and in the specified positions and
10050
10456
  make the changes, even if more recent changes have been made to the playlist.
10051
10457
  If null, the current playlist is updated.
10458
+ Example: `MTk3LGEzMjUwZGYwODljNmI5ZjAxZTRjZThiOGI4NzZhM2U5M2IxOWUyMDQ`
10052
10459
  Default is null.
10053
10460
 
10054
10461
  Returns:
@@ -10336,6 +10743,7 @@ class SpotifyClient:
10336
10743
  snapshotId (str):
10337
10744
  The playlist's snapshot ID against which you want to make the changes.
10338
10745
  If null, the current playlist is updated.
10746
+ Example: `MTk3LGEzMjUwZGYwODljNmI5ZjAxZTRjZThiOGI4NzZhM2U5M2IxOWUyMDQ`
10339
10747
  Default is null.
10340
10748
 
10341
10749
  Returns: