Repository navigation
feat(transport): preserve typed errors on body cap; map 408/504; perf header merge - #286
Conversation
Code Review — PR #286OverviewThree targeted fixes to
The approach is clean and targeted. A few observations below. What's Good
Observations / Suggestions1. Semantic mismatch for HTTP 408
This isn't a correctness bug (treating 408 as potentially committed is conservative, not wrong), but callers who follow the 2. Missing test: oversized body + 408/504 → The PR has def test_oversized_body_408_preserves_timeout_error(self) -> None:
# #251 + #252
resp = httpx.Response(408, headers={"content-length": "20000"}, text="x")
err = _map_error(resp)
assert isinstance(err, KalshiTimeoutError)
assert "suppressed" in str(err)3. Minor: the annotation 4. Reformatting noise ~80 of the 124 deletions / 161 additions are reformatting (line-length adjustments in tests). These are cosmetically fine and ruff-compliant, but they make the meaningful diff harder to scan. Not a blocker. SummaryThe logic changes are correct and the test coverage is good. The only actionable gap is a missing test for the 408/504 + oversized-body combination. The semantic note on 408 is worth a comment but not a blocker. Happy to approve once the missing test is added. |
When a response advertises >MAX_ERROR_BODY_BYTES via Content-Length,
_map_error previously returned a generic KalshiError, bypassing the
status dispatch. This silently broke:
- The retry loop's isinstance(err, KalshiRateLimitError) Retry-After
branch (a hostile 429 with a verbose body fell back to computed backoff,
ignoring the server's hint).
- Caller code structured as except KalshiRateLimitError / KalshiAuthError
/ KalshiConflictError, which silently missed these responses.
Fix: when the body is suppressed, set body={} and build a suppressed-body
message, then fall through to the existing status dispatch so the typed
class is preserved. The 429 Retry-After header is read from
response.headers (not the body), so it still populates correctly.
Closes #252
KalshiTimeoutError (#226) exposes the 'may have committed' semantic so callers can branch on it (e.g., query by client_order_id before retrying an order create). 408 Request Timeout from an intermediary/CDN and 504 Gateway Timeout from upstream carry the same semantic, but routed to generic KalshiError and KalshiServerError respectively. Callers writing 'except KalshiTimeoutError' silently missed the HTTP-status variant. Fix: route 408 and 504 to KalshiTimeoutError before the >=500 catch-all. 500/502/503 continue to route to KalshiServerError. Closes #251
The merge of config_extra / per_call_extra / body_headers is loop-invariant — only auth_headers changes per attempt (fresh timestamp). Previously a fresh 4-way dict was allocated on every attempt, including the common no-retry hot path of every resource method. Build the invariant base once before the loop; the per-attempt merge is now a 2-way dict literal over a single fresh auth_headers map. Applied to both SyncTransport.request and AsyncTransport.request. Behavior unchanged: layer order (config < per-call < body-helper < auth) and final-key wins semantics are preserved. Existing transport tests pass. Closes #262
13c1685 to
72dba04
Compare
Code Review — PR #286: transport: preserve typed errors on body cap; map 408/504; perf header mergeThree distinct fixes, each well-scoped. Overall the logic is correct; a few gaps worth addressing before merge. Fix #252 — Preserve typed exception on body capApproach is correct. The Gap — oversized 408/504 path not tested. Now that 408/504 map to def test_oversized_body_504_preserves_timeout_error(self) -> None:
resp = httpx.Response(504, headers={"content-length": "50000"}, text="x")
err = _map_error(resp)
assert isinstance(err, KalshiTimeoutError)
assert "suppressed" in str(err)Gap — oversized 400/422 path not tested. The Fix #251 — Map 408/504 to
|
| Area | Status |
|---|---|
| #252 logic (suppressed body + typed error) | ✅ Correct |
#251 logic (408/504 → KalshiTimeoutError) |
✅ Correct |
| #262 logic (base_headers hoisted) | ✅ Correct |
| RETRYABLE_STATUS_CODES interaction preserved | ✅ Confirmed |
| Test: oversized body + 408/504 | ❌ Missing |
| Test: oversized body + 400/422 | ❌ Missing |
| Test: 504 retried on GET | ❌ Missing |
| CHANGELOG entry for 504 behavior change | ❌ Missing |
| Blank line style | Minor |
The three core fixes are sound. The missing test combinations (oversized body × new error types) are the most important gap — they cover the intersection of two distinct changes in this PR.
Closes #252
Closes #251
Closes #262
Part of #273 (post-v2.4 audit), Wave 2.