diff options
author | Mike Crute <mike@crute.us> | 2020-06-08 18:20:57 +0000 |
---|---|---|
committer | Mike Crute <mike@crute.us> | 2020-06-08 18:20:57 +0000 |
commit | 3681a9b616212656462efa0a9c78722f90cca442 (patch) | |
tree | 0199712af2f8abc5635d57d7e18ec63fe6d8b58e | |
parent | 13b779eafd66a37ad5c50fa9be2bbc7962a73fce (diff) | |
download | pydora-3681a9b616212656462efa0a9c78722f90cca442.tar.bz2 pydora-3681a9b616212656462efa0a9c78722f90cca442.tar.xz pydora-3681a9b616212656462efa0a9c78722f90cca442.zip |
Use black for formatting
I don't particularly like the black formatter (does anybody, really?)
but it's guaranteed to be consistent so it should prevent nitpicking
style.
-rw-r--r-- | pandora/client.py | 215 | ||||
-rw-r--r-- | pandora/clientbuilder.py | 42 | ||||
-rw-r--r-- | pandora/errors.py | 9 | ||||
-rw-r--r-- | pandora/models/_base.py | 12 | ||||
-rw-r--r-- | pandora/models/ad.py | 5 | ||||
-rw-r--r-- | pandora/models/playlist.py | 36 | ||||
-rw-r--r-- | pandora/models/search.py | 15 | ||||
-rw-r--r-- | pandora/models/station.py | 9 | ||||
-rw-r--r-- | pandora/transport.py | 35 | ||||
-rw-r--r-- | pydora/audio_backend.py | 7 | ||||
-rw-r--r-- | pydora/configure.py | 15 | ||||
-rw-r--r-- | pydora/player.py | 56 | ||||
-rw-r--r-- | pydora/utils.py | 5 | ||||
-rwxr-xr-x | setup.py | 41 | ||||
-rw-r--r-- | tests/test_pandora/test_client.py | 237 | ||||
-rw-r--r-- | tests/test_pandora/test_clientbuilder.py | 112 | ||||
-rw-r--r-- | tests/test_pandora/test_errors.py | 1 | ||||
-rw-r--r-- | tests/test_pandora/test_models.py | 303 | ||||
-rw-r--r-- | tests/test_pandora/test_transport.py | 60 | ||||
-rw-r--r-- | tests/test_pydora/test_utils.py | 27 |
20 files changed, 708 insertions, 534 deletions
diff --git a/pandora/client.py b/pandora/client.py index e0d3da9..a98056c 100644 --- a/pandora/client.py +++ b/pandora/client.py | |||
@@ -29,8 +29,14 @@ class BaseAPIClient: | |||
29 | 29 | ||
30 | ALL_QUALITIES = [LOW_AUDIO_QUALITY, MED_AUDIO_QUALITY, HIGH_AUDIO_QUALITY] | 30 | ALL_QUALITIES = [LOW_AUDIO_QUALITY, MED_AUDIO_QUALITY, HIGH_AUDIO_QUALITY] |
31 | 31 | ||
32 | def __init__(self, transport, partner_user, partner_password, device, | 32 | def __init__( |
33 | default_audio_quality=MED_AUDIO_QUALITY): | 33 | self, |
34 | transport, | ||
35 | partner_user, | ||
36 | partner_password, | ||
37 | device, | ||
38 | default_audio_quality=MED_AUDIO_QUALITY, | ||
39 | ): | ||
34 | self.transport = transport | 40 | self.transport = transport |
35 | self.partner_user = partner_user | 41 | self.partner_user = partner_user |
36 | self.partner_password = partner_password | 42 | self.partner_password = partner_password |
@@ -40,11 +46,13 @@ class BaseAPIClient: | |||
40 | self.password = None | 46 | self.password = None |
41 | 47 | ||
42 | def _partner_login(self): | 48 | def _partner_login(self): |
43 | partner = self.transport("auth.partnerLogin", | 49 | partner = self.transport( |
44 | username=self.partner_user, | 50 | "auth.partnerLogin", |
45 | password=self.partner_password, | 51 | username=self.partner_user, |
46 | deviceModel=self.device, | 52 | password=self.partner_password, |
47 | version=self.transport.API_VERSION) | 53 | deviceModel=self.device, |
54 | version=self.transport.API_VERSION, | ||
55 | ) | ||
48 | 56 | ||
49 | self.transport.set_partner(partner) | 57 | self.transport.set_partner(partner) |
50 | 58 | ||
@@ -59,16 +67,18 @@ class BaseAPIClient: | |||
59 | self._partner_login() | 67 | self._partner_login() |
60 | 68 | ||
61 | try: | 69 | try: |
62 | user = self.transport("auth.userLogin", | 70 | user = self.transport( |
63 | loginType="user", | 71 | "auth.userLogin", |
64 | username=self.username, | 72 | loginType="user", |
65 | password=self.password, | 73 | username=self.username, |
66 | includePandoraOneInfo=True, | 74 | password=self.password, |
67 | includeSubscriptionExpiration=True, | 75 | includePandoraOneInfo=True, |
68 | returnCapped=True, | 76 | includeSubscriptionExpiration=True, |
69 | includeAdAttributes=True, | 77 | returnCapped=True, |
70 | includeAdvertiserAttributes=True, | 78 | includeAdAttributes=True, |
71 | xplatformAdCapable=True) | 79 | includeAdvertiserAttributes=True, |
80 | xplatformAdCapable=True, | ||
81 | ) | ||
72 | except errors.InvalidPartnerLogin: | 82 | except errors.InvalidPartnerLogin: |
73 | raise errors.InvalidUserLogin() | 83 | raise errors.InvalidUserLogin() |
74 | 84 | ||
@@ -80,7 +90,7 @@ class BaseAPIClient: | |||
80 | def get_qualities(cls, start_at, return_all_if_invalid=True): | 90 | def get_qualities(cls, start_at, return_all_if_invalid=True): |
81 | try: | 91 | try: |
82 | idx = cls.ALL_QUALITIES.index(start_at) | 92 | idx = cls.ALL_QUALITIES.index(start_at) |
83 | return cls.ALL_QUALITIES[:idx + 1] | 93 | return cls.ALL_QUALITIES[: idx + 1] |
84 | except ValueError: | 94 | except ValueError: |
85 | if return_all_if_invalid: | 95 | if return_all_if_invalid: |
86 | return cls.ALL_QUALITIES[:] | 96 | return cls.ALL_QUALITIES[:] |
@@ -105,9 +115,9 @@ class APIClient(BaseAPIClient): | |||
105 | def get_station_list(self): | 115 | def get_station_list(self): |
106 | from .models.station import StationList | 116 | from .models.station import StationList |
107 | 117 | ||
108 | return StationList.from_json(self, | 118 | return StationList.from_json( |
109 | self("user.getStationList", | 119 | self, self("user.getStationList", includeStationArtUrl=True) |
110 | includeStationArtUrl=True)) | 120 | ) |
111 | 121 | ||
112 | def get_station_list_checksum(self): | 122 | def get_station_list_checksum(self): |
113 | return self("user.getStationListChecksum")["checksum"] | 123 | return self("user.getStationListChecksum")["checksum"] |
@@ -119,19 +129,21 @@ class APIClient(BaseAPIClient): | |||
119 | additional_urls = [] | 129 | additional_urls = [] |
120 | 130 | ||
121 | if isinstance(additional_urls, str): | 131 | if isinstance(additional_urls, str): |
122 | raise TypeError('Additional urls should be a list') | 132 | raise TypeError("Additional urls should be a list") |
123 | 133 | ||
124 | urls = [getattr(url, "value", url) for url in additional_urls] | 134 | urls = [getattr(url, "value", url) for url in additional_urls] |
125 | 135 | ||
126 | resp = self("station.getPlaylist", | 136 | resp = self( |
127 | stationToken=station_token, | 137 | "station.getPlaylist", |
128 | includeTrackLength=True, | 138 | stationToken=station_token, |
129 | xplatformAdCapable=True, | 139 | includeTrackLength=True, |
130 | audioAdPodCapable=True, | 140 | xplatformAdCapable=True, |
131 | additionalAudioUrl=','.join(urls)) | 141 | audioAdPodCapable=True, |
142 | additionalAudioUrl=",".join(urls), | ||
143 | ) | ||
132 | 144 | ||
133 | for item in resp['items']: | 145 | for item in resp["items"]: |
134 | item['_paramAdditionalUrls'] = additional_urls | 146 | item["_paramAdditionalUrls"] = additional_urls |
135 | 147 | ||
136 | playlist = Playlist.from_json(self, resp) | 148 | playlist = Playlist.from_json(self, resp) |
137 | 149 | ||
@@ -145,58 +157,69 @@ class APIClient(BaseAPIClient): | |||
145 | def get_bookmarks(self): | 157 | def get_bookmarks(self): |
146 | from .models.bookmark import BookmarkList | 158 | from .models.bookmark import BookmarkList |
147 | 159 | ||
148 | return BookmarkList.from_json(self, | 160 | return BookmarkList.from_json(self, self("user.getBookmarks")) |
149 | self("user.getBookmarks")) | ||
150 | 161 | ||
151 | def get_station(self, station_token): | 162 | def get_station(self, station_token): |
152 | from .models.station import Station | 163 | from .models.station import Station |
153 | 164 | ||
154 | return Station.from_json(self, | 165 | return Station.from_json( |
155 | self("station.getStation", | 166 | self, |
156 | stationToken=station_token, | 167 | self( |
157 | includeExtendedAttributes=True)) | 168 | "station.getStation", |
169 | stationToken=station_token, | ||
170 | includeExtendedAttributes=True, | ||
171 | ), | ||
172 | ) | ||
158 | 173 | ||
159 | def add_artist_bookmark(self, track_token): | 174 | def add_artist_bookmark(self, track_token): |
160 | return self("bookmark.addArtistBookmark", | 175 | return self("bookmark.addArtistBookmark", trackToken=track_token) |
161 | trackToken=track_token) | ||
162 | 176 | ||
163 | def add_song_bookmark(self, track_token): | 177 | def add_song_bookmark(self, track_token): |
164 | return self("bookmark.addSongBookmark", | 178 | return self("bookmark.addSongBookmark", trackToken=track_token) |
165 | trackToken=track_token) | ||
166 | 179 | ||
167 | def delete_song_bookmark(self, bookmark_token): | 180 | def delete_song_bookmark(self, bookmark_token): |
168 | return self("bookmark.deleteSongBookmark", | 181 | return self( |
169 | bookmarkToken=bookmark_token) | 182 | "bookmark.deleteSongBookmark", bookmarkToken=bookmark_token |
183 | ) | ||
170 | 184 | ||
171 | def delete_artist_bookmark(self, bookmark_token): | 185 | def delete_artist_bookmark(self, bookmark_token): |
172 | return self("bookmark.deleteArtistBookmark", | 186 | return self( |
173 | bookmarkToken=bookmark_token) | 187 | "bookmark.deleteArtistBookmark", bookmarkToken=bookmark_token |
188 | ) | ||
174 | 189 | ||
175 | def search(self, search_text, | 190 | def search( |
176 | include_near_matches=False, | 191 | self, |
177 | include_genre_stations=False): | 192 | search_text, |
193 | include_near_matches=False, | ||
194 | include_genre_stations=False, | ||
195 | ): | ||
178 | from .models.search import SearchResult | 196 | from .models.search import SearchResult |
179 | 197 | ||
180 | return SearchResult.from_json( | 198 | return SearchResult.from_json( |
181 | self, | 199 | self, |
182 | self("music.search", | 200 | self( |
183 | searchText=search_text, | 201 | "music.search", |
184 | includeNearMatches=include_near_matches, | 202 | searchText=search_text, |
185 | includeGenreStations=include_genre_stations) | 203 | includeNearMatches=include_near_matches, |
204 | includeGenreStations=include_genre_stations, | ||
205 | ), | ||
186 | ) | 206 | ) |
187 | 207 | ||
188 | def add_feedback(self, track_token, positive): | 208 | def add_feedback(self, track_token, positive): |
189 | return self("station.addFeedback", | 209 | return self( |
190 | trackToken=track_token, | 210 | "station.addFeedback", trackToken=track_token, isPositive=positive |
191 | isPositive=positive) | 211 | ) |
192 | 212 | ||
193 | def add_music(self, music_token, station_token): | 213 | def add_music(self, music_token, station_token): |
194 | return self("station.addMusic", | 214 | return self( |
195 | musicToken=music_token, | 215 | "station.addMusic", |
196 | stationToken=station_token) | 216 | musicToken=music_token, |
217 | stationToken=station_token, | ||
218 | ) | ||
197 | 219 | ||
198 | def create_station(self, search_token=None, artist_token=None, | 220 | def create_station( |
199 | track_token=None): | 221 | self, search_token=None, artist_token=None, track_token=None |
222 | ): | ||
200 | from .models.station import Station | 223 | from .models.station import Station |
201 | 224 | ||
202 | kwargs = {} | 225 | kwargs = {} |
@@ -210,26 +233,23 @@ class APIClient(BaseAPIClient): | |||
210 | else: | 233 | else: |
211 | raise KeyError("Must pass a type of token") | 234 | raise KeyError("Must pass a type of token") |
212 | 235 | ||
213 | return Station.from_json(self, | 236 | return Station.from_json(self, self("station.createStation", **kwargs)) |
214 | self("station.createStation", **kwargs)) | ||
215 | 237 | ||
216 | def delete_feedback(self, feedback_id): | 238 | def delete_feedback(self, feedback_id): |
217 | return self("station.deleteFeedback", | 239 | return self("station.deleteFeedback", feedbackId=feedback_id) |
218 | feedbackId=feedback_id) | ||
219 | 240 | ||
220 | def delete_music(self, seed_id): | 241 | def delete_music(self, seed_id): |
221 | return self("station.deleteMusic", | 242 | return self("station.deleteMusic", seedId=seed_id) |
222 | seedId=seed_id) | ||
223 | 243 | ||
224 | def delete_station(self, station_token): | 244 | def delete_station(self, station_token): |
225 | return self("station.deleteStation", | 245 | return self("station.deleteStation", stationToken=station_token) |
226 | stationToken=station_token) | ||
227 | 246 | ||
228 | def get_genre_stations(self): | 247 | def get_genre_stations(self): |
229 | from .models.station import GenreStationList | 248 | from .models.station import GenreStationList |
230 | 249 | ||
231 | genre_stations = GenreStationList.from_json( | 250 | genre_stations = GenreStationList.from_json( |
232 | self, self("station.getGenreStations")) | 251 | self, self("station.getGenreStations") |
252 | ) | ||
233 | genre_stations.checksum = self.get_genre_stations_checksum() | 253 | genre_stations.checksum = self.get_genre_stations_checksum() |
234 | 254 | ||
235 | return genre_stations | 255 | return genre_stations |
@@ -238,44 +258,45 @@ class APIClient(BaseAPIClient): | |||
238 | return self("station.getGenreStationsChecksum")["checksum"] | 258 | return self("station.getGenreStationsChecksum")["checksum"] |
239 | 259 | ||
240 | def rename_station(self, station_token, name): | 260 | def rename_station(self, station_token, name): |
241 | return self("station.renameStation", | 261 | return self( |
242 | stationToken=station_token, | 262 | "station.renameStation", |
243 | stationName=name) | 263 | stationToken=station_token, |
264 | stationName=name, | ||
265 | ) | ||
244 | 266 | ||
245 | def explain_track(self, track_token): | 267 | def explain_track(self, track_token): |
246 | return self("track.explainTrack", | 268 | return self("track.explainTrack", trackToken=track_token) |
247 | trackToken=track_token) | ||
248 | 269 | ||
249 | def set_quick_mix(self, *args): | 270 | def set_quick_mix(self, *args): |
250 | return self("user.setQuickMix", | 271 | return self("user.setQuickMix", quickMixStationIds=args) |
251 | quickMixStationIds=args) | ||
252 | 272 | ||
253 | def sleep_song(self, track_token): | 273 | def sleep_song(self, track_token): |
254 | return self("user.sleepSong", | 274 | return self("user.sleepSong", trackToken=track_token) |
255 | trackToken=track_token) | ||
256 | 275 | ||
257 | def share_station(self, station_id, station_token, *emails): | 276 | def share_station(self, station_id, station_token, *emails): |
258 | return self("station.shareStation", | 277 | return self( |
259 | stationId=station_id, | 278 | "station.shareStation", |
260 | stationToken=station_token, | 279 | stationId=station_id, |
261 | emails=emails) | 280 | stationToken=station_token, |
281 | emails=emails, | ||
282 | ) | ||
262 | 283 | ||
263 | def transform_shared_station(self, station_token): | 284 | def transform_shared_station(self, station_token): |
264 | return self("station.transformSharedStation", | 285 | return self( |
265 | stationToken=station_token) | 286 | "station.transformSharedStation", stationToken=station_token |
287 | ) | ||
266 | 288 | ||
267 | def share_music(self, music_token, *emails): | 289 | def share_music(self, music_token, *emails): |
268 | return self("music.shareMusic", | 290 | return self( |
269 | musicToken=music_token, | 291 | "music.shareMusic", musicToken=music_token, email=emails[0] |
270 | email=emails[0]) | 292 | ) |
271 | 293 | ||
272 | def get_ad_item(self, station_id, ad_token): | 294 | def get_ad_item(self, station_id, ad_token): |
273 | from .models.ad import AdItem | 295 | from .models.ad import AdItem |
274 | 296 | ||
275 | if not station_id: | 297 | if not station_id: |
276 | raise errors.ParameterMissing("The 'station_id' param must be " | 298 | msg = "The 'station_id' param must be defined, got: '{}'" |
277 | "defined, got: '{}'" | 299 | raise errors.ParameterMissing(msg.format(station_id)) |
278 | .format(station_id)) | ||
279 | 300 | ||
280 | ad_item = AdItem.from_json(self, self.get_ad_metadata(ad_token)) | 301 | ad_item = AdItem.from_json(self, self.get_ad_metadata(ad_token)) |
281 | ad_item.station_id = station_id | 302 | ad_item.station_id = station_id |
@@ -283,12 +304,14 @@ class APIClient(BaseAPIClient): | |||
283 | return ad_item | 304 | return ad_item |
284 | 305 | ||
285 | def get_ad_metadata(self, ad_token): | 306 | def get_ad_metadata(self, ad_token): |
286 | return self("ad.getAdMetadata", | 307 | return self( |
287 | adToken=ad_token, | 308 | "ad.getAdMetadata", |
288 | returnAdTrackingTokens=True, | 309 | adToken=ad_token, |
289 | supportAudioAds=True) | 310 | returnAdTrackingTokens=True, |
311 | supportAudioAds=True, | ||
312 | ) | ||
290 | 313 | ||
291 | def register_ad(self, station_id, tokens): | 314 | def register_ad(self, station_id, tokens): |
292 | return self("ad.registerAd", | 315 | return self( |
293 | stationId=station_id, | 316 | "ad.registerAd", stationId=station_id, adTrackingTokens=tokens |
294 | adTrackingTokens=tokens) | 317 | ) |
diff --git a/pandora/clientbuilder.py b/pandora/clientbuilder.py index bc55cec..2b11ffb 100644 --- a/pandora/clientbuilder.py +++ b/pandora/clientbuilder.py | |||
@@ -70,8 +70,7 @@ class TranslatingDict(dict): | |||
70 | 70 | ||
71 | def __setitem__(self, key, value): | 71 | def __setitem__(self, key, value): |
72 | key = self.translate_key(key) | 72 | key = self.translate_key(key) |
73 | super().__setitem__( | 73 | super().__setitem__(key, self.translate_value(key, value)) |
74 | key, self.translate_value(key, value)) | ||
75 | 74 | ||
76 | 75 | ||
77 | class APIClientBuilder: | 76 | class APIClientBuilder: |
@@ -99,19 +98,25 @@ class APIClientBuilder: | |||
99 | self.client_class = client_class or self.DEFAULT_CLIENT_CLASS | 98 | self.client_class = client_class or self.DEFAULT_CLIENT_CLASS |
100 | 99 | ||
101 | def build_from_settings_dict(self, settings): | 100 | def build_from_settings_dict(self, settings): |
102 | enc = Encryptor(settings["DECRYPTION_KEY"], | 101 | enc = Encryptor(settings["DECRYPTION_KEY"], settings["ENCRYPTION_KEY"]) |
103 | settings["ENCRYPTION_KEY"]) | ||
104 | 102 | ||
105 | trans = APITransport(enc, | 103 | trans = APITransport( |
106 | settings.get("API_HOST", DEFAULT_API_HOST), | 104 | enc, |
107 | settings.get("PROXY", None)) | 105 | settings.get("API_HOST", DEFAULT_API_HOST), |
106 | settings.get("PROXY", None), | ||
107 | ) | ||
108 | 108 | ||
109 | quality = settings.get("AUDIO_QUALITY", | 109 | quality = settings.get( |
110 | self.client_class.MED_AUDIO_QUALITY) | 110 | "AUDIO_QUALITY", self.client_class.MED_AUDIO_QUALITY |
111 | ) | ||
111 | 112 | ||
112 | return self.client_class(trans, settings["PARTNER_USER"], | 113 | return self.client_class( |
113 | settings["PARTNER_PASSWORD"], | 114 | trans, |
114 | settings["DEVICE"], quality) | 115 | settings["PARTNER_USER"], |
116 | settings["PARTNER_PASSWORD"], | ||
117 | settings["DEVICE"], | ||
118 | quality, | ||
119 | ) | ||
115 | 120 | ||
116 | 121 | ||
117 | class SettingsDict(TranslatingDict): | 122 | class SettingsDict(TranslatingDict): |
@@ -185,8 +190,9 @@ class FileBasedClientBuilder(APIClientBuilder): | |||
185 | client = self.build_from_settings_dict(config) | 190 | client = self.build_from_settings_dict(config) |
186 | 191 | ||
187 | if self.authenticate: | 192 | if self.authenticate: |
188 | client.login(config["USER"]["USERNAME"], | 193 | client.login( |
189 | config["USER"]["PASSWORD"]) | 194 | config["USER"]["USERNAME"], config["USER"]["PASSWORD"] |
195 | ) | ||
190 | 196 | ||
191 | return client | 197 | return client |
192 | 198 | ||
@@ -201,8 +207,9 @@ class PydoraConfigFileBuilder(FileBasedClientBuilder): | |||
201 | 207 | ||
202 | @staticmethod | 208 | @staticmethod |
203 | def cfg_to_dict(cfg, key, kind=SettingsDict): | 209 | def cfg_to_dict(cfg, key, kind=SettingsDict): |
204 | return kind((k.strip().upper(), v.strip()) | 210 | return kind( |
205 | for k, v in cfg.items(key, raw=True)) | 211 | (k.strip().upper(), v.strip()) for k, v in cfg.items(key, raw=True) |
212 | ) | ||
206 | 213 | ||
207 | def parse_config(self): | 214 | def parse_config(self): |
208 | cfg = ConfigParser() | 215 | cfg = ConfigParser() |
@@ -212,7 +219,8 @@ class PydoraConfigFileBuilder(FileBasedClientBuilder): | |||
212 | 219 | ||
213 | settings = PydoraConfigFileBuilder.cfg_to_dict(cfg, "api") | 220 | settings = PydoraConfigFileBuilder.cfg_to_dict(cfg, "api") |
214 | settings["user"] = PydoraConfigFileBuilder.cfg_to_dict( | 221 | settings["user"] = PydoraConfigFileBuilder.cfg_to_dict( |
215 | cfg, "user", dict) | 222 | cfg, "user", dict |
223 | ) | ||
216 | 224 | ||
217 | return settings | 225 | return settings |
218 | 226 | ||
diff --git a/pandora/errors.py b/pandora/errors.py index c978708..3b03db2 100644 --- a/pandora/errors.py +++ b/pandora/errors.py | |||
@@ -86,10 +86,11 @@ class PandoraException(Exception): | |||
86 | for code, api_message in __API_EXCEPTIONS__.items(): | 86 | for code, api_message in __API_EXCEPTIONS__.items(): |
87 | name = PandoraException._format_name(api_message) | 87 | name = PandoraException._format_name(api_message) |
88 | 88 | ||
89 | exception = type(name, (PandoraException,), { | 89 | exception = type( |
90 | "code": code, | 90 | name, |
91 | "message": api_message, | 91 | (PandoraException,), |
92 | }) | 92 | {"code": code, "message": api_message,}, |
93 | ) | ||
93 | 94 | ||
94 | export_to[name] = __API_EXCEPTIONS__[code] = exception | 95 | export_to[name] = __API_EXCEPTIONS__[code] = exception |
95 | 96 | ||
diff --git a/pandora/models/_base.py b/pandora/models/_base.py index 6975d0a..8904be1 100644 --- a/pandora/models/_base.py +++ b/pandora/models/_base.py | |||
@@ -71,7 +71,6 @@ class DateField(SyntheticField): | |||
71 | 71 | ||
72 | 72 | ||
73 | class ModelMetaClass(type): | 73 | class ModelMetaClass(type): |
74 | |||
75 | def __new__(cls, name, parents, dct): | 74 | def __new__(cls, name, parents, dct): |
76 | dct["_fields"] = fields = {} | 75 | dct["_fields"] = fields = {} |
77 | new_dct = dct.copy() | 76 | new_dct = dct.copy() |
@@ -159,7 +158,8 @@ class PandoraModel(metaclass=ModelMetaClass): | |||
159 | """ | 158 | """ |
160 | items = [ | 159 | items = [ |
161 | "=".join((key, repr(getattr(self, key)))) | 160 | "=".join((key, repr(getattr(self, key)))) |
162 | for key in sorted(self._fields.keys())] | 161 | for key in sorted(self._fields.keys()) |
162 | ] | ||
163 | 163 | ||
164 | if items: | 164 | if items: |
165 | output = ", ".join(items) | 165 | output = ", ".join(items) |
@@ -167,8 +167,9 @@ class PandoraModel(metaclass=ModelMetaClass): | |||
167 | output = None | 167 | output = None |
168 | 168 | ||
169 | if and_also: | 169 | if and_also: |
170 | return "{}({}, {})".format(self.__class__.__name__, | 170 | return "{}({}, {})".format( |
171 | output, and_also) | 171 | self.__class__.__name__, output, and_also |
172 | ) | ||
172 | else: | 173 | else: |
173 | return "{}({})".format(self.__class__.__name__, output) | 174 | return "{}({})".format(self.__class__.__name__, output) |
174 | 175 | ||
@@ -301,7 +302,8 @@ class PandoraDictListModel(PandoraModel, dict): | |||
301 | 302 | ||
302 | for part in item[self.__list_key__]: | 303 | for part in item[self.__list_key__]: |
303 | self[key].append( | 304 | self[key].append( |
304 | cls.__list_model__.from_json(api_client, part)) | 305 | cls.__list_model__.from_json(api_client, part) |
306 | ) | ||
305 | 307 | ||
306 | return self | 308 | return self |
307 | 309 | ||
diff --git a/pandora/models/ad.py b/pandora/models/ad.py index ad4b7b0..48cd302 100644 --- a/pandora/models/ad.py +++ b/pandora/models/ad.py | |||
@@ -24,8 +24,9 @@ class AdItem(PlaylistModel): | |||
24 | if self.tracking_tokens: | 24 | if self.tracking_tokens: |
25 | self._api_client.register_ad(station_id, self.tracking_tokens) | 25 | self._api_client.register_ad(station_id, self.tracking_tokens) |
26 | else: | 26 | else: |
27 | raise ParameterMissing('No ad tracking tokens provided for ' | 27 | raise ParameterMissing( |
28 | 'registration.') | 28 | "No ad tracking tokens provided for registration." |
29 | ) | ||
29 | 30 | ||
30 | def prepare_playback(self): | 31 | def prepare_playback(self): |
31 | try: | 32 | try: |
diff --git a/pandora/models/playlist.py b/pandora/models/playlist.py index 38afb00..8e0d22e 100644 --- a/pandora/models/playlist.py +++ b/pandora/models/playlist.py | |||
@@ -5,15 +5,15 @@ from ._base import Field, SyntheticField, PandoraModel, PandoraListModel | |||
5 | 5 | ||
6 | 6 | ||
7 | class AdditionalAudioUrl(Enum): | 7 | class AdditionalAudioUrl(Enum): |
8 | HTTP_40_AAC_MONO = 'HTTP_40_AAC_MONO' | 8 | HTTP_40_AAC_MONO = "HTTP_40_AAC_MONO" |
9 | HTTP_64_AAC = 'HTTP_64_AAC' | 9 | HTTP_64_AAC = "HTTP_64_AAC" |
10 | HTTP_32_AACPLUS = 'HTTP_32_AACPLUS' | 10 | HTTP_32_AACPLUS = "HTTP_32_AACPLUS" |
11 | HTTP_64_AACPLUS = 'HTTP_64_AACPLUS' | 11 | HTTP_64_AACPLUS = "HTTP_64_AACPLUS" |
12 | HTTP_24_AACPLUS_ADTS = 'HTTP_24_AACPLUS_ADTS' | 12 | HTTP_24_AACPLUS_ADTS = "HTTP_24_AACPLUS_ADTS" |
13 | HTTP_32_AACPLUS_ADTS = 'HTTP_32_AACPLUS_ADTS' | 13 | HTTP_32_AACPLUS_ADTS = "HTTP_32_AACPLUS_ADTS" |
14 | HTTP_64_AACPLUS_ADTS = 'HTTP_64_AACPLUS_ADTS' | 14 | HTTP_64_AACPLUS_ADTS = "HTTP_64_AACPLUS_ADTS" |
15 | HTTP_128_MP3 = 'HTTP_128_MP3' | 15 | HTTP_128_MP3 = "HTTP_128_MP3" |
16 | HTTP_32_WMA = 'HTTP_32_WMA' | 16 | HTTP_32_WMA = "HTTP_32_WMA" |
17 | 17 | ||
18 | 18 | ||
19 | class PandoraType(Enum): | 19 | class PandoraType(Enum): |
@@ -28,14 +28,14 @@ class PandoraType(Enum): | |||
28 | 28 | ||
29 | @staticmethod | 29 | @staticmethod |
30 | def from_string(value): | 30 | def from_string(value): |
31 | return { | 31 | types = { |
32 | "TR": PandoraType.TRACK, | 32 | "TR": PandoraType.TRACK, |
33 | "AR": PandoraType.ARTIST, | 33 | "AR": PandoraType.ARTIST, |
34 | }.get(value, PandoraType.GENRE) | 34 | } |
35 | return types.get(value, PandoraType.GENRE) | ||
35 | 36 | ||
36 | 37 | ||
37 | class AudioField(SyntheticField): | 38 | class AudioField(SyntheticField): |
38 | |||
39 | def formatter(self, api_client, data, newval): | 39 | def formatter(self, api_client, data, newval): |
40 | """Get audio-related fields | 40 | """Get audio-related fields |
41 | 41 | ||
@@ -61,9 +61,11 @@ class AudioField(SyntheticField): | |||
61 | elif not url_map: # No audio url available (e.g. ad tokens) | 61 | elif not url_map: # No audio url available (e.g. ad tokens) |
62 | return None | 62 | return None |
63 | 63 | ||
64 | valid_audio_formats = [BaseAPIClient.HIGH_AUDIO_QUALITY, | 64 | valid_audio_formats = [ |
65 | BaseAPIClient.MED_AUDIO_QUALITY, | 65 | BaseAPIClient.HIGH_AUDIO_QUALITY, |
66 | BaseAPIClient.LOW_AUDIO_QUALITY] | 66 | BaseAPIClient.MED_AUDIO_QUALITY, |
67 | BaseAPIClient.LOW_AUDIO_QUALITY, | ||
68 | ] | ||
67 | 69 | ||
68 | # Only iterate over sublist, starting at preferred audio quality, or | 70 | # Only iterate over sublist, starting at preferred audio quality, or |
69 | # from the beginning of the list if nothing is found. Ensures that the | 71 | # from the beginning of the list if nothing is found. Ensures that the |
@@ -84,7 +86,6 @@ class AudioField(SyntheticField): | |||
84 | 86 | ||
85 | 87 | ||
86 | class AdditionalUrlField(SyntheticField): | 88 | class AdditionalUrlField(SyntheticField): |
87 | |||
88 | def formatter(self, api_client, data, newval): | 89 | def formatter(self, api_client, data, newval): |
89 | """Parse additional url fields and map them to inputs | 90 | """Parse additional url fields and map them to inputs |
90 | 91 | ||
@@ -94,7 +95,7 @@ class AdditionalUrlField(SyntheticField): | |||
94 | if newval is None: | 95 | if newval is None: |
95 | return None | 96 | return None |
96 | 97 | ||
97 | user_param = data['_paramAdditionalUrls'] | 98 | user_param = data["_paramAdditionalUrls"] |
98 | urls = {} | 99 | urls = {} |
99 | if isinstance(newval, str): | 100 | if isinstance(newval, str): |
100 | urls[user_param[0]] = newval | 101 | urls[user_param[0]] = newval |
@@ -105,7 +106,6 @@ class AdditionalUrlField(SyntheticField): | |||
105 | 106 | ||
106 | 107 | ||
107 | class PlaylistModel(PandoraModel): | 108 | class PlaylistModel(PandoraModel): |
108 | |||
109 | def get_is_playable(self): | 109 | def get_is_playable(self): |
110 | if not self.audio_url: | 110 | if not self.audio_url: |
111 | return False | 111 | return False |
diff --git a/pandora/models/search.py b/pandora/models/search.py index 94e6ee6..fe31561 100644 --- a/pandora/models/search.py +++ b/pandora/models/search.py | |||
@@ -12,13 +12,15 @@ class SearchResultItem(PandoraModel): | |||
12 | 12 | ||
13 | @property | 13 | @property |
14 | def is_artist(self): | 14 | def is_artist(self): |
15 | return isinstance(self, ArtistSearchResultItem) and \ | 15 | return isinstance( |
16 | self.token.startswith("R") | 16 | self, ArtistSearchResultItem |
17 | ) and self.token.startswith("R") | ||
17 | 18 | ||
18 | @property | 19 | @property |
19 | def is_composer(self): | 20 | def is_composer(self): |
20 | return isinstance(self, ArtistSearchResultItem) and \ | 21 | return isinstance( |
21 | self.token.startswith("C") | 22 | self, ArtistSearchResultItem |
23 | ) and self.token.startswith("C") | ||
22 | 24 | ||
23 | @property | 25 | @property |
24 | def is_genre_station(self): | 26 | def is_genre_station(self): |
@@ -36,8 +38,9 @@ class SearchResultItem(PandoraModel): | |||
36 | elif data["musicToken"].startswith("G"): | 38 | elif data["musicToken"].startswith("G"): |
37 | return GenreStationSearchResultItem.from_json(api_client, data) | 39 | return GenreStationSearchResultItem.from_json(api_client, data) |
38 | else: | 40 | else: |
39 | raise NotImplementedError("Unknown result token type '{}'" | 41 | raise NotImplementedError( |
40 | .format(data["musicToken"])) | 42 | "Unknown result token type '{}'".format(data["musicToken"]) |
43 | ) | ||
41 | 44 | ||
42 | 45 | ||
43 | class ArtistSearchResultItem(SearchResultItem): | 46 | class ArtistSearchResultItem(SearchResultItem): |
diff --git a/pandora/models/station.py b/pandora/models/station.py index a1880ec..d3f6552 100644 --- a/pandora/models/station.py +++ b/pandora/models/station.py | |||
@@ -80,8 +80,7 @@ class Station(PandoraModel): | |||
80 | feedback = Field("feedback", model=StationFeedback) | 80 | feedback = Field("feedback", model=StationFeedback) |
81 | 81 | ||
82 | def get_playlist(self, additional_urls=None): | 82 | def get_playlist(self, additional_urls=None): |
83 | return iter(self._api_client.get_playlist(self.token, | 83 | return iter(self._api_client.get_playlist(self.token, additional_urls)) |
84 | additional_urls)) | ||
85 | 84 | ||
86 | 85 | ||
87 | class StationList(PandoraListModel): | 86 | class StationList(PandoraListModel): |
@@ -105,8 +104,10 @@ class GenreStation(PandoraModel): | |||
105 | category = Field("categoryName") | 104 | category = Field("categoryName") |
106 | 105 | ||
107 | def get_playlist(self): | 106 | def get_playlist(self): |
108 | raise NotImplementedError("Genre stations do not have playlists. " | 107 | raise NotImplementedError( |
109 | "Create a real station using the token.") | 108 | "Genre stations do not have playlists. " |
109 | "Create a real station using the token." | ||
110 | ) | ||
110 | 111 | ||
111 | 112 | ||
112 | class GenreStationList(PandoraDictListModel): | 113 | class GenreStationList(PandoraDictListModel): |
diff --git a/pandora/transport.py b/pandora/transport.py index edec8a8..84f153b 100644 --- a/pandora/transport.py +++ b/pandora/transport.py | |||
@@ -40,6 +40,7 @@ def retries(max_tries, exceptions=(Exception,)): | |||
40 | function will only be retried if it raises one of the specified | 40 | function will only be retried if it raises one of the specified |
41 | exceptions. | 41 | exceptions. |
42 | """ | 42 | """ |
43 | |||
43 | def decorator(func): | 44 | def decorator(func): |
44 | def function(*args, **kwargs): | 45 | def function(*args, **kwargs): |
45 | 46 | ||
@@ -55,8 +56,9 @@ def retries(max_tries, exceptions=(Exception,)): | |||
55 | if isinstance(exc, PandoraException): | 56 | if isinstance(exc, PandoraException): |
56 | raise | 57 | raise |
57 | if retries_left > 0: | 58 | if retries_left > 0: |
58 | time.sleep(delay_exponential( | 59 | time.sleep( |
59 | 0.5, 2, max_tries - retries_left)) | 60 | delay_exponential(0.5, 2, max_tries - retries_left) |
61 | ) | ||
60 | else: | 62 | else: |
61 | raise | 63 | raise |
62 | 64 | ||
@@ -76,11 +78,12 @@ def delay_exponential(base, growth_factor, attempts): | |||
76 | Base must be greater than 0, otherwise a ValueError will be | 78 | Base must be greater than 0, otherwise a ValueError will be |
77 | raised. | 79 | raised. |
78 | """ | 80 | """ |
79 | if base == 'rand': | 81 | if base == "rand": |
80 | base = random.random() | 82 | base = random.random() |
81 | elif base <= 0: | 83 | elif base <= 0: |
82 | raise ValueError("The 'base' param must be greater than 0, " | 84 | raise ValueError( |
83 | "got: {}".format(base)) | 85 | "The 'base' param must be greater than 0, got: {}".format(base) |
86 | ) | ||
84 | time_to_sleep = base * (growth_factor ** (attempts - 1)) | 87 | time_to_sleep = base * (growth_factor ** (attempts - 1)) |
85 | return time_to_sleep | 88 | return time_to_sleep |
86 | 89 | ||
@@ -95,8 +98,8 @@ class RetryingSession(requests.Session): | |||
95 | 98 | ||
96 | def __init__(self): | 99 | def __init__(self): |
97 | super().__init__() | 100 | super().__init__() |
98 | self.mount('https://', HTTPAdapter(max_retries=3)) | 101 | self.mount("https://", HTTPAdapter(max_retries=3)) |
99 | self.mount('http://', HTTPAdapter(max_retries=3)) | 102 | self.mount("http://", HTTPAdapter(max_retries=3)) |
100 | 103 | ||
101 | 104 | ||
102 | class APITransport: | 105 | class APITransport: |
@@ -109,10 +112,14 @@ class APITransport: | |||
109 | 112 | ||
110 | API_VERSION = "5" | 113 | API_VERSION = "5" |
111 | 114 | ||
112 | REQUIRE_RESET = ("auth.partnerLogin", ) | 115 | REQUIRE_RESET = ("auth.partnerLogin",) |
113 | NO_ENCRYPT = ("auth.partnerLogin", ) | 116 | NO_ENCRYPT = ("auth.partnerLogin",) |
114 | REQUIRE_TLS = ("auth.partnerLogin", "auth.userLogin", | 117 | REQUIRE_TLS = ( |
115 | "station.getPlaylist", "user.createUser") | 118 | "auth.partnerLogin", |
119 | "auth.userLogin", | ||
120 | "station.getPlaylist", | ||
121 | "user.createUser", | ||
122 | ) | ||
116 | 123 | ||
117 | def __init__(self, cryptor, api_host=DEFAULT_API_HOST, proxy=None): | 124 | def __init__(self, cryptor, api_host=DEFAULT_API_HOST, proxy=None): |
118 | self.cryptor = cryptor | 125 | self.cryptor = cryptor |
@@ -199,8 +206,8 @@ class APITransport: | |||
199 | 206 | ||
200 | def _build_url(self, method): | 207 | def _build_url(self, method): |
201 | return "{}://{}".format( | 208 | return "{}://{}".format( |
202 | "https" if method in self.REQUIRE_TLS else "http", | 209 | "https" if method in self.REQUIRE_TLS else "http", self.api_host |
203 | self.api_host) | 210 | ) |
204 | 211 | ||
205 | def _build_data(self, method, data): | 212 | def _build_data(self, method, data): |
206 | data["userAuthToken"] = self.user_auth_token | 213 | data["userAuthToken"] = self.user_auth_token |
@@ -260,7 +267,7 @@ class BlowfishCryptor: | |||
260 | 267 | ||
261 | computed = b"".join([chr(pad_size).encode("ascii")] * pad_size) | 268 | computed = b"".join([chr(pad_size).encode("ascii")] * pad_size) |
262 | if not data[-pad_size:] == computed: | 269 | if not data[-pad_size:] == computed: |
263 | raise ValueError('Invalid padding') | 270 | raise ValueError("Invalid padding") |
264 | 271 | ||
265 | return data[:-pad_size] | 272 | return data[:-pad_size] |
266 | 273 | ||
diff --git a/pydora/audio_backend.py b/pydora/audio_backend.py index bd778ad..aecd87e 100644 --- a/pydora/audio_backend.py +++ b/pydora/audio_backend.py | |||
@@ -15,18 +15,21 @@ log = logging.getLogger("pydora.audio_backend") | |||
15 | class PlayerException(Exception): | 15 | class PlayerException(Exception): |
16 | """Base class for all player exceptions | 16 | """Base class for all player exceptions |
17 | """ | 17 | """ |
18 | |||
18 | pass | 19 | pass |
19 | 20 | ||
20 | 21 | ||
21 | class UnsupportedEncoding(PlayerException): | 22 | class UnsupportedEncoding(PlayerException): |
22 | """Song encoding is not supported by player backend | 23 | """Song encoding is not supported by player backend |
23 | """ | 24 | """ |
25 | |||
24 | pass | 26 | pass |
25 | 27 | ||
26 | 28 | ||
27 | class PlayerUnusable(PlayerException): | 29 | class PlayerUnusable(PlayerException): |
28 | """Player can not be used on this system | 30 | """Player can not be used on this system |
29 | """ | 31 | """ |
32 | |||
30 | pass | 33 | pass |
31 | 34 | ||
32 | 35 | ||
@@ -172,7 +175,8 @@ class BasePlayer: | |||
172 | self._loop_hook() | 175 | self._loop_hook() |
173 | 176 | ||
174 | readers, _, _ = select.select( | 177 | readers, _, _ = select.select( |
175 | self._get_select_readers(), [], [], 1) | 178 | self._get_select_readers(), [], [], 1 |
179 | ) | ||
176 | 180 | ||
177 | for handle in readers: | 181 | for handle in readers: |
178 | if handle.fileno() == self._control_fd: | 182 | if handle.fileno() == self._control_fd: |
@@ -296,7 +300,6 @@ class VLCPlayer(BasePlayer): | |||
296 | 300 | ||
297 | 301 | ||
298 | class RemoteVLC(VLCPlayer): | 302 | class RemoteVLC(VLCPlayer): |
299 | |||
300 | def __init__(self, host, port, callbacks, control_channel): | 303 | def __init__(self, host, port, callbacks, control_channel): |
301 | self._connect_to = (host, int(port)) | 304 | self._connect_to = (host, int(port)) |
302 | self._control_sock = None | 305 | self._control_sock = None |
diff --git a/pydora/configure.py b/pydora/configure.py index d31c097..d58d445 100644 --- a/pydora/configure.py +++ b/pydora/configure.py | |||
@@ -32,11 +32,13 @@ class PandoraKeysConfigParser: | |||
32 | the pandora API docs keys source file. | 32 | the pandora API docs keys source file. |
33 | """ | 33 | """ |
34 | 34 | ||
35 | KEYS_URL = ("http://6xq.net/git/lars/pandora-apidoc.git/" | 35 | KEYS_URL = ( |
36 | "plain/json/partners.rst") | 36 | "http://6xq.net/git/lars/pandora-apidoc.git/plain/json/partners.rst" |
37 | ) | ||
37 | 38 | ||
38 | FIELD_RE = re.compile( | 39 | FIELD_RE = re.compile( |
39 | ":(?P<key>[^:]+): (?:`{2})?(?P<value>[^`\n]+)(?:`{2})?$") | 40 | ":(?P<key>[^:]+): (?:`{2})?(?P<value>[^`\n]+)(?:`{2})?$" |
41 | ) | ||
40 | 42 | ||
41 | def _fixup_key(self, key): | 43 | def _fixup_key(self, key): |
42 | key = key.lower() | 44 | key = key.lower() |
@@ -90,7 +92,7 @@ class PandoraKeysConfigParser: | |||
90 | key = self._clean_device_name(buffer.pop()) | 92 | key = self._clean_device_name(buffer.pop()) |
91 | current_partner = partners[key] = { | 93 | current_partner = partners[key] = { |
92 | "api_host": self._format_api_host(api_host) | 94 | "api_host": self._format_api_host(api_host) |
93 | } | 95 | } |
94 | 96 | ||
95 | buffer.append(line.strip().lower()) | 97 | buffer.append(line.strip().lower()) |
96 | 98 | ||
@@ -165,8 +167,9 @@ class Configurator: | |||
165 | self.add_partner_config(self.get_partner_config()) | 167 | self.add_partner_config(self.get_partner_config()) |
166 | self.get_value("user", "username", "Pandora Username: ") | 168 | self.get_value("user", "username", "Pandora Username: ") |
167 | self.get_password("user", "password", "Pandora Password: ") | 169 | self.get_password("user", "password", "Pandora Password: ") |
168 | self.set_static_value("api", "default_audio_quality", | 170 | self.set_static_value( |
169 | APIClient.HIGH_AUDIO_QUALITY) | 171 | "api", "default_audio_quality", APIClient.HIGH_AUDIO_QUALITY |
172 | ) | ||
170 | 173 | ||
171 | self.write_config() | 174 | self.write_config() |
172 | 175 | ||
diff --git a/pydora/player.py b/pydora/player.py index fdacc63..c4ce2e0 100644 --- a/pydora/player.py +++ b/pydora/player.py | |||
@@ -132,8 +132,12 @@ class PlayerApp: | |||
132 | if song.is_ad: | 132 | if song.is_ad: |
133 | print("{} ".format(Colors.cyan("Advertisement"))) | 133 | print("{} ".format(Colors.cyan("Advertisement"))) |
134 | else: | 134 | else: |
135 | print("{} by {}".format(Colors.cyan(song.song_name), | 135 | print( |
136 | Colors.yellow(song.artist_name))) | 136 | "{} by {}".format( |
137 | Colors.cyan(song.song_name), | ||
138 | Colors.yellow(song.artist_name), | ||
139 | ) | ||
140 | ) | ||
137 | 141 | ||
138 | def skip_song(self, song): | 142 | def skip_song(self, song): |
139 | if song.is_ad: | 143 | if song.is_ad: |
@@ -183,13 +187,15 @@ class PlayerApp: | |||
183 | self.screen.print_error("Failed to bookmark artis") | 187 | self.screen.print_error("Failed to bookmark artis") |
184 | except NotImplementedError: | 188 | except NotImplementedError: |
185 | self.screen.print_error( | 189 | self.screen.print_error( |
186 | "Cannot bookmark artist for this type of track") | 190 | "Cannot bookmark artist for this type of track" |
191 | ) | ||
187 | 192 | ||
188 | def sleep_song(self, song): | 193 | def sleep_song(self, song): |
189 | try: | 194 | try: |
190 | if song.sleep(): | 195 | if song.sleep(): |
191 | self.screen.print_success( | 196 | self.screen.print_success( |
192 | "Song will not be played for 30 days") | 197 | "Song will not be played for 30 days" |
198 | ) | ||
193 | self.player.stop() | 199 | self.player.stop() |
194 | else: | 200 | else: |
195 | self.screen.print_error("Failed to sleep song") | 201 | self.screen.print_error("Failed to sleep song") |
@@ -214,10 +220,14 @@ class PlayerApp: | |||
214 | 220 | ||
215 | def help(self, song): | 221 | def help(self, song): |
216 | print("") | 222 | print("") |
217 | print("\n".join([ | 223 | print( |
218 | "\t{:>2} - {}".format(k, v[0]) | 224 | "\n".join( |
219 | for k, v in sorted(self.CMD_MAP.items()) | 225 | [ |
220 | ])) | 226 | "\t{:>2} - {}".format(k, v[0]) |
227 | for k, v in sorted(self.CMD_MAP.items()) | ||
228 | ] | ||
229 | ) | ||
230 | ) | ||
221 | print("") | 231 | print("") |
222 | 232 | ||
223 | def input(self, input, song): | 233 | def input(self, input, song): |
@@ -227,7 +237,8 @@ class PlayerApp: | |||
227 | cmd = getattr(self, self.CMD_MAP[input][1]) | 237 | cmd = getattr(self, self.CMD_MAP[input][1]) |
228 | except (IndexError, KeyError): | 238 | except (IndexError, KeyError): |
229 | return self.screen.print_error( | 239 | return self.screen.print_error( |
230 | "Invalid command {!r}!".format(input)) | 240 | "Invalid command {!r}!".format(input) |
241 | ) | ||
231 | 242 | ||
232 | cmd(song) | 243 | cmd(song) |
233 | 244 | ||
@@ -240,21 +251,30 @@ class PlayerApp: | |||
240 | def pre_flight_checks(self): | 251 | def pre_flight_checks(self): |
241 | # See #52, this key no longer passes some server-side check | 252 | # See #52, this key no longer passes some server-side check |
242 | if self.client.partner_user == "iphone": | 253 | if self.client.partner_user == "iphone": |
243 | self.screen.print_error(( | 254 | self.screen.print_error( |
244 | "The `iphone` partner key set is no longer compatible with " | 255 | ( |
245 | "pydora. Please re-run pydora-configure to re-generate " | 256 | "The `iphone` partner key set is no longer compatible " |
246 | "your config file before continuing.")) | 257 | "with pydora. Please re-run pydora-configure to " |
258 | "re-generate your config file before continuing." | ||
259 | ) | ||
260 | ) | ||
247 | sys.exit(1) | 261 | sys.exit(1) |
248 | 262 | ||
249 | def _parse_args(self): | 263 | def _parse_args(self): |
250 | parser = argparse.ArgumentParser( | 264 | parser = argparse.ArgumentParser( |
251 | description="command line Pandora player") | 265 | description="command line Pandora player" |
266 | ) | ||
252 | parser.add_argument( | 267 | parser.add_argument( |
253 | "--vlc-net", dest="vlc_net", | 268 | "--vlc-net", |
254 | help="connect to VLC over the network (host:port)") | 269 | dest="vlc_net", |
270 | help="connect to VLC over the network (host:port)", | ||
271 | ) | ||
255 | parser.add_argument( | 272 | parser.add_argument( |
256 | "-v", dest="verbose", action="store_true", | 273 | "-v", |
257 | help="enable verbose logging") | 274 | dest="verbose", |
275 | action="store_true", | ||
276 | help="enable verbose logging", | ||
277 | ) | ||
258 | return parser.parse_args() | 278 | return parser.parse_args() |
259 | 279 | ||
260 | def run(self): | 280 | def run(self): |
diff --git a/pydora/utils.py b/pydora/utils.py index 6675773..5a96ff1 100644 --- a/pydora/utils.py +++ b/pydora/utils.py | |||
@@ -10,11 +10,11 @@ class TerminalPlatformUnsupported(Exception): | |||
10 | Raised by code that can not be used to interact with the terminal on this | 10 | Raised by code that can not be used to interact with the terminal on this |
11 | platform. | 11 | platform. |
12 | """ | 12 | """ |
13 | |||
13 | pass | 14 | pass |
14 | 15 | ||
15 | 16 | ||
16 | class Colors: | 17 | class Colors: |
17 | |||
18 | def __wrap_with(raw_code): | 18 | def __wrap_with(raw_code): |
19 | @staticmethod | 19 | @staticmethod |
20 | def inner(text, bold=False): | 20 | def inner(text, bold=False): |
@@ -22,6 +22,7 @@ class Colors: | |||
22 | if bold: | 22 | if bold: |
23 | code = "1;{}".format(code) | 23 | code = "1;{}".format(code) |
24 | return "\033[{}m{}\033[0m".format(code, text) | 24 | return "\033[{}m{}\033[0m".format(code, text) |
25 | |||
25 | return inner | 26 | return inner |
26 | 27 | ||
27 | red = __wrap_with("31") | 28 | red = __wrap_with("31") |
@@ -44,6 +45,7 @@ class PosixEchoControl: | |||
44 | def __init__(self): | 45 | def __init__(self): |
45 | try: | 46 | try: |
46 | import termios | 47 | import termios |
48 | |||
47 | self.termios = termios | 49 | self.termios = termios |
48 | except ImportError: | 50 | except ImportError: |
49 | raise TerminalPlatformUnsupported("POSIX not supported") | 51 | raise TerminalPlatformUnsupported("POSIX not supported") |
@@ -110,7 +112,6 @@ class Win32EchoControl: | |||
110 | 112 | ||
111 | 113 | ||
112 | class Screen: | 114 | class Screen: |
113 | |||
114 | def __init__(self): | 115 | def __init__(self): |
115 | try: | 116 | try: |
116 | self._echo_driver = PosixEchoControl() | 117 | self._echo_driver = PosixEchoControl() |
@@ -52,23 +52,32 @@ class TestsWithCoverage(test): | |||
52 | self.missed_coverage_goals = True | 52 | self.missed_coverage_goals = True |
53 | self.announce( | 53 | self.announce( |
54 | "Coverage: {!r} coverage is {}%, goal is {}%".format( | 54 | "Coverage: {!r} coverage is {}%, goal is {}%".format( |
55 | rel_path, coverage_percent, self.coverage_goal), log.ERROR) | 55 | rel_path, coverage_percent, self.coverage_goal |
56 | ), | ||
57 | log.ERROR, | ||
58 | ) | ||
56 | 59 | ||
57 | missed_branches = analysis.numbers.n_missing_branches | 60 | missed_branches = analysis.numbers.n_missing_branches |
58 | if missed_branches != self.missed_branches_goal: | 61 | if missed_branches != self.missed_branches_goal: |
59 | self.missed_coverage_goals = True | 62 | self.missed_coverage_goals = True |
60 | self.announce( | 63 | self.announce( |
61 | "Coverage: {!r} missed branch count is {}, goal is {}".format( | 64 | "Coverage: {!r} missed branch count is {}, goal is {}".format( |
62 | rel_path, missed_branches, self.missed_branches_goal), | 65 | rel_path, missed_branches, self.missed_branches_goal |
63 | log.ERROR) | 66 | ), |
67 | log.ERROR, | ||
68 | ) | ||
64 | 69 | ||
65 | partially_covered_branches = analysis.numbers.n_partial_branches | 70 | partially_covered_branches = analysis.numbers.n_partial_branches |
66 | if partially_covered_branches != self.partial_branches_goal: | 71 | if partially_covered_branches != self.partial_branches_goal: |
67 | self.missed_coverage_goals = True | 72 | self.missed_coverage_goals = True |
68 | self.announce( | 73 | self.announce( |
69 | "Coverage: {!r} partial branch count is {}, goal is {}".format( | 74 | "Coverage: {!r} partial branch count is {}, goal is {}".format( |
70 | rel_path, partially_covered_branches, | 75 | rel_path, |
71 | self.partial_branches_goal), log.ERROR) | 76 | partially_covered_branches, |
77 | self.partial_branches_goal, | ||
78 | ), | ||
79 | log.ERROR, | ||
80 | ) | ||
72 | 81 | ||
73 | def run(self): | 82 | def run(self): |
74 | from coverage import Coverage | 83 | from coverage import Coverage |
@@ -114,13 +123,20 @@ class PyPiReleaseCommand(Command): | |||
114 | def configure_environment(self): | 123 | def configure_environment(self): |
115 | log.info("Configuring release environment") | 124 | log.info("Configuring release environment") |
116 | subprocess.check_call(["python3", "-m", "venv", ".release/py3"]) | 125 | subprocess.check_call(["python3", "-m", "venv", ".release/py3"]) |
117 | self.venv_run("pip", "install", "-U", | 126 | self.venv_run( |
118 | "pip", "setuptools", "virtualenv", "twine") | 127 | "pip", "install", "-U", "pip", "setuptools", "virtualenv", "twine" |
128 | ) | ||
119 | 129 | ||
120 | def build_py3_artifact(self): | 130 | def build_py3_artifact(self): |
121 | log.info("Building Python 3 Artifact") | 131 | log.info("Building Python 3 Artifact") |
122 | self.venv_run("python", "setup.py", | 132 | self.venv_run( |
123 | "release", "bdist_wheel", "--python-tag", "py3") | 133 | "python", |
134 | "setup.py", | ||
135 | "release", | ||
136 | "bdist_wheel", | ||
137 | "--python-tag", | ||
138 | "py3", | ||
139 | ) | ||
124 | 140 | ||
125 | def build_sdist_artifact(self): | 141 | def build_sdist_artifact(self): |
126 | log.info("Building Source Dist Artifact") | 142 | log.info("Building Source Dist Artifact") |
@@ -172,10 +188,7 @@ setup( | |||
172 | "setuptools>=36.0.1", | 188 | "setuptools>=36.0.1", |
173 | "coverage>=4.1,<5", | 189 | "coverage>=4.1,<5", |
174 | ], | 190 | ], |
175 | install_requires=[ | 191 | install_requires=["requests>=2,<3", "blowfish>=0.6.1,<1.0",], |
176 | "requests>=2,<3", | ||
177 | "blowfish>=0.6.1,<1.0", | ||
178 | ], | ||
179 | python_requires=">=3.5", | 192 | python_requires=">=3.5", |
180 | classifiers=[ | 193 | classifiers=[ |
181 | "Development Status :: 5 - Production/Stable", | 194 | "Development Status :: 5 - Production/Stable", |
@@ -191,5 +204,5 @@ setup( | |||
191 | "Programming Language :: Python :: 3 :: Only", | 204 | "Programming Language :: Python :: 3 :: Only", |
192 | "Topic :: Internet :: WWW/HTTP", | 205 | "Topic :: Internet :: WWW/HTTP", |
193 | "Topic :: Multimedia :: Sound/Audio :: Players", | 206 | "Topic :: Multimedia :: Sound/Audio :: Players", |
194 | ] | 207 | ], |
195 | ) | 208 | ) |
diff --git a/tests/test_pandora/test_client.py b/tests/test_pandora/test_client.py index 8144f42..1c5a229 100644 --- a/tests/test_pandora/test_client.py +++ b/tests/test_pandora/test_client.py | |||
@@ -13,7 +13,6 @@ from tests.test_pandora.test_models import TestAdItem | |||
13 | 13 | ||
14 | 14 | ||
15 | class TestAPIClientLogin(TestCase): | 15 | class TestAPIClientLogin(TestCase): |
16 | |||
17 | class StubTransport: | 16 | class StubTransport: |
18 | 17 | ||
19 | API_VERSION = None | 18 | API_VERSION = None |
@@ -56,7 +55,6 @@ class TestAPIClientLogin(TestCase): | |||
56 | 55 | ||
57 | 56 | ||
58 | class TestCallingAPIClient(TestCase): | 57 | class TestCallingAPIClient(TestCase): |
59 | |||
60 | def test_call_should_retry_on_token_error(self): | 58 | def test_call_should_retry_on_token_error(self): |
61 | transport = Mock(side_effect=[errors.InvalidAuthToken(), None]) | 59 | transport = Mock(side_effect=[errors.InvalidAuthToken(), None]) |
62 | 60 | ||
@@ -70,37 +68,44 @@ class TestCallingAPIClient(TestCase): | |||
70 | transport.assert_has_calls([call("method"), call("method")]) | 68 | transport.assert_has_calls([call("method"), call("method")]) |
71 | 69 | ||
72 | def test_playlist_fetches_ads(self): | 70 | def test_playlist_fetches_ads(self): |
73 | fake_playlist = {"items": [ | 71 | fake_playlist = { |
74 | {"songName": "test"}, | 72 | "items": [ |
75 | {"adToken": "foo"}, | 73 | {"songName": "test"}, |
76 | {"songName": "test"}, | 74 | {"adToken": "foo"}, |
77 | ]} | 75 | {"songName": "test"}, |
78 | with patch.object(APIClient, '__call__', return_value=fake_playlist): | 76 | ] |
77 | } | ||
78 | with patch.object(APIClient, "__call__", return_value=fake_playlist): | ||
79 | client = APIClient(Mock(), None, None, None, None) | 79 | client = APIClient(Mock(), None, None, None, None) |
80 | client._authenticate = Mock() | 80 | client._authenticate = Mock() |
81 | 81 | ||
82 | items = client.get_playlist('token_mock') | 82 | items = client.get_playlist("token_mock") |
83 | self.assertIsInstance(items[1], AdItem) | 83 | self.assertIsInstance(items[1], AdItem) |
84 | 84 | ||
85 | def test_ad_support_enabled_parameters(self): | 85 | def test_ad_support_enabled_parameters(self): |
86 | with patch.object(APIClient, '__call__') as playlist_mock: | 86 | with patch.object(APIClient, "__call__") as playlist_mock: |
87 | transport = Mock(side_effect=[errors.InvalidAuthToken(), None]) | 87 | transport = Mock(side_effect=[errors.InvalidAuthToken(), None]) |
88 | 88 | ||
89 | client = APIClient(transport, None, None, None, None) | 89 | client = APIClient(transport, None, None, None, None) |
90 | client._authenticate = Mock() | 90 | client._authenticate = Mock() |
91 | 91 | ||
92 | client.get_playlist('token_mock') | 92 | client.get_playlist("token_mock") |
93 | 93 | ||
94 | playlist_mock.assert_has_calls([call("station.getPlaylist", | 94 | playlist_mock.assert_has_calls( |
95 | additionalAudioUrl='', | 95 | [ |
96 | audioAdPodCapable=True, | 96 | call( |
97 | includeTrackLength=True, | 97 | "station.getPlaylist", |
98 | stationToken='token_mock', | 98 | additionalAudioUrl="", |
99 | xplatformAdCapable=True)]) | 99 | audioAdPodCapable=True, |
100 | includeTrackLength=True, | ||
101 | stationToken="token_mock", | ||
102 | xplatformAdCapable=True, | ||
103 | ) | ||
104 | ] | ||
105 | ) | ||
100 | 106 | ||
101 | 107 | ||
102 | class TestGettingQualities(TestCase): | 108 | class TestGettingQualities(TestCase): |
103 | |||
104 | def test_with_invalid_quality_returning_all(self): | 109 | def test_with_invalid_quality_returning_all(self): |
105 | result = BaseAPIClient.get_qualities("foo", True) | 110 | result = BaseAPIClient.get_qualities("foo", True) |
106 | self.assertEqual(BaseAPIClient.ALL_QUALITIES, result) | 111 | self.assertEqual(BaseAPIClient.ALL_QUALITIES, result) |
@@ -111,20 +116,22 @@ class TestGettingQualities(TestCase): | |||
111 | 116 | ||
112 | def test_with_valid_quality(self): | 117 | def test_with_valid_quality(self): |
113 | result = BaseAPIClient.get_qualities( | 118 | result = BaseAPIClient.get_qualities( |
114 | BaseAPIClient.MED_AUDIO_QUALITY, False) | 119 | BaseAPIClient.MED_AUDIO_QUALITY, False |
120 | ) | ||
115 | 121 | ||
116 | expected = [ | 122 | expected = [ |
117 | BaseAPIClient.LOW_AUDIO_QUALITY, | 123 | BaseAPIClient.LOW_AUDIO_QUALITY, |
118 | BaseAPIClient.MED_AUDIO_QUALITY] | 124 | BaseAPIClient.MED_AUDIO_QUALITY, |
125 | ] | ||
119 | 126 | ||
120 | self.assertEqual(expected, result) | 127 | self.assertEqual(expected, result) |
121 | 128 | ||
122 | 129 | ||
123 | class TestGettingAds(TestCase): | 130 | class TestGettingAds(TestCase): |
124 | |||
125 | def test_get_ad_item_(self): | 131 | def test_get_ad_item_(self): |
126 | metamock = patch.object( | 132 | metamock = patch.object( |
127 | APIClient, '__call__', return_value=TestAdItem.JSON_DATA) | 133 | APIClient, "__call__", return_value=TestAdItem.JSON_DATA |
134 | ) | ||
128 | 135 | ||
129 | with metamock as ad_metadata_mock: | 136 | with metamock as ad_metadata_mock: |
130 | transport = Mock(side_effect=[errors.InvalidAuthToken(), None]) | 137 | transport = Mock(side_effect=[errors.InvalidAuthToken(), None]) |
@@ -132,13 +139,20 @@ class TestGettingAds(TestCase): | |||
132 | client = APIClient(transport, None, None, None, None) | 139 | client = APIClient(transport, None, None, None, None) |
133 | client._authenticate = Mock() | 140 | client._authenticate = Mock() |
134 | 141 | ||
135 | ad_item = client.get_ad_item('id_mock', 'token_mock') | 142 | ad_item = client.get_ad_item("id_mock", "token_mock") |
136 | assert ad_item.station_id == 'id_mock' | 143 | assert ad_item.station_id == "id_mock" |
137 | assert ad_item.ad_token == 'token_mock' | 144 | assert ad_item.ad_token == "token_mock" |
138 | 145 | ||
139 | ad_metadata_mock.assert_has_calls([ | 146 | ad_metadata_mock.assert_has_calls( |
140 | call("ad.getAdMetadata", adToken='token_mock', | 147 | [ |
141 | returnAdTrackingTokens=True, supportAudioAds=True)]) | 148 | call( |
149 | "ad.getAdMetadata", | ||
150 | adToken="token_mock", | ||
151 | returnAdTrackingTokens=True, | ||
152 | supportAudioAds=True, | ||
153 | ) | ||
154 | ] | ||
155 | ) | ||
142 | 156 | ||
143 | def test_get_ad_item_with_no_station_id_specified_raises_exception(self): | 157 | def test_get_ad_item_with_no_station_id_specified_raises_exception(self): |
144 | transport = Mock(side_effect=[errors.InvalidAuthToken(), None]) | 158 | transport = Mock(side_effect=[errors.InvalidAuthToken(), None]) |
@@ -147,28 +161,31 @@ class TestGettingAds(TestCase): | |||
147 | client.get_ad_metadata = Mock() | 161 | client.get_ad_metadata = Mock() |
148 | 162 | ||
149 | self.assertRaises( | 163 | self.assertRaises( |
150 | errors.ParameterMissing, client.get_ad_item, '', 'token_mock') | 164 | errors.ParameterMissing, client.get_ad_item, "", "token_mock" |
165 | ) | ||
151 | 166 | ||
152 | 167 | ||
153 | class TestCreatingStation(TestCase): | 168 | class TestCreatingStation(TestCase): |
154 | |||
155 | def test_using_search_token(self): | 169 | def test_using_search_token(self): |
156 | client = APIClient(Mock(return_value={}), None, None, None, None) | 170 | client = APIClient(Mock(return_value={}), None, None, None, None) |
157 | client.create_station(search_token="foo") | 171 | client.create_station(search_token="foo") |
158 | client.transport.assert_called_with( | 172 | client.transport.assert_called_with( |
159 | "station.createStation", musicToken="foo") | 173 | "station.createStation", musicToken="foo" |
174 | ) | ||
160 | 175 | ||
161 | def test_using_artist_token(self): | 176 | def test_using_artist_token(self): |
162 | client = APIClient(Mock(return_value={}), None, None, None, None) | 177 | client = APIClient(Mock(return_value={}), None, None, None, None) |
163 | client.create_station(artist_token="foo") | 178 | client.create_station(artist_token="foo") |
164 | client.transport.assert_called_with( | 179 | client.transport.assert_called_with( |
165 | "station.createStation", trackToken="foo", musicType="artist") | 180 | "station.createStation", trackToken="foo", musicType="artist" |
181 | ) | ||
166 | 182 | ||
167 | def test_using_track_token(self): | 183 | def test_using_track_token(self): |
168 | client = APIClient(Mock(return_value={}), None, None, None, None) | 184 | client = APIClient(Mock(return_value={}), None, None, None, None) |
169 | client.create_station(track_token="foo") | 185 | client.create_station(track_token="foo") |
170 | client.transport.assert_called_with( | 186 | client.transport.assert_called_with( |
171 | "station.createStation", trackToken="foo", musicType="song") | 187 | "station.createStation", trackToken="foo", musicType="song" |
188 | ) | ||
172 | 189 | ||
173 | def test_with_no_token(self): | 190 | def test_with_no_token(self): |
174 | with self.assertRaises(KeyError): | 191 | with self.assertRaises(KeyError): |
@@ -177,25 +194,20 @@ class TestCreatingStation(TestCase): | |||
177 | 194 | ||
178 | 195 | ||
179 | class TestCreatingGenreStation(TestCase): | 196 | class TestCreatingGenreStation(TestCase): |
180 | |||
181 | def test_has_initial_checksum(self): | 197 | def test_has_initial_checksum(self): |
182 | fake_data = { | 198 | fake_data = { |
183 | "categories": [ | 199 | "categories": [{"categoryName": "foo", "stations": []},], |
184 | {"categoryName": "foo", "stations": []}, | ||
185 | ], | ||
186 | |||
187 | # Not actually part of the genre station response but is needed to | 200 | # Not actually part of the genre station response but is needed to |
188 | # fake out the mock for get_genre_stations_checksum | 201 | # fake out the mock for get_genre_stations_checksum |
189 | "checksum": "foo" | 202 | "checksum": "foo", |
190 | } | 203 | } |
191 | with patch.object(APIClient, '__call__', return_value=fake_data): | 204 | with patch.object(APIClient, "__call__", return_value=fake_data): |
192 | client = APIClient(Mock(), None, None, None, None) | 205 | client = APIClient(Mock(), None, None, None, None) |
193 | station = client.get_genre_stations() | 206 | station = client.get_genre_stations() |
194 | self.assertEqual(station.checksum, "foo") | 207 | self.assertEqual(station.checksum, "foo") |
195 | 208 | ||
196 | 209 | ||
197 | class TestAdditionalUrls(TestCase): | 210 | class TestAdditionalUrls(TestCase): |
198 | |||
199 | def test_non_iterable_string(self): | 211 | def test_non_iterable_string(self): |
200 | with self.assertRaises(TypeError): | 212 | with self.assertRaises(TypeError): |
201 | transport = Mock(side_effect=[errors.InvalidAuthToken(), None]) | 213 | transport = Mock(side_effect=[errors.InvalidAuthToken(), None]) |
@@ -203,7 +215,7 @@ class TestAdditionalUrls(TestCase): | |||
203 | client = APIClient(transport, None, None, None, None) | 215 | client = APIClient(transport, None, None, None, None) |
204 | client._authenticate = Mock() | 216 | client._authenticate = Mock() |
205 | 217 | ||
206 | client.get_playlist('token_mock', additional_urls='') | 218 | client.get_playlist("token_mock", additional_urls="") |
207 | 219 | ||
208 | def test_non_iterable_other(self): | 220 | def test_non_iterable_other(self): |
209 | with self.assertRaises(TypeError): | 221 | with self.assertRaises(TypeError): |
@@ -212,50 +224,64 @@ class TestAdditionalUrls(TestCase): | |||
212 | client = APIClient(transport, None, None, None, None) | 224 | client = APIClient(transport, None, None, None, None) |
213 | client._authenticate = Mock() | 225 | client._authenticate = Mock() |
214 | 226 | ||
215 | client.get_playlist('token_mock', | 227 | client.get_playlist( |
216 | additional_urls=AdditionalAudioUrl.HTTP_32_WMA) | 228 | "token_mock", additional_urls=AdditionalAudioUrl.HTTP_32_WMA |
229 | ) | ||
217 | 230 | ||
218 | def test_without_enum(self): | 231 | def test_without_enum(self): |
219 | with patch.object(APIClient, '__call__') as playlist_mock: | 232 | with patch.object(APIClient, "__call__") as playlist_mock: |
220 | transport = Mock(side_effect=[errors.InvalidAuthToken(), None]) | 233 | transport = Mock(side_effect=[errors.InvalidAuthToken(), None]) |
221 | 234 | ||
222 | client = APIClient(transport, None, None, None, None) | 235 | client = APIClient(transport, None, None, None, None) |
223 | client._authenticate = Mock() | 236 | client._authenticate = Mock() |
224 | 237 | ||
225 | urls = ['HTTP_128_MP3', | 238 | urls = ["HTTP_128_MP3", "HTTP_24_AACPLUS_ADTS"] |
226 | 'HTTP_24_AACPLUS_ADTS'] | ||
227 | 239 | ||
228 | desired = 'HTTP_128_MP3,HTTP_24_AACPLUS_ADTS' | 240 | desired = "HTTP_128_MP3,HTTP_24_AACPLUS_ADTS" |
229 | 241 | ||
230 | client.get_playlist('token_mock', additional_urls=urls) | 242 | client.get_playlist("token_mock", additional_urls=urls) |
231 | 243 | ||
232 | playlist_mock.assert_has_calls([call("station.getPlaylist", | 244 | playlist_mock.assert_has_calls( |
233 | additionalAudioUrl=desired, | 245 | [ |
234 | audioAdPodCapable=True, | 246 | call( |
235 | includeTrackLength=True, | 247 | "station.getPlaylist", |
236 | stationToken='token_mock', | 248 | additionalAudioUrl=desired, |
237 | xplatformAdCapable=True)]) | 249 | audioAdPodCapable=True, |
250 | includeTrackLength=True, | ||
251 | stationToken="token_mock", | ||
252 | xplatformAdCapable=True, | ||
253 | ) | ||
254 | ] | ||
255 | ) | ||
238 | 256 | ||
239 | def test_with_enum(self): | 257 | def test_with_enum(self): |
240 | with patch.object(APIClient, '__call__') as playlist_mock: | 258 | with patch.object(APIClient, "__call__") as playlist_mock: |
241 | transport = Mock(side_effect=[errors.InvalidAuthToken(), None]) | 259 | transport = Mock(side_effect=[errors.InvalidAuthToken(), None]) |
242 | 260 | ||
243 | client = APIClient(transport, None, None, None, None) | 261 | client = APIClient(transport, None, None, None, None) |
244 | client._authenticate = Mock() | 262 | client._authenticate = Mock() |
245 | 263 | ||
246 | urls = [AdditionalAudioUrl.HTTP_128_MP3, | 264 | urls = [ |
247 | AdditionalAudioUrl.HTTP_24_AACPLUS_ADTS] | 265 | AdditionalAudioUrl.HTTP_128_MP3, |
266 | AdditionalAudioUrl.HTTP_24_AACPLUS_ADTS, | ||
267 | ] | ||
248 | 268 | ||
249 | desired = 'HTTP_128_MP3,HTTP_24_AACPLUS_ADTS' | 269 | desired = "HTTP_128_MP3,HTTP_24_AACPLUS_ADTS" |
250 | 270 | ||
251 | client.get_playlist('token_mock', additional_urls=urls) | 271 | client.get_playlist("token_mock", additional_urls=urls) |
252 | 272 | ||
253 | playlist_mock.assert_has_calls([call("station.getPlaylist", | 273 | playlist_mock.assert_has_calls( |
254 | additionalAudioUrl=desired, | 274 | [ |
255 | audioAdPodCapable=True, | 275 | call( |
256 | includeTrackLength=True, | 276 | "station.getPlaylist", |
257 | stationToken='token_mock', | 277 | additionalAudioUrl=desired, |
258 | xplatformAdCapable=True)]) | 278 | audioAdPodCapable=True, |
279 | includeTrackLength=True, | ||
280 | stationToken="token_mock", | ||
281 | xplatformAdCapable=True, | ||
282 | ) | ||
283 | ] | ||
284 | ) | ||
259 | 285 | ||
260 | 286 | ||
261 | # On the surface this test class seems dumb because it's mostly just exercising | 287 | # On the surface this test class seems dumb because it's mostly just exercising |
@@ -263,7 +289,6 @@ class TestAdditionalUrls(TestCase): | |||
263 | # introduced to API client methods that will only be spotted at runtime (import | 289 | # introduced to API client methods that will only be spotted at runtime (import |
264 | # errors, etc...) | 290 | # errors, etc...) |
265 | class TestAPIClientExhaustive(TestCase): | 291 | class TestAPIClientExhaustive(TestCase): |
266 | |||
267 | def setUp(self): | 292 | def setUp(self): |
268 | self.transport = Mock() | 293 | self.transport = Mock() |
269 | self.api = APIClient(self.transport, "puser", "ppass", "device") | 294 | self.api = APIClient(self.transport, "puser", "ppass", "device") |
@@ -271,23 +296,29 @@ class TestAPIClientExhaustive(TestCase): | |||
271 | def test_register_ad(self): | 296 | def test_register_ad(self): |
272 | self.api.register_ad("sid", "tokens") | 297 | self.api.register_ad("sid", "tokens") |
273 | self.transport.assert_called_with( | 298 | self.transport.assert_called_with( |
274 | "ad.registerAd", stationId="sid", adTrackingTokens="tokens") | 299 | "ad.registerAd", stationId="sid", adTrackingTokens="tokens" |
300 | ) | ||
275 | 301 | ||
276 | def test_share_music(self): | 302 | def test_share_music(self): |
277 | self.api.share_music("token", "foo@example.com") | 303 | self.api.share_music("token", "foo@example.com") |
278 | self.transport.assert_called_with( | 304 | self.transport.assert_called_with( |
279 | "music.shareMusic", musicToken="token", email="foo@example.com") | 305 | "music.shareMusic", musicToken="token", email="foo@example.com" |
306 | ) | ||
280 | 307 | ||
281 | def test_transform_shared_station(self): | 308 | def test_transform_shared_station(self): |
282 | self.api.transform_shared_station("token") | 309 | self.api.transform_shared_station("token") |
283 | self.transport.assert_called_with( | 310 | self.transport.assert_called_with( |
284 | "station.transformSharedStation", stationToken="token") | 311 | "station.transformSharedStation", stationToken="token" |
312 | ) | ||
285 | 313 | ||
286 | def test_share_station(self): | 314 | def test_share_station(self): |
287 | self.api.share_station("sid", "token", "foo@example.com") | 315 | self.api.share_station("sid", "token", "foo@example.com") |
288 | self.transport.assert_called_with( | 316 | self.transport.assert_called_with( |
289 | "station.shareStation", stationId="sid", stationToken="token", | 317 | "station.shareStation", |
290 | emails=("foo@example.com",)) | 318 | stationId="sid", |
319 | stationToken="token", | ||
320 | emails=("foo@example.com",), | ||
321 | ) | ||
291 | 322 | ||
292 | def test_sleep_song(self): | 323 | def test_sleep_song(self): |
293 | self.api.sleep_song("token") | 324 | self.api.sleep_song("token") |
@@ -296,22 +327,26 @@ class TestAPIClientExhaustive(TestCase): | |||
296 | def test_set_quick_mix(self): | 327 | def test_set_quick_mix(self): |
297 | self.api.set_quick_mix("id") | 328 | self.api.set_quick_mix("id") |
298 | self.transport.assert_called_with( | 329 | self.transport.assert_called_with( |
299 | "user.setQuickMix", quickMixStationIds=("id",)) | 330 | "user.setQuickMix", quickMixStationIds=("id",) |
331 | ) | ||
300 | 332 | ||
301 | def test_explain_track(self): | 333 | def test_explain_track(self): |
302 | self.api.explain_track("token") | 334 | self.api.explain_track("token") |
303 | self.transport.assert_called_with( | 335 | self.transport.assert_called_with( |
304 | "track.explainTrack", trackToken="token") | 336 | "track.explainTrack", trackToken="token" |
337 | ) | ||
305 | 338 | ||
306 | def test_rename_station(self): | 339 | def test_rename_station(self): |
307 | self.api.rename_station("token", "name") | 340 | self.api.rename_station("token", "name") |
308 | self.transport.assert_called_with( | 341 | self.transport.assert_called_with( |
309 | "station.renameStation", stationToken="token", stationName="name") | 342 | "station.renameStation", stationToken="token", stationName="name" |
343 | ) | ||
310 | 344 | ||
311 | def test_delete_station(self): | 345 | def test_delete_station(self): |
312 | self.api.delete_station("token") | 346 | self.api.delete_station("token") |
313 | self.transport.assert_called_with( | 347 | self.transport.assert_called_with( |
314 | "station.deleteStation", stationToken="token") | 348 | "station.deleteStation", stationToken="token" |
349 | ) | ||
315 | 350 | ||
316 | def test_delete_music(self): | 351 | def test_delete_music(self): |
317 | self.api.delete_music("seed") | 352 | self.api.delete_music("seed") |
@@ -320,37 +355,44 @@ class TestAPIClientExhaustive(TestCase): | |||
320 | def test_delete_feedback(self): | 355 | def test_delete_feedback(self): |
321 | self.api.delete_feedback("id") | 356 | self.api.delete_feedback("id") |
322 | self.transport.assert_called_with( | 357 | self.transport.assert_called_with( |
323 | "station.deleteFeedback", feedbackId="id") | 358 | "station.deleteFeedback", feedbackId="id" |
359 | ) | ||
324 | 360 | ||
325 | def test_add_music(self): | 361 | def test_add_music(self): |
326 | self.api.add_music("mt", "st") | 362 | self.api.add_music("mt", "st") |
327 | self.transport.assert_called_with( | 363 | self.transport.assert_called_with( |
328 | "station.addMusic", musicToken="mt", stationToken="st") | 364 | "station.addMusic", musicToken="mt", stationToken="st" |
365 | ) | ||
329 | 366 | ||
330 | def test_add_feedback(self): | 367 | def test_add_feedback(self): |
331 | self.api.add_feedback("token", False) | 368 | self.api.add_feedback("token", False) |
332 | self.transport.assert_called_with( | 369 | self.transport.assert_called_with( |
333 | "station.addFeedback", trackToken="token", isPositive=False) | 370 | "station.addFeedback", trackToken="token", isPositive=False |
371 | ) | ||
334 | 372 | ||
335 | def test_add_artist_bookmark(self): | 373 | def test_add_artist_bookmark(self): |
336 | self.api.add_artist_bookmark("tt") | 374 | self.api.add_artist_bookmark("tt") |
337 | self.transport.assert_called_with( | 375 | self.transport.assert_called_with( |
338 | "bookmark.addArtistBookmark", trackToken="tt") | 376 | "bookmark.addArtistBookmark", trackToken="tt" |
377 | ) | ||
339 | 378 | ||
340 | def test_add_song_bookmark(self): | 379 | def test_add_song_bookmark(self): |
341 | self.api.add_song_bookmark("tt") | 380 | self.api.add_song_bookmark("tt") |
342 | self.transport.assert_called_with( | 381 | self.transport.assert_called_with( |
343 | "bookmark.addSongBookmark", trackToken="tt") | 382 | "bookmark.addSongBookmark", trackToken="tt" |
383 | ) | ||
344 | 384 | ||
345 | def test_delete_song_bookmark(self): | 385 | def test_delete_song_bookmark(self): |
346 | self.api.delete_song_bookmark("bt") | 386 | self.api.delete_song_bookmark("bt") |
347 | self.transport.assert_called_with( | 387 | self.transport.assert_called_with( |
348 | "bookmark.deleteSongBookmark", bookmarkToken="bt") | 388 | "bookmark.deleteSongBookmark", bookmarkToken="bt" |
389 | ) | ||
349 | 390 | ||
350 | def test_delete_artist_bookmark(self): | 391 | def test_delete_artist_bookmark(self): |
351 | self.api.delete_artist_bookmark("bt") | 392 | self.api.delete_artist_bookmark("bt") |
352 | self.transport.assert_called_with( | 393 | self.transport.assert_called_with( |
353 | "bookmark.deleteArtistBookmark", bookmarkToken="bt") | 394 | "bookmark.deleteArtistBookmark", bookmarkToken="bt" |
395 | ) | ||
354 | 396 | ||
355 | def test_get_station_list_checksum(self): | 397 | def test_get_station_list_checksum(self): |
356 | self.transport.return_value = {"checksum": "foo"} | 398 | self.transport.return_value = {"checksum": "foo"} |
@@ -364,7 +406,8 @@ class TestAPIClientExhaustive(TestCase): | |||
364 | self.transport.return_value = {"stations": []} | 406 | self.transport.return_value = {"stations": []} |
365 | self.assertIsInstance(self.api.get_station_list(), StationList) | 407 | self.assertIsInstance(self.api.get_station_list(), StationList) |
366 | self.transport.assert_called_with( | 408 | self.transport.assert_called_with( |
367 | "user.getStationList", includeStationArtUrl=True) | 409 | "user.getStationList", includeStationArtUrl=True |
410 | ) | ||
368 | 411 | ||
369 | def test_get_bookmarks(self): | 412 | def test_get_bookmarks(self): |
370 | self.transport.return_value = {} | 413 | self.transport.return_value = {} |
@@ -375,14 +418,22 @@ class TestAPIClientExhaustive(TestCase): | |||
375 | self.transport.return_value = {} | 418 | self.transport.return_value = {} |
376 | self.assertIsInstance(self.api.get_station("st"), Station) | 419 | self.assertIsInstance(self.api.get_station("st"), Station) |
377 | self.transport.assert_called_with( | 420 | self.transport.assert_called_with( |
378 | "station.getStation", stationToken="st", | 421 | "station.getStation", |
379 | includeExtendedAttributes=True) | 422 | stationToken="st", |
423 | includeExtendedAttributes=True, | ||
424 | ) | ||
380 | 425 | ||
381 | def test_search(self): | 426 | def test_search(self): |
382 | self.transport.return_value = {} | 427 | self.transport.return_value = {} |
383 | self.assertIsInstance(self.api.search( | 428 | self.assertIsInstance( |
384 | "text", include_near_matches=True, include_genre_stations=True), | 429 | self.api.search( |
385 | SearchResult) | 430 | "text", include_near_matches=True, include_genre_stations=True |
431 | ), | ||
432 | SearchResult, | ||
433 | ) | ||
386 | self.transport.assert_called_with( | 434 | self.transport.assert_called_with( |
387 | "music.search", searchText="text", includeNearMatches=True, | 435 | "music.search", |
388 | includeGenreStations=True) | 436 | searchText="text", |
437 | includeNearMatches=True, | ||
438 | includeGenreStations=True, | ||
439 | ) | ||
diff --git a/tests/test_pandora/test_clientbuilder.py b/tests/test_pandora/test_clientbuilder.py index af0e219..17a4036 100644 --- a/tests/test_pandora/test_clientbuilder.py +++ b/tests/test_pandora/test_clientbuilder.py | |||
@@ -8,7 +8,6 @@ from pandora.transport import DEFAULT_API_HOST | |||
8 | 8 | ||
9 | 9 | ||
10 | class TestTranslatingDict(TestCase): | 10 | class TestTranslatingDict(TestCase): |
11 | |||
12 | class TestDict(cb.TranslatingDict): | 11 | class TestDict(cb.TranslatingDict): |
13 | 12 | ||
14 | KEY_TRANSLATIONS = {"FOO": "BAR"} | 13 | KEY_TRANSLATIONS = {"FOO": "BAR"} |
@@ -62,29 +61,32 @@ class TestTranslatingDict(TestCase): | |||
62 | 61 | ||
63 | 62 | ||
64 | class TestSettingsDictBuilder(TestCase): | 63 | class TestSettingsDictBuilder(TestCase): |
65 | |||
66 | @classmethod | 64 | @classmethod |
67 | def _build_minimal(self): | 65 | def _build_minimal(self): |
68 | return cb.SettingsDictBuilder({ | 66 | return cb.SettingsDictBuilder( |
69 | "DECRYPTION_KEY": "blowfishkey", | 67 | { |
70 | "ENCRYPTION_KEY": "blowfishkey", | 68 | "DECRYPTION_KEY": "blowfishkey", |
71 | "PARTNER_USER": "user", | 69 | "ENCRYPTION_KEY": "blowfishkey", |
72 | "PARTNER_PASSWORD": "pass", | 70 | "PARTNER_USER": "user", |
73 | "DEVICE": "dev", | 71 | "PARTNER_PASSWORD": "pass", |
74 | }).build() | 72 | "DEVICE": "dev", |
73 | } | ||
74 | ).build() | ||
75 | 75 | ||
76 | @classmethod | 76 | @classmethod |
77 | def _build_maximal(self): | 77 | def _build_maximal(self): |
78 | return cb.SettingsDictBuilder({ | 78 | return cb.SettingsDictBuilder( |
79 | "DECRYPTION_KEY": "blowfishkey", | 79 | { |
80 | "ENCRYPTION_KEY": "blowfishkey", | 80 | "DECRYPTION_KEY": "blowfishkey", |
81 | "PARTNER_USER": "user", | 81 | "ENCRYPTION_KEY": "blowfishkey", |
82 | "PARTNER_PASSWORD": "pass", | 82 | "PARTNER_USER": "user", |
83 | "DEVICE": "dev", | 83 | "PARTNER_PASSWORD": "pass", |
84 | "PROXY": "proxy.example.com", | 84 | "DEVICE": "dev", |
85 | "AUDIO_QUALITY": "high", | 85 | "PROXY": "proxy.example.com", |
86 | "API_HOST": "example.com", | 86 | "AUDIO_QUALITY": "high", |
87 | }).build() | 87 | "API_HOST": "example.com", |
88 | } | ||
89 | ).build() | ||
88 | 90 | ||
89 | def test_building(self): | 91 | def test_building(self): |
90 | client = TestSettingsDictBuilder._build_minimal() | 92 | client = TestSettingsDictBuilder._build_minimal() |
@@ -97,14 +99,15 @@ class TestSettingsDictBuilder(TestCase): | |||
97 | self.assertEqual({}, client.transport._http.proxies) | 99 | self.assertEqual({}, client.transport._http.proxies) |
98 | self.assertEqual(DEFAULT_API_HOST, client.transport.api_host) | 100 | self.assertEqual(DEFAULT_API_HOST, client.transport.api_host) |
99 | self.assertEqual( | 101 | self.assertEqual( |
100 | APIClient.MED_AUDIO_QUALITY, client.default_audio_quality) | 102 | APIClient.MED_AUDIO_QUALITY, client.default_audio_quality |
103 | ) | ||
101 | 104 | ||
102 | def test_validate_client(self): | 105 | def test_validate_client(self): |
103 | client = TestSettingsDictBuilder._build_maximal() | 106 | client = TestSettingsDictBuilder._build_maximal() |
104 | expected_proxies = { | 107 | expected_proxies = { |
105 | "http": "proxy.example.com", | 108 | "http": "proxy.example.com", |
106 | "https": "proxy.example.com" | 109 | "https": "proxy.example.com", |
107 | } | 110 | } |
108 | 111 | ||
109 | self.assertIsNotNone(client.transport.cryptor.bf_in) | 112 | self.assertIsNotNone(client.transport.cryptor.bf_in) |
110 | self.assertIsNotNone(client.transport.cryptor.bf_out) | 113 | self.assertIsNotNone(client.transport.cryptor.bf_out) |
@@ -119,7 +122,6 @@ class TestSettingsDictBuilder(TestCase): | |||
119 | 122 | ||
120 | 123 | ||
121 | class TestFileBasedBuilder(TestCase): | 124 | class TestFileBasedBuilder(TestCase): |
122 | |||
123 | class StubBuilder(cb.FileBasedClientBuilder): | 125 | class StubBuilder(cb.FileBasedClientBuilder): |
124 | 126 | ||
125 | DEFAULT_CONFIG_FILE = "foo" | 127 | DEFAULT_CONFIG_FILE = "foo" |
@@ -170,7 +172,6 @@ class TestFileBasedBuilder(TestCase): | |||
170 | 172 | ||
171 | 173 | ||
172 | class TestPydoraConfigFileBuilder(TestCase): | 174 | class TestPydoraConfigFileBuilder(TestCase): |
173 | |||
174 | def test_cfg_to_dict(self): | 175 | def test_cfg_to_dict(self): |
175 | cfg = Mock() | 176 | cfg = Mock() |
176 | cfg.items = Mock(return_value=[("a", "b"), ("c", "d")]) | 177 | cfg.items = Mock(return_value=[("a", "b"), ("c", "d")]) |
@@ -184,38 +185,43 @@ class TestPydoraConfigFileBuilder(TestCase): | |||
184 | path = os.path.join(os.path.dirname(__file__), "pydora.cfg") | 185 | path = os.path.join(os.path.dirname(__file__), "pydora.cfg") |
185 | cfg = cb.PydoraConfigFileBuilder(path).parse_config() | 186 | cfg = cb.PydoraConfigFileBuilder(path).parse_config() |
186 | 187 | ||
187 | self.assertDictEqual(cfg, { | 188 | self.assertDictEqual( |
188 | "AUDIO_QUALITY": "test_quality", | 189 | cfg, |
189 | "DECRYPTION_KEY": "test_decryption_key", | 190 | { |
190 | "DEVICE": "test_device", | 191 | "AUDIO_QUALITY": "test_quality", |
191 | "ENCRYPTION_KEY": "test_encryption_key", | 192 | "DECRYPTION_KEY": "test_decryption_key", |
192 | "PARTNER_PASSWORD": "test_partner_password", | 193 | "DEVICE": "test_device", |
193 | "PARTNER_USER": "test_partner_username", | 194 | "ENCRYPTION_KEY": "test_encryption_key", |
194 | "API_HOST": "test_host", | 195 | "PARTNER_PASSWORD": "test_partner_password", |
195 | "USER": { | 196 | "PARTNER_USER": "test_partner_username", |
196 | "USERNAME": "test_username", | 197 | "API_HOST": "test_host", |
197 | "PASSWORD": "test_password", | 198 | "USER": { |
198 | } | 199 | "USERNAME": "test_username", |
199 | }) | 200 | "PASSWORD": "test_password", |
201 | }, | ||
202 | }, | ||
203 | ) | ||
200 | 204 | ||
201 | 205 | ||
202 | class TestPianobarConfigFileBuilder(TestCase): | 206 | class TestPianobarConfigFileBuilder(TestCase): |
203 | |||
204 | def test_integration(self): | 207 | def test_integration(self): |
205 | path = os.path.join(os.path.dirname(__file__), "pianobar.cfg") | 208 | path = os.path.join(os.path.dirname(__file__), "pianobar.cfg") |
206 | cfg = cb.PianobarConfigFileBuilder(path).parse_config() | 209 | cfg = cb.PianobarConfigFileBuilder(path).parse_config() |
207 | 210 | ||
208 | self.assertDictEqual(cfg, { | 211 | self.assertDictEqual( |
209 | "AUDIO_QUALITY": "test_qualityQuality", | 212 | cfg, |
210 | "DECRYPTION_KEY": "test_decryption_key", | 213 | { |
211 | "DEVICE": "test_device", | 214 | "AUDIO_QUALITY": "test_qualityQuality", |
212 | "ENCRYPTION_KEY": "test_encryption_key", | 215 | "DECRYPTION_KEY": "test_decryption_key", |
213 | "PARTNER_PASSWORD": "test_partner_password", | 216 | "DEVICE": "test_device", |
214 | "PARTNER_USER": "test_partner_username", | 217 | "ENCRYPTION_KEY": "test_encryption_key", |
215 | "API_HOST": "test_host/services/json/", | 218 | "PARTNER_PASSWORD": "test_partner_password", |
216 | "PROXY": "test_proxy", | 219 | "PARTNER_USER": "test_partner_username", |
217 | "USER": { | 220 | "API_HOST": "test_host/services/json/", |
218 | "USERNAME": "test_username", | 221 | "PROXY": "test_proxy", |
219 | "PASSWORD": "test_password", | 222 | "USER": { |
220 | } | 223 | "USERNAME": "test_username", |
221 | }) | 224 | "PASSWORD": "test_password", |
225 | }, | ||
226 | }, | ||
227 | ) | ||
diff --git a/tests/test_pandora/test_errors.py b/tests/test_pandora/test_errors.py index 778771a..d9df41f 100644 --- a/tests/test_pandora/test_errors.py +++ b/tests/test_pandora/test_errors.py | |||
@@ -4,7 +4,6 @@ from pandora.errors import InternalServerError, PandoraException | |||
4 | 4 | ||
5 | 5 | ||
6 | class TestPandoraExceptionConstructionFromErrorCode(TestCase): | 6 | class TestPandoraExceptionConstructionFromErrorCode(TestCase): |
7 | |||
8 | def test_it_returns_specific_error_class_if_possible(self): | 7 | def test_it_returns_specific_error_class_if_possible(self): |
9 | error = PandoraException.from_code(0, "Test Message") | 8 | error = PandoraException.from_code(0, "Test Message") |
10 | self.assertIsInstance(error, InternalServerError) | 9 | self.assertIsInstance(error, InternalServerError) |
diff --git a/tests/test_pandora/test_models.py b/tests/test_pandora/test_models.py index cb650df..16307b4 100644 --- a/tests/test_pandora/test_models.py +++ b/tests/test_pandora/test_models.py | |||
@@ -14,7 +14,6 @@ import pandora.models.playlist as plm | |||
14 | 14 | ||
15 | 15 | ||
16 | class TestField(TestCase): | 16 | class TestField(TestCase): |
17 | |||
18 | def test_defaults(self): | 17 | def test_defaults(self): |
19 | field = m.Field("name") | 18 | field = m.Field("name") |
20 | 19 | ||
@@ -24,7 +23,6 @@ class TestField(TestCase): | |||
24 | 23 | ||
25 | 24 | ||
26 | class TestModelMetaClass(TestCase): | 25 | class TestModelMetaClass(TestCase): |
27 | |||
28 | class TestModel(metaclass=m.ModelMetaClass): | 26 | class TestModel(metaclass=m.ModelMetaClass): |
29 | 27 | ||
30 | foo = "bar" | 28 | foo = "bar" |
@@ -40,7 +38,6 @@ class TestModelMetaClass(TestCase): | |||
40 | 38 | ||
41 | 39 | ||
42 | class TestDateField(TestCase): | 40 | class TestDateField(TestCase): |
43 | |||
44 | class SampleModel(m.PandoraModel): | 41 | class SampleModel(m.PandoraModel): |
45 | 42 | ||
46 | date_field = m.DateField("foo") | 43 | date_field = m.DateField("foo") |
@@ -57,34 +54,26 @@ class TestDateField(TestCase): | |||
57 | 54 | ||
58 | 55 | ||
59 | class TestAdditionalUrlField(TestCase): | 56 | class TestAdditionalUrlField(TestCase): |
60 | |||
61 | def test_single_url(self): | 57 | def test_single_url(self): |
62 | dummy_data = { | 58 | dummy_data = {"_paramAdditionalUrls": ["foo"]} |
63 | '_paramAdditionalUrls': ['foo'] | ||
64 | } | ||
65 | 59 | ||
66 | field = plm.AdditionalUrlField("additionalAudioUrl") | 60 | field = plm.AdditionalUrlField("additionalAudioUrl") |
67 | 61 | ||
68 | ret = field.formatter(None, dummy_data, 'test') | 62 | ret = field.formatter(None, dummy_data, "test") |
69 | 63 | ||
70 | self.assertEqual(ret, {'foo': 'test'}) | 64 | self.assertEqual(ret, {"foo": "test"}) |
71 | 65 | ||
72 | def test_multiple_urls(self): | 66 | def test_multiple_urls(self): |
73 | dummy_data = { | 67 | dummy_data = {"_paramAdditionalUrls": ["abc", "def",]} |
74 | '_paramAdditionalUrls': [ | ||
75 | 'abc', | ||
76 | 'def', | ||
77 | ] | ||
78 | } | ||
79 | 68 | ||
80 | field = plm.AdditionalUrlField("additionalAudioUrl") | 69 | field = plm.AdditionalUrlField("additionalAudioUrl") |
81 | 70 | ||
82 | ret = field.formatter(None, dummy_data, ['foo', 'bar']) | 71 | ret = field.formatter(None, dummy_data, ["foo", "bar"]) |
83 | 72 | ||
84 | expected = { | 73 | expected = { |
85 | 'abc': 'foo', | 74 | "abc": "foo", |
86 | 'def': 'bar', | 75 | "def": "bar", |
87 | } | 76 | } |
88 | 77 | ||
89 | self.assertEqual(ret, expected) | 78 | self.assertEqual(ret, expected) |
90 | 79 | ||
@@ -99,7 +88,6 @@ class TestPandoraModel(TestCase): | |||
99 | } | 88 | } |
100 | 89 | ||
101 | class TestModel(m.PandoraModel): | 90 | class TestModel(m.PandoraModel): |
102 | |||
103 | class SubModel(m.PandoraModel): | 91 | class SubModel(m.PandoraModel): |
104 | 92 | ||
105 | field1 = m.Field("field1") | 93 | field1 = m.Field("field1") |
@@ -116,7 +104,6 @@ class TestPandoraModel(TestCase): | |||
116 | pass | 104 | pass |
117 | 105 | ||
118 | class ExtraReprModel(m.PandoraModel): | 106 | class ExtraReprModel(m.PandoraModel): |
119 | |||
120 | def __repr__(self): | 107 | def __repr__(self): |
121 | return self._base_repr("Foo") | 108 | return self._base_repr("Foo") |
122 | 109 | ||
@@ -153,9 +140,11 @@ class TestPandoraModel(TestCase): | |||
153 | self.assertEqual("a string", result[1].field1) | 140 | self.assertEqual("a string", result[1].field1) |
154 | 141 | ||
155 | def test_repr(self): | 142 | def test_repr(self): |
156 | expected = ("TestModel(field1='a string', field2=['test2'], field3=42," | 143 | expected = ( |
157 | " field4=SubModel(field1='foo'), " | 144 | "TestModel(field1='a string', field2=['test2'], field3=42," |
158 | "field5=[SubModel(field1='foo'), SubModel(field1='bar')])") | 145 | " field4=SubModel(field1='foo'), " |
146 | "field5=[SubModel(field1='foo'), SubModel(field1='bar')])" | ||
147 | ) | ||
159 | result = self.TestModel.from_json(None, self.JSON_DATA) | 148 | result = self.TestModel.from_json(None, self.JSON_DATA) |
160 | self.assertEqual(expected, repr(result)) | 149 | self.assertEqual(expected, repr(result)) |
161 | 150 | ||
@@ -179,12 +168,12 @@ class TestSubModel(m.PandoraModel): | |||
179 | class TestPandoraListModel(TestCase): | 168 | class TestPandoraListModel(TestCase): |
180 | 169 | ||
181 | JSON_DATA = { | 170 | JSON_DATA = { |
182 | "field1": 42, | 171 | "field1": 42, |
183 | "field2": [ | 172 | "field2": [ |
184 | {"idx": "foo", "fieldS1": "Foo"}, | 173 | {"idx": "foo", "fieldS1": "Foo"}, |
185 | {"idx": "bar", "fieldS1": "Bar"}, | 174 | {"idx": "bar", "fieldS1": "Bar"}, |
186 | ] | 175 | ], |
187 | } | 176 | } |
188 | 177 | ||
189 | class TestModel(m.PandoraListModel): | 178 | class TestModel(m.PandoraListModel): |
190 | 179 | ||
@@ -204,8 +193,10 @@ class TestPandoraListModel(TestCase): | |||
204 | self.assertEqual("Bar", self.result[1].fieldS1) | 193 | self.assertEqual("Bar", self.result[1].fieldS1) |
205 | 194 | ||
206 | def test_repr(self): | 195 | def test_repr(self): |
207 | expected = ("TestModel(field1=42, [TestSubModel(fieldS1='Foo', " | 196 | expected = ( |
208 | "idx='foo'), TestSubModel(fieldS1='Bar', idx='bar')])") | 197 | "TestModel(field1=42, [TestSubModel(fieldS1='Foo', " |
198 | "idx='foo'), TestSubModel(fieldS1='Bar', idx='bar')])" | ||
199 | ) | ||
209 | self.assertEqual(expected, repr(self.result)) | 200 | self.assertEqual(expected, repr(self.result)) |
210 | 201 | ||
211 | def test_indexed_model(self): | 202 | def test_indexed_model(self): |
@@ -232,17 +223,17 @@ class TestPandoraListModel(TestCase): | |||
232 | class TestPandoraDictListModel(TestCase): | 223 | class TestPandoraDictListModel(TestCase): |
233 | 224 | ||
234 | JSON_DATA = { | 225 | JSON_DATA = { |
235 | "field1": 42, | 226 | "field1": 42, |
236 | "fieldD1": [ | 227 | "fieldD1": [ |
237 | { | 228 | { |
238 | "dictKey": "Foobear", | 229 | "dictKey": "Foobear", |
239 | "listKey": [ | 230 | "listKey": [ |
240 | {"idx": "foo", "fieldS1": "Foo"}, | 231 | {"idx": "foo", "fieldS1": "Foo"}, |
241 | {"idx": "bar", "fieldS1": "Bar"}, | 232 | {"idx": "bar", "fieldS1": "Bar"}, |
242 | ] | 233 | ], |
243 | } | 234 | } |
244 | ] | 235 | ], |
245 | } | 236 | } |
246 | 237 | ||
247 | class TestModel(m.PandoraDictListModel): | 238 | class TestModel(m.PandoraDictListModel): |
248 | 239 | ||
@@ -266,9 +257,11 @@ class TestPandoraDictListModel(TestCase): | |||
266 | self.assertEqual("bar", self.result["Foobear"][1].idx) | 257 | self.assertEqual("bar", self.result["Foobear"][1].idx) |
267 | 258 | ||
268 | def test_repr(self): | 259 | def test_repr(self): |
269 | expected = ("TestModel(field1=42, {'Foobear': " | 260 | expected = ( |
270 | "[TestSubModel(fieldS1='Foo', idx='foo'), " | 261 | "TestModel(field1=42, {'Foobear': " |
271 | "TestSubModel(fieldS1='Bar', idx='bar')]})") | 262 | "[TestSubModel(fieldS1='Foo', idx='foo'), " |
263 | "TestSubModel(fieldS1='Bar', idx='bar')]})" | ||
264 | ) | ||
272 | self.assertEqual(expected, repr(self.result)) | 265 | self.assertEqual(expected, repr(self.result)) |
273 | 266 | ||
274 | 267 | ||
@@ -320,7 +313,6 @@ class TestPlaylistItemModel(TestCase): | |||
320 | 313 | ||
321 | 314 | ||
322 | class TestPlaylistModel(TestCase): | 315 | class TestPlaylistModel(TestCase): |
323 | |||
324 | def setUp(self): | 316 | def setUp(self): |
325 | self.client = Mock() | 317 | self.client = Mock() |
326 | self.playlist = plm.PlaylistModel(self.client) | 318 | self.playlist = plm.PlaylistModel(self.client) |
@@ -354,47 +346,47 @@ class TestPlaylistModel(TestCase): | |||
354 | class TestAdItem(TestCase): | 346 | class TestAdItem(TestCase): |
355 | 347 | ||
356 | JSON_DATA = { | 348 | JSON_DATA = { |
357 | 'audioUrlMap': { | 349 | "audioUrlMap": { |
358 | 'mediumQuality': { | 350 | "mediumQuality": { |
359 | 'audioUrl': 'med_url_mock', | 351 | "audioUrl": "med_url_mock", |
360 | 'bitrate': '64', | 352 | "bitrate": "64", |
361 | 'protocol': 'http', | 353 | "protocol": "http", |
362 | 'encoding': 'aacplus' | 354 | "encoding": "aacplus", |
363 | }, | 355 | }, |
364 | 'highQuality': { | 356 | "highQuality": { |
365 | 'audioUrl': 'high_url_mock', | 357 | "audioUrl": "high_url_mock", |
366 | 'bitrate': '64', | 358 | "bitrate": "64", |
367 | 'protocol': 'http', | 359 | "protocol": "http", |
368 | 'encoding': 'aacplus' | 360 | "encoding": "aacplus", |
361 | }, | ||
362 | "lowQuality": { | ||
363 | "audioUrl": "low_url_mock", | ||
364 | "bitrate": "32", | ||
365 | "protocol": "http", | ||
366 | "encoding": "aacplus", | ||
369 | }, | 367 | }, |
370 | 'lowQuality': { | ||
371 | 'audioUrl': 'low_url_mock', | ||
372 | 'bitrate': '32', | ||
373 | 'protocol': 'http', | ||
374 | 'encoding': 'aacplus' | ||
375 | } | ||
376 | }, | 368 | }, |
377 | 'clickThroughUrl': 'click_url_mock', | 369 | "clickThroughUrl": "click_url_mock", |
378 | 'imageUrl': 'img_url_mock', | 370 | "imageUrl": "img_url_mock", |
379 | 'companyName': '', | 371 | "companyName": "", |
380 | 'title': '', | 372 | "title": "", |
381 | 'trackGain': '0.0', | 373 | "trackGain": "0.0", |
382 | 'adTrackingTokens': ['token_1_mock', 'token_2_mock'] | 374 | "adTrackingTokens": ["token_1_mock", "token_2_mock"], |
383 | } | 375 | } |
384 | 376 | ||
385 | def setUp(self): | 377 | def setUp(self): |
386 | api_client_mock = Mock(spec=APIClient) | 378 | api_client_mock = Mock(spec=APIClient) |
387 | api_client_mock.default_audio_quality = APIClient.HIGH_AUDIO_QUALITY | 379 | api_client_mock.default_audio_quality = APIClient.HIGH_AUDIO_QUALITY |
388 | self.result = am.AdItem.from_json(api_client_mock, self.JSON_DATA) | 380 | self.result = am.AdItem.from_json(api_client_mock, self.JSON_DATA) |
389 | self.result.station_id = 'station_id_mock' | 381 | self.result.station_id = "station_id_mock" |
390 | self.result.ad_token = 'token_mock' | 382 | self.result.ad_token = "token_mock" |
391 | 383 | ||
392 | def test_is_ad_is_true(self): | 384 | def test_is_ad_is_true(self): |
393 | assert self.result.is_ad is True | 385 | assert self.result.is_ad is True |
394 | 386 | ||
395 | def test_register_ad(self): | 387 | def test_register_ad(self): |
396 | self.result._api_client.register_ad = Mock() | 388 | self.result._api_client.register_ad = Mock() |
397 | self.result.register_ad('id_mock') | 389 | self.result.register_ad("id_mock") |
398 | 390 | ||
399 | assert self.result._api_client.register_ad.called | 391 | assert self.result._api_client.register_ad.called |
400 | 392 | ||
@@ -403,12 +395,12 @@ class TestAdItem(TestCase): | |||
403 | self.result.tracking_tokens = [] | 395 | self.result.tracking_tokens = [] |
404 | self.result._api_client.register_ad = Mock(spec=am.AdItem) | 396 | self.result._api_client.register_ad = Mock(spec=am.AdItem) |
405 | 397 | ||
406 | self.result.register_ad('id_mock') | 398 | self.result.register_ad("id_mock") |
407 | 399 | ||
408 | assert self.result._api_client.register_ad.called | 400 | assert self.result._api_client.register_ad.called |
409 | 401 | ||
410 | def test_prepare_playback(self): | 402 | def test_prepare_playback(self): |
411 | with patch.object(plm.PlaylistModel, 'prepare_playback') as super_mock: | 403 | with patch.object(plm.PlaylistModel, "prepare_playback") as super_mock: |
412 | 404 | ||
413 | self.result.register_ad = Mock() | 405 | self.result.register_ad = Mock() |
414 | self.result.prepare_playback() | 406 | self.result.prepare_playback() |
@@ -416,21 +408,26 @@ class TestAdItem(TestCase): | |||
416 | assert super_mock.called | 408 | assert super_mock.called |
417 | 409 | ||
418 | def test_prepare_playback_raises_paramater_missing(self): | 410 | def test_prepare_playback_raises_paramater_missing(self): |
419 | with patch.object(plm.PlaylistModel, 'prepare_playback') as super_mock: | 411 | with patch.object(plm.PlaylistModel, "prepare_playback") as super_mock: |
420 | 412 | ||
421 | self.result.register_ad = Mock(side_effect=ParameterMissing( | 413 | self.result.register_ad = Mock( |
422 | 'No ad tracking tokens provided for registration.') | 414 | side_effect=ParameterMissing( |
423 | ) | 415 | "No ad tracking tokens provided for registration." |
416 | ) | ||
417 | ) | ||
424 | self.assertRaises(ParameterMissing, self.result.prepare_playback) | 418 | self.assertRaises(ParameterMissing, self.result.prepare_playback) |
425 | assert self.result.register_ad.called | 419 | assert self.result.register_ad.called |
426 | assert not super_mock.called | 420 | assert not super_mock.called |
427 | 421 | ||
428 | def test_prepare_playback_handles_paramater_missing_if_no_tokens(self): | 422 | def test_prepare_playback_handles_paramater_missing_if_no_tokens(self): |
429 | with patch.object(plm.PlaylistModel, 'prepare_playback') as super_mock: | 423 | with patch.object(plm.PlaylistModel, "prepare_playback") as super_mock: |
430 | 424 | ||
431 | self.result.tracking_tokens = [] | 425 | self.result.tracking_tokens = [] |
432 | self.result.register_ad = Mock(side_effect=ParameterMissing( | 426 | self.result.register_ad = Mock( |
433 | 'No ad tracking tokens provided for registration.')) | 427 | side_effect=ParameterMissing( |
428 | "No ad tracking tokens provided for registration." | ||
429 | ) | ||
430 | ) | ||
434 | self.result.prepare_playback() | 431 | self.result.prepare_playback() |
435 | assert self.result.register_ad.called | 432 | assert self.result.register_ad.called |
436 | assert super_mock.called | 433 | assert super_mock.called |
@@ -449,43 +446,45 @@ class TestSearchResultItem(TestCase): | |||
449 | "artistName": "artist_name_mock", | 446 | "artistName": "artist_name_mock", |
450 | "musicToken": "S0000000", | 447 | "musicToken": "S0000000", |
451 | "songName": "song_name_mock", | 448 | "songName": "song_name_mock", |
452 | "score": 100 | 449 | "score": 100, |
453 | } | 450 | } |
454 | 451 | ||
455 | ARTIST_JSON_DATA = { | 452 | ARTIST_JSON_DATA = { |
456 | "artistName": "artist_name_mock", | 453 | "artistName": "artist_name_mock", |
457 | "musicToken": "R0000000", | 454 | "musicToken": "R0000000", |
458 | "likelyMatch": False, | 455 | "likelyMatch": False, |
459 | "score": 100 | 456 | "score": 100, |
460 | } | 457 | } |
461 | 458 | ||
462 | COMPOSER_JSON_DATA = { | 459 | COMPOSER_JSON_DATA = { |
463 | "artistName": "composer_name_mock", | 460 | "artistName": "composer_name_mock", |
464 | "musicToken": "C0000000", | 461 | "musicToken": "C0000000", |
465 | "likelyMatch": False, | 462 | "likelyMatch": False, |
466 | "score": 100 | 463 | "score": 100, |
467 | } | 464 | } |
468 | 465 | ||
469 | GENRE_JSON_DATA = { | 466 | GENRE_JSON_DATA = { |
470 | "stationName": "station_name_mock", | 467 | "stationName": "station_name_mock", |
471 | "musicToken": "G0000000", | 468 | "musicToken": "G0000000", |
472 | "score": 100 | 469 | "score": 100, |
473 | } | 470 | } |
474 | 471 | ||
475 | UNKNOWN_JSON_DATA = { | 472 | UNKNOWN_JSON_DATA = { |
476 | "stationName": "unknown_name_mock", | 473 | "stationName": "unknown_name_mock", |
477 | "musicToken": "U0000000", | 474 | "musicToken": "U0000000", |
478 | "score": 100 | 475 | "score": 100, |
479 | } | 476 | } |
480 | 477 | ||
481 | def setUp(self): | 478 | def setUp(self): |
482 | self.api_client_mock = Mock(spec=APIClient) | 479 | self.api_client_mock = Mock(spec=APIClient) |
483 | self.api_client_mock.default_audio_quality = \ | 480 | self.api_client_mock.default_audio_quality = ( |
484 | APIClient.HIGH_AUDIO_QUALITY | 481 | APIClient.HIGH_AUDIO_QUALITY |
482 | ) | ||
485 | 483 | ||
486 | def test_is_song(self): | 484 | def test_is_song(self): |
487 | result = sm.SearchResultItem.from_json( | 485 | result = sm.SearchResultItem.from_json( |
488 | self.api_client_mock, self.SONG_JSON_DATA) | 486 | self.api_client_mock, self.SONG_JSON_DATA |
487 | ) | ||
489 | assert result.is_song | 488 | assert result.is_song |
490 | assert not result.is_artist | 489 | assert not result.is_artist |
491 | assert not result.is_composer | 490 | assert not result.is_composer |
@@ -493,7 +492,8 @@ class TestSearchResultItem(TestCase): | |||
493 | 492 | ||
494 | def test_is_artist(self): | 493 | def test_is_artist(self): |
495 | result = sm.SearchResultItem.from_json( | 494 | result = sm.SearchResultItem.from_json( |
496 | self.api_client_mock, self.ARTIST_JSON_DATA) | 495 | self.api_client_mock, self.ARTIST_JSON_DATA |
496 | ) | ||
497 | assert not result.is_song | 497 | assert not result.is_song |
498 | assert result.is_artist | 498 | assert result.is_artist |
499 | assert not result.is_composer | 499 | assert not result.is_composer |
@@ -501,7 +501,8 @@ class TestSearchResultItem(TestCase): | |||
501 | 501 | ||
502 | def test_is_composer(self): | 502 | def test_is_composer(self): |
503 | result = sm.SearchResultItem.from_json( | 503 | result = sm.SearchResultItem.from_json( |
504 | self.api_client_mock, self.COMPOSER_JSON_DATA) | 504 | self.api_client_mock, self.COMPOSER_JSON_DATA |
505 | ) | ||
505 | assert not result.is_song | 506 | assert not result.is_song |
506 | assert not result.is_artist | 507 | assert not result.is_artist |
507 | assert result.is_composer | 508 | assert result.is_composer |
@@ -509,7 +510,8 @@ class TestSearchResultItem(TestCase): | |||
509 | 510 | ||
510 | def test_is_genre_station(self): | 511 | def test_is_genre_station(self): |
511 | result = sm.SearchResultItem.from_json( | 512 | result = sm.SearchResultItem.from_json( |
512 | self.api_client_mock, self.GENRE_JSON_DATA) | 513 | self.api_client_mock, self.GENRE_JSON_DATA |
514 | ) | ||
513 | assert not result.is_song | 515 | assert not result.is_song |
514 | assert not result.is_artist | 516 | assert not result.is_artist |
515 | assert not result.is_composer | 517 | assert not result.is_composer |
@@ -518,7 +520,8 @@ class TestSearchResultItem(TestCase): | |||
518 | def test_fails_if_unknown(self): | 520 | def test_fails_if_unknown(self): |
519 | with self.assertRaises(NotImplementedError): | 521 | with self.assertRaises(NotImplementedError): |
520 | sm.SearchResultItem.from_json( | 522 | sm.SearchResultItem.from_json( |
521 | self.api_client_mock, self.UNKNOWN_JSON_DATA) | 523 | self.api_client_mock, self.UNKNOWN_JSON_DATA |
524 | ) | ||
522 | 525 | ||
523 | def test_interface(self): | 526 | def test_interface(self): |
524 | result = sm.SearchResultItem(self.api_client_mock) | 527 | result = sm.SearchResultItem(self.api_client_mock) |
@@ -533,44 +536,51 @@ class TestArtistSearchResultItem(TestCase): | |||
533 | "artistName": "artist_name_mock", | 536 | "artistName": "artist_name_mock", |
534 | "musicToken": "R0000000", | 537 | "musicToken": "R0000000", |
535 | "likelyMatch": False, | 538 | "likelyMatch": False, |
536 | "score": 100 | 539 | "score": 100, |
537 | } | 540 | } |
538 | 541 | ||
539 | COMPOSER_JSON_DATA = { | 542 | COMPOSER_JSON_DATA = { |
540 | "artistName": "composer_name_mock", | 543 | "artistName": "composer_name_mock", |
541 | "musicToken": "C0000000", | 544 | "musicToken": "C0000000", |
542 | "likelyMatch": False, | 545 | "likelyMatch": False, |
543 | "score": 100 | 546 | "score": 100, |
544 | } | 547 | } |
545 | 548 | ||
546 | def setUp(self): | 549 | def setUp(self): |
547 | self.api_client_mock = Mock(spec=APIClient) | 550 | self.api_client_mock = Mock(spec=APIClient) |
548 | self.api_client_mock.default_audio_quality = \ | 551 | self.api_client_mock.default_audio_quality = ( |
549 | APIClient.HIGH_AUDIO_QUALITY | 552 | APIClient.HIGH_AUDIO_QUALITY |
553 | ) | ||
550 | 554 | ||
551 | def test_repr(self): | 555 | def test_repr(self): |
552 | result = sm.SearchResultItem.from_json( | 556 | result = sm.SearchResultItem.from_json( |
553 | self.api_client_mock, self.ARTIST_JSON_DATA) | 557 | self.api_client_mock, self.ARTIST_JSON_DATA |
558 | ) | ||
554 | expected = ( | 559 | expected = ( |
555 | "ArtistSearchResultItem(artist='artist_name_mock', " | 560 | "ArtistSearchResultItem(artist='artist_name_mock', " |
556 | "likely_match=False, score=100, token='R0000000')") | 561 | "likely_match=False, score=100, token='R0000000')" |
562 | ) | ||
557 | self.assertEqual(expected, repr(result)) | 563 | self.assertEqual(expected, repr(result)) |
558 | 564 | ||
559 | result = sm.SearchResultItem.from_json( | 565 | result = sm.SearchResultItem.from_json( |
560 | self.api_client_mock, self.COMPOSER_JSON_DATA) | 566 | self.api_client_mock, self.COMPOSER_JSON_DATA |
567 | ) | ||
561 | expected = ( | 568 | expected = ( |
562 | "ArtistSearchResultItem(artist='composer_name_mock', " | 569 | "ArtistSearchResultItem(artist='composer_name_mock', " |
563 | "likely_match=False, score=100, token='C0000000')") | 570 | "likely_match=False, score=100, token='C0000000')" |
571 | ) | ||
564 | self.assertEqual(expected, repr(result)) | 572 | self.assertEqual(expected, repr(result)) |
565 | 573 | ||
566 | def test_create_station(self): | 574 | def test_create_station(self): |
567 | result = sm.SearchResultItem.from_json( | 575 | result = sm.SearchResultItem.from_json( |
568 | self.api_client_mock, self.ARTIST_JSON_DATA) | 576 | self.api_client_mock, self.ARTIST_JSON_DATA |
577 | ) | ||
569 | result._api_client.create_station = Mock() | 578 | result._api_client.create_station = Mock() |
570 | 579 | ||
571 | result.create_station() | 580 | result.create_station() |
572 | result._api_client.create_station.assert_called_with( | 581 | result._api_client.create_station.assert_called_with( |
573 | artist_token=result.token) | 582 | artist_token=result.token |
583 | ) | ||
574 | 584 | ||
575 | 585 | ||
576 | class TestSongSearchResultItem(TestCase): | 586 | class TestSongSearchResultItem(TestCase): |
@@ -579,30 +589,35 @@ class TestSongSearchResultItem(TestCase): | |||
579 | "artistName": "artist_name_mock", | 589 | "artistName": "artist_name_mock", |
580 | "musicToken": "S0000000", | 590 | "musicToken": "S0000000", |
581 | "songName": "song_name_mock", | 591 | "songName": "song_name_mock", |
582 | "score": 100 | 592 | "score": 100, |
583 | } | 593 | } |
584 | 594 | ||
585 | def setUp(self): | 595 | def setUp(self): |
586 | self.api_client_mock = Mock(spec=APIClient) | 596 | self.api_client_mock = Mock(spec=APIClient) |
587 | self.api_client_mock.default_audio_quality = \ | 597 | self.api_client_mock.default_audio_quality = ( |
588 | APIClient.HIGH_AUDIO_QUALITY | 598 | APIClient.HIGH_AUDIO_QUALITY |
599 | ) | ||
589 | 600 | ||
590 | def test_repr(self): | 601 | def test_repr(self): |
591 | result = sm.SearchResultItem.from_json( | 602 | result = sm.SearchResultItem.from_json( |
592 | self.api_client_mock, self.SONG_JSON_DATA) | 603 | self.api_client_mock, self.SONG_JSON_DATA |
604 | ) | ||
593 | expected = ( | 605 | expected = ( |
594 | "SongSearchResultItem(artist='artist_name_mock', score=100, " | 606 | "SongSearchResultItem(artist='artist_name_mock', score=100, " |
595 | "song_name='song_name_mock', token='S0000000')") | 607 | "song_name='song_name_mock', token='S0000000')" |
608 | ) | ||
596 | self.assertEqual(expected, repr(result)) | 609 | self.assertEqual(expected, repr(result)) |
597 | 610 | ||
598 | def test_create_station(self): | 611 | def test_create_station(self): |
599 | result = sm.SearchResultItem.from_json( | 612 | result = sm.SearchResultItem.from_json( |
600 | self.api_client_mock, self.SONG_JSON_DATA) | 613 | self.api_client_mock, self.SONG_JSON_DATA |
614 | ) | ||
601 | result._api_client.create_station = Mock() | 615 | result._api_client.create_station = Mock() |
602 | 616 | ||
603 | result.create_station() | 617 | result.create_station() |
604 | result._api_client.create_station.assert_called_with( | 618 | result._api_client.create_station.assert_called_with( |
605 | track_token=result.token) | 619 | track_token=result.token |
620 | ) | ||
606 | 621 | ||
607 | 622 | ||
608 | class TestGenreStationSearchResultItem(TestCase): | 623 | class TestGenreStationSearchResultItem(TestCase): |
@@ -610,61 +625,69 @@ class TestGenreStationSearchResultItem(TestCase): | |||
610 | GENRE_JSON_DATA = { | 625 | GENRE_JSON_DATA = { |
611 | "stationName": "station_name_mock", | 626 | "stationName": "station_name_mock", |
612 | "musicToken": "G0000000", | 627 | "musicToken": "G0000000", |
613 | "score": 100 | 628 | "score": 100, |
614 | } | 629 | } |
615 | 630 | ||
616 | def setUp(self): | 631 | def setUp(self): |
617 | self.api_client_mock = Mock(spec=APIClient) | 632 | self.api_client_mock = Mock(spec=APIClient) |
618 | self.api_client_mock.default_audio_quality = \ | 633 | self.api_client_mock.default_audio_quality = ( |
619 | APIClient.HIGH_AUDIO_QUALITY | 634 | APIClient.HIGH_AUDIO_QUALITY |
635 | ) | ||
620 | 636 | ||
621 | def test_repr(self): | 637 | def test_repr(self): |
622 | result = sm.SearchResultItem.from_json( | 638 | result = sm.SearchResultItem.from_json( |
623 | self.api_client_mock, self.GENRE_JSON_DATA) | 639 | self.api_client_mock, self.GENRE_JSON_DATA |
640 | ) | ||
624 | expected = ( | 641 | expected = ( |
625 | "GenreStationSearchResultItem(score=100, " | 642 | "GenreStationSearchResultItem(score=100, " |
626 | "station_name='station_name_mock', token='G0000000')") | 643 | "station_name='station_name_mock', token='G0000000')" |
644 | ) | ||
627 | self.assertEqual(expected, repr(result)) | 645 | self.assertEqual(expected, repr(result)) |
628 | 646 | ||
629 | def test_create_station(self): | 647 | def test_create_station(self): |
630 | result = sm.SearchResultItem.from_json( | 648 | result = sm.SearchResultItem.from_json( |
631 | self.api_client_mock, self.GENRE_JSON_DATA) | 649 | self.api_client_mock, self.GENRE_JSON_DATA |
650 | ) | ||
632 | result._api_client.create_station = Mock() | 651 | result._api_client.create_station = Mock() |
633 | 652 | ||
634 | result.create_station() | 653 | result.create_station() |
635 | result._api_client.create_station.assert_called_with( | 654 | result._api_client.create_station.assert_called_with( |
636 | search_token=result.token) | 655 | search_token=result.token |
656 | ) | ||
637 | 657 | ||
638 | 658 | ||
639 | class TestSearchResult(TestCase): | 659 | class TestSearchResult(TestCase): |
640 | 660 | ||
641 | JSON_DATA = { | 661 | JSON_DATA = { |
642 | 'nearMatchesAvailable': True, | 662 | "nearMatchesAvailable": True, |
643 | 'explanation': '', | 663 | "explanation": "", |
644 | 'songs': [{ | 664 | "songs": [ |
645 | 'artistName': 'song_artist_mock', | 665 | { |
646 | 'musicToken': 'S0000000', | 666 | "artistName": "song_artist_mock", |
647 | 'songName': 'song_name_mock', | 667 | "musicToken": "S0000000", |
648 | 'score': 100 | 668 | "songName": "song_name_mock", |
649 | }], | 669 | "score": 100, |
650 | 'artists': [{ | 670 | } |
651 | 'artistName': 'artist_mock', | 671 | ], |
652 | 'musicToken': 'R000000', | 672 | "artists": [ |
653 | 'likelyMatch': False, | 673 | { |
654 | 'score': 80 | 674 | "artistName": "artist_mock", |
655 | }], | 675 | "musicToken": "R000000", |
656 | 'genreStations': [{ | 676 | "likelyMatch": False, |
657 | 'musicToken': 'G0000', | 677 | "score": 80, |
658 | 'stationName': 'station_mock', | 678 | } |
659 | 'score': 50 | 679 | ], |
660 | }] | 680 | "genreStations": [ |
681 | {"musicToken": "G0000", "stationName": "station_mock", "score": 50} | ||
682 | ], | ||
661 | } | 683 | } |
662 | 684 | ||
663 | def setUp(self): | 685 | def setUp(self): |
664 | api_client_mock = Mock(spec=APIClient) | 686 | api_client_mock = Mock(spec=APIClient) |
665 | api_client_mock.default_audio_quality = APIClient.HIGH_AUDIO_QUALITY | 687 | api_client_mock.default_audio_quality = APIClient.HIGH_AUDIO_QUALITY |
666 | self.result = sm.SearchResult.from_json( | 688 | self.result = sm.SearchResult.from_json( |
667 | api_client_mock, self.JSON_DATA) | 689 | api_client_mock, self.JSON_DATA |
690 | ) | ||
668 | 691 | ||
669 | def test_repr(self): | 692 | def test_repr(self): |
670 | expected = ( | 693 | expected = ( |
@@ -676,7 +699,8 @@ class TestSearchResult(TestCase): | |||
676 | "nearest_matches_available=True, " | 699 | "nearest_matches_available=True, " |
677 | "songs=[SongSearchResultItem(artist='song_artist_mock', " | 700 | "songs=[SongSearchResultItem(artist='song_artist_mock', " |
678 | "score=100, song_name='song_name_mock', " | 701 | "score=100, song_name='song_name_mock', " |
679 | "token='S0000000')])") | 702 | "token='S0000000')])" |
703 | ) | ||
680 | self.assertEqual(expected, repr(self.result)) | 704 | self.assertEqual(expected, repr(self.result)) |
681 | 705 | ||
682 | 706 | ||
@@ -684,9 +708,7 @@ class TestGenreStationList(TestCase): | |||
684 | 708 | ||
685 | TEST_DATA = { | 709 | TEST_DATA = { |
686 | "checksum": "bar", | 710 | "checksum": "bar", |
687 | "categories": [ | 711 | "categories": [{"categoryName": "foo", "stations": []},], |
688 | {"categoryName": "foo", "stations": []}, | ||
689 | ] | ||
690 | } | 712 | } |
691 | 713 | ||
692 | def test_has_changed(self): | 714 | def test_has_changed(self): |
@@ -706,7 +728,8 @@ class TestGenreStation(TestCase): | |||
706 | genre_station = stm.GenreStation.from_json(api_client, self.TEST_DATA) | 728 | genre_station = stm.GenreStation.from_json(api_client, self.TEST_DATA) |
707 | 729 | ||
708 | with self.assertRaisesRegex( | 730 | with self.assertRaisesRegex( |
709 | NotImplementedError, "Genre stations do not have playlists.*"): | 731 | NotImplementedError, "Genre stations do not have playlists.*" |
732 | ): | ||
710 | genre_station.get_playlist() | 733 | genre_station.get_playlist() |
711 | 734 | ||
712 | 735 | ||
@@ -750,7 +773,6 @@ class TestBookmark(TestCase): | |||
750 | 773 | ||
751 | 774 | ||
752 | class TestPandoraType(TestCase): | 775 | class TestPandoraType(TestCase): |
753 | |||
754 | def test_it_can_be_built_from_a_model(self): | 776 | def test_it_can_be_built_from_a_model(self): |
755 | pt = plm.PandoraType.from_model(None, "TR") | 777 | pt = plm.PandoraType.from_model(None, "TR") |
756 | self.assertIs(plm.PandoraType.TRACK, pt) | 778 | self.assertIs(plm.PandoraType.TRACK, pt) |
@@ -765,7 +787,6 @@ class TestPandoraType(TestCase): | |||
765 | 787 | ||
766 | 788 | ||
767 | class TestSyntheticField(TestCase): | 789 | class TestSyntheticField(TestCase): |
768 | |||
769 | def test_interface(self): | 790 | def test_interface(self): |
770 | sf = m.SyntheticField(field="foo") | 791 | sf = m.SyntheticField(field="foo") |
771 | 792 | ||
diff --git a/tests/test_pandora/test_transport.py b/tests/test_pandora/test_transport.py index 851fa06..28ca6b2 100644 --- a/tests/test_pandora/test_transport.py +++ b/tests/test_pandora/test_transport.py | |||
@@ -16,12 +16,12 @@ class SysCallError(Exception): | |||
16 | 16 | ||
17 | 17 | ||
18 | class TestTransport(TestCase): | 18 | class TestTransport(TestCase): |
19 | |||
20 | def test_test_url_should_return_true_if_request_okay(self): | 19 | def test_test_url_should_return_true_if_request_okay(self): |
21 | transport = t.APITransport(Mock()) | 20 | transport = t.APITransport(Mock()) |
22 | transport._http = Mock() | 21 | transport._http = Mock() |
23 | transport._http.head.return_value = Mock( | 22 | transport._http.head.return_value = Mock( |
24 | status_code=requests.codes.not_found) | 23 | status_code=requests.codes.not_found |
24 | ) | ||
25 | 25 | ||
26 | self.assertFalse(transport.test_url("foo")) | 26 | self.assertFalse(transport.test_url("foo")) |
27 | 27 | ||
@@ -34,7 +34,8 @@ class TestTransport(TestCase): | |||
34 | 34 | ||
35 | time.sleep = Mock() | 35 | time.sleep = Mock() |
36 | client.transport._make_http_request = Mock( | 36 | client.transport._make_http_request = Mock( |
37 | side_effect=SysCallError("error_mock")) | 37 | side_effect=SysCallError("error_mock") |
38 | ) | ||
38 | client.transport._start_request = Mock() | 39 | client.transport._start_request = Mock() |
39 | 40 | ||
40 | client("method") | 41 | client("method") |
@@ -48,7 +49,8 @@ class TestTransport(TestCase): | |||
48 | 49 | ||
49 | time.sleep = Mock() | 50 | time.sleep = Mock() |
50 | client.transport._make_http_request = Mock( | 51 | client.transport._make_http_request = Mock( |
51 | side_effect=PandoraException("error_mock")) | 52 | side_effect=PandoraException("error_mock") |
53 | ) | ||
52 | client.transport._start_request = Mock() | 54 | client.transport._start_request = Mock() |
53 | 55 | ||
54 | client("method") | 56 | client("method") |
@@ -62,7 +64,8 @@ class TestTransport(TestCase): | |||
62 | 64 | ||
63 | time.sleep = Mock() | 65 | time.sleep = Mock() |
64 | client.transport._make_http_request = Mock( | 66 | client.transport._make_http_request = Mock( |
65 | side_effect=InvalidAuthToken("error_mock")) | 67 | side_effect=InvalidAuthToken("error_mock") |
68 | ) | ||
66 | client.transport._start_request = Mock() | 69 | client.transport._start_request = Mock() |
67 | 70 | ||
68 | client._authenticate = Mock() | 71 | client._authenticate = Mock() |
@@ -82,11 +85,11 @@ class TestTransport(TestCase): | |||
82 | transport._http.post.return_value = http_result | 85 | transport._http.post.return_value = http_result |
83 | 86 | ||
84 | self.assertEqual( | 87 | self.assertEqual( |
85 | "bar", transport(t.APITransport.NO_ENCRYPT[0], foo="bar")) | 88 | "bar", transport(t.APITransport.NO_ENCRYPT[0], foo="bar") |
89 | ) | ||
86 | 90 | ||
87 | 91 | ||
88 | class TestTransportSetters(TestCase): | 92 | class TestTransportSetters(TestCase): |
89 | |||
90 | def setUp(self): | 93 | def setUp(self): |
91 | self.cryptor = Mock() | 94 | self.cryptor = Mock() |
92 | self.transport = t.APITransport(self.cryptor) | 95 | self.transport = t.APITransport(self.cryptor) |
@@ -94,27 +97,29 @@ class TestTransportSetters(TestCase): | |||
94 | def test_set_partner(self): | 97 | def test_set_partner(self): |
95 | self.cryptor.decrypt_sync_time.return_value = 456 | 98 | self.cryptor.decrypt_sync_time.return_value = 456 |
96 | 99 | ||
97 | self.transport.set_partner({ | 100 | self.transport.set_partner( |
98 | "syncTime": "123", | 101 | { |
99 | "partnerAuthToken": "partner_auth_token", | 102 | "syncTime": "123", |
100 | "partnerId": "partner_id", | 103 | "partnerAuthToken": "partner_auth_token", |
101 | }) | 104 | "partnerId": "partner_id", |
105 | } | ||
106 | ) | ||
102 | 107 | ||
103 | self.cryptor.decrypt_sync_time.assert_called_with("123") | 108 | self.cryptor.decrypt_sync_time.assert_called_with("123") |
104 | self.assertEqual("partner_auth_token", self.transport.auth_token) | 109 | self.assertEqual("partner_auth_token", self.transport.auth_token) |
105 | self.assertEqual("partner_id", self.transport.partner_id) | 110 | self.assertEqual("partner_id", self.transport.partner_id) |
106 | self.assertEqual( | 111 | self.assertEqual( |
107 | "partner_auth_token", self.transport.partner_auth_token) | 112 | "partner_auth_token", self.transport.partner_auth_token |
113 | ) | ||
108 | 114 | ||
109 | self.transport.start_time = 10 | 115 | self.transport.start_time = 10 |
110 | with patch.object(time, "time", return_value=30): | 116 | with patch.object(time, "time", return_value=30): |
111 | self.assertEqual(476, self.transport.sync_time) | 117 | self.assertEqual(476, self.transport.sync_time) |
112 | 118 | ||
113 | def test_set_user(self): | 119 | def test_set_user(self): |
114 | self.transport.set_user({ | 120 | self.transport.set_user( |
115 | "userId": "user", | 121 | {"userId": "user", "userAuthToken": "auth",} |
116 | "userAuthToken": "auth", | 122 | ) |
117 | }) | ||
118 | 123 | ||
119 | self.assertEqual("user", self.transport.user_id) | 124 | self.assertEqual("user", self.transport.user_id) |
120 | self.assertEqual("auth", self.transport.user_auth_token) | 125 | self.assertEqual("auth", self.transport.user_auth_token) |
@@ -126,7 +131,6 @@ class TestTransportSetters(TestCase): | |||
126 | 131 | ||
127 | 132 | ||
128 | class TestDelayExponential(TestCase): | 133 | class TestDelayExponential(TestCase): |
129 | |||
130 | def test_fixed_delay(self): | 134 | def test_fixed_delay(self): |
131 | self.assertEqual(8, t.delay_exponential(2, 2, 3)) | 135 | self.assertEqual(8, t.delay_exponential(2, 2, 3)) |
132 | 136 | ||
@@ -143,7 +147,6 @@ class TestDelayExponential(TestCase): | |||
143 | 147 | ||
144 | 148 | ||
145 | class TestRetries(TestCase): | 149 | class TestRetries(TestCase): |
146 | |||
147 | def test_no_retries_returns_none(self): | 150 | def test_no_retries_returns_none(self): |
148 | @t.retries(0) | 151 | @t.retries(0) |
149 | def foo(): | 152 | def foo(): |
@@ -178,7 +181,6 @@ class TestParseResponse(TestCase): | |||
178 | 181 | ||
179 | 182 | ||
180 | class TestTransportRequestPrep(TestCase): | 183 | class TestTransportRequestPrep(TestCase): |
181 | |||
182 | def setUp(self): | 184 | def setUp(self): |
183 | self.cryptor = Mock() | 185 | self.cryptor = Mock() |
184 | self.transport = t.APITransport(self.cryptor) | 186 | self.transport = t.APITransport(self.cryptor) |
@@ -207,7 +209,8 @@ class TestTransportRequestPrep(TestCase): | |||
207 | 209 | ||
208 | self.transport._http = http | 210 | self.transport._http = http |
209 | res = self.transport._make_http_request( | 211 | res = self.transport._make_http_request( |
210 | "/url", b"data", {"a": None, "b": "c"}) | 212 | "/url", b"data", {"a": None, "b": "c"} |
213 | ) | ||
211 | 214 | ||
212 | http.post.assert_called_with("/url", data=b"data", params={"b": "c"}) | 215 | http.post.assert_called_with("/url", data=b"data", params={"b": "c"}) |
213 | retval.raise_for_status.assert_called_with() | 216 | retval.raise_for_status.assert_called_with() |
@@ -237,7 +240,8 @@ class TestTransportRequestPrep(TestCase): | |||
237 | 240 | ||
238 | with patch.object(time, "time", return_value=20): | 241 | with patch.object(time, "time", return_value=20): |
239 | val = self.transport._build_data( | 242 | val = self.transport._build_data( |
240 | t.APITransport.NO_ENCRYPT[0], {"a": "b", "c": None}) | 243 | t.APITransport.NO_ENCRYPT[0], {"a": "b", "c": None} |
244 | ) | ||
241 | 245 | ||
242 | val = json.loads(val) | 246 | val = json.loads(val) |
243 | self.assertEqual("b", val["a"]) | 247 | self.assertEqual("b", val["a"]) |
@@ -247,7 +251,6 @@ class TestTransportRequestPrep(TestCase): | |||
247 | 251 | ||
248 | # All Cryptor implementations must pass these test cases unmodified | 252 | # All Cryptor implementations must pass these test cases unmodified |
249 | class CommonCryptorTestCases: | 253 | class CommonCryptorTestCases: |
250 | |||
251 | def test_decrypt_invalid_padding(self): | 254 | def test_decrypt_invalid_padding(self): |
252 | with self.assertRaises(ValueError): | 255 | with self.assertRaises(ValueError): |
253 | data = b"12345678\x00" | 256 | data = b"12345678\x00" |
@@ -267,7 +270,6 @@ class CommonCryptorTestCases: | |||
267 | 270 | ||
268 | 271 | ||
269 | class TestPurePythonBlowfishCryptor(TestCase, CommonCryptorTestCases): | 272 | class TestPurePythonBlowfishCryptor(TestCase, CommonCryptorTestCases): |
270 | |||
271 | def setUp(self): | 273 | def setUp(self): |
272 | # Ugh... blowfish can't even be *imported* in python2 | 274 | # Ugh... blowfish can't even be *imported* in python2 |
273 | if not t.blowfish: | 275 | if not t.blowfish: |
@@ -288,7 +290,6 @@ class TestEncryptor(TestCase): | |||
288 | ENCODED_TIME = "31353037343131313539" | 290 | ENCODED_TIME = "31353037343131313539" |
289 | 291 | ||
290 | class NoopCrypto: | 292 | class NoopCrypto: |
291 | |||
292 | def __init__(self, key): | 293 | def __init__(self, key): |
293 | pass | 294 | pass |
294 | 295 | ||
@@ -303,14 +304,17 @@ class TestEncryptor(TestCase): | |||
303 | 304 | ||
304 | def test_decrypt(self): | 305 | def test_decrypt(self): |
305 | self.assertEqual( | 306 | self.assertEqual( |
306 | {"foo": "bar"}, self.cryptor.decrypt(self.ENCODED_JSON)) | 307 | {"foo": "bar"}, self.cryptor.decrypt(self.ENCODED_JSON) |
308 | ) | ||
307 | 309 | ||
308 | def test_encrypt(self): | 310 | def test_encrypt(self): |
309 | self.assertEqual( | 311 | self.assertEqual( |
310 | self.ENCODED_JSON.encode("ascii"), | 312 | self.ENCODED_JSON.encode("ascii"), |
311 | self.cryptor.encrypt(self.UNENCODED_JSON)) | 313 | self.cryptor.encrypt(self.UNENCODED_JSON), |
314 | ) | ||
312 | 315 | ||
313 | def test_decrypt_sync_time(self): | 316 | def test_decrypt_sync_time(self): |
314 | self.assertEqual( | 317 | self.assertEqual( |
315 | self.EXPECTED_TIME, | 318 | self.EXPECTED_TIME, |
316 | self.cryptor.decrypt_sync_time(self.ENCODED_TIME)) | 319 | self.cryptor.decrypt_sync_time(self.ENCODED_TIME), |
320 | ) | ||
diff --git a/tests/test_pydora/test_utils.py b/tests/test_pydora/test_utils.py index fc33a40..9d3bc78 100644 --- a/tests/test_pydora/test_utils.py +++ b/tests/test_pydora/test_utils.py | |||
@@ -10,23 +10,26 @@ from pydora.utils import iterate_forever | |||
10 | 10 | ||
11 | 11 | ||
12 | class TestIterateForever(TestCase): | 12 | class TestIterateForever(TestCase): |
13 | |||
14 | def setUp(self): | 13 | def setUp(self): |
15 | self.transport = Mock(side_effect=[InvalidAuthToken(), None]) | 14 | self.transport = Mock(side_effect=[InvalidAuthToken(), None]) |
16 | self.client = APIClient(self.transport, None, None, None, None) | 15 | self.client = APIClient(self.transport, None, None, None, None) |
17 | self.client._authenticate = Mock() | 16 | self.client._authenticate = Mock() |
18 | 17 | ||
19 | def test_handle_missing_params_exception_due_to_missing_ad_tokens(self): | 18 | def test_handle_missing_params_exception_due_to_missing_ad_tokens(self): |
20 | with patch.object(APIClient, 'get_playlist') as get_playlist_mock: | 19 | with patch.object(APIClient, "get_playlist") as get_playlist_mock: |
21 | admock = patch.object( | 20 | admock = patch.object( |
22 | APIClient, 'register_ad', | 21 | APIClient, |
23 | side_effect=ParameterMissing("ParameterMissing")) | 22 | "register_ad", |
23 | side_effect=ParameterMissing("ParameterMissing"), | ||
24 | ) | ||
24 | 25 | ||
25 | with admock: | 26 | with admock: |
26 | station = Station.from_json( | 27 | station = Station.from_json( |
27 | self.client, {'stationToken': 'token_mock'}) | 28 | self.client, {"stationToken": "token_mock"} |
29 | ) | ||
28 | ad_mock = AdItem.from_json( | 30 | ad_mock = AdItem.from_json( |
29 | self.client, {'station_id': 'id_mock'}) | 31 | self.client, {"station_id": "id_mock"} |
32 | ) | ||
30 | get_playlist_mock.return_value = iter([ad_mock]) | 33 | get_playlist_mock.return_value = iter([ad_mock]) |
31 | 34 | ||
32 | station_iter = iterate_forever(station.get_playlist) | 35 | station_iter = iterate_forever(station.get_playlist) |
@@ -36,15 +39,19 @@ class TestIterateForever(TestCase): | |||
36 | 39 | ||
37 | def test_reraise_missing_params_exception(self): | 40 | def test_reraise_missing_params_exception(self): |
38 | plmock = patch.object( | 41 | plmock = patch.object( |
39 | APIClient, 'get_playlist', | 42 | APIClient, |
40 | side_effect=ParameterMissing("ParameterMissing")) | 43 | "get_playlist", |
44 | side_effect=ParameterMissing("ParameterMissing"), | ||
45 | ) | ||
41 | 46 | ||
42 | with plmock as get_playlist_mock: | 47 | with plmock as get_playlist_mock: |
43 | with self.assertRaises(ParameterMissing): | 48 | with self.assertRaises(ParameterMissing): |
44 | station = Station.from_json( | 49 | station = Station.from_json( |
45 | self.client, {'stationToken': 'token_mock'}) | 50 | self.client, {"stationToken": "token_mock"} |
51 | ) | ||
46 | track_mock = PlaylistItem.from_json( | 52 | track_mock = PlaylistItem.from_json( |
47 | self.client, {'token': 'token_mock'}) | 53 | self.client, {"token": "token_mock"} |
54 | ) | ||
48 | get_playlist_mock.return_value = iter([track_mock]) | 55 | get_playlist_mock.return_value = iter([track_mock]) |
49 | 56 | ||
50 | station_iter = iterate_forever(station.get_playlist) | 57 | station_iter = iterate_forever(station.get_playlist) |