aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSkybound1 <mgupta94@hotmail.co.uk>2018-12-10 19:10:49 +0100
committerMike Crute <crutem@amazon.com>2018-12-10 10:10:49 -0800
commit9e49c086a86deff4ae85d802e9d57278f1ac4636 (patch)
tree1b68ce4c19b7dcd6281118acd702b920bc59417d
parentd74a05605401926b6b6fc694753c7de19f67c419 (diff)
downloadpydora-9e49c086a86deff4ae85d802e9d57278f1ac4636.tar.bz2
pydora-9e49c086a86deff4ae85d802e9d57278f1ac4636.tar.xz
pydora-9e49c086a86deff4ae85d802e9d57278f1ac4636.zip
Enhancement: Adds additional audio urls (#58)
* Adds support for additionalAudioUrl for station.getPlaylist * Fixes broken tests * Reworks use of iterables for additional audio urls and adds associated tests * Moves parsing additional url response into a syntethic field * Adds tests for additional urls field
-rw-r--r--pandora/client.py27
-rw-r--r--pandora/models/pandora.py40
-rw-r--r--tests/test_pandora/test_client.py68
-rw-r--r--tests/test_pandora/test_models.py33
4 files changed, 158 insertions, 10 deletions
diff --git a/pandora/client.py b/pandora/client.py
index 01b7654..df09144 100644
--- a/pandora/client.py
+++ b/pandora/client.py
@@ -127,15 +127,28 @@ class APIClient(BaseAPIClient):
127 def get_station_list_checksum(self): # pragma: no cover 127 def get_station_list_checksum(self): # pragma: no cover
128 return self("user.getStationListChecksum")["checksum"] 128 return self("user.getStationListChecksum")["checksum"]
129 129
130 def get_playlist(self, station_token): 130 def get_playlist(self, station_token, additional_urls=None):
131 from .models.pandora import Playlist 131 from .models.pandora import Playlist
132 132
133 playlist = Playlist.from_json(self, 133 if additional_urls is None:
134 self("station.getPlaylist", 134 additional_urls = []
135 stationToken=station_token, 135
136 includeTrackLength=True, 136 if isinstance(additional_urls, str):
137 xplatformAdCapable=True, 137 raise TypeError('Additional urls should be a list')
138 audioAdPodCapable=True)) 138
139 urls = [getattr(url, "value", url) for url in additional_urls]
140
141 resp = self("station.getPlaylist",
142 stationToken=station_token,
143 includeTrackLength=True,
144 xplatformAdCapable=True,
145 audioAdPodCapable=True,
146 additionalAudioUrl=','.join(urls))
147
148 for item in resp['items']:
149 item['_paramAdditionalUrls'] = additional_urls
150
151 playlist = Playlist.from_json(self, resp)
139 152
140 for i, track in enumerate(playlist): 153 for i, track in enumerate(playlist):
141 if track.is_ad: 154 if track.is_ad:
diff --git a/pandora/models/pandora.py b/pandora/models/pandora.py
index cc57eac..ecc1225 100644
--- a/pandora/models/pandora.py
+++ b/pandora/models/pandora.py
@@ -6,6 +6,18 @@ from . import Field, DateField, SyntheticField
6from . import PandoraModel, PandoraListModel, PandoraDictListModel 6from . import PandoraModel, PandoraListModel, PandoraDictListModel
7 7
8 8
9class AdditionalAudioUrl(Enum):
10 HTTP_40_AAC_MONO = 'HTTP_40_AAC_MONO'
11 HTTP_64_AAC = 'HTTP_64_AAC'
12 HTTP_32_AACPLUS = 'HTTP_32_AACPLUS'
13 HTTP_64_AACPLUS = 'HTTP_64_AACPLUS'
14 HTTP_24_AACPLUS_ADTS = 'HTTP_24_AACPLUS_ADTS'
15 HTTP_32_AACPLUS_ADTS = 'HTTP_32_AACPLUS_ADTS'
16 HTTP_64_AACPLUS_ADTS = 'HTTP_64_AACPLUS_ADTS'
17 HTTP_128_MP3 = 'HTTP_128_MP3'
18 HTTP_32_WMA = 'HTTP_32_WMA'
19
20
9class PandoraType(Enum): 21class PandoraType(Enum):
10 22
11 TRACK = "TR" 23 TRACK = "TR"
@@ -100,8 +112,9 @@ class Station(PandoraModel):
100 seeds = Field("music", model=StationSeeds) 112 seeds = Field("music", model=StationSeeds)
101 feedback = Field("feedback", model=StationFeedback) 113 feedback = Field("feedback", model=StationFeedback)
102 114
103 def get_playlist(self): 115 def get_playlist(self, additional_urls=None):
104 return iter(self._api_client.get_playlist(self.token)) 116 return iter(self._api_client.get_playlist(self.token,
117 additional_urls))
105 118
106 119
107class GenreStation(PandoraModel): 120class GenreStation(PandoraModel):
@@ -178,6 +191,27 @@ class AudioField(SyntheticField):
178 return audio_url[self.field] if audio_url else None 191 return audio_url[self.field] if audio_url else None
179 192
180 193
194class AdditionalUrlField(SyntheticField):
195
196 def formatter(self, api_client, data, value):
197 """Parse additional url fields and map them to inputs
198
199 Attempt to create a dictionary with keys being user input, and
200 response being the returned URL
201 """
202 if value is None:
203 return None
204
205 user_param = data['_paramAdditionalUrls']
206 urls = {}
207 if isinstance(value, str):
208 urls[user_param[0]] = value
209 else:
210 for key, url in zip(user_param, value):
211 urls[key] = url
212 return urls
213
214
181class PlaylistModel(PandoraModel): 215class PlaylistModel(PandoraModel):
182 216
183 def get_is_playable(self): 217 def get_is_playable(self):
@@ -243,6 +277,8 @@ class PlaylistItem(PlaylistModel):
243 song_detail_url = Field("songDetailUrl") 277 song_detail_url = Field("songDetailUrl")
244 song_explore_url = Field("songExplorerUrl") 278 song_explore_url = Field("songExplorerUrl")
245 279
280 additional_audio_urls = AdditionalUrlField("additionalAudioUrl")
281
246 @property 282 @property
247 def is_ad(self): 283 def is_ad(self):
248 return self.ad_token is not None 284 return self.ad_token is not None
diff --git a/tests/test_pandora/test_client.py b/tests/test_pandora/test_client.py
index 6c61355..d24ea28 100644
--- a/tests/test_pandora/test_client.py
+++ b/tests/test_pandora/test_client.py
@@ -1,7 +1,7 @@
1from unittest import TestCase 1from unittest import TestCase
2 2
3from pandora import errors 3from pandora import errors
4from pandora.models.pandora import AdItem 4from pandora.models.pandora import AdItem, AdditionalAudioUrl
5from pandora.client import APIClient, BaseAPIClient 5from pandora.client import APIClient, BaseAPIClient
6from pandora.py2compat import Mock, call, patch 6from pandora.py2compat import Mock, call, patch
7from tests.test_pandora.test_models import TestAdItem 7from tests.test_pandora.test_models import TestAdItem
@@ -87,6 +87,7 @@ class TestCallingAPIClient(TestCase):
87 client.get_playlist('token_mock') 87 client.get_playlist('token_mock')
88 88
89 playlist_mock.assert_has_calls([call("station.getPlaylist", 89 playlist_mock.assert_has_calls([call("station.getPlaylist",
90 additionalAudioUrl='',
90 audioAdPodCapable=True, 91 audioAdPodCapable=True,
91 includeTrackLength=True, 92 includeTrackLength=True,
92 stationToken='token_mock', 93 stationToken='token_mock',
@@ -186,3 +187,68 @@ class TestCreatingGenreStation(TestCase):
186 client = APIClient(Mock(), None, None, None, None) 187 client = APIClient(Mock(), None, None, None, None)
187 station = client.get_genre_stations() 188 station = client.get_genre_stations()
188 self.assertEqual(station.checksum, "foo") 189 self.assertEqual(station.checksum, "foo")
190
191
192class TestAdditionalUrls(TestCase):
193
194 def test_non_iterable_string(self):
195 with self.assertRaises(TypeError):
196 transport = Mock(side_effect=[errors.InvalidAuthToken(), None])
197
198 client = APIClient(transport, None, None, None, None)
199 client._authenticate = Mock()
200
201 client.get_playlist('token_mock', additional_urls='')
202
203 def test_non_iterable_other(self):
204 with self.assertRaises(TypeError):
205 transport = Mock(side_effect=[errors.InvalidAuthToken(), None])
206
207 client = APIClient(transport, None, None, None, None)
208 client._authenticate = Mock()
209
210 client.get_playlist('token_mock',
211 additional_urls=AdditionalAudioUrl.HTTP_32_WMA)
212
213 def test_without_enum(self):
214 with patch.object(APIClient, '__call__') as playlist_mock:
215 transport = Mock(side_effect=[errors.InvalidAuthToken(), None])
216
217 client = APIClient(transport, None, None, None, None)
218 client._authenticate = Mock()
219
220 urls = ['HTTP_128_MP3',
221 'HTTP_24_AACPLUS_ADTS']
222
223 desired = 'HTTP_128_MP3,HTTP_24_AACPLUS_ADTS'
224
225 client.get_playlist('token_mock', additional_urls=urls)
226
227 playlist_mock.assert_has_calls([call("station.getPlaylist",
228 additionalAudioUrl=desired,
229 audioAdPodCapable=True,
230 includeTrackLength=True,
231 stationToken='token_mock',
232 xplatformAdCapable=True)])
233
234
235 def test_with_enum(self):
236 with patch.object(APIClient, '__call__') as playlist_mock:
237 transport = Mock(side_effect=[errors.InvalidAuthToken(), None])
238
239 client = APIClient(transport, None, None, None, None)
240 client._authenticate = Mock()
241
242 urls = [AdditionalAudioUrl.HTTP_128_MP3,
243 AdditionalAudioUrl.HTTP_24_AACPLUS_ADTS]
244
245 desired = 'HTTP_128_MP3,HTTP_24_AACPLUS_ADTS'
246
247 client.get_playlist('token_mock', additional_urls=urls)
248
249 playlist_mock.assert_has_calls([call("station.getPlaylist",
250 additionalAudioUrl=desired,
251 audioAdPodCapable=True,
252 includeTrackLength=True,
253 stationToken='token_mock',
254 xplatformAdCapable=True)])
diff --git a/tests/test_pandora/test_models.py b/tests/test_pandora/test_models.py
index ce71ef8..410b0de 100644
--- a/tests/test_pandora/test_models.py
+++ b/tests/test_pandora/test_models.py
@@ -52,6 +52,39 @@ class TestDateField(TestCase):
52 self.assertEqual(expected, model.date_field.replace(microsecond=0)) 52 self.assertEqual(expected, model.date_field.replace(microsecond=0))
53 53
54 54
55class TestAdditionalUrlField(TestCase):
56
57 def test_single_url(self):
58 dummy_data = {
59 '_paramAdditionalUrls': ['foo']
60 }
61
62 field = pm.AdditionalUrlField("additionalAudioUrl")
63
64 ret = field.formatter(None, dummy_data, 'test')
65
66 self.assertEqual(ret, {'foo': 'test'})
67
68 def test_multiple_urls(self):
69 dummy_data = {
70 '_paramAdditionalUrls': [
71 'abc',
72 'def',
73 ]
74 }
75
76 field = pm.AdditionalUrlField("additionalAudioUrl")
77
78 ret = field.formatter(None, dummy_data, ['foo', 'bar'])
79
80 expected = {
81 'abc': 'foo',
82 'def': 'bar',
83 }
84
85 self.assertEqual(ret, expected)
86
87
55class TestPandoraModel(TestCase): 88class TestPandoraModel(TestCase):
56 89
57 JSON_DATA = { 90 JSON_DATA = {