Fix/retry backoff and retry after 21#22
Merged
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #22 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 51 51
Lines 2212 2277 +65
=========================================
+ Hits 2212 2277 +65
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
MarketDataDev01
approved these changes
May 14, 2026
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.
Retry/backoff updates per spec sections 9.3, 9.4, 9.5
Branch:
fix/retry-backoff-and-retry-after-21→mainSummary
Aligns the SDK's retry and status-check behavior with the SDK requirements spec sections 9.3 (backoff strategy), 9.4 (Retry-After header), and 9.5 (API status check). No breaking changes: only one new optional constructor parameter and behavioral changes contained to the retry path.
Three feature commits + one cleanup commit:
ee99d381s/2s/4s, configurablemax_retries93291f6Retry-Afterheader (delta-seconds and HTTP-date)a017ba0/status/cache with 270s/300s thresholds + async refresh2a085f79.3 — Backoff strategy
Spec:
initial * 2^retrywithretry0-indexed, initial 1s fixed, default 3 retries (4 total attempts), schedule 1s / 2s / 4s,max_retriesconfigurable via constructor.Before:
RETRY_BACKOFF=0.5withmin=0.5/max=5clamps. First attempt was outside the retry adapter, so tenacity only produced 2 effective waits instead of 3. Schedule was effectively~0.5s / 1s.After:
internal_settings.py:INITIAL_RETRY_DELAY = 1.0.MIN_RETRY_BACKOFF/MAX_RETRY_BACKOFFremoved.retry.py: custom_compute_wait(retry_state)callable returnsinitial_delay * 2 ** (retry_state.attempt_number - 1)with no clamps.client.py:MarketDataClient(max_retries=3)constructor parameter, validated>= 0.api_error.py: first attempt moved into the retry adapter so tenacity controls all 3 waits between the 4 attempts.9.4 — Retry-After header
Spec: if response includes
Retry-After, respect the server-specified delay and override the calculated backoff.Before: header was never read.
After:
retry.py: new public helperparse_retry_after(value: str | None) -> float | Noneaccepts both delta-seconds ("120","3.5") and HTTP-date ("Wed, 21 Oct 2026 07:28:00 GMT"). ReturnsNoneon missing/invalid/NaN/Inf values so callers fall back.retry.py:_compute_wait: readsRetry-Afterfrom the last failed response (exc.response.headers) before falling back to the exponential formula.9.5 — API status check
Spec: dual-threshold cache (refresh at 270s, validity 300s) with non-blocking async refresh.
offline→ fail immediately;online/unknown→ continue with retry. No blocking refresh inside the retry loop.Before: single 270s threshold, blocking
refresh()call from insideapi_error_handlerbefore invoking the retry adapter.After:
internal_settings.py:CACHE_VALIDITY_INTERVAL = 5 minadded;REFRESH_API_STATUS_INTERVAL = 4m30sunchanged.api_status.py:_last_refresh_atfield tracks our own local refresh time (not the API'supdatedfield).cache_agereturns time since the last successful local refresh,timedelta.maxwhen never refreshed.is_cache_stale(cache_age >= 300s) andshould_refresh(cache_age >= 270s) properties.get_api_statusimplements the three branches: fresh → read cache, refresh-zone → read cache + async refresh, stale/empty → async refresh + returnUNKNOWN._trigger_async_refreshspawnsthreading.Thread(daemon=True). Guarded bythreading.Lock+_refresh_in_flightflag so only one refresh runs at a time._async_refreshclears the in-flight flag infinallyand logs unexpected exceptions viaclient.logger.exception(so network failures in the background thread don't disappear silently).service/status/online) are now guarded by the lock to avoid partial reads while a refresh thread is updating.api_error.py: the blockingrefresh()call is gone. Thebefore_sleepcallback only doesget_api_status(which internally decides whether to trigger an async refresh).Dead code removed in
2a085f7The old
last_updatedproperty (which derived the timestamp from the API'supdatedfield) and the unusedupdated/uptimePct30d/uptimePct90dinstance attributes were removed.update()no longer reads those keys from the response payload (the API still sends them, they're just ignored).Behavioral changes summary
max_retriesMAX_RETRY_ATTEMPTS=3Retry-Afterheaderupdatedfieldclient.logger.exceptionAPI impact
MarketDataClient(max_retries: int = 3).RETRY_BACKOFF,MIN_RETRY_BACKOFF,MAX_RETRY_BACKOFFremoved frominternal_settings.py— these were not part of the public surface (they live underinternal_settings).Files changed
Note:
uv.lockchange is a one-line bump of the package self-reference (1.1.0 → 1.2.0) — pre-existing drift betweenpyproject.toml(already at1.2.0) and the lockfile. Picked up automatically byuv syncwhile developing this branch.Versioning
This branch does not bump
pyproject.toml. The version bump to1.3.0andCHANGELOG.mdentry will go in a separate "Release v1.3.0" PR per the team's release workflow.