Skip to content

Commit cfe55c6

Browse files
Merge branch 'main' into qa
2 parents 3ea93fa + 92f9d7a commit cfe55c6

5 files changed

Lines changed: 54 additions & 24 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
# Changelog
2+
## v1.1.3 11/9/23
3+
- Finalize retry logic in Oauth2 Client
4+
25
## v1.1.2
36
- Update config_helper to accept list environment variables
47

58
## v1.1.0/v1.1.1
6-
- Add retries for empty responses in oauth2 client. This was added to address a known quirk in the Sierra API where this response is returned:
9+
- Add retries for empty responses in Oauth2 Client. This was added to address a known quirk in the Sierra API where this response is returned:
710
```
811
> GET / HTTP/1.1
912
> Host: ilsstaff.nypl.org

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,4 @@ This repo uses the [Main-QA-Production](https://github.com/NYPL/engineering-gene
106106
- Deploy app to production on GitHub and confirm it works
107107

108108
## Deployment
109-
The utils repo is deployed as a PyPI package [here](https://pypi.org/project/nypl-py-utils/) and as a Test PyPI package for QA purposes [here](https://test.pypi.org/project/nypl-py-utils/). In order to be deployed, the version listed in `pyproject.toml` **must be updated**. To deploy to Test PyPI, create a new release in GitHub and tag it `qa-vX.X.X`. The GitHub Actions deploy-qa workflow will then build and publish the package. To deploy to production PyPI, create a release and tag it `production-vX.X.X`.
109+
The utils repo is deployed as a PyPI package [here](https://pypi.org/project/nypl-py-utils/) and as a Test PyPI package for QA purposes [here](https://test.pypi.org/project/nypl-py-utils/). In order to be deployed, the version listed in `pyproject.toml` **must be updated**. To deploy to Test PyPI, [create a new release](https://github.com/NYPL/python-utils/releases) in GitHub and tag it `qa-vX.X.X`. The GitHub Actions deploy-qa workflow will then build and publish the package. To deploy to production PyPI, create a release and tag it `production-vX.X.X`.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "nypl_py_utils"
7-
version = "1.1.2"
7+
version = "1.1.3"
88
authors = [
99
{ name="Aaron Friedman", email="aaronfriedman@nypl.org" },
1010
]

src/nypl_py_utils/classes/oauth2_api_client.py

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,37 @@ def get(self, request_path, **kwargs):
3737
Issue an HTTP GET on the given request_path
3838
"""
3939
resp = self._do_http_method('GET', request_path, **kwargs)
40-
if resp.json() is None and self.with_retries is True:
41-
retries = \
42-
kwargs.get('retries', 0) + 1
43-
if retries < 3:
44-
self.logger.warning(
45-
f'Retrying get request due to empty response from\
46-
Oauth2 Client using path: {request_path}. \
47-
Retry #{retries}')
48-
sleep(pow(2, retries - 1))
49-
kwargs['retries'] = retries
50-
resp = self.get(request_path, **kwargs)
51-
else:
52-
resp = Response()
53-
resp.message = 'Oauth2 Client: Request failed after 3 \
54-
empty responses received from Oauth2 Client'
55-
resp.status_code = 500
40+
# This try/except block is to handle one of at least two possible
41+
# Sierra server errors. One is an empty response, and another is a
42+
# response with a 200 status code but response text in HTML declaring
43+
# Status 500.
44+
try:
45+
resp.json()
46+
except Exception:
47+
# build default server error response
48+
resp = Response()
49+
resp.message = 'Oauth2 Client: Bad response from OauthClient'
50+
resp.status_code = 500
51+
self.logger.warning(f'Get request using path {request_path} \
52+
returned response text:\n{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.
5671
return resp
5772

5873
def post(self, request_path, json, **kwargs):

tests/test_oauth2_api_client.py

Lines changed: 17 additions & 5 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,18 +159,29 @@ 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+
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,
162-
token_server_post, mocker):
173+
mocker):
163174
mocker.patch.object(test_instance_with_retries, '_do_http_method',
164175
return_value=MockEmptyResponse(empty=True))
165176
get_spy = mocker.spy(test_instance_with_retries, 'get')
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,
171-
test_instance_with_retries,
172-
token_server_post, mocker):
184+
test_instance_with_retries, mocker):
173185
mocker.patch.object(test_instance_with_retries, '_do_http_method',
174186
side_effect=[MockEmptyResponse(empty=True),
175187
MockEmptyResponse(empty=False,

0 commit comments

Comments
 (0)