spotifywebapiPython 1.0.35__py3-none-any.whl → 1.0.147__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.
- {spotifywebapiPython-1.0.35.dist-info → spotifywebapiPython-1.0.147.dist-info}/LICENSE +1 -1
- {spotifywebapiPython-1.0.35.dist-info → spotifywebapiPython-1.0.147.dist-info}/METADATA +7 -6
- spotifywebapiPython-1.0.147.dist-info/RECORD +117 -0
- spotifywebapipython/__init__.py +2 -0
- spotifywebapipython/const.py +19 -1
- spotifywebapipython/docs/__init__.html +799 -3
- spotifywebapipython/docs/index.html +21 -3
- spotifywebapipython/docs/search.js +1 -1
- spotifywebapipython/docs/{spotifywebapiPython → spotifywebapipython}/const.html +72 -4
- spotifywebapipython/docs/{spotifywebapiPython → spotifywebapipython}/saappmessages.html +77 -3
- spotifywebapipython/docs/{spotifywebapiPython → spotifywebapipython}/sautils.html +63 -3
- spotifywebapipython/docs/{spotifywebapiPython → spotifywebapipython}/spotifyapierror.html +3 -3
- spotifywebapipython/docs/{spotifywebapiPython → spotifywebapipython}/spotifyapimessage.html +20 -3
- spotifywebapipython/docs/{spotifywebapiPython → spotifywebapipython}/spotifyauthtoken.html +39 -3
- spotifywebapipython/docs/{spotifywebapiPython → spotifywebapipython}/spotifyclient.html +2553 -364
- spotifywebapipython/docs/{spotifywebapiPython → spotifywebapipython}/spotifydiscovery.html +85 -52
- spotifywebapipython/docs/{spotifywebapiPython → spotifywebapipython}/spotifywebapiauthenticationerror.html +3 -3
- spotifywebapipython/docs/{spotifywebapiPython → spotifywebapipython}/spotifywebapierror.html +3 -3
- spotifywebapipython/models/__init__.py +8 -0
- spotifywebapipython/models/albumpagesaved.py +2 -1
- spotifywebapipython/models/albumpagesimplified.py +2 -1
- spotifywebapipython/models/albumsimplified.py +4 -5
- spotifywebapipython/models/artist.py +4 -5
- spotifywebapipython/models/artistinfo.py +282 -0
- spotifywebapipython/models/artistinfotourevent.py +127 -0
- spotifywebapipython/models/artistpage.py +2 -1
- spotifywebapipython/models/artistsimplified.py +4 -0
- spotifywebapipython/models/audiobookpagesimplified.py +2 -1
- spotifywebapipython/models/audiobooksimplified.py +4 -5
- spotifywebapipython/models/category.py +16 -0
- spotifywebapipython/models/categorypage.py +2 -1
- spotifywebapipython/models/chapterpagesimplified.py +2 -1
- spotifywebapipython/models/chaptersimplified.py +6 -7
- spotifywebapipython/models/context.py +1 -3
- spotifywebapipython/models/device.py +85 -2
- spotifywebapipython/models/episodepagesaved.py +2 -1
- spotifywebapipython/models/episodepagesimplified.py +2 -1
- spotifywebapipython/models/episodesaved.py +2 -2
- spotifywebapipython/models/episodesimplified.py +5 -6
- spotifywebapipython/models/imageobject.py +98 -0
- spotifywebapipython/models/pageobject.py +29 -4
- spotifywebapipython/models/playeractions.py +33 -11
- spotifywebapipython/models/playerplaystate.py +128 -13
- spotifywebapipython/models/playerqueueinfo.py +61 -4
- spotifywebapipython/models/playhistorypage.py +2 -1
- spotifywebapipython/models/playlist.py +6 -3
- spotifywebapipython/models/playlistpage.py +2 -1
- spotifywebapipython/models/playlistpagesimplified.py +2 -1
- spotifywebapipython/models/playlistsimplified.py +49 -7
- spotifywebapipython/models/playlisttrack.py +1 -0
- spotifywebapipython/models/playlisttracksummary.py +3 -1
- spotifywebapipython/models/searchresponse.py +1 -1
- spotifywebapipython/models/showpagesaved.py +2 -1
- spotifywebapipython/models/showpagesimplified.py +2 -1
- spotifywebapipython/models/showsaved.py +2 -2
- spotifywebapipython/models/showsimplified.py +8 -5
- spotifywebapipython/models/spotifyconnectdevice.py +188 -0
- spotifywebapipython/models/spotifyconnectdevices.py +628 -0
- spotifywebapipython/models/track.py +2 -1
- spotifywebapipython/models/trackpage.py +42 -1
- spotifywebapipython/models/trackpagesaved.py +2 -1
- spotifywebapipython/models/trackpagesimplified.py +2 -1
- spotifywebapipython/models/trackrecommendations.py +5 -5
- spotifywebapipython/models/tracksimplified.py +10 -3
- spotifywebapipython/models/userprofile.py +25 -1
- spotifywebapipython/models/userprofilesimplified.py +4 -5
- spotifywebapipython/models/zeroconfdiscoveryresult.py +599 -0
- spotifywebapipython/models/zeroconfproperty.py +93 -0
- spotifywebapipython/oauthcli/authclient.py +119 -19
- spotifywebapipython/saappmessages.py +21 -1
- spotifywebapipython/sautils.py +88 -1
- spotifywebapipython/spotifyapimessage.py +18 -1
- spotifywebapipython/spotifyauthtoken.py +26 -0
- spotifywebapipython/spotifyclient.py +4311 -525
- spotifywebapipython/spotifyconnectdeviceeventargs.py +67 -0
- spotifywebapipython/spotifydiscovery.py +326 -136
- spotifywebapipython/zeroconfapi/__init__.py +23 -0
- spotifywebapipython/zeroconfapi/authenticationtypes.py +38 -0
- spotifywebapipython/zeroconfapi/blobbuilder.py +370 -0
- spotifywebapipython/zeroconfapi/credentials.py +37 -0
- spotifywebapipython/zeroconfapi/cryptodiffiehellman.py +89 -0
- spotifywebapipython/zeroconfapi/helpers.py +64 -0
- spotifywebapipython/zeroconfapi/spotifyzeroconfapierror.py +108 -0
- spotifywebapipython/zeroconfapi/zeroconfconnect.py +1315 -0
- spotifywebapipython/zeroconfapi/zeroconfgetinfo.py +619 -0
- spotifywebapipython/zeroconfapi/zeroconfgetinfoalias.py +105 -0
- spotifywebapipython/zeroconfapi/zeroconfgetinfodrmmediaformat.py +90 -0
- spotifywebapipython/zeroconfapi/zeroconfresponse.py +141 -0
- spotifywebapiPython-1.0.35.dist-info/RECORD +0 -98
- {spotifywebapiPython-1.0.35.dist-info → spotifywebapiPython-1.0.147.dist-info}/WHEEL +0 -0
- {spotifywebapiPython-1.0.35.dist-info → spotifywebapiPython-1.0.147.dist-info}/top_level.txt +0 -0
|
@@ -35,13 +35,12 @@ class AuthClient:
|
|
|
35
35
|
Enter the authorization code:
|
|
36
36
|
"""
|
|
37
37
|
|
|
38
|
-
_DEFAULT_AUTH_PROMPT_MESSAGE =
|
|
38
|
+
_DEFAULT_AUTH_PROMPT_MESSAGE = "Please visit this URL to authorize this application: {url}"
|
|
39
39
|
"""
|
|
40
40
|
Please visit this URL to authorize this application: {url}
|
|
41
41
|
"""
|
|
42
42
|
|
|
43
|
-
_DEFAULT_WEB_SUCCESS_MESSAGE =
|
|
44
|
-
)
|
|
43
|
+
_DEFAULT_WEB_SUCCESS_MESSAGE = "The authentication flow has completed; you may close this window (or tab)." #</br>Auth Token:</br>%s"
|
|
45
44
|
"""
|
|
46
45
|
The authentication flow has completed; you may close this window (or tab).
|
|
47
46
|
"""
|
|
@@ -58,7 +57,8 @@ class AuthClient:
|
|
|
58
57
|
tokenProviderId:str=None,
|
|
59
58
|
tokenProfileId:str=None,
|
|
60
59
|
tokenStorageDir:str=None,
|
|
61
|
-
|
|
60
|
+
tokenStorageFile:str=None,
|
|
61
|
+
tokenUpdater:Callable=None,
|
|
62
62
|
) -> None:
|
|
63
63
|
"""
|
|
64
64
|
Initializes a new instance of the class.
|
|
@@ -94,9 +94,12 @@ class AuthClient:
|
|
|
94
94
|
A null value will default to `Shared`.
|
|
95
95
|
Default: `Shared`
|
|
96
96
|
tokenStorageDir (str):
|
|
97
|
-
The directory path that will contain the
|
|
97
|
+
The directory path that will contain the Token Cache file.
|
|
98
98
|
A null value will default to the platform specific storage location:
|
|
99
|
-
Example for Windows OS = `C
|
|
99
|
+
Example for Windows OS = `C:\\ProgramData\\SpotifyWebApiPython`
|
|
100
|
+
tokenStorageFile (str):
|
|
101
|
+
The filename and extension of the Token Cache file.
|
|
102
|
+
Default is `tokens.json`.
|
|
100
103
|
tokenUpdater (Callable):
|
|
101
104
|
A method to call when a token needs to be refreshed by an external provider.
|
|
102
105
|
The defined method is called with no parameters, and should return a token dictionary.
|
|
@@ -111,6 +114,10 @@ class AuthClient:
|
|
|
111
114
|
tokenStorageDir = platformdirs.site_config_dir('SpotifyWebApiPython', ensure_exists=True, appauthor=False)
|
|
112
115
|
os.makedirs(tokenStorageDir, exist_ok=True) # succeeds even if directory exists.
|
|
113
116
|
|
|
117
|
+
# verify token storage filename was specified.
|
|
118
|
+
if tokenStorageFile is None:
|
|
119
|
+
tokenStorageFile = 'tokens.json'
|
|
120
|
+
|
|
114
121
|
# if token providerId not set, then default to shared.
|
|
115
122
|
if tokenProviderId is None:
|
|
116
123
|
tokenProviderId = 'Shared'
|
|
@@ -129,7 +136,8 @@ class AuthClient:
|
|
|
129
136
|
self._Session:OAuth2Session = oauth2Session
|
|
130
137
|
self._TokenProviderId:str = tokenProviderId
|
|
131
138
|
self._TokenStorageDir:str = tokenStorageDir
|
|
132
|
-
self.
|
|
139
|
+
self._TokenStorageFile:str = tokenStorageFile
|
|
140
|
+
self._TokenStoragePath:str = os.path.join(tokenStorageDir, tokenStorageFile)
|
|
133
141
|
self._TokenUpdater:Callable = tokenUpdater
|
|
134
142
|
self._TokenUrl:str = tokenUrl
|
|
135
143
|
self._TokenProfileId:str = tokenProfileId
|
|
@@ -381,9 +389,101 @@ class AuthClient:
|
|
|
381
389
|
raise ConnectionError('Could not find an open port on localhost address "%s", in the range of "%s" to "%s"' % (localhostIp, portStart, portStop))
|
|
382
390
|
|
|
383
391
|
|
|
392
|
+
@staticmethod
|
|
393
|
+
def HasTokenForKey(
|
|
394
|
+
clientId:str=None,
|
|
395
|
+
tokenProviderId:str=None,
|
|
396
|
+
tokenProfileId:str=None,
|
|
397
|
+
tokenStorageDir:str=None,
|
|
398
|
+
tokenStorageFile:str=None,
|
|
399
|
+
) -> bool:
|
|
400
|
+
"""
|
|
401
|
+
Checks if a token exists in the token storage file for the ProviderId / ClientId key.
|
|
402
|
+
|
|
403
|
+
Args:
|
|
404
|
+
clientId (str):
|
|
405
|
+
The unique identifier of the application.
|
|
406
|
+
tokenProviderId (str):
|
|
407
|
+
Provider identifier used when storing the token to disk.
|
|
408
|
+
tokenProfileId (str):
|
|
409
|
+
Profile identifier used when storing the token to disk.
|
|
410
|
+
tokenStorageDir (str):
|
|
411
|
+
The directory path that will contain the `tokens.json` file.
|
|
412
|
+
A null value will default to the platform specific storage location:
|
|
413
|
+
Example for Windows OS = `C:\\ProgramData\\SpotifyWebApiPython`
|
|
414
|
+
tokenStorageFile (str):
|
|
415
|
+
The filename and extension of the Token Cache file.
|
|
416
|
+
Default is `tokens.json`.
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
True if the token was found in the token storage file for the
|
|
420
|
+
specified ProviderId and ClientId key; otherwise, null.
|
|
421
|
+
"""
|
|
422
|
+
apiMethodName:str = 'HasTokenForKey'
|
|
423
|
+
apiMethodParms:SIMethodParmListContext = None
|
|
424
|
+
|
|
425
|
+
try:
|
|
426
|
+
|
|
427
|
+
# trace.
|
|
428
|
+
apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
|
|
429
|
+
_logsi.LogMethodParmList(SILevel.Verbose, "Checking if token exists in token storage file", apiMethodParms)
|
|
430
|
+
|
|
431
|
+
# verify token storage directory exists.
|
|
432
|
+
if tokenStorageDir is None:
|
|
433
|
+
tokenStorageDir = platformdirs.site_config_dir('SpotifyWebApiPython', ensure_exists=True, appauthor=False)
|
|
434
|
+
|
|
435
|
+
# verify token storage filename was specified.
|
|
436
|
+
if tokenStorageFile is None:
|
|
437
|
+
tokenStorageFile = 'tokens.json'
|
|
438
|
+
|
|
439
|
+
# if token providerId not set, then default to shared.
|
|
440
|
+
if tokenProviderId is None:
|
|
441
|
+
tokenProviderId = 'Shared'
|
|
442
|
+
|
|
443
|
+
# if token profileId not set, then default to shared.
|
|
444
|
+
if tokenProfileId is None:
|
|
445
|
+
tokenProfileId = 'Shared'
|
|
446
|
+
|
|
447
|
+
# formulate token storage path.
|
|
448
|
+
tokenStoragePath:str = os.path.join(tokenStorageDir, tokenStorageFile)
|
|
449
|
+
|
|
450
|
+
# formulate token storage key.
|
|
451
|
+
tokenKey:str = f'{tokenProviderId}/{clientId}/{tokenProfileId}'
|
|
452
|
+
_logsi.LogVerbose('Token storage key: "%s"' % (tokenKey))
|
|
453
|
+
_logsi.LogVerbose('Token storage file path: "%s"' % (tokenStoragePath))
|
|
454
|
+
|
|
455
|
+
# does the token storage file exist?
|
|
456
|
+
if os.path.exists(tokenStoragePath):
|
|
457
|
+
|
|
458
|
+
# open the token storage file, and load it's contents.
|
|
459
|
+
_logsi.LogVerbose('Opening token storage file')
|
|
460
|
+
with open(tokenStoragePath, 'r') as f:
|
|
461
|
+
|
|
462
|
+
tokens = json.load(f)
|
|
463
|
+
|
|
464
|
+
# return if token key exists or not.
|
|
465
|
+
if tokenKey in tokens:
|
|
466
|
+
_logsi.LogDictionary(SILevel.Verbose, 'Token was found in token storage file for provider: "%s"' % (tokenKey), tokens[tokenKey], prettyPrint=True)
|
|
467
|
+
return True
|
|
468
|
+
|
|
469
|
+
# indicate token was not found.
|
|
470
|
+
return False
|
|
471
|
+
|
|
472
|
+
except Exception as ex:
|
|
473
|
+
|
|
474
|
+
# trace.
|
|
475
|
+
_logsi.LogException('Could not verify OAuth2 Token from token storage file: "%s"' % (tokenStoragePath), ex)
|
|
476
|
+
raise
|
|
477
|
+
|
|
478
|
+
finally:
|
|
479
|
+
|
|
480
|
+
# trace.
|
|
481
|
+
_logsi.LeaveMethod(SILevel.Debug, apiMethodName)
|
|
482
|
+
|
|
483
|
+
|
|
384
484
|
def _LoadToken(self) -> dict:
|
|
385
485
|
"""
|
|
386
|
-
Loads a token from the token storage file for the ProviderId
|
|
486
|
+
Loads a token from the token storage file for the ProviderId / ClientId key.
|
|
387
487
|
|
|
388
488
|
Returns:
|
|
389
489
|
A token dictionary, if one was found in the token storage file for the
|
|
@@ -436,7 +536,7 @@ class AuthClient:
|
|
|
436
536
|
|
|
437
537
|
def _SaveToken(self, token:dict=None) -> None:
|
|
438
538
|
"""
|
|
439
|
-
Saves a token to the token storage file (e.g. disk) for the ProviderId
|
|
539
|
+
Saves a token to the token storage file (e.g. disk) for the ProviderId / ClientId key.
|
|
440
540
|
|
|
441
541
|
Args:
|
|
442
542
|
token (dict):
|
|
@@ -475,14 +575,14 @@ class AuthClient:
|
|
|
475
575
|
|
|
476
576
|
if token is None:
|
|
477
577
|
|
|
478
|
-
# if token not specified, then remove the existing token for the providerId
|
|
578
|
+
# if token not specified, then remove the existing token for the providerId / clientId.
|
|
479
579
|
_logsi.LogVerbose('Removing token from token storage file')
|
|
480
580
|
if tokenKey in tokens:
|
|
481
581
|
del tokens[tokenKey]
|
|
482
582
|
|
|
483
583
|
else:
|
|
484
584
|
|
|
485
|
-
# store the token for the providerId
|
|
585
|
+
# store the token for the providerId / clientId.
|
|
486
586
|
_logsi.LogVerbose('Storing token in token storage file')
|
|
487
587
|
tokens[tokenKey] = token
|
|
488
588
|
|
|
@@ -776,7 +876,7 @@ class AuthClient:
|
|
|
776
876
|
authorization_prompt_message: Optional[str] = _DEFAULT_AUTH_PROMPT_MESSAGE,
|
|
777
877
|
success_message: str = _DEFAULT_WEB_SUCCESS_MESSAGE,
|
|
778
878
|
open_browser: bool = True,
|
|
779
|
-
|
|
879
|
+
redirect_uri_path: str = '/',
|
|
780
880
|
timeout_seconds: Optional[int] = 120,
|
|
781
881
|
token_audience: Optional[str] = None,
|
|
782
882
|
force: bool = False,
|
|
@@ -819,10 +919,10 @@ class AuthClient:
|
|
|
819
919
|
open_browser (bool):
|
|
820
920
|
True to open the authorization URL in the user's browser; otherwise, False
|
|
821
921
|
to not open a browser.
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
Default value is
|
|
922
|
+
redirect_uri_path (str):
|
|
923
|
+
Path value to add when constructing the redirect_uri; otherwise,
|
|
924
|
+
None to not add a path value.
|
|
925
|
+
Default value is '/'.
|
|
826
926
|
timeout_seconds (int):
|
|
827
927
|
If set, an error will be raised after the timeout value if the user did not
|
|
828
928
|
respond to the authorization request. The value is in seconds.
|
|
@@ -860,7 +960,7 @@ class AuthClient:
|
|
|
860
960
|
apiMethodParms.AppendKeyValue("authorization_prompt_message", authorization_prompt_message)
|
|
861
961
|
apiMethodParms.AppendKeyValue("success_message", success_message)
|
|
862
962
|
apiMethodParms.AppendKeyValue("open_browser", open_browser)
|
|
863
|
-
apiMethodParms.AppendKeyValue("
|
|
963
|
+
apiMethodParms.AppendKeyValue("redirect_uri_path", redirect_uri_path)
|
|
864
964
|
apiMethodParms.AppendKeyValue("timeout_seconds", timeout_seconds)
|
|
865
965
|
apiMethodParms.AppendKeyValue("token_audience", token_audience)
|
|
866
966
|
apiMethodParms.AppendKeyValue("force", force)
|
|
@@ -894,8 +994,8 @@ class AuthClient:
|
|
|
894
994
|
# construct the redirect uri, which is where the user is redirected after
|
|
895
995
|
# authentication success or failure.
|
|
896
996
|
redirectUri:str = 'http://{}:{}'.format(host, wsgiServer.server_port)
|
|
897
|
-
if
|
|
898
|
-
redirectUri = redirectUri +
|
|
997
|
+
if redirect_uri_path is not None:
|
|
998
|
+
redirectUri = redirectUri + redirect_uri_path
|
|
899
999
|
self._Session.redirect_uri = redirectUri
|
|
900
1000
|
|
|
901
1001
|
# construct the authorization url and state values.
|
|
@@ -38,4 +38,24 @@ class SAAppMessages:
|
|
|
38
38
|
MSG_SPOTIFY_WEB_API_AUTH_ERROR:str = "SAM1002E - Spotify Web API returned an authorization error status while processing the \"{methodname}\" method.\nHTTP Status: {httpstatus} - {httpreason}\nError: {error}\nError Description: \"{errordescription}\""
|
|
39
39
|
"""
|
|
40
40
|
SAM1002E - Spotify Web API returned an authorization error status while processing the \"{methodname}\" method.\nHTTP Status: {httpstatus} - {httpreason}\nError: {error}\nError Description: \"{errordescription}\"
|
|
41
|
-
"""
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
MSG_SPOTIFY_ZEROCONF_API_ERROR:str = "SAM1003E - Spotify ZeroConf API returned an error status while processing the \"{methodname}\" method.\nStatus: {status} - {httpreason}\nMessage: \"{message}\""
|
|
44
|
+
"""
|
|
45
|
+
SAM1001E - Spotify ZeroConf API returned an error status while processing the \"{methodname}\" method.\nStatus: {status} - {httpreason}\nMessage: \"{message}\"
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
MSG_SPOTIFY_ACCOUNT_REQUIRED_FOR_NOWPLAYING:str = "SAM1004E - A Spotify Free or Premium level membership is required to get currently playing item information."
|
|
49
|
+
"""
|
|
50
|
+
SAM1004E - A Spotify Premium level membership is required to get currently playing item information.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
MSG_SPOTIFY_ACTIVATE_CREDENTIAL_REQUIRED:str = "SAM1005E - The \"%s\" configuration option was not supplied, which is required in order to reactivate a Spotify Connect device."
|
|
54
|
+
"""
|
|
55
|
+
SAM1005E - The \"%s\" configuration option was not supplied, which is required in order to reactivate a Spotify Connect device.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
MSG_SPOTIFY_DEPRECATED_ENDPOINT:str = "SAM1010E - The \"%s\" endpoint has been deprecated by Spotify without prior notice as of November 27th 2024, and the supporting functionality is no longer available."
|
|
59
|
+
"""
|
|
60
|
+
SAM1010E - The \"%s\" endpoint has been deprecated by Spotify without prior notice as of November 27th 2024, and the supporting functionality is no longer available.
|
|
61
|
+
"""
|
spotifywebapipython/sautils.py
CHANGED
|
@@ -32,6 +32,28 @@ def GetUnixTimestampMSFromUtcNow(days:int=0,
|
|
|
32
32
|
return dtUtcUnixTSMS
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
def passwordMaskString(inputObj:str) -> str:
|
|
36
|
+
"""
|
|
37
|
+
Checks a string for a password value and masks the value so that the password is not displayed
|
|
38
|
+
in a trace file.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
inputObj (str):
|
|
42
|
+
String object to check.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
A copy of the `inputObj` value with password masked.
|
|
46
|
+
"""
|
|
47
|
+
# if input is null then don't bother.
|
|
48
|
+
if (inputObj is None):
|
|
49
|
+
return inputObj
|
|
50
|
+
|
|
51
|
+
# create a new value.
|
|
52
|
+
result:str = ''.ljust(len(inputObj), '*')
|
|
53
|
+
|
|
54
|
+
return result
|
|
55
|
+
|
|
56
|
+
|
|
35
57
|
def static_init(cls):
|
|
36
58
|
"""
|
|
37
59
|
Define the decorator used to call an initializer for a class with all static methods.
|
|
@@ -56,6 +78,66 @@ def export(fn):
|
|
|
56
78
|
return fn
|
|
57
79
|
|
|
58
80
|
|
|
81
|
+
def validateDelay(
|
|
82
|
+
self,
|
|
83
|
+
delay:float,
|
|
84
|
+
default:float=0.5,
|
|
85
|
+
maxDelay:float=10
|
|
86
|
+
) -> float:
|
|
87
|
+
"""
|
|
88
|
+
Validates a delay value.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
delay (int):
|
|
92
|
+
The delay value to validate.
|
|
93
|
+
default (int):
|
|
94
|
+
The default delay value to set if the user-input delay is not valid.
|
|
95
|
+
maxDelay (int):
|
|
96
|
+
The maximum delay value allowed.
|
|
97
|
+
Default is 10.
|
|
98
|
+
"""
|
|
99
|
+
if (isinstance(delay,int)):
|
|
100
|
+
delay = float(delay)
|
|
101
|
+
|
|
102
|
+
if (not isinstance(delay,float)) or (delay < 0):
|
|
103
|
+
result = default
|
|
104
|
+
elif (delay > maxDelay):
|
|
105
|
+
result = maxDelay
|
|
106
|
+
else:
|
|
107
|
+
result = delay
|
|
108
|
+
|
|
109
|
+
return result
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _xmlGetInnerText(elmNode, default:str=None) -> str:
|
|
113
|
+
"""
|
|
114
|
+
Finds the specified xml node inner text from the root Element object and returns its value
|
|
115
|
+
as a string. The default argument is returned if the xml node name is not found.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
root (xml.etree.ElementTree.Element)
|
|
119
|
+
The Element object to search.
|
|
120
|
+
default (str):
|
|
121
|
+
A default value to assign if the xml node does not contain innerText.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
The string value if innerText was found;
|
|
125
|
+
otherwise, the default value.
|
|
126
|
+
"""
|
|
127
|
+
if elmNode is None:
|
|
128
|
+
return default
|
|
129
|
+
|
|
130
|
+
# get text of all child nodes.
|
|
131
|
+
innertext:str = (elmNode.text or '') + ''.join(_xmlGetInnerText(e) for e in elmNode) + (elmNode.tail or '')
|
|
132
|
+
|
|
133
|
+
# did we find anything? if not, then return default.
|
|
134
|
+
if innertext is None:
|
|
135
|
+
return default
|
|
136
|
+
|
|
137
|
+
# return found inner text.
|
|
138
|
+
return innertext
|
|
139
|
+
|
|
140
|
+
|
|
59
141
|
class Event:
|
|
60
142
|
"""
|
|
61
143
|
C# like event processing in Python3.
|
|
@@ -97,9 +179,14 @@ class Event:
|
|
|
97
179
|
def fire(self, *args, **kargs):
|
|
98
180
|
"""
|
|
99
181
|
Calls (i.e. "fires") all method handlers defined for this event.
|
|
182
|
+
|
|
183
|
+
Exceptions will be ignored for individual handlers.
|
|
100
184
|
"""
|
|
101
185
|
for handler in self.handlers:
|
|
102
|
-
|
|
186
|
+
try:
|
|
187
|
+
handler(*args, **kargs)
|
|
188
|
+
except:
|
|
189
|
+
pass
|
|
103
190
|
|
|
104
191
|
def getHandlerCount(self):
|
|
105
192
|
"""
|
|
@@ -42,6 +42,7 @@ class SpotifyApiMessage:
|
|
|
42
42
|
self._RequestHeaders:Mapping[str, str] = requestHeaders
|
|
43
43
|
self._RequestJson:dict = None
|
|
44
44
|
self._ResponseData = {}
|
|
45
|
+
self._IgnoreResponseErrors:bool = False
|
|
45
46
|
self._IsRequestDataEncoded:bool = False
|
|
46
47
|
self._Uri:str = uri
|
|
47
48
|
self._UrlParameters:dict = urlParameters
|
|
@@ -99,6 +100,22 @@ class SpotifyApiMessage:
|
|
|
99
100
|
return self._RequestData is not None
|
|
100
101
|
|
|
101
102
|
|
|
103
|
+
@property
|
|
104
|
+
def IgnoreResponseErrors(self) -> bool:
|
|
105
|
+
"""
|
|
106
|
+
Indicates if request response errors are ignored (True) or not (False, default).
|
|
107
|
+
If not, an exception will be thrown with the error details.
|
|
108
|
+
"""
|
|
109
|
+
return self._IgnoreResponseErrors
|
|
110
|
+
|
|
111
|
+
@IgnoreResponseErrors.setter
|
|
112
|
+
def IgnoreResponseErrors(self, value:bool):
|
|
113
|
+
"""
|
|
114
|
+
Sets the IgnoreResponseErrors property value.
|
|
115
|
+
"""
|
|
116
|
+
self._IgnoreResponseErrors = value
|
|
117
|
+
|
|
118
|
+
|
|
102
119
|
@property
|
|
103
120
|
def IsRequestDataEncoded(self) -> bool:
|
|
104
121
|
"""
|
|
@@ -109,7 +126,7 @@ class SpotifyApiMessage:
|
|
|
109
126
|
@IsRequestDataEncoded.setter
|
|
110
127
|
def IsRequestDataEncoded(self, value:bool):
|
|
111
128
|
"""
|
|
112
|
-
Sets the
|
|
129
|
+
Sets the IsRequestDataEncoded property value.
|
|
113
130
|
"""
|
|
114
131
|
self._IsRequestDataEncoded = value
|
|
115
132
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# external package imports.
|
|
2
2
|
from datetime import datetime, timedelta
|
|
3
|
+
from pprint import pformat
|
|
3
4
|
import time
|
|
4
5
|
|
|
5
6
|
# our package imports.
|
|
@@ -221,6 +222,31 @@ class SpotifyAuthToken:
|
|
|
221
222
|
return self._TokenType
|
|
222
223
|
|
|
223
224
|
|
|
225
|
+
def ToDictionary(self) -> dict:
|
|
226
|
+
"""
|
|
227
|
+
Returns a dictionary representation of the class.
|
|
228
|
+
"""
|
|
229
|
+
result:dict = \
|
|
230
|
+
{
|
|
231
|
+
'access_token': self._AccessToken,
|
|
232
|
+
'expires_at': self._ExpiresAt,
|
|
233
|
+
'expires_in': self._ExpiresIn,
|
|
234
|
+
'refresh_token': self._RefreshToken,
|
|
235
|
+
'scope': self.Scopes,
|
|
236
|
+
'token_type': self._TokenType,
|
|
237
|
+
}
|
|
238
|
+
return result
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def ToDictionaryString(self) -> dict:
|
|
242
|
+
"""
|
|
243
|
+
Returns a dictionary string representation of the class.
|
|
244
|
+
"""
|
|
245
|
+
oDict:dict = self.ToDictionary()
|
|
246
|
+
result:str = pformat(oDict,indent=2,width=132,sort_dicts=False)
|
|
247
|
+
return result
|
|
248
|
+
|
|
249
|
+
|
|
224
250
|
def ToString(self) -> str:
|
|
225
251
|
"""
|
|
226
252
|
Returns a displayable string representation of the class.
|