1010from typing import TYPE_CHECKING , Any , Protocol , runtime_checkable
1111from urllib .parse import urlencode
1212
13- from apify_client ._consts import DEFAULT_MAX_RETRIES , DEFAULT_MIN_DELAY_BETWEEN_RETRIES , DEFAULT_TIMEOUT
13+ from apify_client ._consts import (
14+ DEFAULT_MAX_RETRIES ,
15+ DEFAULT_MIN_DELAY_BETWEEN_RETRIES ,
16+ DEFAULT_TIMEOUT_LONG ,
17+ DEFAULT_TIMEOUT_MAX ,
18+ DEFAULT_TIMEOUT_MEDIUM ,
19+ DEFAULT_TIMEOUT_SHORT ,
20+ )
1421from apify_client ._docs import docs_group
1522from apify_client ._statistics import ClientStatistics
1623from apify_client ._utils import to_seconds
1724
1825if TYPE_CHECKING :
1926 from collections .abc import AsyncIterator , Iterator , Mapping
2027
21- from apify_client ._consts import JsonSerializable
28+ from apify_client ._types import JsonSerializable , Timeout
2229
2330
2431@docs_group ('HTTP clients' )
@@ -85,7 +92,10 @@ def __init__(
8592 self ,
8693 * ,
8794 token : str | None = None ,
88- timeout : timedelta = DEFAULT_TIMEOUT ,
95+ timeout_short : timedelta = DEFAULT_TIMEOUT_SHORT ,
96+ timeout_medium : timedelta = DEFAULT_TIMEOUT_MEDIUM ,
97+ timeout_long : timedelta = DEFAULT_TIMEOUT_LONG ,
98+ timeout_max : timedelta = DEFAULT_TIMEOUT_MAX ,
8999 max_retries : int = DEFAULT_MAX_RETRIES ,
90100 min_delay_between_retries : timedelta = DEFAULT_MIN_DELAY_BETWEEN_RETRIES ,
91101 statistics : ClientStatistics | None = None ,
@@ -95,13 +105,19 @@ def __init__(
95105
96106 Args:
97107 token: Apify API token for authentication.
98- timeout: Request timeout.
108+ timeout_short: Default timeout for short-duration API operations (simple CRUD operations, ...).
109+ timeout_medium: Default timeout for medium-duration API operations (batch operations, listing, ...).
110+ timeout_long: Default timeout for long-duration API operations (long-polling, streaming, ...).
111+ timeout_max: Maximum timeout cap for exponential timeout growth across retries.
99112 max_retries: Maximum number of retries for failed requests.
100113 min_delay_between_retries: Minimum delay between retries.
101114 statistics: Statistics tracker for API calls. Created automatically if not provided.
102115 headers: Additional HTTP headers to include in all requests.
103116 """
104- self ._timeout = timeout
117+ self ._timeout_short = timeout_short
118+ self ._timeout_medium = timeout_medium
119+ self ._timeout_long = timeout_long
120+ self ._timeout_max = timeout_max
105121 self ._max_retries = max_retries
106122 self ._min_delay_between_retries = min_delay_between_retries
107123 self ._statistics = statistics or ClientStatistics ()
@@ -149,6 +165,34 @@ def _parse_params(params: dict[str, Any] | None) -> dict[str, Any] | None:
149165
150166 return parsed_params
151167
168+ def _compute_timeout (self , timeout : Timeout , attempt : int ) -> int | float | None :
169+ """Resolve a timeout tier and compute the timeout for a request attempt with exponential increase.
170+
171+ For `'no_timeout'`, returns `None`. For tier literals and explicit `timedelta` values, doubles the timeout
172+ with each attempt but caps at `timeout_max`.
173+
174+ Args:
175+ timeout: The timeout specification to resolve (tier literal or explicit `timedelta`).
176+ attempt: Current attempt number (1-indexed).
177+
178+ Returns:
179+ Timeout in seconds, or `None` for `'no_timeout'`.
180+ """
181+ if timeout == 'no_timeout' :
182+ return None
183+
184+ if timeout == 'short' :
185+ resolved = self ._timeout_short
186+ elif timeout == 'medium' :
187+ resolved = self ._timeout_medium
188+ elif timeout == 'long' :
189+ resolved = self ._timeout_long
190+ else :
191+ resolved = timeout
192+
193+ new_timeout = min (resolved * (2 ** (attempt - 1 )), self ._timeout_max )
194+ return to_seconds (new_timeout )
195+
152196 def _prepare_request_call (
153197 self ,
154198 headers : dict [str , str ] | None = None ,
@@ -192,12 +236,6 @@ def _build_url_with_params(self, url: str, params: dict[str, Any] | None = None)
192236
193237 return f'{ url } ?{ query_string } '
194238
195- def _calculate_timeout (self , attempt : int , timeout : timedelta | None = None ) -> float :
196- """Calculate timeout for a request attempt with exponential increase, bounded by client timeout."""
197- timeout_secs = to_seconds (timeout or self ._timeout )
198- client_timeout_secs = to_seconds (self ._timeout )
199- return min (client_timeout_secs , timeout_secs * 2 ** (attempt - 1 ))
200-
201239
202240@docs_group ('HTTP clients' )
203241class HttpClient (HttpClientBase , ABC ):
@@ -219,7 +257,7 @@ def call(
219257 data : str | bytes | bytearray | None = None ,
220258 json : Any = None ,
221259 stream : bool | None = None ,
222- timeout : timedelta | None = None ,
260+ timeout : Timeout = 'medium' ,
223261 ) -> HttpResponse :
224262 """Make an HTTP request.
225263
@@ -231,7 +269,9 @@ def call(
231269 data: Raw request body data. Cannot be used together with json.
232270 json: JSON-serializable data for the request body. Cannot be used together with data.
233271 stream: Whether to stream the response body.
234- timeout: Timeout for this specific request.
272+ timeout: Timeout for the API HTTP request. Use `short`, `medium`, or `long` tier literals for
273+ preconfigured timeouts. A `timedelta` overrides it for this call, and `no_timeout` disables
274+ the timeout entirely.
235275
236276 Returns:
237277 The HTTP response object.
@@ -240,7 +280,6 @@ def call(
240280 ApifyApiError: If the request fails after all retries or returns a non-retryable error status.
241281 ValueError: If both json and data are provided.
242282 """
243- ...
244283
245284
246285@docs_group ('HTTP clients' )
@@ -262,7 +301,7 @@ async def call(
262301 data : str | bytes | bytearray | None = None ,
263302 json : Any = None ,
264303 stream : bool | None = None ,
265- timeout : timedelta | None = None ,
304+ timeout : Timeout = 'medium' ,
266305 ) -> HttpResponse :
267306 """Make an HTTP request.
268307
@@ -274,7 +313,9 @@ async def call(
274313 data: Raw request body data. Cannot be used together with json.
275314 json: JSON-serializable data for the request body. Cannot be used together with data.
276315 stream: Whether to stream the response body.
277- timeout: Timeout for this specific request.
316+ timeout: Timeout for the API HTTP request. Use `short`, `medium`, or `long` tier literals for
317+ preconfigured timeouts. A `timedelta` overrides it for this call, and `no_timeout` disables
318+ the timeout entirely.
278319
279320 Returns:
280321 The HTTP response object.
@@ -283,4 +324,3 @@ async def call(
283324 ApifyApiError: If the request fails after all retries or returns a non-retryable error status.
284325 ValueError: If both json and data are provided.
285326 """
286- ...
0 commit comments