Skip to content

AbortRetryError

Raise AbortRetryError from a should_retry callback to stop retrying immediately. The exception propagates as ShellError.base_error.

Abort on permanent failure

import sys

import pytest
from ask_shell.shell import (
    AbortRetryError,
    ShellConfig,
    ShellError,
    ShellRun,
    run_and_wait,
)

PYTHON_EXEC = sys.executable


def should_retry_check(run: ShellRun) -> bool:
    if "transient" in run.stderr:
        return True
    raise AbortRetryError(f"permanent: {run.stderr[:100]}")


script = """\
import sys
sys.stderr.write("permanent error")
sys.exit(1)
"""

with pytest.raises(ShellError) as exc:
    run_and_wait(
        ShellConfig(
            shell_input=f"{PYTHON_EXEC} -c '{script}'",
            attempts=3,
            should_retry=should_retry_check,
        )
    )

assert isinstance(exc.value.base_error, AbortRetryError)
print(str(exc.value.base_error))
#> permanent: permanent error

Retry on transient, then succeed

import sys
from pathlib import Path
from tempfile import mkdtemp

from ask_shell.shell import AbortRetryError, ShellConfig, ShellRun, run_and_wait

PYTHON_EXEC = sys.executable
tmp = Path(mkdtemp())
attempt_file = tmp / "attempt"

script_text = f"""\
import sys
from pathlib import Path
p = Path("{attempt_file}")
n = int(p.read_text()) if p.exists() else 1
p.write_text(str(n + 1))
if n < 2:
    sys.stderr.write("transient error")
    sys.exit(1)
print("ok")
"""
(tmp / "run.py").write_text(script_text)


def should_retry_transient(run: ShellRun) -> bool:
    if "transient" in run.stderr:
        return True
    raise AbortRetryError("permanent")


result = run_and_wait(
    ShellConfig(
        shell_input=f"{PYTHON_EXEC} {tmp / 'run.py'}",
        attempts=3,
        should_retry=should_retry_transient,
        retry_initial_wait=0.01,
        retry_max_wait=0.1,
        retry_jitter=0,
    )
)
print(result.stdout)
#> ok