Skip to content

Commit 3d9f230

Browse files
committed
Update
1 parent ce0dcfb commit 3d9f230

38 files changed

+457
-720
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_short=timedelta(seconds=360),
13+
timeout_medium=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_short=timedelta(seconds=360),
13+
timeout_medium=timedelta(seconds=360),
1414
)

src/apify_client/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
from importlib import metadata
22

33
from ._apify_client import ApifyClient, ApifyClientAsync
4-
from ._consts import Timeout
54
from ._http_clients import (
65
HttpClient,
76
HttpClientAsync,
87
HttpResponse,
98
ImpitHttpClient,
109
ImpitHttpClientAsync,
1110
)
11+
from ._types import Timeout
1212

1313
__version__ = metadata.version('apify-client')
1414

src/apify_client/_apify_client.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
DEFAULT_API_URL,
1010
DEFAULT_MAX_RETRIES,
1111
DEFAULT_MIN_DELAY_BETWEEN_RETRIES,
12-
DEFAULT_TIMEOUT,
1312
DEFAULT_TIMEOUT_LONG,
1413
DEFAULT_TIMEOUT_MAX,
14+
DEFAULT_TIMEOUT_MEDIUM,
1515
DEFAULT_TIMEOUT_SHORT,
1616
)
1717
from apify_client._docs import docs_group
@@ -118,7 +118,7 @@ def __init__(
118118
max_retries: int = DEFAULT_MAX_RETRIES,
119119
min_delay_between_retries: timedelta = DEFAULT_MIN_DELAY_BETWEEN_RETRIES,
120120
timeout_short: timedelta = DEFAULT_TIMEOUT_SHORT,
121-
timeout: timedelta = DEFAULT_TIMEOUT,
121+
timeout_medium: timedelta = DEFAULT_TIMEOUT_MEDIUM,
122122
timeout_long: timedelta = DEFAULT_TIMEOUT_LONG,
123123
timeout_max: timedelta = DEFAULT_TIMEOUT_MAX,
124124
headers: dict[str, str] | None = None,
@@ -139,7 +139,7 @@ def __init__(
139139
min_delay_between_retries: How long will the client wait between retrying requests
140140
(increases exponentially from this value).
141141
timeout_short: Timeout for fast CRUD operations (e.g., get, update, delete).
142-
timeout: Timeout for batch, list, and data transfer operations.
142+
timeout_medium: Timeout for batch, list, and data transfer operations.
143143
timeout_long: Timeout for long-polling, streaming, and other heavy operations.
144144
timeout_max: Maximum timeout cap for exponential timeout growth across retries.
145145
headers: Additional HTTP headers to include in all API requests.
@@ -201,7 +201,7 @@ def __init__(
201201
self._max_retries = max_retries
202202
self._min_delay_between_retries = min_delay_between_retries
203203
self._timeout_short = timeout_short
204-
self._timeout = timeout
204+
self._timeout_medium = timeout_medium
205205
self._timeout_long = timeout_long
206206
self._timeout_max = timeout_max
207207
self._headers = headers
@@ -262,7 +262,7 @@ def http_client(self) -> HttpClient:
262262
self._http_client = ImpitHttpClient(
263263
token=self._token,
264264
timeout_short=self._timeout_short,
265-
timeout=self._timeout,
265+
timeout_medium=self._timeout_medium,
266266
timeout_long=self._timeout_long,
267267
timeout_max=self._timeout_max,
268268
max_retries=self._max_retries,
@@ -471,7 +471,7 @@ def __init__(
471471
max_retries: int = DEFAULT_MAX_RETRIES,
472472
min_delay_between_retries: timedelta = DEFAULT_MIN_DELAY_BETWEEN_RETRIES,
473473
timeout_short: timedelta = DEFAULT_TIMEOUT_SHORT,
474-
timeout: timedelta = DEFAULT_TIMEOUT,
474+
timeout_medium: timedelta = DEFAULT_TIMEOUT_MEDIUM,
475475
timeout_long: timedelta = DEFAULT_TIMEOUT_LONG,
476476
timeout_max: timedelta = DEFAULT_TIMEOUT_MAX,
477477
headers: dict[str, str] | None = None,
@@ -492,7 +492,7 @@ def __init__(
492492
min_delay_between_retries: How long will the client wait between retrying requests
493493
(increases exponentially from this value).
494494
timeout_short: Timeout for fast CRUD operations (e.g., get, update, delete).
495-
timeout: Timeout for batch, list, and data transfer operations.
495+
timeout_medium: Timeout for batch, list, and data transfer operations.
496496
timeout_long: Timeout for long-polling, streaming, and other heavy operations.
497497
timeout_max: Maximum timeout cap for exponential timeout growth across retries.
498498
headers: Additional HTTP headers to include in all API requests.
@@ -554,7 +554,7 @@ def __init__(
554554
self._max_retries = max_retries
555555
self._min_delay_between_retries = min_delay_between_retries
556556
self._timeout_short = timeout_short
557-
self._timeout = timeout
557+
self._timeout_medium = timeout_medium
558558
self._timeout_long = timeout_long
559559
self._timeout_max = timeout_max
560560
self._headers = headers
@@ -615,7 +615,7 @@ def http_client(self) -> HttpClientAsync:
615615
self._http_client = ImpitHttpClientAsync(
616616
token=self._token,
617617
timeout_short=self._timeout_short,
618-
timeout=self._timeout,
618+
timeout_medium=self._timeout_medium,
619619
timeout_long=self._timeout_long,
620620
timeout_max=self._timeout_max,
621621
max_retries=self._max_retries,

src/apify_client/_consts.py

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,9 @@
11
from __future__ import annotations
22

33
from datetime import timedelta
4-
from typing import Any, Literal
54

65
from apify_client._models import ActorJobStatus
76

8-
Timeout = timedelta | Literal['no_timeout', 'short', 'default', 'long']
9-
"""Type for the `timeout` parameter on resource client methods.
10-
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.
13-
"""
14-
15-
JsonSerializable = str | int | float | bool | None | dict[str, Any] | list[Any]
16-
"""Type for representing json-serializable values. It's close enough to the real thing supported by json.parse.
17-
It was suggested in a discussion with (and approved by) Guido van Rossum, so I'd consider it correct enough.
18-
"""
19-
207
DEFAULT_API_URL = 'https://api.apify.com'
218
"""Default base URL for the Apify API."""
229

@@ -26,7 +13,7 @@
2613
DEFAULT_TIMEOUT_SHORT = timedelta(seconds=5)
2714
"""Default timeout for fast CRUD operations (e.g., get, update, delete)."""
2815

29-
DEFAULT_TIMEOUT = timedelta(seconds=30)
16+
DEFAULT_TIMEOUT_MEDIUM = timedelta(seconds=30)
3017
"""Default timeout for batch, list, and data transfer operations."""
3118

3219
DEFAULT_TIMEOUT_LONG = timedelta(seconds=300)

src/apify_client/_http_clients/_base.py

Lines changed: 42 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,19 @@
1313
from apify_client._consts import (
1414
DEFAULT_MAX_RETRIES,
1515
DEFAULT_MIN_DELAY_BETWEEN_RETRIES,
16-
DEFAULT_TIMEOUT,
1716
DEFAULT_TIMEOUT_LONG,
1817
DEFAULT_TIMEOUT_MAX,
18+
DEFAULT_TIMEOUT_MEDIUM,
1919
DEFAULT_TIMEOUT_SHORT,
20-
Timeout,
2120
)
2221
from apify_client._docs import docs_group
2322
from apify_client._statistics import ClientStatistics
23+
from apify_client._utils import to_seconds
2424

2525
if TYPE_CHECKING:
2626
from collections.abc import AsyncIterator, Iterator, Mapping
2727

28-
from apify_client._consts import JsonSerializable, Timeout
28+
from apify_client._types import JsonSerializable, Timeout
2929

3030

3131
@docs_group('HTTP clients')
@@ -93,7 +93,7 @@ def __init__(
9393
*,
9494
token: str | None = None,
9595
timeout_short: timedelta = DEFAULT_TIMEOUT_SHORT,
96-
timeout: timedelta = DEFAULT_TIMEOUT,
96+
timeout_medium: timedelta = DEFAULT_TIMEOUT_MEDIUM,
9797
timeout_long: timedelta = DEFAULT_TIMEOUT_LONG,
9898
timeout_max: timedelta = DEFAULT_TIMEOUT_MAX,
9999
max_retries: int = DEFAULT_MAX_RETRIES,
@@ -106,7 +106,7 @@ def __init__(
106106
Args:
107107
token: Apify API token for authentication.
108108
timeout_short: Timeout for fast CRUD operations (e.g., get, update, delete).
109-
timeout: Timeout for batch, list, and data transfer operations.
109+
timeout_medium: Timeout for batch, list, and data transfer operations.
110110
timeout_long: Timeout for long-polling, streaming, and other heavy operations.
111111
timeout_max: Maximum timeout cap for exponential timeout growth across retries.
112112
max_retries: Maximum number of retries for failed requests.
@@ -115,7 +115,7 @@ def __init__(
115115
headers: Additional HTTP headers to include in all requests.
116116
"""
117117
self._timeout_short = timeout_short
118-
self._timeout = timeout
118+
self._timeout_medium = timeout_medium
119119
self._timeout_long = timeout_long
120120
self._timeout_max = timeout_max
121121
self._max_retries = max_retries
@@ -141,45 +141,6 @@ def __init__(
141141

142142
self._headers = {**default_headers, **(headers or {})}
143143

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-
183144
@staticmethod
184145
def _parse_params(params: dict[str, Any] | None) -> dict[str, Any] | None:
185146
"""Convert request parameters to Apify API-compatible formats.
@@ -204,6 +165,34 @@ def _parse_params(params: dict[str, Any] | None) -> dict[str, Any] | None:
204165

205166
return parsed_params
206167

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+
207196
def _prepare_request_call(
208197
self,
209198
headers: dict[str, str] | None = None,
@@ -268,7 +257,7 @@ def call(
268257
data: str | bytes | bytearray | None = None,
269258
json: Any = None,
270259
stream: bool | None = None,
271-
timeout: Timeout = 'default',
260+
timeout: Timeout = 'medium',
272261
) -> HttpResponse:
273262
"""Make an HTTP request.
274263
@@ -280,9 +269,9 @@ def call(
280269
data: Raw request body data. Cannot be used together with json.
281270
json: JSON-serializable data for the request body. Cannot be used together with data.
282271
stream: Whether to stream the response body.
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.
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.
286275
287276
Returns:
288277
The HTTP response object.
@@ -312,7 +301,7 @@ async def call(
312301
data: str | bytes | bytearray | None = None,
313302
json: Any = None,
314303
stream: bool | None = None,
315-
timeout: Timeout = 'default',
304+
timeout: Timeout = 'medium',
316305
) -> HttpResponse:
317306
"""Make an HTTP request.
318307
@@ -324,9 +313,9 @@ async def call(
324313
data: Raw request body data. Cannot be used together with json.
325314
json: JSON-serializable data for the request body. Cannot be used together with data.
326315
stream: Whether to stream the response body.
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.
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.
330319
331320
Returns:
332321
The HTTP response object.

0 commit comments

Comments
 (0)