feat(client): exponential backoff retry for transient 5xx errors#167
Open
bennytimz wants to merge 1 commit into
Open
feat(client): exponential backoff retry for transient 5xx errors#167bennytimz wants to merge 1 commit into
bennytimz wants to merge 1 commit into
Conversation
Adds RetryPolicy and wires it into PaymentTransport and Client.
RetryPolicy (src/mpp/client/retry.py):
- max_attempts=3 with backoff_delays=[0.5, 1.0] seconds by default
- Retries on status codes in retryable_status_codes (500/502/503/504)
- Optionally retries on httpx.ConnectError / httpx.RemoteProtocolError
- Exported from mpp and mpp.client for easy import
PaymentTransport:
- New retry_policy: RetryPolicy | None = None parameter
- _dispatch() helper runs the retry loop before 402 handling so that
transient 5xx errors are resolved before the payment flow begins
- asyncio.sleep provides the configurable backoff between attempts
- Each retry attempt logs a warning with attempt number and URL
- Async-generator bodies (guarded by PR tempoxyz#166) propagate before the
retry loop, so no unsafe body replay can occur
Client:
- retry_policy defaults to RetryPolicy() (retry on by default)
- Pass retry_policy=None to get raw no-retry behaviour
Tests (tests/test_client.py):
- test_5xx_retried_with_backoff: 503x2 then 200; verifies sleep(0.5) and sleep(1.0)
- test_5xx_exhausted_returns_last_response: max_attempts=2, always 503
- test_connect_error_retried: ConnectError then 200
- test_retry_disabled_when_policy_is_none: PaymentTransport with retry_policy=None
- test_non_retryable_5xx_not_retried: 501 passes through in one attempt
- test_client_retry_disabled_when_policy_is_none: Client(retry_policy=None) via HTTPXMock
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds configurable exponential-backoff retry for transient 5xx errors to
PaymentTransportandClient. This builds on the 402 retry work introduced in PR #156 and PR #166.RetryPolicydataclass — configurablemax_attempts,backoff_delays,retryable_status_codes, andretry_on_connect_errorPaymentTransport— acceptsretry_policy: RetryPolicy | None = None; a private_dispatch()method runs the retry loop before the 402 payment flow, so transient server errors are resolved before any payment credential is createdClient—retry_policydefaults toRetryPolicy()(on by default with sensible defaults); passretry_policy=Nonefor raw no-retry behaviourRetryPolicyexported from bothmppandmpp.clientfor convenient importsDesign notes
Retry is scoped to the initial dispatch only. The 402 paid-retry request is not subject to 5xx retry — it is a distinct operation that has already incurred payment.
Async-generator bodies (guarded by PR #166) raise
PaymentErrorbefore the dispatch loop, so the retry path never encounters a half-consumed stream.RetryPolicyis on by default inClient. Users who want zero-overhead raw behaviour passretry_policy=None.Test plan
test_5xx_retried_with_backoff— 503×2 then 200; assertsasyncio.sleepcalled with0.5and1.0test_5xx_exhausted_returns_last_response—max_attempts=2, always 503; asserts 503 returned after 2 callstest_connect_error_retried—ConnectErrorthen 200; asserts final response is 200test_retry_disabled_when_policy_is_none—PaymentTransport(retry_policy=None), 503 returned immediatelytest_non_retryable_5xx_not_retried— 501 not inretryable_status_codes; single attempttest_client_retry_disabled_when_policy_is_none—Client(retry_policy=None)via HTTPXMocktests/test_client.pypass (24 existing + 5 new retry tests + 1 existing)