Skip to content

Commit 83edcca

Browse files
Rolf Timmermans and Simon Wahlströmrolftimmermans
authored andcommitted
Implement retry logic.
1 parent 3fa96c8 commit 83edcca

File tree

5 files changed

+95
-21
lines changed

5 files changed

+95
-21
lines changed

CHANGES.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
## 1.5.0
2+
* Retry failed requests by default.
3+
4+
## 1.4.0
5+
* Added support for HTTP proxies.

test/helper.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
import httpretty
88
from nose.exc import SkipTest
99

10+
if sys.version_info < (3, 3):
11+
from mock import DEFAULT
12+
else:
13+
from unittest.mock import DEFAULT
14+
1015
if sys.version_info < (2, 7):
1116
import unittest2 as unittest
1217
else:
@@ -20,11 +25,16 @@
2025
import tinify
2126

2227
class RaiseException(object):
23-
def __init__(self, exception):
28+
def __init__(self, exception, num=None):
2429
self.exception = exception
30+
self.num = num
2531

2632
def __call__(self, *args, **kwargs):
27-
raise self.exception
33+
if self.num == 0:
34+
return DEFAULT
35+
else:
36+
if self.num: self.num -= 1
37+
raise self.exception
2838

2939
class TestHelper(unittest.TestCase):
3040
def setUp(self):

test/tinify_client_test.py

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
except ImportError:
1616
from mock import patch
1717

18+
Client.RETRY_DELAY = 10
19+
1820
class TinifyClientRequestWhenValid(TestHelper):
1921
def setUp(self):
2022
super(type(self), self).setUp()
@@ -79,7 +81,7 @@ def test_should_issue_request_with_proxy_authorization(self):
7981

8082
self.assertEqual(self.request.headers['proxy-authorization'], 'Basic dXNlcjpwYXNz')
8183

82-
class TinifyClientRequestWithTimeout(TestHelper):
84+
class TinifyClientRequestWithTimeoutRepeatedly(TestHelper):
8385
@patch('requests.sessions.Session.request', RaiseException(requests.exceptions.Timeout))
8486
def test_should_raise_connection_error(self):
8587
with self.assertRaises(ConnectionError) as context:
@@ -92,7 +94,15 @@ def test_should_raise_connection_error_with_cause(self):
9294
Client('key').request('GET', '/')
9395
self.assertIsInstance(context.exception.__cause__, requests.exceptions.Timeout)
9496

95-
class TinifyClientRequestWithConnectionError(TestHelper):
97+
class TinifyClientRequestWithTimeoutOnce(TestHelper):
98+
@patch('requests.sessions.Session.request')
99+
def test_should_issue_request(self, mock):
100+
mock.side_effect = RaiseException(requests.exceptions.Timeout, num=1)
101+
mock.return_value = requests.Response()
102+
mock.return_value.status_code = 201
103+
self.assertIsInstance(Client('key').request('GET', '/', {}), requests.Response)
104+
105+
class TinifyClientRequestWithConnectionErrorRepeatedly(TestHelper):
96106
@patch('requests.sessions.Session.request', RaiseException(requests.exceptions.ConnectionError('connection error')))
97107
def test_should_raise_connection_error(self):
98108
with self.assertRaises(ConnectionError) as context:
@@ -105,14 +115,30 @@ def test_should_raise_connection_error_with_cause(self):
105115
Client('key').request('GET', '/')
106116
self.assertIsInstance(context.exception.__cause__, requests.exceptions.ConnectionError)
107117

108-
class TinifyClientRequestWithSomeError(TestHelper):
118+
class TinifyClientRequestWithConnectionErrorOnce(TestHelper):
119+
@patch('requests.sessions.Session.request')
120+
def test_should_issue_request(self, mock):
121+
mock.side_effect = RaiseException(requests.exceptions.ConnectionError, num=1)
122+
mock.return_value = requests.Response()
123+
mock.return_value.status_code = 201
124+
self.assertIsInstance(Client('key').request('GET', '/', {}), requests.Response)
125+
126+
class TinifyClientRequestWithSomeErrorRepeatedly(TestHelper):
109127
@patch('requests.sessions.Session.request', RaiseException(RuntimeError('some error')))
110128
def test_should_raise_connection_error(self):
111129
with self.assertRaises(ConnectionError) as context:
112130
Client('key').request('GET', '/')
113131
self.assertEqual('Error while connecting: some error', str(context.exception))
114132

115-
class TinifyClientRequestWithServerError(TestHelper):
133+
class TinifyClientRequestWithSomeErrorOnce(TestHelper):
134+
@patch('requests.sessions.Session.request')
135+
def test_should_issue_request(self, mock):
136+
mock.side_effect = RaiseException(RuntimeError('some error'), num=1)
137+
mock.return_value = requests.Response()
138+
mock.return_value.status_code = 201
139+
self.assertIsInstance(Client('key').request('GET', '/', {}), requests.Response)
140+
141+
class TinifyClientRequestWithServerErrorRepeatedly(TestHelper):
116142
def test_should_raise_server_error(self):
117143
httpretty.register_uri(httpretty.GET, 'https://api.tinify.com/', status=584,
118144
body='{"error":"InternalServerError","message":"Oops!"}')
@@ -121,7 +147,18 @@ def test_should_raise_server_error(self):
121147
Client('key').request('GET', '/')
122148
self.assertEqual('Oops! (HTTP 584/InternalServerError)', str(context.exception))
123149

124-
class TinifyClientRequestWithBadServerResponse(TestHelper):
150+
class TinifyClientRequestWithServerErrorOnce(TestHelper):
151+
def test_should_issue_request(self):
152+
httpretty.register_uri(httpretty.GET, 'https://api.tinify.com/',
153+
responses=[
154+
httpretty.Response(body='{"error":"InternalServerError","message":"Oops!"}', status=584),
155+
httpretty.Response(body='all good', status=201),
156+
])
157+
158+
response = Client('key').request('GET', '/')
159+
self.assertEqual('201', str(response.status_code))
160+
161+
class TinifyClientRequestWithBadServerResponseRepeatedly(TestHelper):
125162
def test_should_raise_server_error(self):
126163
httpretty.register_uri(httpretty.GET, 'https://api.tinify.com/', status=543,
127164
body='<!-- this is not json -->')
@@ -132,6 +169,17 @@ def test_should_raise_server_error(self):
132169
msg = r'Error while parsing response: .* \(HTTP 543/ParseError\)'
133170
self.assertRegexpMatches(str(context.exception), msg)
134171

172+
class TinifyClientRequestWithBadServerResponseOnce(TestHelper):
173+
def test_should_issue_request(self):
174+
httpretty.register_uri(httpretty.GET, 'https://api.tinify.com/',
175+
responses=[
176+
httpretty.Response(body='<!-- this is not json -->', status=543),
177+
httpretty.Response(body='all good', status=201),
178+
])
179+
180+
response = Client('key').request('GET', '/')
181+
self.assertEqual('201', str(response.status_code))
182+
135183
class TinifyClientRequestWithClientError(TestHelper):
136184
def test_should_raise_client_error(self):
137185
httpretty.register_uri(httpretty.GET, 'https://api.tinify.com/', status=492,

tinify/client.py

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@
88
import requests.exceptions
99
from requests.compat import json
1010
import traceback
11+
import time
1112

1213
import tinify
1314
from .errors import ConnectionError, Error
1415

1516
class Client(object):
1617
API_ENDPOINT = 'https://api.tinify.com'
18+
19+
RETRY_COUNT = 1
20+
RETRY_DELAY = 500
21+
1722
USER_AGENT = 'Tinify/{0} Python/{1} ({2})'.format(tinify.__version__, platform.python_version(), platform.python_implementation())
1823

1924
def __init__(self, key, app_identifier=None, proxy=None):
@@ -35,7 +40,7 @@ def __exit__(self, *args):
3540
def close(self):
3641
self.session.close()
3742

38-
def request(self, method, url, body=None, header={}):
43+
def request(self, method, url, body=None):
3944
url = url if url.lower().startswith('https://') else self.API_ENDPOINT + url
4045
params = {}
4146
if isinstance(body, dict):
@@ -46,23 +51,29 @@ def request(self, method, url, body=None, header={}):
4651
elif body:
4752
params['data'] = body
4853

49-
try:
50-
response = self.session.request(method, url, **params)
51-
except requests.exceptions.Timeout as err:
52-
raise ConnectionError('Timeout while connecting', cause=err)
53-
except Exception as err:
54-
raise ConnectionError('Error while connecting: {0}'.format(err), cause=err)
54+
for retries in range(self.RETRY_COUNT, -1, -1):
55+
if retries < self.RETRY_COUNT: time.sleep(self.RETRY_DELAY / 1000.0)
56+
57+
try:
58+
response = self.session.request(method, url, **params)
59+
except requests.exceptions.Timeout as err:
60+
if retries > 0: continue
61+
raise ConnectionError('Timeout while connecting', cause=err)
62+
except Exception as err:
63+
if retries > 0: continue
64+
raise ConnectionError('Error while connecting: {0}'.format(err), cause=err)
65+
66+
count = response.headers.get('compression-count')
67+
if count:
68+
tinify.compression_count = int(count)
5569

56-
count = response.headers.get('compression-count')
57-
if count:
58-
tinify.compression_count = int(count)
70+
if response.ok:
71+
return response
5972

60-
if response.ok:
61-
return response
62-
else:
6373
details = None
6474
try:
6575
details = response.json()
6676
except Exception as err:
6777
details = {'message': 'Error while parsing response: {0}'.format(err), 'error': 'ParseError'}
78+
if retries > 0 and response.status_code >= 500: continue
6879
raise Error.create(details.get('message'), details.get('error'), response.status_code)

tinify/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '1.4.0'
1+
__version__ = '1.5.0'

0 commit comments

Comments
 (0)