Skip to content

Commit fb84698

Browse files
authored
[TOOLSLIBS-503] Add retry to Airship (#192)
* adds retry on AirshipFailure * cleanup * adds backoff module * adds retry prop test
1 parent c28e518 commit fb84698

File tree

4 files changed

+61
-24
lines changed

4 files changed

+61
-24
lines changed

requirements-dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ tox
66
black
77
pre-commit
88
sphinx-rtd-theme
9+
backoff>=1.11

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ six
33
nose
44
mock
55
sphinx-rtd-theme
6+
backoff>=1.11

tests/core/test_airship.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ def test_airship_timeout_exception(self):
2525
with self.assertRaises(ValueError):
2626
ua.Airship(key=TEST_KEY, secret=TEST_SECRET, timeout=timeout_str)
2727

28+
def test_airship_retry(self):
29+
retry_int = 5
30+
31+
airship_w_retry = ua.Airship(TEST_KEY, TEST_SECRET, retries=retry_int)
32+
33+
self.assertEqual(retry_int, airship_w_retry.retries)
34+
2835
def test_airship_location(self):
2936
location = "eu"
3037

urbanairship/core.py

Lines changed: 52 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22
import re
33

4+
import backoff
45
import requests
56

67
from . import __about__, common
@@ -67,7 +68,9 @@ def get(self, endpoint):
6768

6869

6970
class Airship(object):
70-
def __init__(self, key, secret=None, token=None, location="us", timeout=None):
71+
def __init__(
72+
self, key, secret=None, token=None, location="us", timeout=None, retries=0
73+
):
7174
"""Main client class for interacting with the Airship API.
7275
7376
:param key: [required] An Airship project key used to authenticate
@@ -77,13 +80,17 @@ def __init__(self, key, secret=None, token=None, location="us", timeout=None):
7780
:param location: [optional] The Airship cloud site your project is associated
7881
with. Possible values: 'us', 'eu'. Defaults to 'us'.
7982
:param: timeout: [optional] An integer specifying the number of seconds used
80-
for a timeout threshold
83+
for a response timeout threshold
84+
:param retries: [optional] An integer specifying the number of times to retry a
85+
failed request. Retried requests use exponential backoff between requests.
86+
Defaults to 0, no retry.
8187
"""
8288
self.key = key
8389
self.secret = secret
8490
self.token = token
8591
self.location = location
8692
self.timeout = timeout
93+
self.retries = retries
8794
self.urls = Urls(self.location)
8895

8996
if all([secret, token]):
@@ -99,6 +106,14 @@ def __init__(self, key, secret=None, token=None, location="us", timeout=None):
99106
else:
100107
raise ValueError("Either token or secret must be included")
101108

109+
@property
110+
def retries(self):
111+
return self._retries
112+
113+
@retries.setter
114+
def retries(self, value):
115+
self._retries = value
116+
102117
@property
103118
def timeout(self):
104119
return self._timeout
@@ -182,33 +197,46 @@ def _request(
182197
if encoding:
183198
headers["Content-Encoding"] = encoding
184199

185-
logger.debug(
186-
"Making %s request to %s. Headers:\n\t%s\nBody:\n\t%s",
187-
method,
188-
url,
189-
"\n\t".join("%s: %s" % (key, value) for (key, value) in headers.items()),
190-
body,
200+
@backoff.on_exception(
201+
backoff.expo, common.AirshipFailure, max_tries=(self.retries + 1)
191202
)
203+
def make_retryable_request(method, url, body, params, headers):
204+
response = self.session.request(
205+
method,
206+
url,
207+
data=body,
208+
params=params,
209+
headers=headers,
210+
timeout=self.timeout,
211+
)
192212

193-
response = self.session.request(
194-
method, url, data=body, params=params, headers=headers, timeout=self.timeout
195-
)
213+
logger.debug(
214+
"Making %s request to %s. Headers:\n\t%s\nBody:\n\t%s",
215+
method,
216+
url,
217+
"\n\t".join(
218+
"%s: %s" % (key, value) for (key, value) in headers.items()
219+
),
220+
body,
221+
)
196222

197-
logger.debug(
198-
"Received %s response. Headers:\n\t%s\nBody:\n\t%s",
199-
response.status_code,
200-
"\n\t".join(
201-
"%s: %s" % (key, value) for (key, value) in response.headers.items()
202-
),
203-
response.content,
204-
)
223+
logger.debug(
224+
"Received %s response. Headers:\n\t%s\nBody:\n\t%s",
225+
response.status_code,
226+
"\n\t".join(
227+
"%s: %s" % (key, value) for (key, value) in response.headers.items()
228+
),
229+
response.content,
230+
)
231+
232+
if response.status_code == 401:
233+
raise common.Unauthorized
234+
elif not (200 <= response.status_code < 300):
235+
raise common.AirshipFailure.from_response(response)
205236

206-
if response.status_code == 401:
207-
raise common.Unauthorized
208-
elif not (200 <= response.status_code < 300):
209-
raise common.AirshipFailure.from_response(response)
237+
return response
210238

211-
return response
239+
return make_retryable_request(method, url, body, params, headers)
212240

213241
def create_push(self):
214242
"""Create a Push notification."""

0 commit comments

Comments
 (0)