Skip to content

Commit 8786432

Browse files
add multiple retries for empty sierra responses
1 parent 0cf21cc commit 8786432

2 files changed

Lines changed: 63 additions & 3 deletions

File tree

src/nypl_py_utils/classes/oauth2_api_client.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import os
2+
from time import sleep
3+
from requests.models import Response
24
from oauthlib.oauth2 import BackendApplicationClient, TokenExpiredError
35
from requests_oauthlib import OAuth2Session
46
from nypl_py_utils.functions.log_helper import create_log
@@ -11,7 +13,7 @@ class Oauth2ApiClient:
1113
"""
1214

1315
def __init__(self, client_id=None, client_secret=None, base_url=None,
14-
token_url=None):
16+
token_url=None, with_retries=False):
1517
self.client_id = client_id \
1618
or os.environ.get('NYPL_API_CLIENT_ID', None)
1719
self.client_secret = client_secret \
@@ -25,11 +27,29 @@ def __init__(self, client_id=None, client_secret=None, base_url=None,
2527

2628
self.logger = create_log('oauth2_api_client')
2729

30+
self.with_retries = with_retries
31+
2832
def get(self, request_path, **kwargs):
2933
"""
3034
Issue an HTTP GET on the given request_path
3135
"""
32-
return self._do_http_method('GET', request_path, **kwargs)
36+
resp = self._do_http_method('GET', request_path, **kwargs)
37+
if resp.json() is None and self.with_retries is True:
38+
retries = \
39+
kwargs.get('retries', 0) + 1
40+
if retries < 3:
41+
self.logger.warning(
42+
f'Retrying get request due to empty response from\
43+
Oauth2 Client. Retry #{retries}')
44+
sleep(pow(2, retries - 1))
45+
kwargs['retries'] = retries
46+
resp = self.get(request_path, **kwargs)
47+
else:
48+
resp = Response()
49+
resp.message = 'Oauth2 Client: Request failed after 3 \
50+
empty responses received from Oauth2 Client'
51+
resp.status_code = 500
52+
return resp
3353

3454
def post(self, request_path, json, **kwargs):
3555
"""
@@ -79,7 +99,7 @@ def _do_http_method(self, method, request_path, **kwargs):
7999
from None
80100

81101
self._generate_access_token()
82-
return self._do_http_method(method, request_path, **kwargs)
102+
return self._do_http_method(method, request_path, **kwargs).json()
83103

84104
def _create_oauth_client(self):
85105
"""

tests/test_oauth2_api_client.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,18 @@
2020
TOKEN_URL = 'https://oauth.example.com/oauth/token'
2121

2222

23+
class MockEmptyResponse:
24+
def __init__(self, empty, status_code=None):
25+
self.status_code = status_code
26+
self.empty = empty
27+
28+
def json(self):
29+
if self.empty:
30+
return None
31+
else:
32+
return 'success'
33+
34+
2335
class TestOauth2ApiClient:
2436

2537
@pytest.fixture
@@ -36,6 +48,15 @@ def test_instance(self, requests_mock):
3648
client_secret='clientsecret'
3749
)
3850

51+
@pytest.fixture
52+
def test_instance_with_retries(self, requests_mock):
53+
return Oauth2ApiClient(base_url=BASE_URL,
54+
token_url=TOKEN_URL,
55+
client_id='clientid',
56+
client_secret='clientsecret',
57+
with_retries=True
58+
)
59+
3960
def test_uses_env_vars(self):
4061
env = {
4162
'NYPL_API_CLIENT_ID': 'env client id',
@@ -136,3 +157,22 @@ def test_token_refresh_failure_raises_error(
136157
test_instance._do_http_method('GET', 'foo')
137158
# Expect 1 initial token fetch, plus 3 retries:
138159
assert len(token_server_post.request_history) == 4
160+
161+
def test_http_retry_fail(self, requests_mock, test_instance_with_retries,
162+
token_server_post, mocker):
163+
mocker.patch.object(test_instance_with_retries, '_do_http_method',
164+
return_value=MockEmptyResponse(empty=True))
165+
get_spy = mocker.spy(test_instance_with_retries, 'get')
166+
resp = test_instance_with_retries.get('spaghetti')
167+
assert get_spy.call_count == 3
168+
assert resp.status_code == 500
169+
170+
def test_http_retry_success(self, requests_mock, test_instance_with_retries,
171+
token_server_post, mocker):
172+
mocker.patch.object(test_instance_with_retries, '_do_http_method',
173+
side_effect=[MockEmptyResponse(empty=True),
174+
MockEmptyResponse(empty=False, status_code=200)])
175+
get_spy = mocker.spy(test_instance_with_retries, 'get')
176+
resp = test_instance_with_retries.get('spaghetti')
177+
assert get_spy.call_count == 2
178+
assert resp.json() == 'success'

0 commit comments

Comments
 (0)