A schedule decides whether to try again and how long to wait. retry uses one to re-run an
effect after a failure; repeat re-runs it after a success.
from dataclasses import dataclass
import pyfect as fx
from pyfect import schedule
@dataclass
class Unavailable(fx.TaggedError):
attempt: int
def flaky_service(failures: int) -> fx.Effect[str, Unavailable]:
"""Fails `failures` times, then succeeds."""
attempts: list[int] = []
def attempt() -> fx.Effect[str, Unavailable]:
attempts.append(len(attempts) + 1)
if len(attempts) <= failures:
return fx.fail(Unavailable(len(attempts)))
return fx.succeed("ok")
# suspend: each retry calls attempt() again
return fx.suspend(attempt)
# Wait 10ms, 20ms, 40ms... between attempts, at most 5 retries.
policy = schedule.exponential(0.01).up_to(times=5)
assert fx.run_sync_exit(flaky_service(2).retry(policy)) == fx.Success("ok")
# The keyword form: retry while the error matches, at most twice.
again = flaky_service(2).retry(times=2, while_=lambda e: e.attempt < 10)
assert fx.run_sync_exit(again) == fx.Success("ok")
# When the retries run out, the effect fails with the last error.
exhausted = flaky_service(5).retry(times=2)
assert fx.run_sync_exit(exhausted) == fx.exit.fail(Unavailable(3))The waits of policy above, before each of its five retries:
retry and repeat take a schedule, or the keywords schedule=, times=, while_= and
until=. When retries run out, the effect fails with the last error.
import pyfect as fx
from pyfect import schedule
ticks: list[int] = []
def tick() -> fx.Effect[None]:
return fx.sync(lambda: ticks.append(len(ticks)))
# Run once, then repeat every 10ms, three more times.
fx.run_sync(tick().repeat(schedule.spaced(0.01).up_to(times=3)))
assert ticks == [0, 1, 2, 3]
# Schedules combine: jittered exponential backoff, at most one second per wait.
backoff = schedule.min(schedule.exponential(0.1).jittered(), schedule.spaced(1))
assert fx.run_sync(fx.succeed("first try").retry(backoff)) == "first try"Constructors: spaced, fixed, exponential, fibonacci, windowed, recurs, cron.
Combinators: up_to(times=, duration=), jittered, while_, max / min, concat, and
more. Schedules use the Clock service, so tests can run them on virtual time
(Testing).
Reference: pyfect.schedule.