Skip to content

Commit beedfb9

Browse files
Expand polling retry tests for 429 behavior
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent d71bcd4 commit beedfb9

File tree

1 file changed

+174
-0
lines changed

1 file changed

+174
-0
lines changed

tests/test_polling.py

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,31 @@ def get_status() -> str:
144144
assert attempts["count"] == 3
145145

146146

147+
def test_poll_until_terminal_status_async_retries_rate_limit_errors():
148+
async def run() -> None:
149+
attempts = {"count": 0}
150+
151+
async def get_status() -> str:
152+
attempts["count"] += 1
153+
if attempts["count"] < 3:
154+
raise HyperbrowserError("rate limited", status_code=429)
155+
return "completed"
156+
157+
status = await poll_until_terminal_status_async(
158+
operation_name="async poll rate limit retries",
159+
get_status=get_status,
160+
is_terminal_status=lambda value: value == "completed",
161+
poll_interval_seconds=0.0001,
162+
max_wait_seconds=1.0,
163+
max_status_failures=5,
164+
)
165+
166+
assert status == "completed"
167+
assert attempts["count"] == 3
168+
169+
asyncio.run(run())
170+
171+
147172
def test_poll_until_terminal_status_retries_server_errors():
148173
attempts = {"count": 0}
149174

@@ -292,6 +317,26 @@ def operation() -> str:
292317
assert attempts["count"] == 3
293318

294319

320+
def test_retry_operation_retries_rate_limit_errors():
321+
attempts = {"count": 0}
322+
323+
def operation() -> str:
324+
attempts["count"] += 1
325+
if attempts["count"] < 3:
326+
raise HyperbrowserError("rate limited", status_code=429)
327+
return "ok"
328+
329+
result = retry_operation(
330+
operation_name="sync retry rate limit error",
331+
operation=operation,
332+
max_attempts=5,
333+
retry_delay_seconds=0.0001,
334+
)
335+
336+
assert result == "ok"
337+
assert attempts["count"] == 3
338+
339+
295340
def test_retry_operation_rejects_awaitable_operation_result():
296341
async def async_operation() -> str:
297342
return "ok"
@@ -479,6 +524,29 @@ async def operation() -> str:
479524
asyncio.run(run())
480525

481526

527+
def test_retry_operation_async_retries_rate_limit_errors():
528+
async def run() -> None:
529+
attempts = {"count": 0}
530+
531+
async def operation() -> str:
532+
attempts["count"] += 1
533+
if attempts["count"] < 3:
534+
raise HyperbrowserError("rate limited", status_code=429)
535+
return "ok"
536+
537+
result = await retry_operation_async(
538+
operation_name="async retry rate limit error",
539+
operation=operation,
540+
max_attempts=5,
541+
retry_delay_seconds=0.0001,
542+
)
543+
544+
assert result == "ok"
545+
assert attempts["count"] == 3
546+
547+
asyncio.run(run())
548+
549+
482550
def test_async_poll_until_terminal_status_allows_immediate_terminal_on_zero_max_wait():
483551
async def run() -> None:
484552
status = await poll_until_terminal_status_async(
@@ -1031,6 +1099,31 @@ def get_next_page(page: int) -> dict:
10311099
assert attempts["count"] == 3
10321100

10331101

1102+
def test_collect_paginated_results_retries_rate_limit_errors():
1103+
attempts = {"count": 0}
1104+
collected = []
1105+
1106+
def get_next_page(page: int) -> dict:
1107+
attempts["count"] += 1
1108+
if attempts["count"] < 3:
1109+
raise HyperbrowserError("rate limited", status_code=429)
1110+
return {"current": 1, "total": 1, "items": ["a"]}
1111+
1112+
collect_paginated_results(
1113+
operation_name="sync paginated rate limit retries",
1114+
get_next_page=get_next_page,
1115+
get_current_page_batch=lambda response: response["current"],
1116+
get_total_page_batches=lambda response: response["total"],
1117+
on_page_success=lambda response: collected.extend(response["items"]),
1118+
max_wait_seconds=1.0,
1119+
max_attempts=5,
1120+
retry_delay_seconds=0.0001,
1121+
)
1122+
1123+
assert collected == ["a"]
1124+
assert attempts["count"] == 3
1125+
1126+
10341127
def test_collect_paginated_results_raises_when_page_batch_stagnates():
10351128
with pytest.raises(HyperbrowserPollingError, match="No pagination progress"):
10361129
collect_paginated_results(
@@ -1250,6 +1343,34 @@ async def get_next_page(page: int) -> dict:
12501343
asyncio.run(run())
12511344

12521345

1346+
def test_collect_paginated_results_async_retries_rate_limit_errors():
1347+
async def run() -> None:
1348+
attempts = {"count": 0}
1349+
collected = []
1350+
1351+
async def get_next_page(page: int) -> dict:
1352+
attempts["count"] += 1
1353+
if attempts["count"] < 3:
1354+
raise HyperbrowserError("rate limited", status_code=429)
1355+
return {"current": 1, "total": 1, "items": ["a"]}
1356+
1357+
await collect_paginated_results_async(
1358+
operation_name="async paginated rate limit retries",
1359+
get_next_page=get_next_page,
1360+
get_current_page_batch=lambda response: response["current"],
1361+
get_total_page_batches=lambda response: response["total"],
1362+
on_page_success=lambda response: collected.extend(response["items"]),
1363+
max_wait_seconds=1.0,
1364+
max_attempts=5,
1365+
retry_delay_seconds=0.0001,
1366+
)
1367+
1368+
assert collected == ["a"]
1369+
assert attempts["count"] == 3
1370+
1371+
asyncio.run(run())
1372+
1373+
12531374
def test_wait_for_job_result_returns_fetched_value():
12541375
status_values = iter(["running", "completed"])
12551376

@@ -1291,6 +1412,31 @@ def fetch_result() -> dict:
12911412
assert fetch_attempts["count"] == 1
12921413

12931414

1415+
def test_wait_for_job_result_retries_rate_limit_fetch_errors():
1416+
fetch_attempts = {"count": 0}
1417+
1418+
def fetch_result() -> dict:
1419+
fetch_attempts["count"] += 1
1420+
if fetch_attempts["count"] < 3:
1421+
raise HyperbrowserError("rate limited", status_code=429)
1422+
return {"ok": True}
1423+
1424+
result = wait_for_job_result(
1425+
operation_name="sync wait helper rate limit retries",
1426+
get_status=lambda: "completed",
1427+
is_terminal_status=lambda value: value == "completed",
1428+
fetch_result=fetch_result,
1429+
poll_interval_seconds=0.0001,
1430+
max_wait_seconds=1.0,
1431+
max_status_failures=2,
1432+
fetch_max_attempts=5,
1433+
fetch_retry_delay_seconds=0.0001,
1434+
)
1435+
1436+
assert result == {"ok": True}
1437+
assert fetch_attempts["count"] == 3
1438+
1439+
12941440
def test_wait_for_job_result_async_returns_fetched_value():
12951441
async def run() -> None:
12961442
status_values = iter(["running", "completed"])
@@ -1338,6 +1484,34 @@ async def fetch_result() -> dict:
13381484
asyncio.run(run())
13391485

13401486

1487+
def test_wait_for_job_result_async_retries_rate_limit_fetch_errors():
1488+
async def run() -> None:
1489+
fetch_attempts = {"count": 0}
1490+
1491+
async def fetch_result() -> dict:
1492+
fetch_attempts["count"] += 1
1493+
if fetch_attempts["count"] < 3:
1494+
raise HyperbrowserError("rate limited", status_code=429)
1495+
return {"ok": True}
1496+
1497+
result = await wait_for_job_result_async(
1498+
operation_name="async wait helper rate limit retries",
1499+
get_status=lambda: asyncio.sleep(0, result="completed"),
1500+
is_terminal_status=lambda value: value == "completed",
1501+
fetch_result=fetch_result,
1502+
poll_interval_seconds=0.0001,
1503+
max_wait_seconds=1.0,
1504+
max_status_failures=2,
1505+
fetch_max_attempts=5,
1506+
fetch_retry_delay_seconds=0.0001,
1507+
)
1508+
1509+
assert result == {"ok": True}
1510+
assert fetch_attempts["count"] == 3
1511+
1512+
asyncio.run(run())
1513+
1514+
13411515
def test_wait_for_job_result_validates_configuration():
13421516
with pytest.raises(HyperbrowserError, match="max_attempts must be at least 1"):
13431517
wait_for_job_result(

0 commit comments

Comments
 (0)