You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(config): honour timeout=0 and base_url; stop crashing on max_retries=0 (#120, #121) (#126)
Both halves of the constructor argument handling #115 started and did not
finish, plus the version bump and changelog that change needed.
#120 -- #115 replaced two of the three `or` defaults and left the third, on the
line directly above its own fix, in both clients:
self.base_url = (base_url or self.DEFAULT_BASE_URL).rstrip("/") # unchanged
self.timeout = timeout or self.DEFAULT_TIMEOUT # unchanged
self.max_retries = (... if max_retries is None else ...) # fixed
self.retry_on = (... if retry_on is None else ...) # fixed
`timeout=0` is a real httpx timeout meaning "fail immediately" and is what
`float(os.getenv("OPA_TIMEOUT", "0"))` produces. It silently became 30, and the
resulting hang is very hard to attribute back to the constructor. A negative
timeout was passed down to httpx unvalidated. `base_url=""` pointed the client
at PRODUCTION, which also pins the #113 origin guard to an origin the caller
did not choose -- the worst available answer for an input nobody meant.
Both are now explicit None checks with validation, matching the two lines
below them. `timeout=None` and `base_url=None` still take the defaults.
#121 -- `max_retries=0` and `max_retries=3.0` started raising
`ConfigurationError` AT CLIENT CONSTRUCTION, having constructed fine in 1.13.0,
with no version bump and no changelog. The validation is right; the delivery
was not. This fails at startup, so it takes a whole process down rather than
degrading one call, and `int(os.getenv("OPA_MAX_RETRIES", "0"))` is the common
way to produce it.
Recommendation taken: accept them again, do not break.
0 -> 1 attempt, with a DeprecationWarning saying the argument counts
total ATTEMPTS, not retries after the first
3.0 -> 3, with a DeprecationWarning
-1 -> still ConfigurationError
2.5 -> still ConfigurationError (not a whole number of attempts)
"3" -> still ConfigurationError
True -> still ConfigurationError (a typo hazard; it would mean 1)
The bug #104 fixed does not come back: 0 resolves to ONE attempt, never 3, and
`test_zero_max_retries_does_not_go_back_to_three` counts what reaches the
transport.
Version bumped 1.13.0 -> 1.14.0 in `version.py` and `pyproject.toml`, with a
CHANGELOG entry covering both, including an "Upgrading" note: nothing that
worked in 1.13.0 raises in 1.14.0, but `timeout=0` and `max_retries=0` now mean
what they say instead of 30 and 3.
Two tests added in #115 are updated rather than deleted:
`test_invalid_max_retries_fails_loudly` drops `0` from its parametrize and
keeps the negatives; `test_async_invalid_max_retries_fails_loudly` uses -1. The
new contract for 0 is pinned in
tests/unit/test_constructor_config_validation.py (43 tests, every one run
against BOTH clients).
Proven red against `origin/main` source: 22 failed / 21 passed.
Suite: 112 failed / 673 passed / 63 skipped, against a measured baseline of
112 failed / 630 passed / 63 skipped on clean main (the 112 are pre-existing,
all missing pytest-asyncio and respx in the environment).
Claude-Session: https://claude.ai/code/session_015ao5paex73xXvuM424Libo
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
0 commit comments