fix: Order.type rename, BASE_URL validation, Retry-After NaN/negative guard#112
Conversation
Code Review — PR #112Overall: Three focused, well-targeted correctness fixes. Logic is sound, test coverage is solid. A few items to address before merge. #91 —
|
| Item | Severity |
|---|---|
| CHANGELOG missing breaking-change entry | 🔴 Blocker |
ws_base_url not validated |
🟡 Should fix |
No deprecation shim for .type |
🟡 Consider |
Multi-line comment in _base_client.py |
🟡 Style |
Order lacks model_config (pre-existing) |
📝 Follow-up issue |
The ws_base_url gap and the CHANGELOG entry are the two items I'd want resolved before merge. Everything else is non-blocking.
Review by claude[bot]
Per code review on PR #112: - Validate ws_base_url with the same scheme/host rules as base_url. WebSocket connect carries KALSHI-ACCESS-KEY too, so plaintext-to-remote is the same credential-leakage surface as #94. Generalized _validate_base_url -> _validate_url(secure=, plaintext=) so http/https and ws/wss share the rule. - CHANGELOG.md: added [Unreleased] Breaking section for Order.type -> Order.order_type. Version bump (v1.2 vs v2.0) still deferred to release cut, but the entry now exists. - _base_client.py: trimmed the 5-line Retry-After comment block to one line per CLAUDE.md style guide. - Added TestWsBaseUrlValidation (+6 tests). Deferred (not addressed): - Optional @Property def type(self) deprecation shim. User explicitly picked the clean-break rename earlier. - Pre-existing Order lacks model_config; outside this PR's scope, will open a follow-up issue. - Cosmetic host -> url_host test variable rename; low value. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Addressed in latest commit. Summary of what landed vs. what was deferred: Fixed
Deferred
Skipped
|
Code Review — PR #112Three disjoint correctness fixes, well-scoped and surgical. Each one ships with solid regression tests. Overall this is clean work. A few things worth addressing before merge. Bug:
|
Per second-pass bot review on #112: the prior commit kept Retry-After: 0 on the error object, but transport's "if error.retry_after:" check dropped 0 as falsy and fell through to backoff. Switched to "is not None" in both sync and async transport. Added regression tests that monkeypatch time.sleep / asyncio.sleep and assert the delay is exactly 0.0 (not the backoff fallback). Also addressed: - config._validate_url docstring trimmed to one line (CLAUDE.md). - Called via KalshiConfig._validate_url(...) rather than self. (staticmethod-through-self hides that no instance state is used). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Second-pass review addressed: Fixed
Skipped
|
Code Review — PR #112Three focused correctness fixes with clear motivation, well-isolated commits, and solid regression coverage. Mostly approval-ready; a few items below worth addressing before merge. Overview
Issues1. CHANGELOG missing
|
…params F-N-03: Order.type was leftover from the pre-v0.8.0 CreateOrderRequest.type field. The spec's Order schema does still include a `type` enum on responses, so rename SDK-side to `order_type` (validation alias accepts the wire `type`) — same builtin-shadow rationale as milestone_type, target_type, and incentive_type elsewhere. F-N-08: events.py and series.py built bool query params inline with `"true" if x else None`, which silently drops explicit False. Replace with the documented `_bool_param(...)` helper so callers can opt out (False -> "false"). Mechanical, matches markets.py / live_data.py. Closes #91 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reject base_url with a non-http(s) scheme or http:// pointing at a remote host. The previous config accepted any string, so an attacker who could write to a process's environment (docker run -e, CI variable, shell history) could redirect every authenticated request — and the KALSHI-ACCESS-KEY header and request signature — to an endpoint they control. - https://* with a known Kalshi host (production, demo): silent. - https://* with any other host: warn (keeps legitimate proxy use cases working but surfaces awareness). - http://localhost, 127.0.0.1, ::1: allowed (local mocks, tests). - http://<remote host>: ValueError at config construction. - non-http(s) scheme or missing host: ValueError. Closes #94 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Retry-After was parsed with bare float() and used straight in ``min(retry_after, retry_max_delay)``. Three failure modes the cap didn't cover: - ``Retry-After: -1`` -> ``min(-1, 30) = -1`` -> ``time.sleep(-1)`` is a POSIX no-op; the client busy-loops the server-controlled retry. - ``Retry-After: nan`` -> ``min(nan, 30) = nan`` -> ``time.sleep(nan)`` raises ValueError outside the documented retry pathway. - ``Retry-After: inf`` -> would survive the cap math but had no test. Reject all three at parse time and fall back to computed backoff. HTTP-date format still falls through unchanged. Closes #96 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per code review on PR #112: - Validate ws_base_url with the same scheme/host rules as base_url. WebSocket connect carries KALSHI-ACCESS-KEY too, so plaintext-to-remote is the same credential-leakage surface as #94. Generalized _validate_base_url -> _validate_url(secure=, plaintext=) so http/https and ws/wss share the rule. - CHANGELOG.md: added [Unreleased] Breaking section for Order.type -> Order.order_type. Version bump (v1.2 vs v2.0) still deferred to release cut, but the entry now exists. - _base_client.py: trimmed the 5-line Retry-After comment block to one line per CLAUDE.md style guide. - Added TestWsBaseUrlValidation (+6 tests). Deferred (not addressed): - Optional @Property def type(self) deprecation shim. User explicitly picked the clean-break rename earlier. - Pre-existing Order lacks model_config; outside this PR's scope, will open a follow-up issue. - Cosmetic host -> url_host test variable rename; low value. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per second-pass bot review on #112: the prior commit kept Retry-After: 0 on the error object, but transport's "if error.retry_after:" check dropped 0 as falsy and fell through to backoff. Switched to "is not None" in both sync and async transport. Added regression tests that monkeypatch time.sleep / asyncio.sleep and assert the delay is exactly 0.0 (not the backoff fallback). Also addressed: - config._validate_url docstring trimmed to one line (CLAUDE.md). - Called via KalshiConfig._validate_url(...) rather than self. (staticmethod-through-self hides that no instance state is used). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
a6a8fe6 to
95e9fbf
Compare
Code ReviewOverall: Well-structured PR. Three independent correctness fixes, each with clear rationale, regression tests, and CHANGELOG entries. The bundled approach is appropriate given the fixes are unrelated and small. Fix #91 —
|
| Area | Status |
|---|---|
Order.order_type rename correctness |
✅ |
| Breaking change documented | ✅ |
| URL validation security logic | ✅ |
| Retry-After guard correctness | ✅ |
is not None fix for Retry-After: 0 |
✅ |
| Test coverage | ✅ (minor async gap, non-blocking) |
| CHANGELOG | ✅ |
No blockers. The minor nits above are purely polish-level. Ship when the v1.2 vs v2.0 version decision is made.
…ecision (#121) - Wave 1: 5 PRs merged 2026-05-17 closing 10 issues. Listed each PR with its scope so the table is self-contained without cross-referencing GitHub. - Captured 4 Wave 1 learnings worth carrying forward (bot iterates, Order.type rename triggers v1.2-vs-v2.0 decision, worktree CWD slips, pip-audit needs pip seeded into uv venv). - Wave 2 marked ⏸ paused pending interim work. Added an "Interim work" section as a placeholder for items to scope. - Captured follow-ups opened during Wave 1 review (#114 audit, #113 closed-as-superseded). - Release-cut criteria: replaced static "HIGH severity items" list with ✅/⏳ progress markers, and added an explicit v1.2.0 vs v2.0.0 decision block driven by #112's Order.type rename. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Order.type→Order.order_type. Wire format unchanged (incoming JSONtypestill populates the model viavalidation_alias=AliasChoices("type", "order_type")), but any user code accessing.typeon anOrderinstance must migrate to.order_type. This must be called out inCHANGELOG.mdand is a candidate for v2.0 rather than v1.2 — defer to release-cut decision.Rationale (per #91 issue body + agent review): the spec v3.13.0 actually defines
typeas required (enum: limit, market), so removing the field would orphan a spec-required field. Renaming follows the existing project convention formilestone_type/target_type/incentive_type.Summary
Three disjoint correctness fixes from the v1.2 audit:
#91 — Order.type cleanup + bool query-param normalization (
de92e87)Order.type: str | None→Order.order_type: str | Nonewithvalidation_alias=AliasChoices("type", "order_type")(see breaking-change banner above)"true" if x else Noneternaries inkalshi/resources/events.pyandkalshi/resources/series.pyreplaced with_bool_param(...). Behavioral delta:Falsenow serializes as"false"instead of being dropped from the query string — matches the issue's documented intent.#94 —
KALSHI_API_BASE_URLvalidation (0198a73)_validate_base_urlcalled fromKalshiConfig.__post_init__.#96 —
Retry-Afternegative/NaN/infinite guard (6bfb585)math.isfinite+< 0check added in_map_error. Negative/NaN/inf treated as "no retry-after" (falls back to backoff).Test deltas
1407 → 1429 (+22 regression tests). All passing.
ruff+mypy --strictclean.Closes #91
Closes #94
Closes #96
Test plan
uv run pytest tests/ --ignore=tests/integration -vuv run ruff check .uv run mypy kalshi/