aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjcass <john.cass77@gmail.com>2015-12-28 16:23:57 +0200
committerjcass <john.cass77@gmail.com>2015-12-28 16:23:57 +0200
commitd2a520dbf6257986128d14a6c7d23aa37725d5e3 (patch)
tree847cf884fe129f8ff4d6cfef243b7b55cd3fa720
parenteb91a5c00701689085b6f427d62e487f417c6f51 (diff)
downloadpydora-d2a520dbf6257986128d14a6c7d23aa37725d5e3.tar.bz2
pydora-d2a520dbf6257986128d14a6c7d23aa37725d5e3.tar.xz
pydora-d2a520dbf6257986128d14a6c7d23aa37725d5e3.zip
Handle IOErrors and ValueErrors when attempting to register ads.
-rw-r--r--pandora/models/pandora.py23
-rw-r--r--pandora/transport.py15
-rw-r--r--pydora/utils.py9
-rw-r--r--tests/test_pandora/test_models.py23
-rw-r--r--tests/test_pandora/test_transport.py2
-rw-r--r--tests/test_pydora/test_utils.py14
6 files changed, 57 insertions, 29 deletions
diff --git a/pandora/models/pandora.py b/pandora/models/pandora.py
index 40b368a..6abd3a7 100644
--- a/pandora/models/pandora.py
+++ b/pandora/models/pandora.py
@@ -130,6 +130,8 @@ class PlaylistModel(PandoraModel):
130 return cls.get_audio_field(data, "bitrate", preferred_quality) 130 return cls.get_audio_field(data, "bitrate", preferred_quality)
131 131
132 def get_is_playable(self): 132 def get_is_playable(self):
133 if not self.audio_url:
134 return False
133 return self._api_client.transport.test_url(self.audio_url) 135 return self._api_client.transport.test_url(self.audio_url)
134 136
135 def prepare_playback(self): 137 def prepare_playback(self):
@@ -223,11 +225,26 @@ class AdItem(PlaylistModel):
223 def is_ad(self): 225 def is_ad(self):
224 return True 226 return True
225 227
226 def register_ad(self, station_id): 228 def register_ad(self, station_id=None):
227 self._api_client.register_ad(station_id, self.tracking_tokens) 229 if not station_id:
230 station_id = self.station_id
231 if self.tracking_tokens:
232 self._api_client.register_ad(station_id, self.tracking_tokens)
233 else:
234 raise ValueError('No ad tracking tokens available for '
235 'registration.')
228 236
229 def prepare_playback(self): 237 def prepare_playback(self):
230 self.register_ad(self.station_id) 238 try:
239 self.register_ad(self.station_id)
240 except ValueError as e:
241 if not self.tracking_tokens:
242 # Ignore registration attempts if no ad tracking tokens are
243 # available
244 pass
245 else:
246 # Unexpected error, re-raise.
247 raise e
231 return super(AdItem, self).prepare_playback() 248 return super(AdItem, self).prepare_playback()
232 249
233 250
diff --git a/pandora/transport.py b/pandora/transport.py
index 1a2ee69..62f426a 100644
--- a/pandora/transport.py
+++ b/pandora/transport.py
@@ -9,6 +9,7 @@ exception.
9 9
10API consumers should use one of the API clients in the pandora.client package. 10API consumers should use one of the API clients in the pandora.client package.
11""" 11"""
12import logging
12import random 13import random
13import time 14import time
14import json 15import json
@@ -22,8 +23,10 @@ from .errors import PandoraException
22 23
23DEFAULT_API_HOST = "tuner.pandora.com/services/json/" 24DEFAULT_API_HOST = "tuner.pandora.com/services/json/"
24 25
26logger = logging.getLogger(__name__)
25 27
26def retries(max_tries, exceptions=(Exception,)): 28
29def retries(max_tries, exceptions=(IOError,)):
27 """Function decorator implementing retrying logic. 30 """Function decorator implementing retrying logic.
28 31
29 exceptions: A tuple of exception classes; default (Exception,) 32 exceptions: A tuple of exception classes; default (Exception,)
@@ -46,7 +49,11 @@ def retries(max_tries, exceptions=(Exception,)):
46 retries_left -= 1 49 retries_left -= 1
47 return func(*args, **kwargs) 50 return func(*args, **kwargs)
48 51
49 except exceptions: 52 except exceptions as e:
53 # Don't retry for PandoraExceptions - unlikely that result
54 # will change for same set of input parameters.
55 if isinstance(e, PandoraException):
56 continue
50 if retries_left > 0: 57 if retries_left > 0:
51 time.sleep(delay_exponential( 58 time.sleep(delay_exponential(
52 0.5, 2, max_tries - retries_left)) 59 0.5, 2, max_tries - retries_left))
@@ -227,9 +234,13 @@ class APITransport(object):
227 self._start_request(method) 234 self._start_request(method)
228 235
229 url = self._build_url(method) 236 url = self._build_url(method)
237 log_data = data
230 data = self._build_data(method, data) 238 data = self._build_data(method, data)
231 params = self._build_params(method) 239 params = self._build_params(method)
240 logger.info('TRANSPORT: \nMETHOD: {}\nPARAMS: {}\nDATA: {}\nURL: {}'
241 .format(method, params, log_data, url))
232 result = self._make_http_request(url, data, params) 242 result = self._make_http_request(url, data, params)
243 logger.info('TRANSPORT: \nRESULT: {}\n'.format(result))
233 244
234 return self._parse_response(result) 245 return self._parse_response(result)
235 246
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_models.py b/tests/test_pandora/test_models.py
index ecd8cf1..06c988c 100644
--- a/tests/test_pandora/test_models.py
+++ b/tests/test_pandora/test_models.py
@@ -205,19 +205,19 @@ class TestAdItem(TestCase):
205 JSON_DATA = { 205 JSON_DATA = {
206 'audioUrlMap': { 206 'audioUrlMap': {
207 'mediumQuality': { 207 'mediumQuality': {
208 'audioUrl': 'mock_med_url', 'bitrate': '64', 'protocol': 'http', 'encoding': 'aacplus' 208 'audioUrl': 'med_url_mock', 'bitrate': '64', 'protocol': 'http', 'encoding': 'aacplus'
209 }, 209 },
210 'highQuality': { 210 'highQuality': {
211 'audioUrl': 'mock_high_url', 'bitrate': '64', 'protocol': 'http', 'encoding': 'aacplus' 211 'audioUrl': 'high_url_mock', 'bitrate': '64', 'protocol': 'http', 'encoding': 'aacplus'
212 }, 212 },
213 'lowQuality': { 213 'lowQuality': {
214 'audioUrl': 'mock_low_url', 'bitrate': '32', 'protocol': 'http', 'encoding': 'aacplus'}}, 214 'audioUrl': 'low_url_mock', 'bitrate': '32', 'protocol': 'http', 'encoding': 'aacplus'}},
215 'clickThroughUrl': 'mock_click_url', 215 'clickThroughUrl': 'click_url_mock',
216 'imageUrl': 'mock_img_url', 216 'imageUrl': 'img_url_mock',
217 'companyName': '', 217 'companyName': '',
218 'title': '', 218 'title': '',
219 'trackGain': '0.0', 219 'trackGain': '0.0',
220 'adTrackingTokens': ['mock_token_1', 'mock_token_2'] 220 'adTrackingTokens': ['token_1_mock', 'token_2_mock']
221 } 221 }
222 222
223 def setUp(self): 223 def setUp(self):
@@ -230,10 +230,19 @@ class TestAdItem(TestCase):
230 230
231 def test_register_ad(self): 231 def test_register_ad(self):
232 self.result._api_client.register_ad = Mock() 232 self.result._api_client.register_ad = Mock()
233 self.result.register_ad('id_dummy') 233 self.result.register_ad('id_mock')
234 234
235 assert self.result._api_client.register_ad.called 235 assert self.result._api_client.register_ad.called
236 236
237 def test_register_ad_raises_exception_if_no_tracking_tokens_available(self):
238 with self.assertRaises(ValueError):
239 self.result.tracking_tokens = []
240 self.result._api_client.register_ad = Mock(spec=AdItem)
241
242 self.result.register_ad('id_mock')
243
244 assert self.result._api_client.register_ad.called
245
237 def test_prepare_playback(self): 246 def test_prepare_playback(self):
238 with patch.object(PlaylistModel, 'prepare_playback') as super_mock: 247 with patch.object(PlaylistModel, 'prepare_playback') as super_mock:
239 248
diff --git a/tests/test_pandora/test_transport.py b/tests/test_pandora/test_transport.py
index 59d4cea..bdd549c 100644
--- a/tests/test_pandora/test_transport.py
+++ b/tests/test_pandora/test_transport.py
@@ -6,7 +6,7 @@ from pandora.py2compat import Mock, call
6from tests.test_pandora.test_clientbuilder import TestSettingsDictBuilder 6from tests.test_pandora.test_clientbuilder import TestSettingsDictBuilder
7 7
8 8
9class SysCallError(Exception): 9class SysCallError(IOError):
10 pass 10 pass
11 11
12 12
diff --git a/tests/test_pydora/test_utils.py b/tests/test_pydora/test_utils.py
index 64cdf74..e5a81b5 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': 'mock_token'})
22 dummy_ad = AdItem.from_json(self.client, {'station_id': 'dummy_id'}) 22 ad_mock = AdItem.from_json(self.client, {'station_id': 'mock_id'})
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': 'mock_token'})
35 dummy_track = PlaylistItem.from_json(self.client, {'token': 'dummy_token'}) 35 mock_track = PlaylistItem.from_json(self.client, {'token': 'mock_token'})
36 get_playlist_mock.return_value=iter([dummy_track]) 36 get_playlist_mock.return_value=iter([mock_track])
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()