diff options
author | John Cass <john.cass77@gmail.com> | 2016-06-07 07:23:57 +0200 |
---|---|---|
committer | Mike Crute <mcrute@gmail.com> | 2016-06-06 22:23:57 -0700 |
commit | 79af8c5a61de0feb54bd14c17fb81dd742c04e2c (patch) | |
tree | b95360a631e9565920081e061230d7c388c27375 | |
parent | b414e2c38b8bc13bafed500b656aa68be0db820c (diff) | |
download | pydora-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.py | 14 | ||||
-rw-r--r-- | pandora/models/pandora.py | 83 | ||||
-rw-r--r-- | tests/test_pandora/test_models.py | 173 |
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 | ||
285 | class SearchResultItem(PandoraModel): | 285 | class 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 | |||
326 | class 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 | |||
341 | class 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 | |||
356 | class 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 | ||
312 | class SearchResult(PandoraModel): | 370 | class 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 | ||
320 | class GenreStationList(PandoraDictListModel): | 381 | class 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 | ||
278 | class TestSearchResultItem(TestCase): | 278 | class 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 | |||
345 | class 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): | 382 | class 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 | |||
408 | class 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 | ||
330 | class TestSearchResult(TestCase): | 433 | class 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)) |