Skip to content

feat: add bounded HTTP retries and structured exceptions - #259

Merged
Mattsface merged 2 commits into
release/0.8.0from
feat/http-retries-errors
Aug 1, 2026
Merged

feat: add bounded HTTP retries and structured exceptions#259
Mattsface merged 2 commits into
release/0.8.0from
feat/http-retries-errors

Conversation

@Mattsface

@Mattsface Mattsface commented Aug 1, 2026

Copy link
Copy Markdown
Member

Why

Version 0.8.0 needs bounded recovery from temporary MLB/network failures and structured exceptions so callers can distinguish transport, timeout, HTTP, and decode failures while keeping the existing synchronous API and 4xx empty-result compatibility.

Depends on merged PR #258 (shared Sessions and timeouts). Issue 3 merge commit 6c4db03 is in this branch’s history.

What

  1. Retry policy (urllib3 Retry via Requests HTTPAdapter):
    • total=3, connect=3, read=2, status=3, backoff_factor=0.5
    • Maximum attempts: initial request + up to 3 retries = 4 total attempts
    • respect_retry_after_header=True
    • raise_on_status=False so the final response returns to MlbDataAdapter for package rules
  2. Only GET is retried (allowed_methods=frozenset({"GET"})) so unsafe methods are never retried
  3. Retry statuses: 429, 500, 502, 503, 504 (plus connection/read failures via Retry)
  4. Not retried: 400, 401, 403, 404, invalid JSON, Pydantic/parsing errors
  5. raise_on_status=False keeps urllib3 from raising status-retry exceptions to callers; the adapter applies compatibility rules
  6. Final 429 / 404 compatibility: after retries are exhausted, both remain 4xx and return MlbResult(data={}) (no MlbHttpError for 429)
  7. Exception hierarchy:
    • TheMlbStatsApiException
      • MlbTransportErrorMlbTimeoutError
      • MlbHttpError (status_code, reason, url)
      • MlbDecodeError
  8. Preserved messages: "Request failed", "Bad JSON in response", "500: Internal Server Error"
  9. Exception chaining: original Requests / JSON decode exceptions preserved as __cause__
  10. Session policy: library-created Sessions get default retry adapters; both v1 and v1.1 share one Session
  11. Injected Sessions are not modified (adapters/retries left untouched)
  12. Public exports updated in mlbstatsapi/__init__.py
  13. Issue 5 (docs/CI) was not included

Tests

poetry run pytest tests/test_mlb_exceptions.py tests/test_mlb_dataadapter.py -v
# 31 passed

poetry run pytest tests/test_mlb_retries.py tests/test_mlb_session.py -v
# 48 passed

poetry run pytest \
  tests/test_mlb_exceptions.py \
  tests/test_mlb_retries.py \
  tests/test_mlb_session.py \
  tests/test_mlb_dataadapter.py \
  tests/test_mlb_result.py \
  -v
# 88 passed

poetry run pytest tests/ --ignore=tests/external_tests
# 157 passed

poetry run pytest tests/external_tests/mlbdataadapter/test_mlbadapter.py -v
# 3 passed (live MLB API available)

Retry behavior is proven with an in-process ThreadingHTTPServer on 127.0.0.1 (no public network). Retry.sleep is patched to avoid real backoff waits.

Risk and impact

  • Normal
  • Callers catching only TheMlbStatsApiException continue to work
  • Temporary GET failures may take longer (bounded to 4 attempts)
  • Injected Sessions keep their own retry/TLS configuration
  • If retries misbehave, temporary outages could surface as final MlbHttpError / empty 4xx results after the attempt budget
Open in Web Open in Cursor 

cursoragent and others added 2 commits August 1, 2026 07:29
Add bounded urllib3 Retry adapters for library-created Sessions and
introduce structured transport, timeout, HTTP, and decode exceptions
while preserving 4xx empty-result compatibility.

Co-authored-by: Matthew Spah <[email protected]>
Remove an unused local-server fixture dependency from the decode
no-retry test.

Co-authored-by: Matthew Spah <[email protected]>

@Mattsface Mattsface left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good

@Mattsface
Mattsface marked this pull request as ready for review August 1, 2026 07:33
@Mattsface
Mattsface merged commit 6bcd543 into release/0.8.0 Aug 1, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants