Skip to content

Commit 137e468

Browse files
update retry logic
1 parent 1aefa62 commit 137e468

2 files changed

Lines changed: 50 additions & 25 deletions

File tree

src/nypl_py_utils/classes/oauth2_api_client.py

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ class Oauth2ApiClient:
1010
"""
1111
Client for interacting with an Oauth2 authenticated API such as NYPL's
1212
Platform API endpoints. Note with_retries is a boolean flag which
13-
determines if empty get requests will be retried self.MAX_RETRIES times or
14-
until they are successful. This is to address a known issue with the Sierra
15-
API where empty and misformed responses are returned intermittently.
13+
determines if empty get requests will be retried 3 times or until
14+
they are successful. This is to address a known issue with the Sierra
15+
API where empty responses are returned intermittently.
1616
"""
1717

1818
def __init__(self, client_id=None, client_secret=None, base_url=None,
@@ -32,30 +32,42 @@ def __init__(self, client_id=None, client_secret=None, base_url=None,
3232

3333
self.with_retries = with_retries
3434

35-
self.MAX_RETRIES = 3
36-
3735
def get(self, request_path, **kwargs):
3836
"""
3937
Issue an HTTP GET on the given request_path
4038
"""
4139
resp = self._do_http_method('GET', request_path, **kwargs)
42-
if resp.json() is None and self.with_retries is True:
43-
retries = \
44-
kwargs.get('retries', 0) + 1
45-
if retries < self.MAX_RETRIES:
46-
self.logger.warning(
47-
f'Retrying get request due to empty response from\
48-
Oauth2 Client using path: {request_path}. \
49-
Retry #{retries}')
50-
sleep(pow(2, retries - 1))
51-
kwargs['retries'] = retries
52-
resp = self.get(request_path, **kwargs)
53-
else:
54-
resp = Response()
55-
resp.message = f'Oauth2 Client: Request failed after \
56-
{self.MAX_RETRIES} empty responses received \
57-
from Oauth2 Client'
58-
resp.status_code = 500
40+
self.logger.debug('spaghetti')
41+
# This try/except block is to handle one of at least two possible
42+
# Sierra server errors. One is an empty response, and another is a
43+
# response with a 200 status code but response text in HTML declaring
44+
# Status 500.
45+
try:
46+
resp.json()
47+
except Exception:
48+
# build default server error response
49+
resp = Response()
50+
resp.message = 'Oauth2 Client: Bad response from OauthClient'
51+
resp.status_code = 500
52+
self.logger.warning(f'Bad response text: {resp.text}')
53+
# if client has specified that we want to retry failed requests and
54+
# we haven't hit max retries
55+
if self.with_retries is True:
56+
retries = kwargs.get('retries', 0) + 1
57+
if retries < 3:
58+
self.logger.warning(
59+
f'Retrying get request due to empty response from\
60+
Oauth2 Client using path: {request_path}. \
61+
Retry #{retries}')
62+
sleep(pow(2, retries - 1))
63+
kwargs['retries'] = retries
64+
# try request again
65+
resp = self.get(request_path, **kwargs)
66+
else:
67+
resp.message = 'Oauth2 Client: Request failed after 3 \
68+
empty responses received from Oauth2 Client'
69+
# Return request. If retries returned real data, it will be here,
70+
# otherwise it will be the default 500 response generated earlier.
5971
return resp
6072

6173
def post(self, request_path, json, **kwargs):
@@ -101,7 +113,7 @@ def _do_http_method(self, method, request_path, **kwargs):
101113
# Raise error after 3 successive token refreshes
102114
kwargs['_do_http_method_token_refreshes'] = \
103115
kwargs.get('_do_http_method_token_refreshes', 0) + 1
104-
if kwargs['_do_http_method_token_refreshes'] > self.MAX_RETRIES:
116+
if kwargs['_do_http_method_token_refreshes'] > 3:
105117
raise Oauth2ApiClientError('Exhausted token refreshes') \
106118
from None
107119

tests/test_oauth2_api_client.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import json
44
import pytest
55
from requests_oauthlib import OAuth2Session
6-
from requests import HTTPError
6+
from requests import HTTPError, JSONDecodeError
77

88
from nypl_py_utils.classes.oauth2_api_client import (Oauth2ApiClient,
99
Oauth2ApiClientError)
@@ -24,10 +24,11 @@ class MockEmptyResponse:
2424
def __init__(self, empty, status_code=None):
2525
self.status_code = status_code
2626
self.empty = empty
27+
self.text = "error text"
2728

2829
def json(self):
2930
if self.empty:
30-
return None
31+
raise JSONDecodeError
3132
else:
3233
return 'success'
3334

@@ -158,6 +159,16 @@ def test_token_refresh_failure_raises_error(
158159
# Expect 1 initial token fetch, plus 3 retries:
159160
assert len(token_server_post.request_history) == 4
160161

162+
def test_bad_response_no_retries(self, requests_mock, test_instance,
163+
token_server_post, mocker):
164+
mocker.patch.object(test_instance, '_do_http_method',
165+
return_value=MockEmptyResponse(empty=True))
166+
get_spy = mocker.spy(test_instance, 'get')
167+
resp = test_instance.get('spaghetti')
168+
assert get_spy.call_count == 1
169+
assert resp.status_code == 500
170+
assert resp.message == 'Oauth2 Client: Bad response from OauthClient'
171+
161172
def test_http_retry_fail(self, requests_mock, test_instance_with_retries,
162173
token_server_post, mocker):
163174
mocker.patch.object(test_instance_with_retries, '_do_http_method',
@@ -166,6 +177,8 @@ def test_http_retry_fail(self, requests_mock, test_instance_with_retries,
166177
resp = test_instance_with_retries.get('spaghetti')
167178
assert get_spy.call_count == 3
168179
assert resp.status_code == 500
180+
assert resp.message == 'Oauth2 Client: Request failed after 3 \
181+
empty responses received from Oauth2 Client'
169182

170183
def test_http_retry_success(self, requests_mock,
171184
test_instance_with_retries,

0 commit comments

Comments
 (0)