Skip to content

Commit 35bee23

Browse files
committed
Add inspection modes to Python client library
1 parent c8c4830 commit 35bee23

File tree

3 files changed

+82
-0
lines changed

3 files changed

+82
-0
lines changed

dev/local/setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ packages =
2424
delphi.epidata.acquisition.twtr
2525
delphi.epidata.acquisition.wiki
2626
delphi.epidata.client
27+
delphi.epidata.common
2728
delphi.epidata.server
2829
delphi.epidata.server.admin
2930
delphi.epidata.server.admin.templates

integrations/client/test_delphi_epidata.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# standard library
44
import time
55
from json import JSONDecodeError
6+
from requests.models import Response
67
from unittest.mock import MagicMock, patch
78

89
# first party
@@ -41,6 +42,8 @@ def localSetUp(self):
4142
# use the local instance of the Epidata API
4243
Epidata.BASE_URL = 'http://delphi_web_epidata/epidata'
4344
Epidata.auth = ('epidata', 'key')
45+
Epidata.debug = False
46+
Epidata.sandbox = False
4447

4548
# use the local instance of the epidata database
4649
secrets.db.host = 'delphi_database_epidata'
@@ -221,6 +224,65 @@ def test_retry_request(self, get):
221224
{'result': 0, 'message': 'error: Expecting value: line 1 column 1 (char 0)'}
222225
)
223226

227+
@patch('requests.post')
228+
@patch('requests.get')
229+
def test_debug(self, get, post):
230+
"""Test that in debug mode request params are correctly logged."""
231+
class MockResponse:
232+
def __init__(self, content, status_code):
233+
self.content = content
234+
self.status_code = status_code
235+
def raise_for_status(self): pass
236+
237+
Epidata.debug = True
238+
239+
with self.subTest(name='test multiple GET'):
240+
with self.assertLogs('delphi_epidata_client', level='INFO') as logs:
241+
get.reset_mock()
242+
get.return_value = MockResponse(b'{"key": "value"}', 200)
243+
Epidata._request_with_retry("test_endpoint1", params={"key1": "value1"})
244+
Epidata._request_with_retry("test_endpoint2", params={"key2": "value2"})
245+
246+
output = logs.output
247+
self.assertEqual(len(output), 4) # [request, response, request, response]
248+
self.assertIn("Sending GET request to URL: http://delphi_web_epidata/epidata/test_endpoint1/", output[0])
249+
self.assertIn("params: {'key1': 'value1'}", output[0])
250+
self.assertIn("Received 200 response (16 bytes)", output[1])
251+
self.assertIn("Sending GET request to URL: http://delphi_web_epidata/epidata/test_endpoint2/", output[2])
252+
self.assertIn("params: {'key2': 'value2'}", output[2])
253+
self.assertIn("Received 200 response (16 bytes)", output[3])
254+
255+
with self.subTest(name='test GET and POST'):
256+
with self.assertLogs('delphi_epidata_client', level='INFO') as logs:
257+
get.reset_mock()
258+
get.return_value = MockResponse(b'{"key": "value"}', 414)
259+
post.reset_mock()
260+
post.return_value = MockResponse(b'{"key": "value"}', 200)
261+
Epidata._request_with_retry("test_endpoint3", params={"key3": "value3"})
262+
263+
output = logs.output
264+
self.assertEqual(len(output), 4) # [request, response, request, response]
265+
self.assertIn("Sending GET request to URL: http://delphi_web_epidata/epidata/test_endpoint3/", output[0])
266+
self.assertIn("params: {'key3': 'value3'}", output[0])
267+
self.assertIn("Received 414 response (16 bytes)", output[1])
268+
self.assertIn("Sending POST request to URL: http://delphi_web_epidata/epidata/test_endpoint3/", output[2])
269+
self.assertIn("params: {'key3': 'value3'}", output[2])
270+
self.assertIn("Received 200 response (16 bytes)", output[3])
271+
272+
@patch('requests.post')
273+
@patch('requests.get')
274+
def test_sandbox(self, get, post):
275+
"""Test that in debug + sandbox mode request params are correctly logged, but no queries are sent."""
276+
Epidata.debug = True
277+
Epidata.sandbox = True
278+
with self.assertLogs('delphi_epidata_client', level='INFO') as logs:
279+
Epidata.covidcast('src', 'sig', 'day', 'county', 20200414, '01234')
280+
output = logs.output
281+
self.assertEqual(len(output), 1)
282+
self.assertIn("Sending GET request to URL: http://delphi_web_epidata/epidata/covidcast/", output[0])
283+
get.assert_not_called()
284+
post.assert_not_called()
285+
224286
def test_geo_value(self):
225287
"""test different variants of geo types: single, *, multi."""
226288

src/client/delphi_epidata.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@
1010

1111
# External modules
1212
import requests
13+
import time
1314
import asyncio
1415
from tenacity import retry, stop_after_attempt
1516

1617
from aiohttp import ClientSession, TCPConnector, BasicAuth
1718
from importlib.metadata import version, PackageNotFoundError
1819

20+
from delphi.epidata.common.logger import get_structured_logger
21+
1922
# Obtain package version for the user-agent. Uses the installed version by
2023
# preference, even if you've installed it and then use this script independently
2124
# by accident.
@@ -49,6 +52,10 @@ class Epidata:
4952

5053
client_version = _version
5154

55+
logger = get_structured_logger('delphi_epidata_client')
56+
debug = False # if True, prints extra logging statements
57+
sandbox = False # if True, will not execute any queries
58+
5259
# Helper function to cast values and/or ranges to strings
5360
@staticmethod
5461
def _listitem(value):
@@ -71,9 +78,19 @@ def _list(values):
7178
def _request_with_retry(endpoint, params={}):
7279
"""Make request with a retry if an exception is thrown."""
7380
request_url = f"{Epidata.BASE_URL}/{endpoint}/"
81+
if Epidata.debug:
82+
Epidata.logger.info(f"Sending GET request to URL: {request_url} | params: {params} | headers: {_HEADERS}")
83+
if Epidata.sandbox:
84+
return
85+
start_time = time.time()
7486
req = requests.get(request_url, params, auth=Epidata.auth, headers=_HEADERS)
7587
if req.status_code == 414:
88+
if Epidata.debug:
89+
Epidata.logger.info(f"Received {req.status_code} response ({len(req.content)} bytes) in {(time.time() - start_time):.3f} seconds")
90+
Epidata.logger.info(f"Sending POST request to URL: {request_url} | params: {params} | headers: {_HEADERS}")
7691
req = requests.post(request_url, params, auth=Epidata.auth, headers=_HEADERS)
92+
if Epidata.debug:
93+
Epidata.logger.info(f"Received {req.status_code} response ({len(req.content)} bytes) in {(time.time() - start_time):.3f} seconds")
7794
# handle 401 and 429
7895
req.raise_for_status()
7996
return req
@@ -88,6 +105,8 @@ def _request(endpoint, params={}):
88105
"""
89106
try:
90107
result = Epidata._request_with_retry(endpoint, params)
108+
if Epidata.sandbox:
109+
return
91110
except Exception as e:
92111
return {"result": 0, "message": "error: " + str(e)}
93112
if params is not None and "format" in params and params["format"] == "csv":

0 commit comments

Comments
 (0)