Add transient-network retry with exponential backoff

- bls_client/retry.py: with_retries() retries timeouts/connection drops and
  429/5xx with exponential backoff; honors Retry-After; never retries 4xx or
  BLSQuotaError (those won't fix themselves)
- BLSClient(retries=2, backoff=0.5) wraps the data POST and discovery GETs;
  qcew CSV fetch wrapped too. retries=0 disables.
- tests/test_retry.py: 6 offline tests (injectable sleep, no real delays)
- README: retry behavior documented; drop the now-resolved limitation

Offline suite 125 passing; live smoke green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-22 10:49:18 -04:00
parent 9e2c55c568
commit 0d0d6dabe2
5 changed files with 188 additions and 8 deletions

View File

@ -68,6 +68,7 @@ bls_client/
├── series.py low-level series-ID builders (LAUS, CES, CPI, PPI, OES, JOLTS, ECI, ECEC, QCEW, productivity)
├── qcew.py QCEW Open Data CSV client (county/industry detail; no key, no quota)
├── cache.py on-disk response cache (FileCache)
├── retry.py exponential-backoff retry for transient network errors
├── errors.py BLSError / BLSQuotaError / BLSRequestError
└── queries/
├── employment.py payrolls, unemployment (LAUS), JOLTS, QCEW totals
@ -114,6 +115,11 @@ client.queries_used # network calls made this session (ca
A blown daily quota raises `BLSQuotaError` (vs `BLSRequestError` for a bad request), so
"am I throttled or is my series ID wrong?" is no longer ambiguous.
Transient network failures (timeouts, dropped connections, `429`/`5xx`) are retried with
exponential backoff — `BLSClient(API_KEY, retries=2, backoff=0.5)` (set `retries=0` to
disable). A server `Retry-After` header is honored. The QCEW CSV client retries too. Note
that retries deliberately exclude `BLSQuotaError` and 4xx — those won't fix themselves.
## QCEW county/industry detail
The timeseries helpers give QCEW national/state totals; the `qcew` module reaches the full
@ -128,8 +134,6 @@ hospitals = qcew.industry("622", 2024, "a") # one NAICS across all a
## Known limitations
- **No automatic retry/backoff** on transient network errors (a `requests` failure surfaces
directly). Caching mitigates repeat load but there's no rate-limit pacing.
- **Coverage is the headline cut** of each survey, not an exhaustive mirror — e.g. CES is
national supersectors + a couple of states; CPI is the common items; OES ships 15 named
occupations (any SOC works via `occupation_*`). Broaden the helper dicts as needed.