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
With #158 in place and #159–#162 completed, additive-drift warnings are at zero. Flip warnings.warn(...) → pytest.fail(...) so future spec syncs that introduce unmodelled fields fail CI loudly instead of slipping through five rounds of review (the v2.1.0 Balance.balance_dollars failure mode).
Also closes two ROADMAP carry-overs that are trivial alongside the flip:
Register kalshi.ws.models.base.ErrorPayload in WS_CONTRACT_MAP — currently flagged by test_ws_contract_map_completeness.
Apply model_config = {"extra": "allow"} to the WS envelope models and small helpers. PR polish(models): uniform extra="allow" on all response models (#114) #143 made WS payloadsextra="allow" but left the envelopes and helpers at the Pydantic default (extra="ignore"). This is the matching ROADMAP item under "Next milestone".
Depends on #159, #160, #161, #162 (all backfills must land first; otherwise this PR turns 53 warnings into 53 failures).
Scope
Files
tests/test_contracts.py — flip three warning sites to failures, rename for clarity
kalshi/_contract_map.py — add WS_CONTRACT_MAP entry for ErrorPayload
kalshi/ws/models/base.py — set extra="allow" on ErrorMessage, ErrorPayload, SubscribedMessage, SubscriptionInfo, UnsubscribedMessage
tests/test_model_extra_policy.py — extend the existing test to cover envelopes and helpers
CHANGELOG.md — Unreleased section
Changes
1. tests/test_contracts.py
In TestSpecDrift::test_additive_drift (line ~636): change warnings.warn(...) → pytest.fail(...). Drop the import warnings if no other call site remains.
In TestWsSpecDrift::test_ws_additive_drift (line ~734): same flip.
In TestSpecDrift::test_contract_map_completeness (line ~674) and TestWsSpecDrift::test_ws_contract_map_completeness (line ~885): same flip — these currently warn; promote to fail. (ErrorPayload registration in _contract_map.py makes the WS completeness check pass.)
Leave test_required_drift and test_ws_required_drift as warnings. The required-but-optional axis is a separate policy decision (~204 entries; see "Out of scope" below).
Update module docstring (lines 1–8) — drop "Additive drift ... WARNING" and "Unmapped SDK models ... WARNING" lines; replace with the new FAIL semantics.
2. kalshi/_contract_map.py
Add to WS_CONTRACT_MAP:
ContractEntry(
sdk_model="kalshi.ws.models.base.ErrorPayload",
spec_schema="errorPayload", # verify exact AsyncAPI schema name at edit timenotes="WS error-frame payload; surfaced via KalshiSubscriptionError",
),
Confirm the spec component name by reading specs/asyncapi.yaml — it's not ErrorPayload; AsyncAPI uses lowerCamelCase per the existing WS_CONTRACT_MAP entries.
3. kalshi/ws/models/*.py
For every class listed under "Files" above that currently has no model_config, add:
model_config= {"extra": "allow"}
Place it immediately after the field declarations, matching the existing pattern on *Payload siblings in the same module. Do not change populate_by_name, validators, or anything else.
4. tests/test_model_extra_policy.py
Extend the existing test to iterate over every class in kalshi.ws.models.* (envelopes + payloads + helpers) and assert model_config.get("extra") == "allow". This pins the policy so a future model addition that forgets the config fails the test.
5. CHANGELOG.md
Add under ## Unreleased (or whatever the active section is named at the time):
### Changed
- Response-side additive spec drift now hard-fails CI (was a warning). Closes
the regression path that allowed `Balance.balance_dollars` to ship missing in
v2.1.0. Intentional deviations require an entry in `EXCLUSIONS`
(`tests/_contract_support.py`) with a typed `kind` and `reason`.
- WS envelope and helper models now use `extra="allow"`, matching the WS
payload policy from #143. Closes ROADMAP item.
### Added
- `ErrorPayload` registered in `WS_CONTRACT_MAP` so WS contract coverage is
complete.
Acceptance criteria
uv run pytest tests/test_contracts.py -v passes with zero UserWarning for any additive drift or contract-map-completeness check.
Temporarily delete a backfilled field locally (e.g. Market.exchange_index) and re-run tests/test_contracts.py::TestSpecDrift::test_additive_drift[Market] — it must FAIL, not warn. (Smoke test; do not commit.)
Temporarily delete the ErrorPayloadWS_CONTRACT_MAP entry and re-run test_ws_contract_map_completeness — must FAIL. (Smoke test; do not commit.)
uv run pytest tests/test_model_extra_policy.py -v passes for both REST and WS models.
uv run pytest tests/ -q --ignore=tests/integration passes with the existing 1945-ish baseline.
uv run mypy kalshi/ strict-clean.
uv run ruff check . clean.
Required-drift tests still emit warnings (intentional — see "Out of scope").
Out of scope
Required-but-Optional drift (test_required_drift, test_ws_required_drift) stays as warning-only. The SDK is intentionally permissive on required-fields because demo API has been known to omit fields the spec marks required. Promoting this axis is a separate policy decision and would require either:
A ~204-entry allowlist with kind="response_required_sdk_permissive", or
A tightening pass that drops None defaults on fields we trust the server to send.
File a follow-up issue if/when the team wants to take that on. Not part of this stack.
Effort
~50 lines test + WS code. Mostly mechanical once #159–#162 are in.
Part of the response-side spec drift hardening stack.
Context
With #158 in place and #159–#162 completed, additive-drift warnings are at zero. Flip
warnings.warn(...)→pytest.fail(...)so future spec syncs that introduce unmodelled fields fail CI loudly instead of slipping through five rounds of review (the v2.1.0Balance.balance_dollarsfailure mode).Also closes two ROADMAP carry-overs that are trivial alongside the flip:
kalshi.ws.models.base.ErrorPayloadinWS_CONTRACT_MAP— currently flagged bytest_ws_contract_map_completeness.model_config = {"extra": "allow"}to the WS envelope models and small helpers. PR polish(models): uniform extra="allow" on all response models (#114) #143 made WS payloadsextra="allow"but left the envelopes and helpers at the Pydantic default (extra="ignore"). This is the matching ROADMAP item under "Next milestone".Depends on #159, #160, #161, #162 (all backfills must land first; otherwise this PR turns 53 warnings into 53 failures).
Scope
Files
tests/test_contracts.py— flip three warning sites to failures, rename for claritykalshi/_contract_map.py— addWS_CONTRACT_MAPentry forErrorPayloadkalshi/ws/models/base.py— setextra="allow"onErrorMessage,ErrorPayload,SubscribedMessage,SubscriptionInfo,UnsubscribedMessagekalshi/ws/models/fill.py—FillMessagekalshi/ws/models/market_lifecycle.py—MarketLifecycleMessagekalshi/ws/models/market_positions.py—MarketPositionsMessagekalshi/ws/models/multivariate.py—MultivariateLifecycleMessage,MultivariateMessage,SelectedMarketkalshi/ws/models/order_group.py—OrderGroupMessagekalshi/ws/models/orderbook_delta.py—OrderbookDeltaMessage,OrderbookSnapshotMessagekalshi/ws/models/ticker.py—TickerMessagekalshi/ws/models/trade.py—TradeMessagekalshi/ws/models/user_orders.py—UserOrdersMessagetests/test_model_extra_policy.py— extend the existing test to cover envelopes and helpersCHANGELOG.md— Unreleased sectionChanges
1.
tests/test_contracts.pyTestSpecDrift::test_additive_drift(line ~636): changewarnings.warn(...)→pytest.fail(...). Drop theimport warningsif no other call site remains.TestWsSpecDrift::test_ws_additive_drift(line ~734): same flip.TestSpecDrift::test_contract_map_completeness(line ~674) andTestWsSpecDrift::test_ws_contract_map_completeness(line ~885): same flip — these currently warn; promote to fail. (ErrorPayloadregistration in_contract_map.pymakes the WS completeness check pass.)test_required_driftandtest_ws_required_driftas warnings. The required-but-optional axis is a separate policy decision (~204 entries; see "Out of scope" below).2.
kalshi/_contract_map.pyAdd to
WS_CONTRACT_MAP:Confirm the spec component name by reading
specs/asyncapi.yaml— it's notErrorPayload; AsyncAPI uses lowerCamelCase per the existingWS_CONTRACT_MAPentries.3.
kalshi/ws/models/*.pyFor every class listed under "Files" above that currently has no
model_config, add:Place it immediately after the field declarations, matching the existing pattern on
*Payloadsiblings in the same module. Do not changepopulate_by_name, validators, or anything else.4.
tests/test_model_extra_policy.pyExtend the existing test to iterate over every class in
kalshi.ws.models.*(envelopes + payloads + helpers) and assertmodel_config.get("extra") == "allow". This pins the policy so a future model addition that forgets the config fails the test.5.
CHANGELOG.mdAdd under
## Unreleased(or whatever the active section is named at the time):Acceptance criteria
uv run pytest tests/test_contracts.py -vpasses with zeroUserWarningfor any additive drift or contract-map-completeness check.Market.exchange_index) and re-runtests/test_contracts.py::TestSpecDrift::test_additive_drift[Market]— it must FAIL, not warn. (Smoke test; do not commit.)ErrorPayloadWS_CONTRACT_MAPentry and re-runtest_ws_contract_map_completeness— must FAIL. (Smoke test; do not commit.)uv run pytest tests/test_model_extra_policy.py -vpasses for both REST and WS models.uv run pytest tests/ -q --ignore=tests/integrationpasses with the existing 1945-ish baseline.uv run mypy kalshi/strict-clean.uv run ruff check .clean.Out of scope
Required-but-Optional drift (
test_required_drift,test_ws_required_drift) stays as warning-only. The SDK is intentionally permissive on required-fields because demo API has been known to omit fields the spec marks required. Promoting this axis is a separate policy decision and would require either:kind="response_required_sdk_permissive", orNonedefaults on fields we trust the server to send.File a follow-up issue if/when the team wants to take that on. Not part of this stack.
Effort
~50 lines test + WS code. Mostly mechanical once #159–#162 are in.
Part of the response-side spec drift hardening stack.
Tracking: #157.