Skip to content

Commit 448fd45

Browse files
authored
Merge pull request #557 from cmu-delphi/retries
Add retry to Epidata request
2 parents dd5d710 + 882c8df commit 448fd45

File tree

5 files changed

+41
-9
lines changed

5 files changed

+41
-9
lines changed

integrations/client/test_delphi_epidata.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# standard library
44
import unittest
55
from unittest.mock import patch, MagicMock
6+
from json import JSONDecodeError
67

78
# third party
89
from aiohttp.client_exceptions import ClientResponseError
@@ -293,13 +294,38 @@ def test_request_method(self, get, post):
293294
get.assert_called_once()
294295
post.assert_not_called()
295296
with self.subTest(name='post request'):
297+
get.reset_mock()
296298
mock_response = MagicMock()
297299
mock_response.status_code = 414
298300
get.return_value = mock_response
299301
Epidata.covidcast('src', 'sig', 'day', 'county', 20200414, '01234')
300-
self.assertEqual(get.call_count, 2) # one from post test and one from get test
302+
get.assert_called_once()
301303
post.assert_called_once()
302304

305+
@patch('requests.get')
306+
def test_retry_request(self, get):
307+
"""Test that a GET request is default and POST is used if a 414 is returned."""
308+
with self.subTest(name='test successful retry'):
309+
mock_response = MagicMock()
310+
mock_response.status_code = 200
311+
get.side_effect = [JSONDecodeError('Expecting value', "", 0), mock_response]
312+
response = Epidata._request(None)
313+
self.assertEqual(get.call_count, 2)
314+
self.assertEqual(response, mock_response.json())
315+
316+
with self.subTest(name='test retry'):
317+
get.reset_mock()
318+
mock_response = MagicMock()
319+
mock_response.status_code = 200
320+
get.side_effect = [JSONDecodeError('Expecting value', "", 0),
321+
JSONDecodeError('Expecting value', "", 0),
322+
mock_response]
323+
response = Epidata._request(None)
324+
self.assertEqual(get.call_count, 2) # 2 from previous test + 2 from this one
325+
self.assertEqual(response,
326+
{'result': 0, 'message': 'error: Expecting value: line 1 column 1 (char 0)'}
327+
)
328+
303329
def test_geo_value(self):
304330
"""test different variants of geo types: single, *, multi."""
305331

requirements.dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ black>=20.8b1
33
sqlalchemy-stubs>=0.3
44
mypy>=0.790
55
pytest
6+
tenacity==7.0.0
67
bump2version

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ python-dotenv==0.15.0
55
orjson==3.4.7
66
pandas==1.2.3
77
scipy==1.6.2
8+
tenacity==7.0.0
89
newrelic

src/client/delphi_epidata.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# External modules
1212
import requests
1313
import asyncio
14-
import warnings
14+
from tenacity import retry, stop_after_attempt
1515

1616
from aiohttp import ClientSession, TCPConnector
1717
from pkg_resources import get_distribution, DistributionNotFound
@@ -52,7 +52,15 @@ def _list(values):
5252
values = [values]
5353
return ','.join([Epidata._listitem(value) for value in values])
5454

55-
# Helper function to request and parse epidata
55+
@staticmethod
56+
@retry(reraise=True, stop=stop_after_attempt(2))
57+
def _request_with_retry(params):
58+
"""Make request with a retry if an exception is thrown."""
59+
req = requests.get(Epidata.BASE_URL, params, headers=_HEADERS)
60+
if req.status_code == 414:
61+
req = requests.post(Epidata.BASE_URL, params, headers=_HEADERS)
62+
return req
63+
5664
@staticmethod
5765
def _request(params):
5866
"""Request and parse epidata.
@@ -62,13 +70,8 @@ def _request(params):
6270
long and returns a 414.
6371
"""
6472
try:
65-
# API call
66-
req = requests.get(Epidata.BASE_URL, params, headers=_HEADERS)
67-
if req.status_code == 414:
68-
req = requests.post(Epidata.BASE_URL, params, headers=_HEADERS)
69-
return req.json()
73+
return Epidata._request_with_retry(params).json()
7074
except Exception as e:
71-
# Something broke
7275
return {'result': 0, 'message': 'error: ' + str(e)}
7376

7477
# Raise an Exception on error, otherwise return epidata

src/client/packaging/pypi/setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
install_requires=[
1717
'aiohttp',
1818
'requests>=2.7.0',
19+
'tenacity'
1920
],
2021
classifiers=[
2122
'Programming Language :: Python',

0 commit comments

Comments
 (0)