aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Cass <john.cass77@gmail.com>2016-06-07 07:23:57 +0200
committerMike Crute <mcrute@gmail.com>2016-06-06 22:23:57 -0700
commit79af8c5a61de0feb54bd14c17fb81dd742c04e2c (patch)
treeb95360a631e9565920081e061230d7c388c27375
parentb414e2c38b8bc13bafed500b656aa68be0db820c (diff)
downloadpydora-79af8c5a61de0feb54bd14c17fb81dd742c04e2c.tar.bz2
pydora-79af8c5a61de0feb54bd14c17fb81dd742c04e2c.tar.xz
pydora-79af8c5a61de0feb54bd14c17fb81dd742c04e2c.zip
Add support for searching genre stations. (#45)
Add support for searching genre stations.
-rw-r--r--pandora/client.py14
-rw-r--r--pandora/models/pandora.py83
-rw-r--r--tests/test_pandora/test_models.py173
3 files changed, 223 insertions, 47 deletions
diff --git a/pandora/client.py b/pandora/client.py
index 7c9791e..48077cb 100644
--- a/pandora/client.py
+++ b/pandora/client.py
@@ -174,12 +174,18 @@ class APIClient(BaseAPIClient):
174 return self("bookmark.deleteArtistBookmark", 174 return self("bookmark.deleteArtistBookmark",
175 bookmarkToken=bookmark_token) 175 bookmarkToken=bookmark_token)
176 176
177 def search(self, search_text): 177 def search(self, search_text,
178 include_near_matches=False,
179 include_genre_stations=False):
178 from .models.pandora import SearchResult 180 from .models.pandora import SearchResult
179 181
180 return SearchResult.from_json(self, 182 return SearchResult.from_json(
181 self("music.search", 183 self,
182 searchText=search_text)) 184 self("music.search",
185 searchText=search_text,
186 includeNearMatches=include_near_matches,
187 includeGenreStations=include_genre_stations)
188 )
183 189
184 def add_feedback(self, track_token, positive): 190 def add_feedback(self, track_token, positive):
185 return self("station.addFeedback", 191 return self("station.addFeedback",
diff --git a/pandora/models/pandora.py b/pandora/models/pandora.py
index f2fd36e..8fa8e21 100644
--- a/pandora/models/pandora.py
+++ b/pandora/models/pandora.py
@@ -284,37 +284,98 @@ class BookmarkList(PandoraModel):
284 284
285class SearchResultItem(PandoraModel): 285class SearchResultItem(PandoraModel):
286 286
287 artist = Field("artistName")
288 song_name = Field("songName")
289 score = Field("score") 287 score = Field("score")
290 likely_match = Field("likelyMatch", default=False)
291 token = Field("musicToken") 288 token = Field("musicToken")
292 289
293 @property 290 @property
294 def is_song(self): 291 def is_song(self):
295 return self.token.startswith('S') 292 return isinstance(self, SongSearchResultItem)
296 293
297 @property 294 @property
298 def is_artist(self): 295 def is_artist(self):
299 return self.token.startswith('R') 296 return isinstance(self, ArtistSearchResultItem) and \
297 self.token.startswith("R")
300 298
301 @property 299 @property
302 def is_composer(self): 300 def is_composer(self):
303 return self.token.startswith('C') 301 return isinstance(self, ArtistSearchResultItem) and \
302 self.token.startswith("C")
303
304 @property
305 def is_genre_station(self):
306 return isinstance(self, GenreStationSearchResultItem)
304 307
305 def create_station(self): 308 def create_station(self):
306 if self.is_song: 309 raise NotImplementedError
307 self._api_client.create_station(track_token=self.token) 310
311 @classmethod
312 def from_json(cls, api_client, data):
313 if data["musicToken"].startswith("S"):
314 return SongSearchResultItem.from_json(api_client, data)
315
316 elif data["musicToken"].startswith(("R", "C")):
317 return ArtistSearchResultItem.from_json(api_client, data)
318
319 elif data["musicToken"].startswith("G"):
320 return GenreStationSearchResultItem.from_json(api_client, data)
308 else: 321 else:
309 self._api_client.create_station(artist_token=self.token) 322 raise NotImplementedError("Unknown result token type '{}'"
323 .format(data["musicToken"]))
324
325
326class ArtistSearchResultItem(SearchResultItem):
327
328 score = Field("score")
329 token = Field("musicToken")
330 artist = Field("artistName")
331 likely_match = Field("likelyMatch", default=False)
332
333 def create_station(self):
334 self._api_client.create_station(artist_token=self.token)
335
336 @classmethod
337 def from_json(cls, api_client, data):
338 return super(SearchResultItem, cls).from_json(api_client, data)
339
340
341class SongSearchResultItem(SearchResultItem):
342
343 score = Field("score")
344 token = Field("musicToken")
345 artist = Field("artistName")
346 song_name = Field("songName")
347
348 def create_station(self):
349 self._api_client.create_station(track_token=self.token)
350
351 @classmethod
352 def from_json(cls, api_client, data):
353 return super(SearchResultItem, cls).from_json(api_client, data)
354
355
356class GenreStationSearchResultItem(SearchResultItem):
357
358 score = Field("score")
359 token = Field("musicToken")
360 station_name = Field("stationName")
361
362 def create_station(self):
363 self._api_client.create_station(search_token=self.token)
364
365 @classmethod
366 def from_json(cls, api_client, data):
367 return super(SearchResultItem, cls).from_json(api_client, data)
310 368
311 369
312class SearchResult(PandoraModel): 370class SearchResult(PandoraModel):
313 371
314 nearest_matches_available = Field("nearMatchesAvailable") 372 nearest_matches_available = Field("nearMatchesAvailable")
315 explanation = Field("explanation") 373 explanation = Field("explanation")
316 songs = Field("songs", formatter=SearchResultItem.from_json_list) 374 songs = Field("songs", formatter=SongSearchResultItem.from_json_list)
317 artists = Field("artists", formatter=SearchResultItem.from_json_list) 375 artists = Field("artists", formatter=ArtistSearchResultItem.from_json_list)
376 genre_stations = Field(
377 "genreStations",
378 formatter=GenreStationSearchResultItem.from_json_list)
318 379
319 380
320class GenreStationList(PandoraDictListModel): 381class GenreStationList(PandoraDictListModel):
diff --git a/tests/test_pandora/test_models.py b/tests/test_pandora/test_models.py
index 7383d85..3a771ab 100644
--- a/tests/test_pandora/test_models.py
+++ b/tests/test_pandora/test_models.py
@@ -277,54 +277,157 @@ class TestAdItem(TestCase):
277 277
278class TestSearchResultItem(TestCase): 278class TestSearchResultItem(TestCase):
279 279
280 JSON_DATA = { 280 SONG_JSON_DATA = {
281 "artistName": "artist_name_mock", 281 "artistName": "artist_name_mock",
282 "musicToken": "S0000000", 282 "musicToken": "S0000000",
283 "songName": "song_name_mock", 283 "songName": "song_name_mock",
284 "score": 100 284 "score": 100
285 } 285 }
286 286
287 ARTIST_JSON_DATA = {
288 "artistName": "artist_name_mock",
289 "musicToken": "R0000000",
290 "likelyMatch": False,
291 "score": 100
292 }
293
294 COMPOSER_JSON_DATA = {
295 "artistName": "composer_name_mock",
296 "musicToken": "C0000000",
297 "likelyMatch": False,
298 "score": 100
299 }
300
301 GENRE_JSON_DATA = {
302 "stationName": "station_name_mock",
303 "musicToken": "G0000000",
304 "score": 100
305 }
306
287 def setUp(self): 307 def setUp(self):
288 api_client_mock = Mock(spec=APIClient) 308 self.api_client_mock = Mock(spec=APIClient)
289 api_client_mock.default_audio_quality = APIClient.HIGH_AUDIO_QUALITY 309 self.api_client_mock.default_audio_quality = APIClient.HIGH_AUDIO_QUALITY
290 self.result = SearchResultItem.from_json(api_client_mock, self.JSON_DATA) 310
311 def test_is_song(self):
312 result = SearchResultItem.from_json(self.api_client_mock, self.SONG_JSON_DATA)
313 assert result.is_song
314 assert not result.is_artist
315 assert not result.is_composer
316 assert not result.is_genre_station
317
318 def test_is_artist(self):
319 result = SearchResultItem.from_json(self.api_client_mock, self.ARTIST_JSON_DATA)
320 assert not result.is_song
321 assert result.is_artist
322 assert not result.is_composer
323 assert not result.is_genre_station
324
325 def test_is_composer(self):
326 result = SearchResultItem.from_json(self.api_client_mock, self.COMPOSER_JSON_DATA)
327 assert not result.is_song
328 assert not result.is_artist
329 assert result.is_composer
330 assert not result.is_genre_station
331
332 def test_is_genre_station(self):
333 result = SearchResultItem.from_json(self.api_client_mock, self.GENRE_JSON_DATA)
334 assert not result.is_song
335 assert not result.is_artist
336 assert not result.is_composer
337 assert result.is_genre_station
338
339 def test_create_station(self):
340 result = SearchResultItem.from_json(self.api_client_mock, self.SONG_JSON_DATA)
341
342 self.assertRaises(NotImplementedError, result.create_station())
343
344
345class TestArtistSearchResultItem(TestCase):
346
347 ARTIST_JSON_DATA = {
348 "artistName": "artist_name_mock",
349 "musicToken": "R0000000",
350 "likelyMatch": False,
351 "score": 100
352 }
353
354 COMPOSER_JSON_DATA = {
355 "artistName": "composer_name_mock",
356 "musicToken": "C0000000",
357 "likelyMatch": False,
358 "score": 100
359 }
360
361 def setUp(self):
362 self.api_client_mock = Mock(spec=APIClient)
363 self.api_client_mock.default_audio_quality = APIClient.HIGH_AUDIO_QUALITY
291 364
292 def test_repr(self): 365 def test_repr(self):
293 expected = ("SearchResultItem(artist='artist_name_mock', likely_match=False, score=100, " 366 result = SearchResultItem.from_json(self.api_client_mock, self.ARTIST_JSON_DATA)
294 "song_name='song_name_mock', token='S0000000')") 367 expected = ("ArtistSearchResultItem(artist='artist_name_mock', likely_match=False, score=100, token='R0000000')")
295 self.assertEqual(expected, repr(self.result)) 368 self.assertEqual(expected, repr(result))
296 369
297 def test_is_song(self): 370 result = SearchResultItem.from_json(self.api_client_mock, self.COMPOSER_JSON_DATA)
298 assert self.result.is_song 371 expected = ("ArtistSearchResultItem(artist='composer_name_mock', likely_match=False, score=100, token='C0000000')")
372 self.assertEqual(expected, repr(result))
299 373
300 self.result.token = 'R123456' 374 def test_create_station(self):
301 assert not self.result.is_song 375 result = SearchResultItem.from_json(self.api_client_mock, self.ARTIST_JSON_DATA)
376 result._api_client.create_station = Mock()
302 377
303 def test_is_artist(self): 378 result.create_station()
304 assert not self.result.is_artist 379 result._api_client.create_station.assert_called_with(artist_token=result.token)
305 380
306 self.result.token = 'R123456'
307 assert self.result.is_artist
308 381
309 def test_is_composer(self): 382class TestSongSearchResultItem(TestCase):
310 assert not self.result.is_composer
311 383
312 self.result.token = 'C12345' 384 SONG_JSON_DATA = {
313 assert self.result.is_composer 385 "artistName": "artist_name_mock",
386 "musicToken": "S0000000",
387 "songName": "song_name_mock",
388 "score": 100
389 }
314 390
391 def setUp(self):
392 self.api_client_mock = Mock(spec=APIClient)
393 self.api_client_mock.default_audio_quality = APIClient.HIGH_AUDIO_QUALITY
315 394
316 def test_create_station_song(self): 395 def test_repr(self):
317 self.result._api_client.create_station = Mock() 396 result = SearchResultItem.from_json(self.api_client_mock, self.SONG_JSON_DATA)
397 expected = ("SongSearchResultItem(artist='artist_name_mock', score=100, song_name='song_name_mock', token='S0000000')")
398 self.assertEqual(expected, repr(result))
399
400 def test_create_station(self):
401 result = SearchResultItem.from_json(self.api_client_mock, self.SONG_JSON_DATA)
402 result._api_client.create_station = Mock()
403
404 result.create_station()
405 result._api_client.create_station.assert_called_with(track_token=result.token)
406
407
408class TestGenreStationSearchResultItem(TestCase):
409
410 GENRE_JSON_DATA = {
411 "stationName": "station_name_mock",
412 "musicToken": "G0000000",
413 "score": 100
414 }
318 415
319 self.result.create_station() 416 def setUp(self):
320 self.result._api_client.create_station.assert_called_with(track_token=self.result.token) 417 self.api_client_mock = Mock(spec=APIClient)
418 self.api_client_mock.default_audio_quality = APIClient.HIGH_AUDIO_QUALITY
321 419
322 def test_create_station_artist(self): 420 def test_repr(self):
323 self.result.token = 'R123456' 421 result = SearchResultItem.from_json(self.api_client_mock, self.GENRE_JSON_DATA)
324 self.result._api_client.create_station = Mock() 422 expected = ("GenreStationSearchResultItem(score=100, station_name='station_name_mock', token='G0000000')")
423 self.assertEqual(expected, repr(result))
424
425 def test_create_station(self):
426 result = SearchResultItem.from_json(self.api_client_mock, self.GENRE_JSON_DATA)
427 result._api_client.create_station = Mock()
325 428
326 self.result.create_station() 429 result.create_station()
327 self.result._api_client.create_station.assert_called_with(artist_token=self.result.token) 430 result._api_client.create_station.assert_called_with(search_token=result.token)
328 431
329 432
330class TestSearchResult(TestCase): 433class TestSearchResult(TestCase):
@@ -343,6 +446,11 @@ class TestSearchResult(TestCase):
343 'musicToken': 'R000000', 446 'musicToken': 'R000000',
344 'likelyMatch': False, 447 'likelyMatch': False,
345 'score': 80 448 'score': 80
449 }],
450 'genreStations': [{
451 'musicToken': 'G0000',
452 'stationName': 'station_mock',
453 'score': 50
346 }] 454 }]
347 } 455 }
348 456
@@ -352,8 +460,9 @@ class TestSearchResult(TestCase):
352 self.result = SearchResult.from_json(api_client_mock, self.JSON_DATA) 460 self.result = SearchResult.from_json(api_client_mock, self.JSON_DATA)
353 461
354 def test_repr(self): 462 def test_repr(self):
355 expected = ("SearchResult(artists=[SearchResultItem(artist='artist_mock', " 463 expected = ("SearchResult(artists=[ArtistSearchResultItem(artist='artist_mock', likely_match=False, score=80, "
356 "likely_match=False, score=80, song_name=None, token='R000000')], explanation='', " 464 "token='R000000')], explanation='', genre_stations=[GenreStationSearchResultItem(score=50, "
357 "nearest_matches_available=True, songs=[SearchResultItem(artist='song_artist_mock', " 465 "station_name='station_mock', token='G0000')], nearest_matches_available=True, "
358 "likely_match=False, score=100, song_name='song_name_mock', token='S0000000')])") 466 "songs=[SongSearchResultItem(artist='song_artist_mock', score=100, song_name='song_name_mock', "
467 "token='S0000000')])")
359 self.assertEqual(expected, repr(self.result)) 468 self.assertEqual(expected, repr(self.result))