diff options
author | jcass <john.cass77@gmail.com> | 2015-12-28 16:23:57 +0200 |
---|---|---|
committer | jcass <john.cass77@gmail.com> | 2015-12-28 16:23:57 +0200 |
commit | d2a520dbf6257986128d14a6c7d23aa37725d5e3 (patch) | |
tree | 847cf884fe129f8ff4d6cfef243b7b55cd3fa720 | |
parent | eb91a5c00701689085b6f427d62e487f417c6f51 (diff) | |
download | pydora-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.py | 23 | ||||
-rw-r--r-- | pandora/transport.py | 15 | ||||
-rw-r--r-- | pydora/utils.py | 9 | ||||
-rw-r--r-- | tests/test_pandora/test_models.py | 23 | ||||
-rw-r--r-- | tests/test_pandora/test_transport.py | 2 | ||||
-rw-r--r-- | tests/test_pydora/test_utils.py | 14 |
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 | ||
10 | API consumers should use one of the API clients in the pandora.client package. | 10 | API consumers should use one of the API clients in the pandora.client package. |
11 | """ | 11 | """ |
12 | import logging | ||
12 | import random | 13 | import random |
13 | import time | 14 | import time |
14 | import json | 15 | import json |
@@ -22,8 +23,10 @@ from .errors import PandoraException | |||
22 | 23 | ||
23 | DEFAULT_API_HOST = "tuner.pandora.com/services/json/" | 24 | DEFAULT_API_HOST = "tuner.pandora.com/services/json/" |
24 | 25 | ||
26 | logger = logging.getLogger(__name__) | ||
25 | 27 | ||
26 | def retries(max_tries, exceptions=(Exception,)): | 28 | |
29 | def 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 | ||
139 | class SilentPopen(subprocess.Popen): | 130 | class 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 | |||
6 | from tests.test_pandora.test_clientbuilder import TestSettingsDictBuilder | 6 | from tests.test_pandora.test_clientbuilder import TestSettingsDictBuilder |
7 | 7 | ||
8 | 8 | ||
9 | class SysCallError(Exception): | 9 | class 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() |