Skip to content

Commit 6a22ea5

Browse files
committed
Introduce tiered timeout system with per-endpoint configuration
1 parent 21f2665 commit 6a22ea5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1526
-472
lines changed

docs/02_concepts/code/05_retries_async.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
async def main() -> None:
99
apify_client = ApifyClientAsync(
1010
token=TOKEN,
11-
max_retries=8,
11+
max_retries=4,
1212
min_delay_between_retries=timedelta(milliseconds=500),
13-
timeout=timedelta(seconds=360),
13+
timeout_short=timedelta(seconds=5),
14+
timeout_medium=timedelta(seconds=30),
15+
timeout_long=timedelta(seconds=360),
16+
timeout_max=timedelta(seconds=360),
1417
)

docs/02_concepts/code/05_retries_sync.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
def main() -> None:
99
apify_client = ApifyClient(
1010
token=TOKEN,
11-
max_retries=8,
11+
max_retries=4,
1212
min_delay_between_retries=timedelta(milliseconds=500),
13-
timeout=timedelta(seconds=360),
13+
timeout_short=timedelta(seconds=5),
14+
timeout_medium=timedelta(seconds=30),
15+
timeout_long=timedelta(seconds=360),
16+
timeout_max=timedelta(seconds=360),
1417
)

docs/02_concepts/code/10_default_http_client_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=4,
1212
min_delay_between_retries=timedelta(milliseconds=500),
13-
timeout=timedelta(seconds=360),
13+
timeout_medium=timedelta(seconds=360),
1414
)

docs/02_concepts/code/10_default_http_client_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=4,
1212
min_delay_between_retries=timedelta(milliseconds=500),
13-
timeout=timedelta(seconds=360),
13+
timeout_medium=timedelta(seconds=360),
1414
)

docs/02_concepts/code/10_plugging_in_async.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
from datetime import timedelta
21
from typing import Any
32

4-
from apify_client import ApifyClientAsync, HttpClientAsync, HttpResponse
3+
from apify_client import ApifyClientAsync, HttpClientAsync, HttpResponse, Timeout
54

65
TOKEN = 'MY-APIFY-TOKEN'
76

@@ -19,7 +18,7 @@ async def call(
1918
data: str | bytes | bytearray | None = None,
2019
json: Any = None,
2120
stream: bool | None = None,
22-
timeout: timedelta | None = None,
21+
timeout: Timeout = 'medium',
2322
) -> HttpResponse: ...
2423

2524

docs/02_concepts/code/10_plugging_in_sync.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
from datetime import timedelta
21
from typing import Any
32

4-
from apify_client import ApifyClient, HttpClient, HttpResponse
3+
from apify_client import ApifyClient, HttpClient, HttpResponse, Timeout
54

65
TOKEN = 'MY-APIFY-TOKEN'
76

@@ -19,7 +18,7 @@ def call(
1918
data: str | bytes | bytearray | None = None,
2019
json: Any = None,
2120
stream: bool | None = None,
22-
timeout: timedelta | None = None,
21+
timeout: Timeout = 'medium',
2322
) -> HttpResponse: ...
2423

2524

docs/03_guides/code/05_custom_http_client_async.py

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

33
import asyncio
4-
from typing import TYPE_CHECKING, Any
4+
from typing import Any
55

66
import httpx
77

8-
from apify_client import ApifyClientAsync, HttpClientAsync, HttpResponse
9-
10-
if TYPE_CHECKING:
11-
from datetime import timedelta
8+
from apify_client import ApifyClientAsync, HttpClientAsync, HttpResponse, Timeout
129

1310
TOKEN = 'MY-APIFY-TOKEN'
1411

@@ -30,9 +27,9 @@ async def call(
3027
data: str | bytes | bytearray | None = None,
3128
json: Any = None,
3229
stream: bool | None = None,
33-
timeout: timedelta | None = None,
30+
timeout: Timeout = 'medium',
3431
) -> HttpResponse:
35-
timeout_secs = timeout.total_seconds() if timeout else 0
32+
timeout_secs = self._compute_timeout(timeout, attempt=1) or 0
3633

3734
# httpx.Response satisfies the HttpResponse protocol,
3835
# so it can be returned directly.

docs/03_guides/code/05_custom_http_client_sync.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
from __future__ import annotations
22

3-
from typing import TYPE_CHECKING, Any
3+
from typing import Any
44

55
import httpx
66

7-
from apify_client import ApifyClient, HttpClient, HttpResponse
8-
9-
if TYPE_CHECKING:
10-
from datetime import timedelta
7+
from apify_client import ApifyClient, HttpClient, HttpResponse, Timeout
118

129
TOKEN = 'MY-APIFY-TOKEN'
1310

@@ -29,9 +26,9 @@ def call(
2926
data: str | bytes | bytearray | None = None,
3027
json: Any = None,
3128
stream: bool | None = None,
32-
timeout: timedelta | None = None,
29+
timeout: Timeout = 'medium',
3330
) -> HttpResponse:
34-
timeout_secs = timeout.total_seconds() if timeout else 0
31+
timeout_secs = self._compute_timeout(timeout, attempt=1) or 0
3532

3633
# httpx.Response satisfies the HttpResponse protocol,
3734
# so it can be returned directly.

src/apify_client/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
ImpitHttpClient,
99
ImpitHttpClientAsync,
1010
)
11+
from ._types import Timeout
1112

1213
__version__ = metadata.version('apify-client')
1314

@@ -19,5 +20,6 @@
1920
'HttpResponse',
2021
'ImpitHttpClient',
2122
'ImpitHttpClientAsync',
23+
'Timeout',
2224
'__version__',
2325
]

src/apify_client/_apify_client.py

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
DEFAULT_API_URL,
1010
DEFAULT_MAX_RETRIES,
1111
DEFAULT_MIN_DELAY_BETWEEN_RETRIES,
12-
DEFAULT_TIMEOUT,
12+
DEFAULT_TIMEOUT_LONG,
13+
DEFAULT_TIMEOUT_MAX,
14+
DEFAULT_TIMEOUT_MEDIUM,
15+
DEFAULT_TIMEOUT_SHORT,
1316
)
1417
from apify_client._docs import docs_group
1518
from apify_client._http_clients import HttpClient, HttpClientAsync, ImpitHttpClient, ImpitHttpClientAsync
@@ -114,7 +117,10 @@ def __init__(
114117
api_public_url: str | None = DEFAULT_API_URL,
115118
max_retries: int = DEFAULT_MAX_RETRIES,
116119
min_delay_between_retries: timedelta = DEFAULT_MIN_DELAY_BETWEEN_RETRIES,
117-
timeout: timedelta = DEFAULT_TIMEOUT,
120+
timeout_short: timedelta = DEFAULT_TIMEOUT_SHORT,
121+
timeout_medium: timedelta = DEFAULT_TIMEOUT_MEDIUM,
122+
timeout_long: timedelta = DEFAULT_TIMEOUT_LONG,
123+
timeout_max: timedelta = DEFAULT_TIMEOUT_MAX,
118124
headers: dict[str, str] | None = None,
119125
) -> None:
120126
"""Initialize the Apify API client.
@@ -132,7 +138,10 @@ def __init__(
132138
max_retries: How many times to retry a failed request at most.
133139
min_delay_between_retries: How long will the client wait between retrying requests
134140
(increases exponentially from this value).
135-
timeout: The socket timeout of the HTTP requests sent to the Apify API.
141+
timeout_short: Default timeout for short-duration API operations (simple CRUD operations, ...).
142+
timeout_medium: Default timeout for medium-duration API operations (batch operations, listing, ...).
143+
timeout_long: Default timeout for long-duration API operations (long-polling, streaming, ...).
144+
timeout_max: Maximum timeout cap for exponential timeout growth across retries.
136145
headers: Additional HTTP headers to include in all API requests.
137146
"""
138147
# We need to do this because of mocking in tests and default mutable arguments.
@@ -191,7 +200,10 @@ def __init__(
191200
# Configuration for the default HTTP client (used if a custom client is not provided).
192201
self._max_retries = max_retries
193202
self._min_delay_between_retries = min_delay_between_retries
194-
self._timeout = timeout
203+
self._timeout_short = timeout_short
204+
self._timeout_medium = timeout_medium
205+
self._timeout_long = timeout_long
206+
self._timeout_max = timeout_max
195207
self._headers = headers
196208

197209
@classmethod
@@ -249,7 +261,10 @@ def http_client(self) -> HttpClient:
249261
if self._http_client is None:
250262
self._http_client = ImpitHttpClient(
251263
token=self._token,
252-
timeout=self._timeout,
264+
timeout_short=self._timeout_short,
265+
timeout_medium=self._timeout_medium,
266+
timeout_long=self._timeout_long,
267+
timeout_max=self._timeout_max,
253268
max_retries=self._max_retries,
254269
min_delay_between_retries=self._min_delay_between_retries,
255270
statistics=self._statistics,
@@ -455,7 +470,10 @@ def __init__(
455470
api_public_url: str | None = DEFAULT_API_URL,
456471
max_retries: int = DEFAULT_MAX_RETRIES,
457472
min_delay_between_retries: timedelta = DEFAULT_MIN_DELAY_BETWEEN_RETRIES,
458-
timeout: timedelta = DEFAULT_TIMEOUT,
473+
timeout_short: timedelta = DEFAULT_TIMEOUT_SHORT,
474+
timeout_medium: timedelta = DEFAULT_TIMEOUT_MEDIUM,
475+
timeout_long: timedelta = DEFAULT_TIMEOUT_LONG,
476+
timeout_max: timedelta = DEFAULT_TIMEOUT_MAX,
459477
headers: dict[str, str] | None = None,
460478
) -> None:
461479
"""Initialize the Apify API client.
@@ -473,7 +491,10 @@ def __init__(
473491
max_retries: How many times to retry a failed request at most.
474492
min_delay_between_retries: How long will the client wait between retrying requests
475493
(increases exponentially from this value).
476-
timeout: The socket timeout of the HTTP requests sent to the Apify API.
494+
timeout_short: Default timeout for short-duration API operations (simple CRUD operations, ...).
495+
timeout_medium: Default timeout for medium-duration API operations (batch operations, listing, ...).
496+
timeout_long: Default timeout for long-duration API operations (long-polling, streaming, ...).
497+
timeout_max: Maximum timeout cap for exponential timeout growth across retries.
477498
headers: Additional HTTP headers to include in all API requests.
478499
"""
479500
# We need to do this because of mocking in tests and default mutable arguments.
@@ -532,7 +553,10 @@ def __init__(
532553
# Configuration for the default HTTP client (used if a custom client is not provided).
533554
self._max_retries = max_retries
534555
self._min_delay_between_retries = min_delay_between_retries
535-
self._timeout = timeout
556+
self._timeout_short = timeout_short
557+
self._timeout_medium = timeout_medium
558+
self._timeout_long = timeout_long
559+
self._timeout_max = timeout_max
536560
self._headers = headers
537561

538562
@classmethod
@@ -590,7 +614,10 @@ def http_client(self) -> HttpClientAsync:
590614
if self._http_client is None:
591615
self._http_client = ImpitHttpClientAsync(
592616
token=self._token,
593-
timeout=self._timeout,
617+
timeout_short=self._timeout_short,
618+
timeout_medium=self._timeout_medium,
619+
timeout_long=self._timeout_long,
620+
timeout_max=self._timeout_max,
594621
max_retries=self._max_retries,
595622
min_delay_between_retries=self._min_delay_between_retries,
596623
statistics=self._statistics,

0 commit comments

Comments
 (0)