Skip to content

Commit 61df74d

Browse files
committed
feat(core): add timeout param to JSONConnection.api_request()
1 parent 38d8f2b commit 61df74d

File tree

2 files changed

+110
-11
lines changed

2 files changed

+110
-11
lines changed

core/google/cloud/_http.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ def _make_request(
222222
content_type=None,
223223
headers=None,
224224
target_object=None,
225+
timeout=None,
225226
):
226227
"""A low level method to send a request to the API.
227228
@@ -250,6 +251,13 @@ def _make_request(
250251
custom behavior, for example, to defer an HTTP request and complete
251252
initialization of the object at a later time.
252253
254+
:type timeout: float or tuple
255+
:param timeout: (optional) The amount of time, in seconds, to wait
256+
for the server response. By default, the method waits indefinitely.
257+
258+
Can also be passed as a tuple (connect_timeout, read_timeout).
259+
See :meth:`requests.Session.request` documentation for details.
260+
253261
:rtype: :class:`requests.Response`
254262
:returns: The HTTP response.
255263
"""
@@ -263,10 +271,12 @@ def _make_request(
263271
headers[CLIENT_INFO_HEADER] = self.user_agent
264272
headers["User-Agent"] = self.user_agent
265273

266-
return self._do_request(method, url, headers, data, target_object)
274+
return self._do_request(
275+
method, url, headers, data, target_object, timeout=timeout
276+
)
267277

268278
def _do_request(
269-
self, method, url, headers, data, target_object
279+
self, method, url, headers, data, target_object, timeout=None
270280
): # pylint: disable=unused-argument
271281
"""Low-level helper: perform the actual API request over HTTP.
272282
@@ -289,10 +299,19 @@ def _do_request(
289299
(Optional) Unused ``target_object`` here but may be used by a
290300
superclass.
291301
302+
:type timeout: float or tuple
303+
:param timeout: (optional) The amount of time, in seconds, to wait
304+
for the server response. By default, the method waits indefinitely.
305+
306+
Can also be passed as a tuple (connect_timeout, read_timeout).
307+
See :meth:`requests.Session.request` documentation for details.
308+
292309
:rtype: :class:`requests.Response`
293310
:returns: The HTTP response.
294311
"""
295-
return self.http.request(url=url, method=method, headers=headers, data=data)
312+
return self.http.request(
313+
url=url, method=method, headers=headers, data=data, timeout=timeout
314+
)
296315

297316
def api_request(
298317
self,
@@ -306,6 +325,7 @@ def api_request(
306325
api_version=None,
307326
expect_json=True,
308327
_target_object=None,
328+
timeout=None,
309329
):
310330
"""Make a request over the HTTP transport to the API.
311331
@@ -360,6 +380,13 @@ def api_request(
360380
can allow custom behavior, for example, to defer an HTTP request
361381
and complete initialization of the object at a later time.
362382
383+
:type timeout: float or tuple
384+
:param timeout: (optional) The amount of time, in seconds, to wait
385+
for the server response. By default, the method waits indefinitely.
386+
387+
Can also be passed as a tuple (connect_timeout, read_timeout).
388+
See :meth:`requests.Session.request` documentation for details.
389+
363390
:raises ~google.cloud.exceptions.GoogleCloudError: if the response code
364391
is not 200 OK.
365392
:raises ValueError: if the response content type is not JSON.
@@ -387,6 +414,7 @@ def api_request(
387414
content_type=content_type,
388415
headers=headers,
389416
target_object=_target_object,
417+
timeout=timeout,
390418
)
391419

392420
if not 200 <= response.status_code < 300:

core/tests/unit/test__http.py

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ def test__make_request_no_data_no_content_type_no_headers(self):
217217
CLIENT_INFO_HEADER: conn.user_agent,
218218
}
219219
http.request.assert_called_once_with(
220-
method="GET", url=url, headers=expected_headers, data=None
220+
method="GET", url=url, headers=expected_headers, data=None, timeout=None
221221
)
222222

223223
def test__make_request_w_data_no_extra_headers(self):
@@ -238,7 +238,7 @@ def test__make_request_w_data_no_extra_headers(self):
238238
CLIENT_INFO_HEADER: conn.user_agent,
239239
}
240240
http.request.assert_called_once_with(
241-
method="GET", url=url, headers=expected_headers, data=data
241+
method="GET", url=url, headers=expected_headers, data=data, timeout=None
242242
)
243243

244244
def test__make_request_w_extra_headers(self):
@@ -258,7 +258,30 @@ def test__make_request_w_extra_headers(self):
258258
CLIENT_INFO_HEADER: conn.user_agent,
259259
}
260260
http.request.assert_called_once_with(
261-
method="GET", url=url, headers=expected_headers, data=None
261+
method="GET", url=url, headers=expected_headers, data=None, timeout=None
262+
)
263+
264+
def test__make_request_w_timeout(self):
265+
from google.cloud._http import CLIENT_INFO_HEADER
266+
267+
http = make_requests_session([make_response()])
268+
client = mock.Mock(_http=http, spec=["_http"])
269+
conn = self._make_one(client)
270+
271+
url = "http://example.com/test"
272+
conn._make_request("GET", url, timeout=(5.5, 2.8))
273+
274+
expected_headers = {
275+
"Accept-Encoding": "gzip",
276+
"User-Agent": conn.user_agent,
277+
CLIENT_INFO_HEADER: conn.user_agent,
278+
}
279+
http.request.assert_called_once_with(
280+
method="GET",
281+
url=url,
282+
headers=expected_headers,
283+
data=None,
284+
timeout=(5.5, 2.8),
262285
)
263286

264287
def test_api_request_defaults(self):
@@ -282,7 +305,11 @@ def test_api_request_defaults(self):
282305
base=conn.API_BASE_URL, version=conn.API_VERSION, path=path
283306
)
284307
http.request.assert_called_once_with(
285-
method="GET", url=expected_url, headers=expected_headers, data=None
308+
method="GET",
309+
url=expected_url,
310+
headers=expected_headers,
311+
data=None,
312+
timeout=None,
286313
)
287314

288315
def test_api_request_w_non_json_response(self):
@@ -321,7 +348,11 @@ def test_api_request_w_query_params(self):
321348
CLIENT_INFO_HEADER: conn.user_agent,
322349
}
323350
http.request.assert_called_once_with(
324-
method="GET", url=mock.ANY, headers=expected_headers, data=None
351+
method="GET",
352+
url=mock.ANY,
353+
headers=expected_headers,
354+
data=None,
355+
timeout=None,
325356
)
326357

327358
url = http.request.call_args[1]["url"]
@@ -351,7 +382,11 @@ def test_api_request_w_headers(self):
351382
CLIENT_INFO_HEADER: conn.user_agent,
352383
}
353384
http.request.assert_called_once_with(
354-
method="GET", url=mock.ANY, headers=expected_headers, data=None
385+
method="GET",
386+
url=mock.ANY,
387+
headers=expected_headers,
388+
data=None,
389+
timeout=None,
355390
)
356391

357392
def test_api_request_w_extra_headers(self):
@@ -377,7 +412,11 @@ def test_api_request_w_extra_headers(self):
377412
CLIENT_INFO_HEADER: conn.user_agent,
378413
}
379414
http.request.assert_called_once_with(
380-
method="GET", url=mock.ANY, headers=expected_headers, data=None
415+
method="GET",
416+
url=mock.ANY,
417+
headers=expected_headers,
418+
data=None,
419+
timeout=None,
381420
)
382421

383422
def test_api_request_w_data(self):
@@ -400,7 +439,39 @@ def test_api_request_w_data(self):
400439
}
401440

402441
http.request.assert_called_once_with(
403-
method="POST", url=mock.ANY, headers=expected_headers, data=expected_data
442+
method="POST",
443+
url=mock.ANY,
444+
headers=expected_headers,
445+
data=expected_data,
446+
timeout=None,
447+
)
448+
449+
def test_api_request_w_timeout(self):
450+
from google.cloud._http import CLIENT_INFO_HEADER
451+
452+
http = make_requests_session(
453+
[make_response(content=b"{}", headers=self.JSON_HEADERS)]
454+
)
455+
client = mock.Mock(_http=http, spec=["_http"])
456+
conn = self._make_mock_one(client)
457+
path = "/path/required"
458+
459+
self.assertEqual(conn.api_request("GET", path, timeout=(2.2, 3.3)), {})
460+
461+
expected_headers = {
462+
"Accept-Encoding": "gzip",
463+
"User-Agent": conn.user_agent,
464+
CLIENT_INFO_HEADER: conn.user_agent,
465+
}
466+
expected_url = "{base}/mock/{version}{path}".format(
467+
base=conn.API_BASE_URL, version=conn.API_VERSION, path=path
468+
)
469+
http.request.assert_called_once_with(
470+
method="GET",
471+
url=expected_url,
472+
headers=expected_headers,
473+
data=None,
474+
timeout=(2.2, 3.3),
404475
)
405476

406477
def test_api_request_w_404(self):

0 commit comments

Comments
 (0)