Skip to content

Commit ce0dcfb

Browse files
committed
three tier timeout
1 parent ac20367 commit ce0dcfb

37 files changed

+949
-776
lines changed

docs/02_concepts/code/05_retries_async.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ async def main() -> None:
1010
token=TOKEN,
1111
max_retries=8,
1212
min_delay_between_retries=timedelta(milliseconds=500),
13-
timeout=timedelta(seconds=360),
13+
timeout_short=timedelta(seconds=360),
1414
)

docs/02_concepts/code/05_retries_sync.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ def main() -> None:
1010
token=TOKEN,
1111
max_retries=8,
1212
min_delay_between_retries=timedelta(milliseconds=500),
13-
timeout=timedelta(seconds=360),
13+
timeout_short=timedelta(seconds=360),
1414
)

src/apify_client/_apify_client.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
DEFAULT_API_URL,
1010
DEFAULT_MAX_RETRIES,
1111
DEFAULT_MIN_DELAY_BETWEEN_RETRIES,
12-
DEFAULT_REQUEST_TIMEOUT,
13-
DEFAULT_REQUEST_TIMEOUT_MAX,
12+
DEFAULT_TIMEOUT,
13+
DEFAULT_TIMEOUT_LONG,
14+
DEFAULT_TIMEOUT_MAX,
15+
DEFAULT_TIMEOUT_SHORT,
1416
)
1517
from apify_client._docs import docs_group
1618
from apify_client._http_clients import HttpClient, HttpClientAsync, ImpitHttpClient, ImpitHttpClientAsync
@@ -115,8 +117,10 @@ def __init__(
115117
api_public_url: str | None = DEFAULT_API_URL,
116118
max_retries: int = DEFAULT_MAX_RETRIES,
117119
min_delay_between_retries: timedelta = DEFAULT_MIN_DELAY_BETWEEN_RETRIES,
118-
timeout: timedelta = DEFAULT_REQUEST_TIMEOUT,
119-
timeout_max: timedelta = DEFAULT_REQUEST_TIMEOUT_MAX,
120+
timeout_short: timedelta = DEFAULT_TIMEOUT_SHORT,
121+
timeout: timedelta = DEFAULT_TIMEOUT,
122+
timeout_long: timedelta = DEFAULT_TIMEOUT_LONG,
123+
timeout_max: timedelta = DEFAULT_TIMEOUT_MAX,
120124
headers: dict[str, str] | None = None,
121125
) -> None:
122126
"""Initialize the Apify API client.
@@ -134,7 +138,9 @@ def __init__(
134138
max_retries: How many times to retry a failed request at most.
135139
min_delay_between_retries: How long will the client wait between retrying requests
136140
(increases exponentially from this value).
137-
timeout: The initial socket timeout of the HTTP requests sent to the Apify API.
141+
timeout_short: Timeout for fast CRUD operations (e.g., get, update, delete).
142+
timeout: Timeout for batch, list, and data transfer operations.
143+
timeout_long: Timeout for long-polling, streaming, and other heavy operations.
138144
timeout_max: Maximum timeout cap for exponential timeout growth across retries.
139145
headers: Additional HTTP headers to include in all API requests.
140146
"""
@@ -194,7 +200,9 @@ def __init__(
194200
# Configuration for the default HTTP client (used if a custom client is not provided).
195201
self._max_retries = max_retries
196202
self._min_delay_between_retries = min_delay_between_retries
203+
self._timeout_short = timeout_short
197204
self._timeout = timeout
205+
self._timeout_long = timeout_long
198206
self._timeout_max = timeout_max
199207
self._headers = headers
200208

@@ -253,7 +261,9 @@ def http_client(self) -> HttpClient:
253261
if self._http_client is None:
254262
self._http_client = ImpitHttpClient(
255263
token=self._token,
264+
timeout_short=self._timeout_short,
256265
timeout=self._timeout,
266+
timeout_long=self._timeout_long,
257267
timeout_max=self._timeout_max,
258268
max_retries=self._max_retries,
259269
min_delay_between_retries=self._min_delay_between_retries,
@@ -460,8 +470,10 @@ def __init__(
460470
api_public_url: str | None = DEFAULT_API_URL,
461471
max_retries: int = DEFAULT_MAX_RETRIES,
462472
min_delay_between_retries: timedelta = DEFAULT_MIN_DELAY_BETWEEN_RETRIES,
463-
timeout: timedelta = DEFAULT_REQUEST_TIMEOUT,
464-
timeout_max: timedelta = DEFAULT_REQUEST_TIMEOUT_MAX,
473+
timeout_short: timedelta = DEFAULT_TIMEOUT_SHORT,
474+
timeout: timedelta = DEFAULT_TIMEOUT,
475+
timeout_long: timedelta = DEFAULT_TIMEOUT_LONG,
476+
timeout_max: timedelta = DEFAULT_TIMEOUT_MAX,
465477
headers: dict[str, str] | None = None,
466478
) -> None:
467479
"""Initialize the Apify API client.
@@ -479,7 +491,9 @@ def __init__(
479491
max_retries: How many times to retry a failed request at most.
480492
min_delay_between_retries: How long will the client wait between retrying requests
481493
(increases exponentially from this value).
482-
timeout: The initial socket timeout of the HTTP requests sent to the Apify API.
494+
timeout_short: Timeout for fast CRUD operations (e.g., get, update, delete).
495+
timeout: Timeout for batch, list, and data transfer operations.
496+
timeout_long: Timeout for long-polling, streaming, and other heavy operations.
483497
timeout_max: Maximum timeout cap for exponential timeout growth across retries.
484498
headers: Additional HTTP headers to include in all API requests.
485499
"""
@@ -539,7 +553,9 @@ def __init__(
539553
# Configuration for the default HTTP client (used if a custom client is not provided).
540554
self._max_retries = max_retries
541555
self._min_delay_between_retries = min_delay_between_retries
556+
self._timeout_short = timeout_short
542557
self._timeout = timeout
558+
self._timeout_long = timeout_long
543559
self._timeout_max = timeout_max
544560
self._headers = headers
545561

@@ -598,7 +614,9 @@ def http_client(self) -> HttpClientAsync:
598614
if self._http_client is None:
599615
self._http_client = ImpitHttpClientAsync(
600616
token=self._token,
617+
timeout_short=self._timeout_short,
601618
timeout=self._timeout,
619+
timeout_long=self._timeout_long,
602620
timeout_max=self._timeout_max,
603621
max_retries=self._max_retries,
604622
min_delay_between_retries=self._min_delay_between_retries,

src/apify_client/_consts.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55

66
from apify_client._models import ActorJobStatus
77

8-
Timeout = timedelta | Literal['no_timeout'] | None
8+
Timeout = timedelta | Literal['no_timeout', 'short', 'default', 'long']
99
"""Type for the `timeout` parameter on resource client methods.
1010
11-
`None` uses the timeout configured on the HTTP client, a `timedelta` overrides it for this call,
12-
and `'no_timeout'` disables the timeout entirely.
11+
`'short'`, `'default'`, and `'long'` are tier literals resolved by the HTTP client to configured values.
12+
A `timedelta` overrides the timeout for this call, and `'no_timeout'` disables the timeout entirely.
1313
"""
1414

1515
JsonSerializable = str | int | float | bool | None | dict[str, Any] | list[Any]
@@ -23,10 +23,16 @@
2323
API_VERSION = 'v2'
2424
"""Current Apify API version."""
2525

26-
DEFAULT_REQUEST_TIMEOUT = timedelta(seconds=10)
27-
"""Default initial timeout for individual API requests."""
26+
DEFAULT_TIMEOUT_SHORT = timedelta(seconds=5)
27+
"""Default timeout for fast CRUD operations (e.g., get, update, delete)."""
2828

29-
DEFAULT_REQUEST_TIMEOUT_MAX = timedelta(seconds=600)
29+
DEFAULT_TIMEOUT = timedelta(seconds=30)
30+
"""Default timeout for batch, list, and data transfer operations."""
31+
32+
DEFAULT_TIMEOUT_LONG = timedelta(seconds=300)
33+
"""Default timeout for long-polling, streaming, and other heavy operations."""
34+
35+
DEFAULT_TIMEOUT_MAX = timedelta(seconds=600)
3036
"""Default maximum timeout cap for individual API requests (limits exponential growth)."""
3137

3238
DEFAULT_MAX_RETRIES = 4

src/apify_client/_http_clients/_base.py

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
from apify_client._consts import (
1414
DEFAULT_MAX_RETRIES,
1515
DEFAULT_MIN_DELAY_BETWEEN_RETRIES,
16-
DEFAULT_REQUEST_TIMEOUT,
17-
DEFAULT_REQUEST_TIMEOUT_MAX,
16+
DEFAULT_TIMEOUT,
17+
DEFAULT_TIMEOUT_LONG,
18+
DEFAULT_TIMEOUT_MAX,
19+
DEFAULT_TIMEOUT_SHORT,
1820
Timeout,
1921
)
2022
from apify_client._docs import docs_group
@@ -90,8 +92,10 @@ def __init__(
9092
self,
9193
*,
9294
token: str | None = None,
93-
timeout: timedelta = DEFAULT_REQUEST_TIMEOUT,
94-
timeout_max: timedelta = DEFAULT_REQUEST_TIMEOUT_MAX,
95+
timeout_short: timedelta = DEFAULT_TIMEOUT_SHORT,
96+
timeout: timedelta = DEFAULT_TIMEOUT,
97+
timeout_long: timedelta = DEFAULT_TIMEOUT_LONG,
98+
timeout_max: timedelta = DEFAULT_TIMEOUT_MAX,
9599
max_retries: int = DEFAULT_MAX_RETRIES,
96100
min_delay_between_retries: timedelta = DEFAULT_MIN_DELAY_BETWEEN_RETRIES,
97101
statistics: ClientStatistics | None = None,
@@ -101,14 +105,18 @@ def __init__(
101105
102106
Args:
103107
token: Apify API token for authentication.
104-
timeout: Initial request timeout.
108+
timeout_short: Timeout for fast CRUD operations (e.g., get, update, delete).
109+
timeout: Timeout for batch, list, and data transfer operations.
110+
timeout_long: Timeout for long-polling, streaming, and other heavy operations.
105111
timeout_max: Maximum timeout cap for exponential timeout growth across retries.
106112
max_retries: Maximum number of retries for failed requests.
107113
min_delay_between_retries: Minimum delay between retries.
108114
statistics: Statistics tracker for API calls. Created automatically if not provided.
109115
headers: Additional HTTP headers to include in all requests.
110116
"""
117+
self._timeout_short = timeout_short
111118
self._timeout = timeout
119+
self._timeout_long = timeout_long
112120
self._timeout_max = timeout_max
113121
self._max_retries = max_retries
114122
self._min_delay_between_retries = min_delay_between_retries
@@ -133,6 +141,45 @@ def __init__(
133141

134142
self._headers = {**default_headers, **(headers or {})}
135143

144+
@property
145+
def timeout_short(self) -> timedelta:
146+
"""Timeout for fast CRUD operations (e.g., get, update, delete)."""
147+
return self._timeout_short
148+
149+
@property
150+
def timeout(self) -> timedelta:
151+
"""Timeout for batch, list, and data transfer operations."""
152+
return self._timeout
153+
154+
@property
155+
def timeout_long(self) -> timedelta:
156+
"""Timeout for long-polling, streaming, and other heavy operations."""
157+
return self._timeout_long
158+
159+
@property
160+
def timeout_max(self) -> timedelta:
161+
"""Maximum timeout cap for exponential timeout growth across retries."""
162+
return self._timeout_max
163+
164+
def _resolve_timeout(self, timeout: Timeout) -> timedelta | None:
165+
"""Resolve a timeout tier literal or value to a concrete timedelta.
166+
167+
Args:
168+
timeout: The timeout specification to resolve.
169+
170+
Returns:
171+
A `timedelta` for tier literals and explicit values, `None` for `'no_timeout'`.
172+
"""
173+
if timeout == 'no_timeout':
174+
return None
175+
if timeout == 'short':
176+
return self._timeout_short
177+
if timeout == 'default':
178+
return self._timeout
179+
if timeout == 'long':
180+
return self._timeout_long
181+
return timeout
182+
136183
@staticmethod
137184
def _parse_params(params: dict[str, Any] | None) -> dict[str, Any] | None:
138185
"""Convert request parameters to Apify API-compatible formats.
@@ -221,7 +268,7 @@ def call(
221268
data: str | bytes | bytearray | None = None,
222269
json: Any = None,
223270
stream: bool | None = None,
224-
timeout: Timeout = None,
271+
timeout: Timeout = 'default',
225272
) -> HttpResponse:
226273
"""Make an HTTP request.
227274
@@ -233,8 +280,9 @@ def call(
233280
data: Raw request body data. Cannot be used together with json.
234281
json: JSON-serializable data for the request body. Cannot be used together with data.
235282
stream: Whether to stream the response body.
236-
timeout: Timeout for the API HTTP request. `None` uses the timeout configured on the client,
237-
a `timedelta` overrides it for this call, and `'no_timeout'` disables the timeout entirely.
283+
timeout: Timeout for the API HTTP request.
284+
Use `'short'`, `'default'`, or `'long'` tier literals for preconfigured timeouts.
285+
A `timedelta` overrides it for this call, and `'no_timeout'` disables the timeout entirely.
238286
239287
Returns:
240288
The HTTP response object.
@@ -264,7 +312,7 @@ async def call(
264312
data: str | bytes | bytearray | None = None,
265313
json: Any = None,
266314
stream: bool | None = None,
267-
timeout: Timeout = None,
315+
timeout: Timeout = 'default',
268316
) -> HttpResponse:
269317
"""Make an HTTP request.
270318
@@ -276,8 +324,9 @@ async def call(
276324
data: Raw request body data. Cannot be used together with json.
277325
json: JSON-serializable data for the request body. Cannot be used together with data.
278326
stream: Whether to stream the response body.
279-
timeout: Timeout for the API HTTP request. `None` uses the timeout configured on the client,
280-
a `timedelta` overrides it for this call, and `'no_timeout'` disables the timeout entirely.
327+
timeout: Timeout for the API HTTP request.
328+
Use `'short'`, `'default'`, or `'long'` tier literals for preconfigured timeouts.
329+
A `timedelta` overrides it for this call, and `'no_timeout'` disables the timeout entirely.
281330
282331
Returns:
283332
The HTTP response object.

0 commit comments

Comments
 (0)