Skip to content

Commit cc06f2c

Browse files
Guard status-code normalization strip failures in polling
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent e425a34 commit cc06f2c

File tree

2 files changed

+68
-6
lines changed

2 files changed

+68
-6
lines changed

hyperbrowser/client/polling.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -395,12 +395,15 @@ def _normalize_status_code_for_retry(status_code: object) -> Optional[int]:
395395
status_text = _decode_ascii_bytes_like(status_code)
396396

397397
if status_text is not None:
398-
normalized_status = status_text.strip()
399-
if not normalized_status:
400-
return None
401-
if len(normalized_status) > _MAX_STATUS_CODE_TEXT_LENGTH:
402-
return None
403-
if not normalized_status.isascii() or not normalized_status.isdigit():
398+
try:
399+
normalized_status = status_text.strip()
400+
if not normalized_status:
401+
return None
402+
if len(normalized_status) > _MAX_STATUS_CODE_TEXT_LENGTH:
403+
return None
404+
if not normalized_status.isascii() or not normalized_status.isdigit():
405+
return None
406+
except Exception:
404407
return None
405408
try:
406409
return int(normalized_status, 10)

tests/test_polling.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1768,6 +1768,34 @@ def operation() -> str:
17681768
assert attempts["count"] == 3
17691769

17701770

1771+
def test_retry_operation_handles_status_code_strip_runtime_failures_as_retryable():
1772+
class _BrokenStatusCode(str):
1773+
def strip(self, chars=None): # type: ignore[override]
1774+
_ = chars
1775+
raise RuntimeError("status code strip exploded")
1776+
1777+
attempts = {"count": 0}
1778+
1779+
def operation() -> str:
1780+
attempts["count"] += 1
1781+
if attempts["count"] < 3:
1782+
raise HyperbrowserError(
1783+
"status metadata strip failure",
1784+
status_code=_BrokenStatusCode("429"), # type: ignore[arg-type]
1785+
)
1786+
return "ok"
1787+
1788+
result = retry_operation(
1789+
operation_name="sync retry status code strip failure",
1790+
operation=operation,
1791+
max_attempts=5,
1792+
retry_delay_seconds=0.0001,
1793+
)
1794+
1795+
assert result == "ok"
1796+
assert attempts["count"] == 3
1797+
1798+
17711799
def test_retry_operation_rejects_awaitable_operation_result():
17721800
async def async_operation() -> str:
17731801
return "ok"
@@ -2544,6 +2572,37 @@ async def operation() -> str:
25442572
asyncio.run(run())
25452573

25462574

2575+
def test_retry_operation_async_handles_status_code_strip_runtime_failures_as_retryable():
2576+
class _BrokenStatusCode(str):
2577+
def strip(self, chars=None): # type: ignore[override]
2578+
_ = chars
2579+
raise RuntimeError("status code strip exploded")
2580+
2581+
async def run() -> None:
2582+
attempts = {"count": 0}
2583+
2584+
async def operation() -> str:
2585+
attempts["count"] += 1
2586+
if attempts["count"] < 3:
2587+
raise HyperbrowserError(
2588+
"status metadata strip failure",
2589+
status_code=_BrokenStatusCode("429"), # type: ignore[arg-type]
2590+
)
2591+
return "ok"
2592+
2593+
result = await retry_operation_async(
2594+
operation_name="async retry status code strip failure",
2595+
operation=operation,
2596+
max_attempts=5,
2597+
retry_delay_seconds=0.0001,
2598+
)
2599+
2600+
assert result == "ok"
2601+
assert attempts["count"] == 3
2602+
2603+
asyncio.run(run())
2604+
2605+
25472606
def test_retry_operation_async_does_not_retry_stop_async_iteration_errors():
25482607
async def run() -> None:
25492608
attempts = {"count": 0}

0 commit comments

Comments
 (0)