aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mcrute@gmail.com>2016-01-09 13:30:33 -0800
committerMike Crute <mcrute@gmail.com>2016-01-09 13:30:33 -0800
commit0705462ee5644d57d825e37a295a5f698247aba0 (patch)
tree3489d4e608e7747807ab78e8dab0ffbc0b8aa581
parenteb91a5c00701689085b6f427d62e487f417c6f51 (diff)
parent4d05e233de4cbb77f5998ad86be7f921d521ff37 (diff)
downloadpydora-0705462ee5644d57d825e37a295a5f698247aba0.tar.bz2
pydora-0705462ee5644d57d825e37a295a5f698247aba0.tar.xz
pydora-0705462ee5644d57d825e37a295a5f698247aba0.zip
Merge pull request #40 from jcass77/develop
Resolving ad playback issues
-rw-r--r--pandora/client.py20
-rw-r--r--pandora/models/pandora.py27
-rw-r--r--pandora/transport.py6
-rw-r--r--pydora/utils.py9
-rw-r--r--tests/test_pandora/test_client.py15
-rw-r--r--tests/test_pandora/test_models.py46
-rw-r--r--tests/test_pandora/test_transport.py2
-rw-r--r--tests/test_pydora/test_utils.py14
8 files changed, 94 insertions, 45 deletions
diff --git a/pandora/client.py b/pandora/client.py
index fafd872..403216e 100644
--- a/pandora/client.py
+++ b/pandora/client.py
@@ -219,9 +219,10 @@ class APIClient(BaseAPIClient):
219 from .models.pandora import GenreStationList 219 from .models.pandora import GenreStationList
220 220
221 genres = self("station.getGenreStations") 221 genres = self("station.getGenreStations")
222 genres["checksum"] = self.get_genre_stations_checksum()
223 222
224 return GenreStationList.from_json(self, genres) 223 genre_stations = GenreStationList.from_json(self, genres)
224 genre_stations.checksum = self.get_genre_stations_checksum()
225 return genre_stations
225 226
226 def get_genre_stations_checksum(self): 227 def get_genre_stations_checksum(self):
227 return self("station.getGenreStationsChecksum")["checksum"] 228 return self("station.getGenreStationsChecksum")["checksum"]
@@ -262,13 +263,14 @@ class APIClient(BaseAPIClient):
262 from .models.pandora import AdItem 263 from .models.pandora import AdItem
263 264
264 if not station_id: 265 if not station_id:
265 raise ValueError("The 'station_id' param must be defined, " 266 raise errors.ParameterMissing("The 'station_id' param must be "
266 "got: '{}'".format(station_id)) 267 "defined, got: '{}'"
267 268 .format(station_id))
268 ad_metadata = self.get_ad_metadata(ad_token) 269
269 ad_metadata["stationId"] = station_id 270 ad_item = AdItem.from_json(self, self.get_ad_metadata(ad_token))
270 271 ad_item.station_id = station_id
271 return AdItem.from_json(self, ad_metadata) 272 ad_item.ad_token = ad_token
273 return ad_item
272 274
273 def get_ad_metadata(self, ad_token): 275 def get_ad_metadata(self, ad_token):
274 return self("ad.getAdMetadata", 276 return self("ad.getAdMetadata",
diff --git a/pandora/models/pandora.py b/pandora/models/pandora.py
index 40b368a..7c134e1 100644
--- a/pandora/models/pandora.py
+++ b/pandora/models/pandora.py
@@ -1,6 +1,7 @@
1from .. import BaseAPIClient 1from .. import BaseAPIClient
2from . import with_metaclass, ModelMetaClass 2from . import with_metaclass, ModelMetaClass
3from . import Field, PandoraModel, PandoraListModel, PandoraDictListModel 3from . import Field, PandoraModel, PandoraListModel, PandoraDictListModel
4from ..errors import ParameterMissing
4 5
5 6
6class Station(PandoraModel): 7class Station(PandoraModel):
@@ -130,6 +131,8 @@ class PlaylistModel(PandoraModel):
130 return cls.get_audio_field(data, "bitrate", preferred_quality) 131 return cls.get_audio_field(data, "bitrate", preferred_quality)
131 132
132 def get_is_playable(self): 133 def get_is_playable(self):
134 if not self.audio_url:
135 return False
133 return self._api_client.transport.test_url(self.audio_url) 136 return self._api_client.transport.test_url(self.audio_url)
134 137
135 def prepare_playback(self): 138 def prepare_playback(self):
@@ -217,17 +220,33 @@ class AdItem(PlaylistModel):
217 audio_url = Field("audioUrl") 220 audio_url = Field("audioUrl")
218 image_url = Field("imageUrl") 221 image_url = Field("imageUrl")
219 click_through_url = Field("clickThroughUrl") 222 click_through_url = Field("clickThroughUrl")
220 station_id = Field("stationId") 223 station_id = None
224 ad_token = None
221 225
222 @property 226 @property
223 def is_ad(self): 227 def is_ad(self):
224 return True 228 return True
225 229
226 def register_ad(self, station_id): 230 def register_ad(self, station_id=None):
227 self._api_client.register_ad(station_id, self.tracking_tokens) 231 if not station_id:
232 station_id = self.station_id
233 if self.tracking_tokens:
234 self._api_client.register_ad(station_id, self.tracking_tokens)
235 else:
236 raise ParameterMissing('No ad tracking tokens provided for '
237 'registration.')
228 238
229 def prepare_playback(self): 239 def prepare_playback(self):
230 self.register_ad(self.station_id) 240 try:
241 self.register_ad(self.station_id)
242 except ParameterMissing as e:
243 if not self.tracking_tokens:
244 # Ignore registration attempts if no ad tracking tokens are
245 # available
246 pass
247 else:
248 # Unexpected error, re-raise.
249 raise e
231 return super(AdItem, self).prepare_playback() 250 return super(AdItem, self).prepare_playback()
232 251
233 252
diff --git a/pandora/transport.py b/pandora/transport.py
index 1a2ee69..8fab7af 100644
--- a/pandora/transport.py
+++ b/pandora/transport.py
@@ -46,7 +46,11 @@ def retries(max_tries, exceptions=(Exception,)):
46 retries_left -= 1 46 retries_left -= 1
47 return func(*args, **kwargs) 47 return func(*args, **kwargs)
48 48
49 except exceptions: 49 except exceptions as e:
50 # Don't retry for PandoraExceptions - unlikely that result
51 # will change for same set of input parameters.
52 if isinstance(e, PandoraException):
53 continue
50 if retries_left > 0: 54 if retries_left > 0:
51 time.sleep(delay_exponential( 55 time.sleep(delay_exponential(
52 0.5, 2, max_tries - retries_left)) 56 0.5, 2, max_tries - retries_left))
diff --git a/pydora/utils.py b/pydora/utils.py
index 756810a..275c386 100644
--- a/pydora/utils.py
+++ b/pydora/utils.py
@@ -125,15 +125,6 @@ def iterate_forever(func, *args, **kwargs):
125 yield playlistItem 125 yield playlistItem
126 except StopIteration: 126 except StopIteration:
127 output = func(*args, **kwargs) 127 output = func(*args, **kwargs)
128 except errors.ParameterMissing as e:
129 if isinstance(playlistItem, AdItem):
130 if (not playlistItem.tracking_tokens or
131 len(playlistItem.tracking_tokens) == 0):
132 # Ad item does not contain any tracking tokens, yield
133 # without registering
134 yield playlistItem
135 # Something else went wrong, re-raise
136 raise e
137 128
138 129
139class SilentPopen(subprocess.Popen): 130class SilentPopen(subprocess.Popen):
diff --git a/tests/test_pandora/test_client.py b/tests/test_pandora/test_client.py
index 24d9734..cf486e0 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.client import APIClient, BaseAPIClient 3from pandora.client import APIClient, BaseAPIClient
4from pandora.errors import InvalidAuthToken 4from pandora.errors import InvalidAuthToken, ParameterMissing
5from pandora.py2compat import Mock, call, patch 5from pandora.py2compat import Mock, call, patch
6from tests.test_pandora.test_models import TestAdItem 6from tests.test_pandora.test_models import TestAdItem
7 7
@@ -64,12 +64,12 @@ class TestCallingAPIClient(TestCase):
64 client = APIClient(transport, None, None, None, None) 64 client = APIClient(transport, None, None, None, None)
65 client._authenticate = Mock() 65 client._authenticate = Mock()
66 66
67 client.get_playlist('mock_token') 67 client.get_playlist('token_mock')
68 68
69 playlist_mock.assert_has_calls([call("station.getPlaylist", 69 playlist_mock.assert_has_calls([call("station.getPlaylist",
70 audioAdPodCapable=True, 70 audioAdPodCapable=True,
71 includeTrackLength=True, 71 includeTrackLength=True,
72 stationToken='mock_token', 72 stationToken='token_mock',
73 xplatformAdCapable=True)]) 73 xplatformAdCapable=True)])
74 74
75 75
@@ -103,11 +103,12 @@ class TestGettingAds(TestCase):
103 client = APIClient(transport, None, None, None, None) 103 client = APIClient(transport, None, None, None, None)
104 client._authenticate = Mock() 104 client._authenticate = Mock()
105 105
106 ad_item = client.get_ad_item('mock_id', 'mock_token') 106 ad_item = client.get_ad_item('id_mock', 'token_mock')
107 assert ad_item.station_id == 'mock_id' 107 assert ad_item.station_id == 'id_mock'
108 assert ad_item.ad_token == 'token_mock'
108 109
109 ad_metadata_mock.assert_has_calls([call("ad.getAdMetadata", 110 ad_metadata_mock.assert_has_calls([call("ad.getAdMetadata",
110 adToken='mock_token', 111 adToken='token_mock',
111 returnAdTrackingTokens=True, 112 returnAdTrackingTokens=True,
112 supportAudioAds=True)]) 113 supportAudioAds=True)])
113 114
@@ -117,4 +118,4 @@ class TestGettingAds(TestCase):
117 client = APIClient(transport, None, None, None, None) 118 client = APIClient(transport, None, None, None, None)
118 client.get_ad_metadata = Mock() 119 client.get_ad_metadata = Mock()
119 120
120 self.assertRaises(ValueError, client.get_ad_item, '', 'mock_token') 121 self.assertRaises(ParameterMissing, client.get_ad_item, '', 'token_mock')
diff --git a/tests/test_pandora/test_models.py b/tests/test_pandora/test_models.py
index ecd8cf1..ec6e9d5 100644
--- a/tests/test_pandora/test_models.py
+++ b/tests/test_pandora/test_models.py
@@ -3,6 +3,7 @@ from datetime import datetime
3from pandora.py2compat import Mock, patch 3from pandora.py2compat import Mock, patch
4from pandora import APIClient 4from pandora import APIClient
5from pandora.models.pandora import AdItem, PlaylistModel 5from pandora.models.pandora import AdItem, PlaylistModel
6from pandora.errors import ParameterMissing
6 7
7import pandora.models as m 8import pandora.models as m
8 9
@@ -205,35 +206,46 @@ class TestAdItem(TestCase):
205 JSON_DATA = { 206 JSON_DATA = {
206 'audioUrlMap': { 207 'audioUrlMap': {
207 'mediumQuality': { 208 'mediumQuality': {
208 'audioUrl': 'mock_med_url', 'bitrate': '64', 'protocol': 'http', 'encoding': 'aacplus' 209 'audioUrl': 'med_url_mock', 'bitrate': '64', 'protocol': 'http', 'encoding': 'aacplus'
209 }, 210 },
210 'highQuality': { 211 'highQuality': {
211 'audioUrl': 'mock_high_url', 'bitrate': '64', 'protocol': 'http', 'encoding': 'aacplus' 212 'audioUrl': 'high_url_mock', 'bitrate': '64', 'protocol': 'http', 'encoding': 'aacplus'
212 }, 213 },
213 'lowQuality': { 214 'lowQuality': {
214 'audioUrl': 'mock_low_url', 'bitrate': '32', 'protocol': 'http', 'encoding': 'aacplus'}}, 215 'audioUrl': 'low_url_mock', 'bitrate': '32', 'protocol': 'http', 'encoding': 'aacplus'}},
215 'clickThroughUrl': 'mock_click_url', 216 'clickThroughUrl': 'click_url_mock',
216 'imageUrl': 'mock_img_url', 217 'imageUrl': 'img_url_mock',
217 'companyName': '', 218 'companyName': '',
218 'title': '', 219 'title': '',
219 'trackGain': '0.0', 220 'trackGain': '0.0',
220 'adTrackingTokens': ['mock_token_1', 'mock_token_2'] 221 'adTrackingTokens': ['token_1_mock', 'token_2_mock']
221 } 222 }
222 223
223 def setUp(self): 224 def setUp(self):
224 api_client_mock = Mock(spec=APIClient) 225 api_client_mock = Mock(spec=APIClient)
225 api_client_mock.default_audio_quality = APIClient.HIGH_AUDIO_QUALITY 226 api_client_mock.default_audio_quality = APIClient.HIGH_AUDIO_QUALITY
226 self.result = AdItem.from_json(api_client_mock, self.JSON_DATA) 227 self.result = AdItem.from_json(api_client_mock, self.JSON_DATA)
228 self.result.station_id = 'station_id_mock'
229 self.result.ad_token = 'token_mock'
227 230
228 def test_is_ad_is_true(self): 231 def test_is_ad_is_true(self):
229 assert self.result.is_ad is True 232 assert self.result.is_ad is True
230 233
231 def test_register_ad(self): 234 def test_register_ad(self):
232 self.result._api_client.register_ad = Mock() 235 self.result._api_client.register_ad = Mock()
233 self.result.register_ad('id_dummy') 236 self.result.register_ad('id_mock')
234 237
235 assert self.result._api_client.register_ad.called 238 assert self.result._api_client.register_ad.called
236 239
240 def test_register_ad_raises_exception_if_no_tracking_tokens_available(self):
241 with self.assertRaises(ParameterMissing):
242 self.result.tracking_tokens = []
243 self.result._api_client.register_ad = Mock(spec=AdItem)
244
245 self.result.register_ad('id_mock')
246
247 assert self.result._api_client.register_ad.called
248
237 def test_prepare_playback(self): 249 def test_prepare_playback(self):
238 with patch.object(PlaylistModel, 'prepare_playback') as super_mock: 250 with patch.object(PlaylistModel, 'prepare_playback') as super_mock:
239 251
@@ -241,3 +253,23 @@ class TestAdItem(TestCase):
241 self.result.prepare_playback() 253 self.result.prepare_playback()
242 assert self.result.register_ad.called 254 assert self.result.register_ad.called
243 assert super_mock.called 255 assert super_mock.called
256
257 def test_prepare_playback_raises_paramater_missing(self):
258 with patch.object(PlaylistModel, 'prepare_playback') as super_mock:
259
260 self.result.register_ad = Mock(side_effect=ParameterMissing('No ad tracking tokens provided for '
261 'registration.')
262 )
263 self.assertRaises(ParameterMissing, self.result.prepare_playback)
264 assert self.result.register_ad.called
265 assert not super_mock.called
266
267 def test_prepare_playback_handles_paramater_missing_if_no_tokens(self):
268 with patch.object(PlaylistModel, 'prepare_playback') as super_mock:
269
270 self.result.tracking_tokens = []
271 self.result.register_ad = Mock(side_effect=ParameterMissing('No ad tracking tokens provided for '
272 'registration.'))
273 self.result.prepare_playback()
274 assert self.result.register_ad.called
275 assert super_mock.called
diff --git a/tests/test_pandora/test_transport.py b/tests/test_pandora/test_transport.py
index 59d4cea..6596a0e 100644
--- a/tests/test_pandora/test_transport.py
+++ b/tests/test_pandora/test_transport.py
@@ -18,7 +18,7 @@ class TestTransport(TestCase):
18 18
19 time.sleep = Mock() 19 time.sleep = Mock()
20 client.transport._make_http_request = Mock( 20 client.transport._make_http_request = Mock(
21 side_effect=SysCallError("mock_error")) 21 side_effect=SysCallError("error_mock"))
22 client.transport._start_request = Mock() 22 client.transport._start_request = Mock()
23 23
24 client("method") 24 client("method")
diff --git a/tests/test_pydora/test_utils.py b/tests/test_pydora/test_utils.py
index 64cdf74..ae6fa0b 100644
--- a/tests/test_pydora/test_utils.py
+++ b/tests/test_pydora/test_utils.py
@@ -18,22 +18,22 @@ class TestIterateForever(TestCase):
18 with patch.object(APIClient, 'get_playlist') as get_playlist_mock: 18 with patch.object(APIClient, 'get_playlist') as get_playlist_mock:
19 with patch.object(APIClient, 'register_ad', side_effect=ParameterMissing("ParameterMissing")): 19 with patch.object(APIClient, 'register_ad', side_effect=ParameterMissing("ParameterMissing")):
20 20
21 station = Station.from_json(self.client, {'stationToken': 'dummy_token'}) 21 station = Station.from_json(self.client, {'stationToken': 'token_mock'})
22 dummy_ad = AdItem.from_json(self.client, {'station_id': 'dummy_id'}) 22 ad_mock = AdItem.from_json(self.client, {'station_id': 'id_mock'})
23 get_playlist_mock.return_value=iter([dummy_ad]) 23 get_playlist_mock.return_value=iter([ad_mock])
24 24
25 station_iter = iterate_forever(station.get_playlist) 25 station_iter = iterate_forever(station.get_playlist)
26 26
27 next_track = station_iter.next() 27 next_track = station_iter.next()
28 self.assertEqual(dummy_ad, next_track) 28 self.assertEqual(ad_mock, next_track)
29 29
30 def test_reraise_missing_params_exception(self): 30 def test_reraise_missing_params_exception(self):
31 with patch.object(APIClient, 'get_playlist', side_effect=ParameterMissing("ParameterMissing")) as get_playlist_mock: 31 with patch.object(APIClient, 'get_playlist', side_effect=ParameterMissing("ParameterMissing")) as get_playlist_mock:
32 with self.assertRaises(ParameterMissing): 32 with self.assertRaises(ParameterMissing):
33 33
34 station = Station.from_json(self.client, {'stationToken': 'dummy_token'}) 34 station = Station.from_json(self.client, {'stationToken': 'token_mock'})
35 dummy_track = PlaylistItem.from_json(self.client, {'token': 'dummy_token'}) 35 track_mock = PlaylistItem.from_json(self.client, {'token': 'token_mock'})
36 get_playlist_mock.return_value=iter([dummy_track]) 36 get_playlist_mock.return_value=iter([track_mock])
37 37
38 station_iter = iterate_forever(station.get_playlist) 38 station_iter = iterate_forever(station.get_playlist)
39 station_iter.next() 39 station_iter.next()