One useful idea
A retry makes another request, so it needs a reason and a limit. Catch only TransientError from the declared read policy. Authentication refusals, malformed responses and programming errors do not become more correct when repeated. Return the actual callback result; never manufacture a success-shaped fallback.
A callback is a function supplied to another function. request performs one operation; sleep receives a planned delay; log receives a controlled event. Tests pass list.append to sleep/log, recording values without waiting. A def can retain access to a list in its surrounding scope; the worked request uses len(calls) to select its next fixture outcome.
Validate policy before callbacks: caller-declared GET only, 1–5 total attempts, finite nonboolean base/max delays 0–60s and base no greater than max. Attempt one is the first request, not the first retry. After failure number attempt, use min(max_delay, base_delay * 2 ** (attempt - 1)). Honor a supported larger server wait. If that exceeds max_delay, stop rather than clamp below the server’s request. Never sleep after the last failed attempt.
Emit fresh attempt/reason/action/delay/cause events: retry or stop, planned float delay or None, and attempt_limit/delay_budget or None. Reasons are controlled categories, not raw exception text. Raise RetryExhausted on stop; request/sleep/log programming errors propagate. This bounds attempts and planned delay, not a callback’s runtime or total elapsed execution. The wrapper cannot inspect a closure to prove GET: pair it with this kit’s GET fetcher. Do not retry generation POSTs or claim universal idempotency. Jitter, cancellation and total deadlines require later production policies.
Refresh first: Declared transient HTTP failures, Narrow exception handling.
Trace a finished example
from provider_tools.core import retry_request
from provider_tools.errors import TransientError
calls, waits, events = [], [], []
def request():
calls.append(1)
if len(calls) < 3:
raise TransientError("server_error")
return {"job_id": "fresh_3", "state": "done"}
result = retry_request(request, sleep=waits.append, log=events.append)
print(len(calls), result["job_id"])
print(waits)
print([event["action"] for event in events])The first two calls raise controlled transient failures. The callbacks record delays 0.25 and 0.5 plus two retry events. The third call returns actual data, which is passed to the caller. There is no HTTP, real sleeping or extra successful-request retry in this trace.
The finished implementation is in provider_tools/core.py. Reading it is guided practice, not independent evidence.
Predict attempts
With max_attempts=3, how many request calls can occur?
Compare your answer · self-reviewed
At most three including the first. It does not mean one initial call plus three retries.
Find the dangerous clamp
Retry-After is 3s but max_delay is 2s. Should you sleep 2s and retry?
Compare your answer · self-reviewed
No. Stop with delay_budget before a second request. Clamping below a server-requested wait violates this declared policy.
Recall narrow failures
Should sleep or log RuntimeError become a recovered request?
Compare your answer · self-reviewed
No. Callback programming failures propagate; only a TransientError raised by request enters the retry policy.
Try the idea in this browser
Runs in this browser · optional preparation · local project checks remain separate
Try a small function before opening your local files. Python downloads when you choose Run; if it cannot load, your code stays here and the local kit still works. The worker executes on your device, not on a DVP server. Only run code you trust: this is not a hostile-code security sandbox.
JavaScript loads the practice controls. Python starts only after Run.
The tutor button only prepares a question locally. Review it and choose Send yourself; no code is sent merely by running or opening a lesson.
Output
Errors and check feedback
Read the browser task briefs without running Python
Guided finite retry policy
Implement retry_request in practice.py. Validate the declared method, callbacks, attempt count and finite delays before invoking any callback. Catch only TransientError, preserve actual success, cap exponential backoff, honor or refuse supported server waits and log every planned retry/stop without raw context. Raise RetryExhausted when the attempt/delay budget ends; never sleep after the final failed call. This browser preparation uses only invented in-memory values and reviewed helpers shown below. No HTTPX client, live request or actual sleep runs. It does not complete your local adapter, HTTP or Portfolio II task. Guided and independent attempts use changed inputs and the same declared contract.
Independent finite retry policy
Implement retry_request in practice.py. Validate the declared method, callbacks, attempt count and finite delays before invoking any callback. Catch only TransientError, preserve actual success, cap exponential backoff, honor or refuse supported server waits and log every planned retry/stop without raw context. Raise RetryExhausted when the attempt/delay budget ends; never sleep after the final failed call. This browser preparation uses only invented in-memory values and reviewed helpers shown below. No HTTPX client, live request or actual sleep runs. It does not complete your local adapter, HTTP or Portfolio II task. Guided and independent attempts use changed inputs and the same declared contract.
Change it, then build your own
One controlled change
Succeed on the second call and raise rate_limited with retry_after=1.5 on the first. Then change that wait to 3 under a 2-second delay budget. Predict request count, log action and waits.
Your independent task
Implement retry_request in practice.py. Validate the declared method, callbacks, attempt count and finite delays before invoking any callback. Catch only TransientError, preserve actual success, cap exponential backoff, honor or refuse supported server waits and log every planned retry/stop without raw context. Raise RetryExhausted when the attempt/delay budget ends; never sleep after the final failed call.
What success looks like
The build3 group passes third-attempt success, capped exhaustion, supported Retry-After, delay-budget stop, input refusal before callbacks, permanent-error propagation and sleep/log errors. Tests record waits instead of sleeping. A pure retry pass is not a live HTTP or idempotency certification.
Hint 1 · a question
Write down what counts as attempt one and which exception family may enter the retry branch. Validate before the loop.
Hint 2 · a concept cue
After a transient failure compute the next delay, decide stop versus retry, emit one event and either raise exhaustion or call sleep. No last-attempt wait.
Hint 3 · a localized example
min(max_delay, base_delay * 2 ** (attempt - 1)) caps exponential backoff. A larger supported Retry-After still needs a delay-budget check; the formula alone is not the full policy.
Need the complete worked solution?
Open provider_tools/core.py from the kit. Trace it, close it, then try fresh inputs in your own files. Treat the attempt as guided; seeing the solution does not award a practical pass.
Course help is guidance, not independent evidence. With JavaScript, opening help records guidance locally; otherwise note it in your README. Reset does not erase that history.
Repair a failed check
If there are four calls under max_attempts=3, include the initial call in the count. If the loop never ends, add explicit attempt/delay stops. If a permanent refusal becomes success, remove the broad catch/fallback. If logs contain private text, keep only declared reason/action/count fields.
NotImplementedError means a practice stub is still unfinished. Read the failing test name and the last error line. Change one behavior, rerun that build, then rerun all implemented builds.
Show it works on new inputs
Create success-on-two, always-timeout, permanent-404 and server-wait-over-budget scenarios with recorded callbacks. Show exact calls/waits/events, actual returned data and visible refusals. Explain why POST retries and total elapsed deadlines need different policies.
Self-review: name the input, result, refused case and reason. Your local test output and explanation are separate from a quiz score; this page does not certify a pass.
Keep the idea
A finite retry is a deliberate recovery policy, not permission to hide failures or repeat side effects.