|
1 | 1 | import asyncio |
| 2 | +from array import array |
2 | 3 | from concurrent.futures import BrokenExecutor as ConcurrentBrokenExecutor |
3 | 4 | from concurrent.futures import CancelledError as ConcurrentCancelledError |
4 | 5 | from concurrent.futures import InvalidStateError as ConcurrentInvalidStateError |
@@ -168,6 +169,29 @@ def get_status() -> str: |
168 | 169 | assert attempts["count"] == 1 |
169 | 170 |
|
170 | 171 |
|
| 172 | +def test_poll_until_terminal_status_does_not_retry_bytes_like_client_errors(): |
| 173 | + attempts = {"count": 0} |
| 174 | + |
| 175 | + def get_status() -> str: |
| 176 | + attempts["count"] += 1 |
| 177 | + raise HyperbrowserError( |
| 178 | + "client failure", |
| 179 | + status_code=array("B", [52, 48, 48]), # type: ignore[arg-type] |
| 180 | + ) |
| 181 | + |
| 182 | + with pytest.raises(HyperbrowserError, match="client failure"): |
| 183 | + poll_until_terminal_status( |
| 184 | + operation_name="sync poll bytes-like client error", |
| 185 | + get_status=get_status, |
| 186 | + is_terminal_status=lambda value: value == "completed", |
| 187 | + poll_interval_seconds=0.0001, |
| 188 | + max_wait_seconds=1.0, |
| 189 | + max_status_failures=5, |
| 190 | + ) |
| 191 | + |
| 192 | + assert attempts["count"] == 1 |
| 193 | + |
| 194 | + |
171 | 195 | def test_poll_until_terminal_status_retries_overlong_numeric_status_codes(): |
172 | 196 | attempts = {"count": 0} |
173 | 197 |
|
@@ -778,6 +802,29 @@ def operation() -> str: |
778 | 802 | assert attempts["count"] == 3 |
779 | 803 |
|
780 | 804 |
|
| 805 | +def test_retry_operation_retries_bytes_like_rate_limit_errors(): |
| 806 | + attempts = {"count": 0} |
| 807 | + |
| 808 | + def operation() -> str: |
| 809 | + attempts["count"] += 1 |
| 810 | + if attempts["count"] < 3: |
| 811 | + raise HyperbrowserError( |
| 812 | + "rate limited", |
| 813 | + status_code=array("B", [52, 50, 57]), # type: ignore[arg-type] |
| 814 | + ) |
| 815 | + return "ok" |
| 816 | + |
| 817 | + result = retry_operation( |
| 818 | + operation_name="sync retry bytes-like rate limit", |
| 819 | + operation=operation, |
| 820 | + max_attempts=5, |
| 821 | + retry_delay_seconds=0.0001, |
| 822 | + ) |
| 823 | + |
| 824 | + assert result == "ok" |
| 825 | + assert attempts["count"] == 3 |
| 826 | + |
| 827 | + |
781 | 828 | def test_retry_operation_does_not_retry_numeric_bytes_client_errors(): |
782 | 829 | attempts = {"count": 0} |
783 | 830 |
|
@@ -1261,6 +1308,32 @@ async def get_status() -> str: |
1261 | 1308 | asyncio.run(run()) |
1262 | 1309 |
|
1263 | 1310 |
|
| 1311 | +def test_poll_until_terminal_status_async_does_not_retry_bytes_like_client_errors(): |
| 1312 | + async def run() -> None: |
| 1313 | + attempts = {"count": 0} |
| 1314 | + |
| 1315 | + async def get_status() -> str: |
| 1316 | + attempts["count"] += 1 |
| 1317 | + raise HyperbrowserError( |
| 1318 | + "client failure", |
| 1319 | + status_code=array("B", [52, 48, 52]), # type: ignore[arg-type] |
| 1320 | + ) |
| 1321 | + |
| 1322 | + with pytest.raises(HyperbrowserError, match="client failure"): |
| 1323 | + await poll_until_terminal_status_async( |
| 1324 | + operation_name="async poll bytes-like client error", |
| 1325 | + get_status=get_status, |
| 1326 | + is_terminal_status=lambda value: value == "completed", |
| 1327 | + poll_interval_seconds=0.0001, |
| 1328 | + max_wait_seconds=1.0, |
| 1329 | + max_status_failures=5, |
| 1330 | + ) |
| 1331 | + |
| 1332 | + assert attempts["count"] == 1 |
| 1333 | + |
| 1334 | + asyncio.run(run()) |
| 1335 | + |
| 1336 | + |
1264 | 1337 | def test_poll_until_terminal_status_async_retries_overlong_numeric_status_codes(): |
1265 | 1338 | async def run() -> None: |
1266 | 1339 | attempts = {"count": 0} |
@@ -1568,6 +1641,32 @@ async def operation() -> str: |
1568 | 1641 | asyncio.run(run()) |
1569 | 1642 |
|
1570 | 1643 |
|
| 1644 | +def test_retry_operation_async_retries_bytes_like_rate_limit_errors(): |
| 1645 | + async def run() -> None: |
| 1646 | + attempts = {"count": 0} |
| 1647 | + |
| 1648 | + async def operation() -> str: |
| 1649 | + attempts["count"] += 1 |
| 1650 | + if attempts["count"] < 3: |
| 1651 | + raise HyperbrowserError( |
| 1652 | + "rate limited", |
| 1653 | + status_code=array("B", [52, 50, 57]), # type: ignore[arg-type] |
| 1654 | + ) |
| 1655 | + return "ok" |
| 1656 | + |
| 1657 | + result = await retry_operation_async( |
| 1658 | + operation_name="async retry bytes-like rate limit", |
| 1659 | + operation=operation, |
| 1660 | + max_attempts=5, |
| 1661 | + retry_delay_seconds=0.0001, |
| 1662 | + ) |
| 1663 | + |
| 1664 | + assert result == "ok" |
| 1665 | + assert attempts["count"] == 3 |
| 1666 | + |
| 1667 | + asyncio.run(run()) |
| 1668 | + |
| 1669 | + |
1571 | 1670 | def test_retry_operation_async_retries_numeric_bytes_rate_limit_errors(): |
1572 | 1671 | async def run() -> None: |
1573 | 1672 | attempts = {"count": 0} |
@@ -4198,6 +4297,34 @@ def fetch_result() -> dict: |
4198 | 4297 | assert fetch_attempts["count"] == 3 |
4199 | 4298 |
|
4200 | 4299 |
|
| 4300 | +def test_wait_for_job_result_retries_bytes_like_rate_limit_fetch_errors(): |
| 4301 | + fetch_attempts = {"count": 0} |
| 4302 | + |
| 4303 | + def fetch_result() -> dict: |
| 4304 | + fetch_attempts["count"] += 1 |
| 4305 | + if fetch_attempts["count"] < 3: |
| 4306 | + raise HyperbrowserError( |
| 4307 | + "rate limited", |
| 4308 | + status_code=array("B", [52, 50, 57]), # type: ignore[arg-type] |
| 4309 | + ) |
| 4310 | + return {"ok": True} |
| 4311 | + |
| 4312 | + result = wait_for_job_result( |
| 4313 | + operation_name="sync wait helper fetch bytes-like rate limit", |
| 4314 | + get_status=lambda: "completed", |
| 4315 | + is_terminal_status=lambda value: value == "completed", |
| 4316 | + fetch_result=fetch_result, |
| 4317 | + poll_interval_seconds=0.0001, |
| 4318 | + max_wait_seconds=1.0, |
| 4319 | + max_status_failures=5, |
| 4320 | + fetch_max_attempts=5, |
| 4321 | + fetch_retry_delay_seconds=0.0001, |
| 4322 | + ) |
| 4323 | + |
| 4324 | + assert result == {"ok": True} |
| 4325 | + assert fetch_attempts["count"] == 3 |
| 4326 | + |
| 4327 | + |
4201 | 4328 | def test_wait_for_job_result_retries_request_timeout_fetch_errors(): |
4202 | 4329 | fetch_attempts = {"count": 0} |
4203 | 4330 |
|
@@ -5037,6 +5164,37 @@ async def fetch_result() -> dict: |
5037 | 5164 | asyncio.run(run()) |
5038 | 5165 |
|
5039 | 5166 |
|
| 5167 | +def test_wait_for_job_result_async_retries_bytes_like_rate_limit_fetch_errors(): |
| 5168 | + async def run() -> None: |
| 5169 | + fetch_attempts = {"count": 0} |
| 5170 | + |
| 5171 | + async def fetch_result() -> dict: |
| 5172 | + fetch_attempts["count"] += 1 |
| 5173 | + if fetch_attempts["count"] < 3: |
| 5174 | + raise HyperbrowserError( |
| 5175 | + "rate limited", |
| 5176 | + status_code=array("B", [52, 50, 57]), # type: ignore[arg-type] |
| 5177 | + ) |
| 5178 | + return {"ok": True} |
| 5179 | + |
| 5180 | + result = await wait_for_job_result_async( |
| 5181 | + operation_name="async wait helper fetch bytes-like rate limit", |
| 5182 | + get_status=lambda: asyncio.sleep(0, result="completed"), |
| 5183 | + is_terminal_status=lambda value: value == "completed", |
| 5184 | + fetch_result=fetch_result, |
| 5185 | + poll_interval_seconds=0.0001, |
| 5186 | + max_wait_seconds=1.0, |
| 5187 | + max_status_failures=5, |
| 5188 | + fetch_max_attempts=5, |
| 5189 | + fetch_retry_delay_seconds=0.0001, |
| 5190 | + ) |
| 5191 | + |
| 5192 | + assert result == {"ok": True} |
| 5193 | + assert fetch_attempts["count"] == 3 |
| 5194 | + |
| 5195 | + asyncio.run(run()) |
| 5196 | + |
| 5197 | + |
5040 | 5198 | def test_wait_for_job_result_async_retries_request_timeout_fetch_errors(): |
5041 | 5199 | async def run() -> None: |
5042 | 5200 | fetch_attempts = {"count": 0} |
|
0 commit comments