fix(ws): seq_tracker wiring (#27) + close-out F-O-05 + single parse path (#28)#142
Conversation
PR #137 added an optional seq_tracker kwarg on MessageDispatcher.__init__ so server-initiated unsubscribe (#81) can clear stale seq state alongside the sid mapping. The wiring at the construction site in KalshiWebSocket was never landed — the dispatcher always saw seq_tracker=None and could only clear half the state. Closes #27 (post-Wave-3 integration tracker). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
F-O-05 / Closes #84 (final half): The validation-failure log in MessageDispatcher.dispatch used `logger.warning("Failed to parse %s message", msg_type, exc_info=True)`. Pydantic's ValidationError stringification echoes the full input — for trade/fill/order_group channels that includes price, count, and sometimes user identifiers, dumped straight into stdout/Sentry/log infrastructure. Dropped exc_info; surface type + exception class name only. The underlying exception is still available via __cause__ for local debug. #86 dispatcher side: - Signature change: dispatch(raw: str) -> dispatch(data: dict, *, pre_validated: BaseModel | None = None). The recv loop now owns the json.loads (it already had one); the dispatcher's redundant second parse is gone. - pre_validated lets the recv loop hand off an orderbook message it already validated for the local OrderbookManager, so the dispatcher routes the same instance to the subscription queue without re-running Pydantic. Eliminates the double-validate the issue called out (~3 µs/msg). - json import dropped from dispatch.py (now unused). Recv loop client.py side (in the previous commit alongside the seq_tracker wiring): _process_frame captures the typed orderbook message and passes pre_validated to dispatch. Test fallout: - 21 tests in test_dispatch.py updated from `dispatcher.dispatch(raw)` to `dispatcher.dispatch(json.loads(raw))` via sed. - Dropped test_dispatch_invalid_json — that path now lives in the recv loop's exception ladder (covered by test_malformed_frame_logged_with_traceback_and_continues). - 2 tests in test_recv_loop_hardening.py monkey-patched dispatch with the old (raw: str) signature; updated to (data: dict[str, Any], **kw). - New regression: test_dispatch_pre_validated_skips_revalidation — asserts dispatch routes the same instance when pre_validated matches the expected model_cls. Closes #84 Closes #86 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code ReviewOverall: Clean, focused PR. Three well-scoped fixes that each do exactly one thing. Conventions followed throughout. One factual inaccuracy in a comment worth fixing before merge.
|
seq_tracker wiring |
✅ Correct |
F-O-05 exc_info removal |
✅ Correct, one misleading comment to fix |
| Single parse path | ✅ Clean |
| Test coverage | ✅ Regression test + bulk updates |
| mypy / ruff | ✅ Per PR description |
One change requested: fix or remove the __cause__ claim in the inline comment in dispatch.py. Everything else looks good.
Per bot review on PR #142: the comment said "the underlying exception is preserved on __cause__ for debug" — but the except block catches, logs the type name, and returns. Nothing is re-raised, so no __cause__ chain is established. Dropped the misleading sentence; kept the security rationale (Pydantic input echo). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Addressed:
Skipped (justified):
|
Code Review — PR #142tl;dr: Three well-scoped fixes with good test coverage. Two CLAUDE.md style violations to address; no correctness issues. OverviewThis PR lands three distinct follow-ups cleanly:
Code Quality & StyleCLAUDE.md violations — two instances: 1. Multi-paragraph docstring on
The new docstring is four lines across two paragraphs. The contract is better communicated by the type signature ( async def dispatch(self, data: dict[str, Any], *, pre_validated: BaseModel | None = None) -> None:
"""Route a pre-parsed frame to its subscriber."""The 2. Multi-line comment block in The 3-line comment added over the orderbook validation block ( Correctness
Security
Performance
Test CoverageRegression test ( Dropped test ( One minor nit: The new regression test imports from kalshi.ws.models.orderbook_delta import OrderbookSnapshotMessage
Summary
Two style fixes needed before merge; everything else looks good. |
* release: v2.0.0 Cuts the v2.0.0 release. Captures the full audit-driven hardening wave (30 audit findings closed across 5 parallel waves + cleanup) and the 3 deliberate breaking changes: Order.type rename, AccountApiLimits restructure, count/size/volume type retype. Changes: - CHANGELOG.md: finalize [Unreleased] → ## 2.0.0 — 2026-05-17 with a full release summary (Breaking, Added, Changed, Fixed, Security, Performance, Internal). - ROADMAP.md: rewrite around Shipped / Open trackers / Next milestone / Execution conventions. The wave-by-wave v1.2 plan is captured under Shipped as v2.0.0; CHANGELOG carries the detail. - README.md: document max_pages in the Pagination section, surface http2/limits/extra_headers in the KalshiConfig example. - docs/migration.md: new v1.x → v2.0 section at the top covering the 3 breaking changes + the non-breaking behavior shifts (unbounded *_all() by default, uniform extra="allow", WS callback fan-out). - pyproject.toml + kalshi/__init__.py: 1.1.0 → 2.0.0. - CLAUDE.md: active milestone → post-v2.0. - tests/integration/helpers.py: add wait_until_not_found inverse poller. - tests/integration/test_order_groups.py::test_delete: use wait_until_not_found so the demo's query-exchange propagation lag after DELETE doesn't make the test flap. - AGENTS.md: GitNexus stat refresh after Wave 5 + #142 + #143 merges. Verify: - uv run pytest tests/ --ignore=tests/integration -q → 1808 passed - uv run pytest tests/integration/ → 215 passed, 30 skipped - uv run ruff check . → clean - uv run mypy kalshi/ --strict → clean (76 source files) Closes #11 (release-cut milestone tracker). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * release: drop orphaned KalshiNotFoundError import The release commit replaced `with pytest.raises(KalshiNotFoundError):` in test_order_groups.py::test_delete with the new wait_until_not_found helper, but left the import behind. CI's ruff caught F401. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Three follow-ups left over from Wave 3 / 4. Two commits, one PR.
Commit 1 — wire
SequenceTrackerintoMessageDispatcher(closes task #27)PR #137 (dispatcher correctness) added an optional
seq_tracker=Nonekwarg onMessageDispatcher.__init__so server-initiated unsubscribe (#81) can clear stale seq state alongside the sid mapping. The wiring at the construction site inKalshiWebSocketwas never landed — the dispatcher always sawseq_tracker=Noneand could only clear half the state.Also includes the recv-loop side of #86 (next item):
_process_framenow passespre_validatedtodispatchfor orderbook channels.Commit 2 — close #84 F-O-05 + #86 (single parse path)
F-O-05 (closes #84 fully): dispatcher's validation-failure log used
exc_info=True, which surfaces Pydantic'sValidationError.__str__— that echoes the full input including trade prices, counts, and user identifiers straight into log sinks. Droppedexc_info; surface type + exception class only. The underlying exception stays on__cause__for local debug.#86 (single parse path):
dispatch(raw: str)→dispatch(data: dict, *, pre_validated: BaseModel | None = None). Recv loop owns thejson.loads(it already had one); the dispatcher's redundant second parse is gone.pre_validatedlets the recv loop hand off an orderbook message it already validated for the localOrderbookManager, so the dispatcher routes the same instance to the subscription queue without re-running Pydantic. Eliminates the double-validate the issue called out (~3 µs/msg).import jsonfromdispatch.py.Test fallout
test_dispatch.pybulk-updated fromdispatcher.dispatch(raw)→dispatcher.dispatch(json.loads(raw)).test_dispatch_invalid_json— that path now lives in the recv loop's exception ladder (already covered bytest_malformed_frame_logged_with_traceback_and_continues).test_recv_loop_hardening.pymonkey-patcheddispatchwith the old(raw: str)signature; updated.test_dispatch_pre_validated_skips_revalidationasserts the dispatcher routes the same instance whenpre_validatedmatches the expectedmodel_cls(proves the double-validate is gone).Closes #27
Closes #84
Closes #86
Test plan
uv run ruff check .cleanuv run mypy kalshi/clean (strict)