Skip to content

Commit e82f05f

Browse files
committed
client: Make request timeout configurable
Signed-off-by: Phoevos Kalemkeris <phoevos.kalemkeris@ucl.ac.uk>
1 parent 9a017d4 commit e82f05f

2 files changed

Lines changed: 47 additions & 26 deletions

File tree

client/cogstack_model_gateway_client/client.py

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,30 @@ def __init__(
1414
self,
1515
base_url: str,
1616
default_model: str = None,
17+
request_timeout: float = 300.0,
1718
polling_interval: float = 2.0,
18-
timeout: float | None = None,
19+
polling_timeout: float | None = None,
1920
):
2021
"""Initialize the GatewayClient with the base Gateway URL and optional parameters.
2122
2223
Args:
2324
base_url (str): The base URL of the Gateway service.
2425
default_model (str, optional): The default model to use for tasks. Defaults to None.
26+
request_timeout (float, optional): The HTTP request timeout in seconds for individual
27+
requests to the Gateway. Defaults to 300.0 seconds to accommodate slower requests,
28+
e.g. the ones triggering model auto-deployment, but should be adjusted as needed.
2529
polling_interval (float, optional): The interval in seconds to poll for task completion.
2630
Defaults to 2.0 seconds, with a minimum of 0.5 and maximum of 3.0 seconds.
27-
timeout (float, optional): The client polling timeout while waiting for task completion.
28-
Defaults to None (no timeout). When set to a float value, a TimeoutError will be
29-
raised if the task takes longer than the specified number of seconds. When None,
30-
the client will wait indefinitely for task completion.
31+
polling_timeout (float, optional): The client polling timeout while waiting for task
32+
completion. Defaults to None (no timeout). When set to a float value, a TimeoutError
33+
will be raised if the task takes longer than the specified number of seconds. When
34+
None, the client will wait indefinitely for task completion.
3135
"""
3236
self.base_url = base_url.rstrip("/")
3337
self.default_model = default_model
38+
self.request_timeout = request_timeout
3439
self.polling_interval = polling_interval
35-
self.timeout = timeout
40+
self.polling_timeout = polling_timeout
3641
self._client = None
3742

3843
@property
@@ -44,7 +49,7 @@ def polling_interval(self, value: float):
4449
self._polling_interval = max(0.5, min(value, 3.0))
4550

4651
async def __aenter__(self):
47-
self._client = httpx.AsyncClient()
52+
self._client = httpx.AsyncClient(timeout=self.request_timeout)
4853
return self
4954

5055
async def __aexit__(self, exc_type, exc, tb):
@@ -274,7 +279,10 @@ async def wait_for_task(
274279
error_message = task.get("error_message", "Unknown error")
275280
raise TaskFailedError(task_uuid, error_message, task)
276281
return task
277-
if self.timeout is not None and asyncio.get_event_loop().time() - start > self.timeout:
282+
if (
283+
self.polling_timeout is not None
284+
and asyncio.get_event_loop().time() - start > self.polling_timeout
285+
):
278286
raise TimeoutError(f"Timed out waiting for task '{task_uuid}' to complete")
279287
await asyncio.sleep(self.polling_interval)
280288

@@ -469,6 +477,10 @@ def __del__(self):
469477
def base_url(self):
470478
return self._client.base_url
471479

480+
@base_url.setter
481+
def base_url(self, value: str):
482+
self._client.base_url = value
483+
472484
@property
473485
def default_model(self):
474486
return self._client.default_model
@@ -477,6 +489,14 @@ def default_model(self):
477489
def default_model(self, value: str):
478490
self._client.default_model = value
479491

492+
@property
493+
def request_timeout(self):
494+
return self._client.request_timeout
495+
496+
@request_timeout.setter
497+
def request_timeout(self, value: float | None):
498+
self._client.request_timeout = value
499+
480500
@property
481501
def polling_interval(self):
482502
return self._client.polling_interval
@@ -486,12 +506,12 @@ def polling_interval(self, value: float):
486506
self._client.polling_interval = value
487507

488508
@property
489-
def timeout(self):
490-
return self._client.timeout
509+
def polling_timeout(self):
510+
return self._client.polling_timeout
491511

492-
@timeout.setter
493-
def timeout(self, value: float | None):
494-
self._client.timeout = value
512+
@polling_timeout.setter
513+
def polling_timeout(self, value: float | None):
514+
self._client.polling_timeout = value
495515

496516
def submit_task(
497517
self,

tests/unit/client/test_client.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,23 @@ async def test_gateway_client_init():
3030
base_url="http://localhost:8888/",
3131
default_model="test-model",
3232
polling_interval=0.5,
33-
timeout=0.1,
33+
polling_timeout=0.1,
34+
request_timeout=120.0,
3435
)
3536
assert client.base_url == "http://localhost:8888"
3637
assert client.default_model == "test-model"
3738
assert client.polling_interval == 0.5
38-
assert client.timeout == 0.1
39+
assert client.polling_timeout == 0.1
40+
assert client.request_timeout == 120.0
3941
assert client._client is None
4042

4143
client = GatewayClient(
4244
base_url="http://localhost:8888/",
4345
polling_interval=10,
4446
)
4547
assert client.polling_interval == 3.0 # Maximum 3.0 seconds
46-
assert client.timeout is None # Default timeout should be None
48+
assert client.polling_timeout is None # Default polling_timeout should be None
49+
assert client.request_timeout == 300.0 # Default request_timeout
4750

4851
client.polling_interval = 0.05
4952
assert client.polling_interval == 0.5 # Minimum is 0.5 seconds
@@ -377,16 +380,15 @@ async def test_wait_for_task_timeout(mock_httpx_async_client, mocker):
377380
mocker.patch("cogstack_model_gateway_client.client.GatewayClient.get_task", new=mock_get_task)
378381
mocker.patch("asyncio.sleep", new=AsyncMock())
379382

380-
async with GatewayClient(base_url="http://test-gateway.com") as client:
381-
client.timeout = 0.05
382-
client.polling_interval = 0.5
383-
383+
async with GatewayClient(
384+
base_url="http://test-gateway.com", polling_timeout=0.05, polling_interval=0.5
385+
) as client:
384386
with pytest.raises(
385387
TimeoutError, match="Timed out waiting for task 'task-polling' to complete"
386388
):
387389
await client.wait_for_task("task-polling")
388390

389-
assert mock_get_task.await_count >= (client.timeout / client.polling_interval)
391+
assert mock_get_task.await_count >= (client.polling_timeout / client.polling_interval)
390392

391393

392394
@pytest.mark.asyncio
@@ -406,10 +408,9 @@ async def mock_get_task_side_effect(*args, **kwargs):
406408
mocker.patch("cogstack_model_gateway_client.client.GatewayClient.get_task", new=mock_get_task)
407409
mocker.patch("asyncio.sleep", new=AsyncMock())
408410

409-
async with GatewayClient(base_url="http://test-gateway.com") as client:
410-
assert client.timeout is None
411-
client.polling_interval = 0.01
412-
411+
async with GatewayClient(
412+
base_url="http://test-gateway.com", polling_timeout=None, polling_interval=0.01
413+
) as client:
413414
result = await client.wait_for_task("task-polling")
414415

415416
assert result["status"] == "succeeded"
@@ -937,7 +938,7 @@ def test_sync_client_is_healthy(mock_httpx_async_client):
937938

938939

939940
def test_sync_client_timeout_handling(mock_httpx_async_client):
940-
"""Test that timeouts work correctly in the sync client."""
941+
"""Test that polling_timeout works correctly in the sync client."""
941942
_, mock_client_instance = mock_httpx_async_client
942943

943944
async def slow_response(*args, **kwargs):

0 commit comments

Comments
 (0)