spotifywebapiPython 1.0.242__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.
- spotifywebapipython/__init__.py +35 -0
- spotifywebapipython/const.py +114 -0
- spotifywebapipython/docs/__init__.html +2156 -0
- spotifywebapipython/docs/codecopy.js +70 -0
- spotifywebapipython/docs/index.html +514 -0
- spotifywebapipython/docs/search.js +46 -0
- spotifywebapipython/docs/spotifywebapipython/const.html +636 -0
- spotifywebapipython/docs/spotifywebapipython/saappmessages.html +540 -0
- spotifywebapipython/docs/spotifywebapipython/sautils.html +620 -0
- spotifywebapipython/docs/spotifywebapipython/spotifyapierror.html +341 -0
- spotifywebapipython/docs/spotifywebapipython/spotifyapimessage.html +598 -0
- spotifywebapipython/docs/spotifywebapipython/spotifyauthtoken.html +612 -0
- spotifywebapipython/docs/spotifywebapipython/spotifyclient.html +16557 -0
- spotifywebapipython/docs/spotifywebapipython/spotifydiscovery.html +518 -0
- spotifywebapipython/docs/spotifywebapipython/spotifywebapiauthenticationerror.html +421 -0
- spotifywebapipython/docs/spotifywebapipython/spotifywebapierror.html +400 -0
- spotifywebapipython/docs/spotifywebapipython/spotifywebplayertoken.html +635 -0
- spotifywebapipython/docs/spotifywebapipython.ico +0 -0
- spotifywebapipython/docs/spotifywebapipython_OLD.ico +0 -0
- spotifywebapipython/models/__init__.py +121 -0
- spotifywebapipython/models/album.py +170 -0
- spotifywebapipython/models/albumpagesaved.py +85 -0
- spotifywebapipython/models/albumpagesimplified.py +88 -0
- spotifywebapipython/models/albumsaved.py +102 -0
- spotifywebapipython/models/albumsimplified.py +308 -0
- spotifywebapipython/models/artist.py +146 -0
- spotifywebapipython/models/artistinfo.py +282 -0
- spotifywebapipython/models/artistinfotourevent.py +127 -0
- spotifywebapipython/models/artistpage.py +70 -0
- spotifywebapipython/models/artistsimplified.py +163 -0
- spotifywebapipython/models/audiobook.py +87 -0
- spotifywebapipython/models/audiobookpagesimplified.py +88 -0
- spotifywebapipython/models/audiobooksimplified.py +362 -0
- spotifywebapipython/models/audiofeatures.py +333 -0
- spotifywebapipython/models/author.py +67 -0
- spotifywebapipython/models/category.py +168 -0
- spotifywebapipython/models/categorypage.py +70 -0
- spotifywebapipython/models/chapter.py +84 -0
- spotifywebapipython/models/chapterpagesimplified.py +88 -0
- spotifywebapipython/models/chaptersimplified.py +387 -0
- spotifywebapipython/models/context.py +113 -0
- spotifywebapipython/models/copyright.py +79 -0
- spotifywebapipython/models/device.py +301 -0
- spotifywebapipython/models/episode.py +84 -0
- spotifywebapipython/models/episodepagesaved.py +85 -0
- spotifywebapipython/models/episodepagesimplified.py +88 -0
- spotifywebapipython/models/episodesaved.py +99 -0
- spotifywebapipython/models/episodesimplified.py +370 -0
- spotifywebapipython/models/explicitcontent.py +80 -0
- spotifywebapipython/models/externalids.py +91 -0
- spotifywebapipython/models/externalurls.py +104 -0
- spotifywebapipython/models/followers.py +81 -0
- spotifywebapipython/models/imageobject.py +193 -0
- spotifywebapipython/models/imagevibrantcolors.py +182 -0
- spotifywebapipython/models/linkedfrom.py +165 -0
- spotifywebapipython/models/narrator.py +67 -0
- spotifywebapipython/models/owner.py +162 -0
- spotifywebapipython/models/pageobject.py +343 -0
- spotifywebapipython/models/playeractions.py +196 -0
- spotifywebapipython/models/playerlastplayedinfo.py +334 -0
- spotifywebapipython/models/playerplaystate.py +467 -0
- spotifywebapipython/models/playerqueueinfo.py +207 -0
- spotifywebapipython/models/playhistory.py +136 -0
- spotifywebapipython/models/playhistorypage.py +85 -0
- spotifywebapipython/models/playlist.py +125 -0
- spotifywebapipython/models/playlistpage.py +85 -0
- spotifywebapipython/models/playlistpagesimplified.py +107 -0
- spotifywebapipython/models/playlistsimplified.py +334 -0
- spotifywebapipython/models/playlisttrack.py +143 -0
- spotifywebapipython/models/playlisttracksummary.py +80 -0
- spotifywebapipython/models/recommendationseed.py +135 -0
- spotifywebapipython/models/restrictions.py +86 -0
- spotifywebapipython/models/resumepoint.py +79 -0
- spotifywebapipython/models/searchresponse.py +391 -0
- spotifywebapipython/models/searchresultbase.py +299 -0
- spotifywebapipython/models/show.py +87 -0
- spotifywebapipython/models/showpagesaved.py +85 -0
- spotifywebapipython/models/showpagesimplified.py +88 -0
- spotifywebapipython/models/showsaved.py +99 -0
- spotifywebapipython/models/showsimplified.py +320 -0
- spotifywebapipython/models/spotifyconnectdevice.py +321 -0
- spotifywebapipython/models/spotifyconnectdevices.py +441 -0
- spotifywebapipython/models/track.py +142 -0
- spotifywebapipython/models/trackpage.py +110 -0
- spotifywebapipython/models/trackpagesaved.py +85 -0
- spotifywebapipython/models/trackpagesimplified.py +88 -0
- spotifywebapipython/models/trackrecommendations.py +88 -0
- spotifywebapipython/models/tracksaved.py +99 -0
- spotifywebapipython/models/tracksimplified.py +398 -0
- spotifywebapipython/models/userprofile.py +184 -0
- spotifywebapipython/models/userprofilesimplified.py +206 -0
- spotifywebapipython/models/zeroconfdiscoveryresult.py +592 -0
- spotifywebapipython/models/zeroconfproperty.py +93 -0
- spotifywebapipython/oauthcli/__init__.py +7 -0
- spotifywebapipython/oauthcli/authclient.py +1229 -0
- spotifywebapipython/saappmessages.py +86 -0
- spotifywebapipython/sautils.py +285 -0
- spotifywebapipython/spotifyapierror.py +66 -0
- spotifywebapipython/spotifyapimessage.py +269 -0
- spotifywebapipython/spotifyauthtoken.py +264 -0
- spotifywebapipython/spotifyclient.py +17885 -0
- spotifywebapipython/spotifyconnect/__init__.py +19 -0
- spotifywebapipython/spotifyconnect/spotifyconnectdeviceeventargs.py +67 -0
- spotifywebapipython/spotifyconnect/spotifyconnectdirectorytask.py +2564 -0
- spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastapptask.py +467 -0
- spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastcontroller.py +701 -0
- spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastlistener.py +359 -0
- spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastmultizonelistener.py +212 -0
- spotifywebapipython/spotifyconnect/spotifyconnectzeroconfexceptions.py +20 -0
- spotifywebapipython/spotifyconnect/spotifyconnectzeroconflistener.py +335 -0
- spotifywebapipython/spotifydiscovery.py +473 -0
- spotifywebapipython/spotifymediatypes.py +53 -0
- spotifywebapipython/spotifywebapiauthenticationerror.py +122 -0
- spotifywebapipython/spotifywebapierror.py +106 -0
- spotifywebapipython/spotifywebplayertoken.py +743 -0
- spotifywebapipython/vibrant/__init__.py +17 -0
- spotifywebapipython/vibrant/generator.py +261 -0
- spotifywebapipython/vibrant/image.py +100 -0
- spotifywebapipython/vibrant/main.py +34 -0
- spotifywebapipython/vibrant/models.py +93 -0
- spotifywebapipython/vibrant/utils.py +56 -0
- 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 +1327 -0
- spotifywebapipython/zeroconfapi/zeroconfgetinfo.py +572 -0
- spotifywebapipython/zeroconfapi/zeroconfgetinfoalias.py +109 -0
- spotifywebapipython/zeroconfapi/zeroconfgetinfodrmmediaformat.py +90 -0
- spotifywebapipython/zeroconfapi/zeroconfresponse.py +195 -0
- spotifywebapipython-1.0.242.dist-info/LICENSE +22 -0
- spotifywebapipython-1.0.242.dist-info/METADATA +219 -0
- spotifywebapipython-1.0.242.dist-info/RECORD +138 -0
- spotifywebapipython-1.0.242.dist-info/WHEEL +5 -0
- spotifywebapipython-1.0.242.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# external package imports.
|
|
2
|
+
# none
|
|
3
|
+
|
|
4
|
+
# our package imports.
|
|
5
|
+
from .sautils import export
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@export
|
|
9
|
+
class SAAppMessages:
|
|
10
|
+
"""
|
|
11
|
+
A strongly-typed resource class, for looking up localized strings, etc.
|
|
12
|
+
|
|
13
|
+
Threadsafety:
|
|
14
|
+
This class is fully thread-safe.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
UNHANDLED_EXCEPTION:str = "SAM0001E - An unhandled exception occured while processing method \"{0}\".\n{1}"
|
|
18
|
+
"""
|
|
19
|
+
SAM0001E - An unhandled exception occured while processing method \"{0}\".
|
|
20
|
+
{1}
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
ARGUMENT_REQUIRED_ERROR:str = "SAM0002E - The \"%s\" method \"%s\" argument is required, and cannot be null or None."
|
|
24
|
+
"""
|
|
25
|
+
SAM0002E - The \"%s\" method \"%s\" argument is required, and cannot be null or None.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
ARGUMENT_TYPE_ERROR:str = "SAM0003E - The \"%s\" method \"%s\" argument must be of type \"%s\"; the \"%s\" type is not supported for this argument."
|
|
29
|
+
"""
|
|
30
|
+
SAM0003E - The \"%s\" method \"%s\" argument must be of type \"%s\"; the \"%s\" type is not supported for this argument.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
MSG_SPOTIFY_WEB_API_ERROR:str = "SAM1001E - Spotify Web API returned an error status while processing the \"{methodname}\" method.\nStatus: {status} - {httpreason}\nMessage: \"{message}\""
|
|
34
|
+
"""
|
|
35
|
+
SAM1001E - Spotify Web API returned an error status while processing the \"{methodname}\" method.\nStatus: {status} - {httpreason}\nMessage: \"{message}\"
|
|
36
|
+
"""
|
|
37
|
+
|
|
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
|
+
"""
|
|
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
|
+
"""
|
|
42
|
+
|
|
43
|
+
MSG_SPOTIFY_WEB_API_RETRY_REQUEST_DELAY:str = "Delaying for %s seconds before retry to allow Spotify Gateway to become available"
|
|
44
|
+
"""
|
|
45
|
+
Delaying for %s seconds before retry to allow Spotify Gateway to become available
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
MSG_SPOTIFY_WEB_API_RETRY_RESPONSE_STATUS:str = "Spotify Web API request returned with a %s status (%s); request will be retried"
|
|
49
|
+
"""
|
|
50
|
+
Spotify Web API request returned with a %s status (%s); request will be retried
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
MSG_SPOTIFY_WEB_API_RETRY_TIMEOUT:str = "Timed out waiting for Spotify Gateway to become available; gave up after %f seconds from initial request."
|
|
54
|
+
"""
|
|
55
|
+
Timed out waiting for Spotify Gateway to become available; gave up after %f seconds from initial request.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
MSG_SPOTIFY_ZEROCONF_API_ERROR:str = "SAM1003E - Spotify ZeroConf API returned an error status while processing the \"{methodname}\" method.\nStatus: {status} - {httpreason}\nMessage: \"{message}\""
|
|
59
|
+
"""
|
|
60
|
+
SAM1001E - Spotify ZeroConf API returned an error status while processing the \"{methodname}\" method.\nStatus: {status} - {httpreason}\nMessage: \"{message}\"
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
MSG_SPOTIFY_ACCOUNT_REQUIRED_FOR_NOWPLAYING:str = "SAM1004E - A Spotify Free or Premium level membership is required to get currently playing item information."
|
|
64
|
+
"""
|
|
65
|
+
SAM1004E - A Spotify Premium level membership is required to get currently playing item information.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
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."
|
|
69
|
+
"""
|
|
70
|
+
SAM1005E - The \"%s\" configuration option was not supplied, which is required in order to reactivate a Spotify Connect device.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
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."
|
|
74
|
+
"""
|
|
75
|
+
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.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
MSG_SPOTIFY_WEB_PLAYER_TOKEN_INUSE:str = "Spotify Web Player authorization access token will be used for accessing Spotify Web API endpoint."
|
|
79
|
+
"""
|
|
80
|
+
Spotify Web Player authorization access token will be used for accessing Spotify Web API endpoint.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
MSG_SPOTIFY_WEB_PLAYER_TOKEN_REFRESHED:str = "Converting Spotify Web Player cookie credentials to an access token for loginId \"%s\"."
|
|
84
|
+
"""
|
|
85
|
+
Converting Spotify Web Player cookie credentials to an access token for loginId \"%s\".
|
|
86
|
+
"""
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# external package imports.
|
|
2
|
+
from datetime import datetime, timedelta, timezone
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Utility module of helper functions.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
SONOS_NOT_IMPLEMENTED:str = 'NOT_IMPLEMENTED'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def GetUnixTimestampMSFromUtcNow(days:int=0,
|
|
13
|
+
hours:int=0,
|
|
14
|
+
minutes:int=0,
|
|
15
|
+
seconds:int=0,
|
|
16
|
+
) -> int:
|
|
17
|
+
"""
|
|
18
|
+
Returns a Unix millisecond timestamp value from the current utc datetime for the
|
|
19
|
+
difference specified by input arguments.
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
# get current utc datetime (in a timezone aware datetime instance).
|
|
23
|
+
dtUtc:datetime = datetime.now(timezone.utc)
|
|
24
|
+
|
|
25
|
+
# get offset timedelta.
|
|
26
|
+
dtUtcOffset = (dtUtc + timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds))
|
|
27
|
+
|
|
28
|
+
# convert to unix timestamp (# of seconds since epoch date of 1970/01/01).
|
|
29
|
+
dtUtcUnixTS:float = dtUtcOffset.timestamp()
|
|
30
|
+
|
|
31
|
+
# convert to milliseconds.
|
|
32
|
+
dtUtcUnixTSMS:int = int(dtUtcUnixTS * 1000)
|
|
33
|
+
|
|
34
|
+
# return unix milliseconds timestamp value.
|
|
35
|
+
return dtUtcUnixTSMS
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def passwordMaskString(inputObj:str) -> str:
|
|
39
|
+
"""
|
|
40
|
+
Checks a string for a password value and masks the value so that the password is not displayed
|
|
41
|
+
in a trace file.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
inputObj (str):
|
|
45
|
+
String object to check.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
A copy of the `inputObj` value with password masked.
|
|
49
|
+
"""
|
|
50
|
+
# if input is null then don't bother.
|
|
51
|
+
if (inputObj is None):
|
|
52
|
+
return inputObj
|
|
53
|
+
|
|
54
|
+
# create a new value.
|
|
55
|
+
result:str = ''.ljust(len(inputObj), '*')
|
|
56
|
+
|
|
57
|
+
return result
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def mediaPositionHMS_fromSeconds(
|
|
61
|
+
position:int,
|
|
62
|
+
) -> float:
|
|
63
|
+
"""
|
|
64
|
+
Converts an integer media position value from seconds to a string value in H:MM:SS format.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
position (int):
|
|
68
|
+
The position value (as specified in seconds) to convert.
|
|
69
|
+
"""
|
|
70
|
+
result:str = '0:00:00'
|
|
71
|
+
|
|
72
|
+
# validations.
|
|
73
|
+
if (isinstance(position, float)):
|
|
74
|
+
position = int(position)
|
|
75
|
+
if (position is None) or (position < 1):
|
|
76
|
+
return result
|
|
77
|
+
|
|
78
|
+
# convert seconds to H:MM:SS string format.
|
|
79
|
+
nSeconds = int(position)
|
|
80
|
+
mm, ss = divmod(nSeconds, 60) # get minutes and seconds first
|
|
81
|
+
hh, mm= divmod(mm, 60) # get hours next
|
|
82
|
+
result = "%d:%02d:%02d" % (hh, mm, ss) # format to hh:mm:ss
|
|
83
|
+
|
|
84
|
+
# return result to caller.
|
|
85
|
+
return result
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def mediaPositionHMS_toSeconds(
|
|
89
|
+
timestr:str
|
|
90
|
+
) -> int:
|
|
91
|
+
"""
|
|
92
|
+
Convert an "HH:MM:SS" string value to total number of seconds.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
timestr (int):
|
|
96
|
+
The position value (as specified in H:MM:SS) to convert.
|
|
97
|
+
"""
|
|
98
|
+
if (timestr is None) or (timestr == '') or (timestr == SONOS_NOT_IMPLEMENTED):
|
|
99
|
+
return 0
|
|
100
|
+
|
|
101
|
+
seconds= 0
|
|
102
|
+
for part in timestr.split(':'):
|
|
103
|
+
seconds= seconds*60 + int(part, 10)
|
|
104
|
+
return seconds
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def static_init(cls):
|
|
108
|
+
"""
|
|
109
|
+
Define the decorator used to call an initializer for a class with all static methods.
|
|
110
|
+
This allows static variables to be initialized one time for the class.
|
|
111
|
+
"""
|
|
112
|
+
if getattr(cls, "static_init", None):
|
|
113
|
+
cls.static_init()
|
|
114
|
+
return cls
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def export(fn):
|
|
118
|
+
"""
|
|
119
|
+
Define the decorator used to modify a module's "__all__" variable.
|
|
120
|
+
This avoids us having to manually modify a module's "__all__" variable when adding new classes.
|
|
121
|
+
"""
|
|
122
|
+
mod = sys.modules[fn.__module__]
|
|
123
|
+
if hasattr(mod, '__all__'):
|
|
124
|
+
mod.__all__.append(fn.__name__)
|
|
125
|
+
else:
|
|
126
|
+
mod.__all__ = [fn.__name__]
|
|
127
|
+
|
|
128
|
+
return fn
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def validateDelay(
|
|
132
|
+
delay:float,
|
|
133
|
+
default:float=0.5,
|
|
134
|
+
maxDelay:float=10
|
|
135
|
+
) -> float:
|
|
136
|
+
"""
|
|
137
|
+
Validates a delay value.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
delay (int):
|
|
141
|
+
The delay value to validate.
|
|
142
|
+
default (int):
|
|
143
|
+
The default delay value to set if the user-input delay is not valid.
|
|
144
|
+
maxDelay (int):
|
|
145
|
+
The maximum delay value allowed.
|
|
146
|
+
Default is 10.
|
|
147
|
+
"""
|
|
148
|
+
if (isinstance(delay,int)):
|
|
149
|
+
delay = float(delay)
|
|
150
|
+
|
|
151
|
+
if (not isinstance(delay,float)) or (delay < 0):
|
|
152
|
+
result = default
|
|
153
|
+
elif (delay > maxDelay):
|
|
154
|
+
result = maxDelay
|
|
155
|
+
else:
|
|
156
|
+
result = delay
|
|
157
|
+
|
|
158
|
+
return result
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _xmlGetInnerText(elmNode, default:str=None) -> str:
|
|
162
|
+
"""
|
|
163
|
+
Finds the specified xml node inner text from the root Element object and returns its value
|
|
164
|
+
as a string. The default argument is returned if the xml node name is not found.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
root (xml.etree.ElementTree.Element)
|
|
168
|
+
The Element object to search.
|
|
169
|
+
default (str):
|
|
170
|
+
A default value to assign if the xml node does not contain innerText.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
The string value if innerText was found;
|
|
174
|
+
otherwise, the default value.
|
|
175
|
+
"""
|
|
176
|
+
if elmNode is None:
|
|
177
|
+
return default
|
|
178
|
+
|
|
179
|
+
# get text of all child nodes.
|
|
180
|
+
innertext:str = (elmNode.text or '') + ''.join(_xmlGetInnerText(e) for e in elmNode) + (elmNode.tail or '')
|
|
181
|
+
|
|
182
|
+
# did we find anything? if not, then return default.
|
|
183
|
+
if innertext is None:
|
|
184
|
+
return default
|
|
185
|
+
|
|
186
|
+
# return found inner text.
|
|
187
|
+
return innertext
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class Event:
|
|
191
|
+
"""
|
|
192
|
+
C# like event processing in Python3.
|
|
193
|
+
|
|
194
|
+
<details>
|
|
195
|
+
<summary>View Sample Code</summary>
|
|
196
|
+
```python
|
|
197
|
+
# Define the class that will be raising events:
|
|
198
|
+
class MyFileWatcher:
|
|
199
|
+
def __init__(self):
|
|
200
|
+
self.fileChanged = Event() # define event
|
|
201
|
+
|
|
202
|
+
def watchFiles(self):
|
|
203
|
+
source_path = "foo"
|
|
204
|
+
self.fileChanged(source_path) # fire event
|
|
205
|
+
|
|
206
|
+
def log_file_change(source_path): # event handler 1
|
|
207
|
+
print "%r changed." % (source_path,)
|
|
208
|
+
|
|
209
|
+
def log_file_change2(source_path): # event handler 2
|
|
210
|
+
print "%r changed!" % (source_path,)
|
|
211
|
+
|
|
212
|
+
# Define the code that will be handling raised events.
|
|
213
|
+
watcher = MyFileWatcher()
|
|
214
|
+
watcher.fileChanged += log_file_change2
|
|
215
|
+
watcher.fileChanged += log_file_change
|
|
216
|
+
watcher.fileChanged -= log_file_change2
|
|
217
|
+
watcher.watchFiles()
|
|
218
|
+
```
|
|
219
|
+
</details>
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
def __init__(self, *args) -> None:
|
|
223
|
+
"""
|
|
224
|
+
Initializes a new instance of the class.
|
|
225
|
+
"""
|
|
226
|
+
self.handlers = set()
|
|
227
|
+
|
|
228
|
+
def fire(self, *args, **kargs):
|
|
229
|
+
"""
|
|
230
|
+
Calls (i.e. "fires") all method handlers defined for this event.
|
|
231
|
+
|
|
232
|
+
Exceptions will be ignored for individual handlers.
|
|
233
|
+
"""
|
|
234
|
+
for handler in self.handlers:
|
|
235
|
+
try:
|
|
236
|
+
handler(*args, **kargs)
|
|
237
|
+
except:
|
|
238
|
+
pass
|
|
239
|
+
|
|
240
|
+
def getHandlerCount(self):
|
|
241
|
+
"""
|
|
242
|
+
Returns the number of method handlers defined for this event.
|
|
243
|
+
"""
|
|
244
|
+
return len(self.handlers)
|
|
245
|
+
|
|
246
|
+
def handle(self, handler):
|
|
247
|
+
"""
|
|
248
|
+
Adds a method handler for this event.
|
|
249
|
+
"""
|
|
250
|
+
self.handlers.add(handler)
|
|
251
|
+
return self
|
|
252
|
+
|
|
253
|
+
def unhandle(self, handler):
|
|
254
|
+
"""
|
|
255
|
+
Removes the specified method handler for this event.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
handler (object):
|
|
259
|
+
The method handler to remove.
|
|
260
|
+
|
|
261
|
+
This method will not throw an exception.
|
|
262
|
+
"""
|
|
263
|
+
try:
|
|
264
|
+
self.handlers.remove(handler)
|
|
265
|
+
except:
|
|
266
|
+
pass # ignore exceptions.
|
|
267
|
+
return self
|
|
268
|
+
|
|
269
|
+
def unhandle_all(self):
|
|
270
|
+
"""
|
|
271
|
+
Removes all method handlers (if any) for this event.
|
|
272
|
+
|
|
273
|
+
This method will not throw an exception.
|
|
274
|
+
"""
|
|
275
|
+
try:
|
|
276
|
+
self.handlers.clear()
|
|
277
|
+
except:
|
|
278
|
+
pass # ignore exceptions.
|
|
279
|
+
return self
|
|
280
|
+
|
|
281
|
+
# alias method definitions.
|
|
282
|
+
__iadd__ = handle
|
|
283
|
+
__isub__ = unhandle
|
|
284
|
+
__call__ = fire
|
|
285
|
+
__len__ = getHandlerCount
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# external package imports.
|
|
2
|
+
from smartinspectpython.sisession import SISession
|
|
3
|
+
|
|
4
|
+
# our package imports.
|
|
5
|
+
from .sautils import export
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@export
|
|
9
|
+
class SpotifyApiError(Exception):
|
|
10
|
+
"""
|
|
11
|
+
Exception thrown when a SpotifyApi error occurs.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, message:str, ex:Exception=None, logsi:SISession=None) -> None:
|
|
15
|
+
"""
|
|
16
|
+
Initializes a new instance of the class.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
message (str):
|
|
20
|
+
A short description of the cause of the error.
|
|
21
|
+
ex (Exception):
|
|
22
|
+
Inner exception that caused the current exception.
|
|
23
|
+
Default is None.
|
|
24
|
+
logsi (SISession):
|
|
25
|
+
Trace session object that this exception will be logged to, or null to bypass trace logging.
|
|
26
|
+
Default is None.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
# initialize base class.
|
|
30
|
+
super().__init__(message)
|
|
31
|
+
|
|
32
|
+
# initialize class instance.
|
|
33
|
+
self._Message:str = message
|
|
34
|
+
|
|
35
|
+
# trace.
|
|
36
|
+
if logsi is not None:
|
|
37
|
+
if (isinstance(logsi, SISession)):
|
|
38
|
+
if ex is None:
|
|
39
|
+
logsi.LogError(self.ToString())
|
|
40
|
+
else:
|
|
41
|
+
logsi.LogException(message, ex, logToSystemLogger=False)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def __repr__(self) -> str:
|
|
45
|
+
return self.ToString()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def __str__(self) -> str:
|
|
49
|
+
return self.ToString()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def Message(self) -> str:
|
|
54
|
+
"""
|
|
55
|
+
A description of the cause of the error.
|
|
56
|
+
"""
|
|
57
|
+
return self._Message
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def ToString(self) -> str:
|
|
61
|
+
"""
|
|
62
|
+
Returns a displayable string representation of the class.
|
|
63
|
+
"""
|
|
64
|
+
msg:str = 'SpotifyApiError:'
|
|
65
|
+
if self._Message is not None: msg = '%s %s' % (msg, str(self._Message))
|
|
66
|
+
return msg
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# external package imports.
|
|
2
|
+
from typing import Mapping
|
|
3
|
+
|
|
4
|
+
# our package imports.
|
|
5
|
+
from .sautils import export
|
|
6
|
+
|
|
7
|
+
@export
|
|
8
|
+
class SpotifyApiMessage:
|
|
9
|
+
"""
|
|
10
|
+
A class representing an exchange object between a client and the Spotify Web API.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, methodName:str, uri:str, urlParameters:dict=None, requestData:dict=None, requestHeaders:dict=None) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Initializes a new instance of the class.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
methodName (str):
|
|
19
|
+
Name of the client method that executed the request.
|
|
20
|
+
uri (str):
|
|
21
|
+
Target uri which should be queried.
|
|
22
|
+
urlParameters (dict):
|
|
23
|
+
Parameter values to be placed into the http request url, if the specified
|
|
24
|
+
uri service requires it; otherwise, None.
|
|
25
|
+
requestData (dict):
|
|
26
|
+
Request data that is to be placed into the http request body, if the specified
|
|
27
|
+
uri service requires it; otherwise, None.
|
|
28
|
+
requestHeaders (dict):
|
|
29
|
+
Request header that is to be placed into the http request headers, if the specified
|
|
30
|
+
uri service requires it; otherwise, None.
|
|
31
|
+
"""
|
|
32
|
+
# validation.
|
|
33
|
+
if requestData is None:
|
|
34
|
+
requestData = {}
|
|
35
|
+
if requestHeaders is None:
|
|
36
|
+
requestHeaders = {}
|
|
37
|
+
if urlParameters is None:
|
|
38
|
+
urlParameters = {}
|
|
39
|
+
|
|
40
|
+
self._MethodName:str = methodName
|
|
41
|
+
self._RequestData:dict = requestData
|
|
42
|
+
self._RequestHeaders:Mapping[str, str] = requestHeaders
|
|
43
|
+
self._RequestJson:dict = None
|
|
44
|
+
self._ResponseData = {}
|
|
45
|
+
self._IgnoreResponseErrors:bool = False
|
|
46
|
+
self._IsRequestDataEncoded:bool = False
|
|
47
|
+
self._Uri:str = uri
|
|
48
|
+
self._UrlParameters:dict = urlParameters
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def __repr__(self) -> str:
|
|
52
|
+
return self.ToString()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def __str__(self) -> str:
|
|
56
|
+
return self.ToString()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def HasRequestData(self) -> bool:
|
|
61
|
+
"""
|
|
62
|
+
Returns True if request data is present, and should be supplied as part of the
|
|
63
|
+
http request body; otherwise, False.
|
|
64
|
+
"""
|
|
65
|
+
return self._RequestData is not None and len(self._RequestData) != 0
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def HasRequestHeaders(self) -> bool:
|
|
70
|
+
"""
|
|
71
|
+
Returns True if request headers are present, and should be supplied as part of the
|
|
72
|
+
http request headers; otherwise, False.
|
|
73
|
+
"""
|
|
74
|
+
return self._RequestHeaders is not None and len(self._RequestHeaders) != 0
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def HasRequestJson(self) -> bool:
|
|
79
|
+
"""
|
|
80
|
+
Returns True if request json is present, and should be supplied as part of the
|
|
81
|
+
http request body; otherwise, False.
|
|
82
|
+
"""
|
|
83
|
+
return self._RequestJson is not None and len(self._RequestJson) != 0
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def HasUrlParameters(self) -> bool:
|
|
88
|
+
"""
|
|
89
|
+
Returns True if url parameters are present, and should be supplied as part of the
|
|
90
|
+
http request url; otherwise, False.
|
|
91
|
+
"""
|
|
92
|
+
return self._UrlParameters is not None and len(self._UrlParameters) != 0
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def HasResponseData(self) -> bool:
|
|
97
|
+
"""
|
|
98
|
+
Returns True if data was returned with the response; otherwise, False.
|
|
99
|
+
"""
|
|
100
|
+
return self._RequestData is not None
|
|
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
|
+
|
|
119
|
+
@property
|
|
120
|
+
def IsRequestDataEncoded(self) -> bool:
|
|
121
|
+
"""
|
|
122
|
+
Indicates if the `RequestData` property is already encoded (True) or not (False).
|
|
123
|
+
"""
|
|
124
|
+
return self._IsRequestDataEncoded
|
|
125
|
+
|
|
126
|
+
@IsRequestDataEncoded.setter
|
|
127
|
+
def IsRequestDataEncoded(self, value:bool):
|
|
128
|
+
"""
|
|
129
|
+
Sets the IsRequestDataEncoded property value.
|
|
130
|
+
"""
|
|
131
|
+
self._IsRequestDataEncoded = value
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def IsResponseEmpty(self) -> bool:
|
|
136
|
+
"""
|
|
137
|
+
Returns True if the response contains no data (e.g. a pass / fail response)
|
|
138
|
+
and is not null; otherwise, False.
|
|
139
|
+
"""
|
|
140
|
+
return self._ResponseData is not None and len(self._ResponseData) == 0
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def IsSimpleResponse(self) -> bool:
|
|
145
|
+
"""
|
|
146
|
+
Returns True if the response contains no data (e.g. a pass / fail response);
|
|
147
|
+
otherwise, False if the response requires further processing.
|
|
148
|
+
"""
|
|
149
|
+
return self._ResponseData is not None and len(self._ResponseData) != 0
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def MethodName(self) -> str:
|
|
154
|
+
"""
|
|
155
|
+
The client method that executed the request.
|
|
156
|
+
|
|
157
|
+
Example: `GetArtist`.
|
|
158
|
+
"""
|
|
159
|
+
return self._MethodName
|
|
160
|
+
|
|
161
|
+
@MethodName.setter
|
|
162
|
+
def MethodName(self, value:str):
|
|
163
|
+
"""
|
|
164
|
+
Sets the MethodName property value.
|
|
165
|
+
"""
|
|
166
|
+
if value is not None:
|
|
167
|
+
if isinstance(value, str):
|
|
168
|
+
self._MethodName = value
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def RequestData(self) -> dict:
|
|
173
|
+
"""
|
|
174
|
+
Request data that is to be placed into the http request body, if the specified
|
|
175
|
+
uri service requires it.
|
|
176
|
+
"""
|
|
177
|
+
return self._RequestData
|
|
178
|
+
|
|
179
|
+
@RequestData.setter
|
|
180
|
+
def RequestData(self, value:dict):
|
|
181
|
+
"""
|
|
182
|
+
Sets the RequestData property value.
|
|
183
|
+
"""
|
|
184
|
+
if value is not None:
|
|
185
|
+
self._RequestData = value
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def RequestHeaders(self) -> Mapping[str, str]:
|
|
190
|
+
"""
|
|
191
|
+
Request header data that is to be sent with the http request body, if the specified
|
|
192
|
+
uri service requires it.
|
|
193
|
+
"""
|
|
194
|
+
return self._RequestHeaders
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def RequestJson(self) -> dict:
|
|
199
|
+
"""
|
|
200
|
+
Request data that is to be placed into the http request body, if the specified
|
|
201
|
+
uri service requires it.
|
|
202
|
+
"""
|
|
203
|
+
return self._RequestJson
|
|
204
|
+
|
|
205
|
+
@RequestJson.setter
|
|
206
|
+
def RequestJson(self, value:dict):
|
|
207
|
+
"""
|
|
208
|
+
Sets the RequestJson property value.
|
|
209
|
+
"""
|
|
210
|
+
if value is not None:
|
|
211
|
+
if isinstance(value, dict):
|
|
212
|
+
self._RequestJson = value
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def ResponseData(self) -> dict:
|
|
217
|
+
"""
|
|
218
|
+
Response data returned from the server, if the specified uri returned
|
|
219
|
+
a response that contains data.
|
|
220
|
+
"""
|
|
221
|
+
return self._ResponseData
|
|
222
|
+
|
|
223
|
+
@ResponseData.setter
|
|
224
|
+
def ResponseData(self, value:dict):
|
|
225
|
+
"""
|
|
226
|
+
Sets the ResponseData property value.
|
|
227
|
+
"""
|
|
228
|
+
if value is not None:
|
|
229
|
+
# some Spotify responses can contain non-dictionary data!
|
|
230
|
+
#if isinstance(value, dict):
|
|
231
|
+
self._ResponseData = value
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def Uri(self) -> str:
|
|
236
|
+
"""
|
|
237
|
+
Target uri which should be queried.
|
|
238
|
+
"""
|
|
239
|
+
return self._Uri
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@property
|
|
243
|
+
def UrlParameters(self) -> dict:
|
|
244
|
+
"""
|
|
245
|
+
Parameter values to be placed into the http request url, if the specified
|
|
246
|
+
uri service requires it.
|
|
247
|
+
"""
|
|
248
|
+
return self._UrlParameters
|
|
249
|
+
|
|
250
|
+
@UrlParameters.setter
|
|
251
|
+
def UrlParameters(self, value:dict):
|
|
252
|
+
"""
|
|
253
|
+
Sets the UrlParameters property value.
|
|
254
|
+
"""
|
|
255
|
+
if value is not None:
|
|
256
|
+
if isinstance(value, dict):
|
|
257
|
+
self._UrlParameters = value
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def ToString(self) -> str:
|
|
261
|
+
"""
|
|
262
|
+
Returns a displayable string representation of the class.
|
|
263
|
+
"""
|
|
264
|
+
msg:str = 'SpotifyApiMessage:'
|
|
265
|
+
msg = '%s uri="%s"' % (msg, str(self._Uri))
|
|
266
|
+
if self.HasRequestData: msg = '%s RequestData="%s"' % (msg, self._RequestData)
|
|
267
|
+
if self.HasRequestJson: msg = '%s RequestJson="%s"' % (msg, self._RequestJson)
|
|
268
|
+
if self.IsSimpleResponse: msg = '%s response="%s"' % (msg, str(self.IsSimpleResponse).lower())
|
|
269
|
+
return msg
|