|
3 | 3 | from concurrent.futures import BrokenExecutor as ConcurrentBrokenExecutor |
4 | 4 | from concurrent.futures import CancelledError as ConcurrentCancelledError |
5 | 5 | from concurrent.futures import InvalidStateError as ConcurrentInvalidStateError |
| 6 | +from decimal import Decimal |
6 | 7 | import math |
7 | 8 | from fractions import Fraction |
8 | 9 |
|
@@ -120,6 +121,20 @@ def test_poll_until_terminal_status_accepts_fraction_timing_values(): |
120 | 121 | assert status == "completed" |
121 | 122 |
|
122 | 123 |
|
| 124 | +def test_poll_until_terminal_status_accepts_decimal_timing_values(): |
| 125 | + status_values = iter(["running", "completed"]) |
| 126 | + |
| 127 | + status = poll_until_terminal_status( |
| 128 | + operation_name="sync poll decimal timings", |
| 129 | + get_status=lambda: next(status_values), |
| 130 | + is_terminal_status=lambda value: value == "completed", |
| 131 | + poll_interval_seconds=Decimal("0.0001"), # type: ignore[arg-type] |
| 132 | + max_wait_seconds=Decimal("1"), # type: ignore[arg-type] |
| 133 | + ) |
| 134 | + |
| 135 | + assert status == "completed" |
| 136 | + |
| 137 | + |
123 | 138 | def test_poll_until_terminal_status_does_not_retry_non_retryable_client_errors(): |
124 | 139 | attempts = {"count": 0} |
125 | 140 |
|
@@ -838,6 +853,26 @@ def operation() -> str: |
838 | 853 | assert attempts["count"] == 3 |
839 | 854 |
|
840 | 855 |
|
| 856 | +def test_retry_operation_accepts_decimal_retry_delay(): |
| 857 | + attempts = {"count": 0} |
| 858 | + |
| 859 | + def operation() -> str: |
| 860 | + attempts["count"] += 1 |
| 861 | + if attempts["count"] < 3: |
| 862 | + raise ValueError("transient") |
| 863 | + return "ok" |
| 864 | + |
| 865 | + result = retry_operation( |
| 866 | + operation_name="sync retry decimal delay", |
| 867 | + operation=operation, |
| 868 | + max_attempts=3, |
| 869 | + retry_delay_seconds=Decimal("0.0001"), # type: ignore[arg-type] |
| 870 | + ) |
| 871 | + |
| 872 | + assert result == "ok" |
| 873 | + assert attempts["count"] == 3 |
| 874 | + |
| 875 | + |
841 | 876 | def test_retry_operation_raises_after_max_attempts(): |
842 | 877 | with pytest.raises(HyperbrowserError, match="sync retry failure"): |
843 | 878 | retry_operation( |
@@ -1464,6 +1499,38 @@ async def get_next_page(page_batch: int) -> dict: |
1464 | 1499 | asyncio.run(run()) |
1465 | 1500 |
|
1466 | 1501 |
|
| 1502 | +def test_async_helpers_accept_decimal_timing_values(): |
| 1503 | + async def run() -> None: |
| 1504 | + status_values = iter(["pending", "completed"]) |
| 1505 | + status = await poll_until_terminal_status_async( |
| 1506 | + operation_name="async poll decimal timings", |
| 1507 | + get_status=lambda: asyncio.sleep(0, result=next(status_values)), |
| 1508 | + is_terminal_status=lambda value: value == "completed", |
| 1509 | + poll_interval_seconds=Decimal("0.0001"), # type: ignore[arg-type] |
| 1510 | + max_wait_seconds=Decimal("1"), # type: ignore[arg-type] |
| 1511 | + ) |
| 1512 | + assert status == "completed" |
| 1513 | + |
| 1514 | + attempts = {"count": 0} |
| 1515 | + |
| 1516 | + async def operation() -> str: |
| 1517 | + attempts["count"] += 1 |
| 1518 | + if attempts["count"] < 2: |
| 1519 | + raise ValueError("temporary") |
| 1520 | + return "ok" |
| 1521 | + |
| 1522 | + result = await retry_operation_async( |
| 1523 | + operation_name="async retry decimal delay", |
| 1524 | + operation=operation, |
| 1525 | + max_attempts=2, |
| 1526 | + retry_delay_seconds=Decimal("0.0001"), # type: ignore[arg-type] |
| 1527 | + ) |
| 1528 | + assert result == "ok" |
| 1529 | + assert attempts["count"] == 2 |
| 1530 | + |
| 1531 | + asyncio.run(run()) |
| 1532 | + |
| 1533 | + |
1467 | 1534 | def test_retry_operation_async_rejects_non_awaitable_operation_result() -> None: |
1468 | 1535 | async def run() -> None: |
1469 | 1536 | with pytest.raises( |
|
0 commit comments