Skip to content

polish(models): uniform extra="allow" on all response models (#114)#143

Merged
TexasCoding merged 2 commits into
mainfrom
fix/issue-114-uniform-extra-allow
May 17, 2026
Merged

polish(models): uniform extra="allow" on all response models (#114)#143
TexasCoding merged 2 commits into
mainfrom
fix/issue-114-uniform-extra-allow

Conversation

@TexasCoding

Copy link
Copy Markdown
Owner

Summary

Audit + fix for #114 — make response models forwards-compatible by default.

Audit findings

Policy Count Action
extra="allow" 80 ✅ already correct
extra="forbid" (request bodies) 17 ✅ keep (per v0.8.0+ convention)
extra="ignore" (implicit Pydantic default) 5 🔧 fixed

The 5 outliers: Page, Orderbook, OrderbookLevel, BidAskDistribution, PriceDistribution. All gained model_config = {"extra": "allow"} (preserving the existing populate_by_name=True where present).

Behavior change

Instances of those 5 classes will now preserve unknown fields on __pydantic_extra__ rather than silently dropping them. Called out in CHANGELOG.md [Unreleased] → Changed.

Drift guard

New tests/test_model_extra_policy.py parametrizes over kalshi.models.__all__:

  • 85 response models must have extra="allow" (every BaseModel not ending in Request/RequestOrder)
  • 17 request bodies must have extra="forbid"

A future model added with the wrong policy fails at collect time.

Closes #114

Test plan

  • Full suite: 1808 passed (was 1705; +103 from the new parametrized tests)
  • uv run ruff check . clean
  • uv run mypy kalshi/ clean (strict)

Pre-PR audit of all 102 exported model classes:
- 80 response models already on `extra="allow"`
- 17 request bodies on `extra="forbid"` (per the v0.8.0+ convention)
- 5 response models falling back to Pydantic's default `extra="ignore"`:
  Page, Orderbook, OrderbookLevel, BidAskDistribution, PriceDistribution

Added `model_config = {"extra": "allow"}` to all 5 so the SDK behaves
uniformly when Kalshi adds new fields: unknown fields preserved on
`__pydantic_extra__` rather than silently dropped.

Drift guard: tests/test_model_extra_policy.py iterates
kalshi.models.__all__ and parametrizes two asserts — every response
model must use `extra="allow"`, every `*Request` body must use
`extra="forbid"`. A future model added without the right policy fails
at collect time.

Behavior change called out in CHANGELOG under [Unreleased] → Changed:
instances of the 5 affected classes will now preserve unknown fields
where they previously discarded them.

Closes #114

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@TexasCoding TexasCoding force-pushed the fix/issue-114-uniform-extra-allow branch from 8259139 to 62202c6 Compare May 17, 2026 18:47
@claude

claude Bot commented May 17, 2026

Copy link
Copy Markdown

Code Review — PR #143: polish(models): uniform extra="allow" on all response models

Overview

Clean, well-scoped fix for #114. The 5 outlier response models (Page, Orderbook, OrderbookLevel, BidAskDistribution, PriceDistribution) have been promoted from Pydantic's default extra="ignore" to the project-standard extra="allow". The new drift guard (test_model_extra_policy.py) locks this in going forward. Changes are surgical and consistent with the CLAUDE.md convention ("touch only what you must").


Issues / Suggestions

1. model_config placement in Page (minor style)

class Page(BaseModel, Generic[T]):
    items: list[T]
    cursor: str | None = None

    model_config = {"extra": "allow"}   # ← between fields and @property

    @property
    def has_next(self) -> bool:
        ...

Pydantic v2 accepts this, but the rest of the codebase places model_config at the bottom of field declarations (as the other four fixes in this PR do). Placing it after the fields but before the @property creates a minor visual inconsistency. Suggest moving it below cursor and before the blank line that precedes has_next.


2. _REQUEST_BODY_SUFFIXES includes "RequestOrder" — purpose unclear (minor)

_REQUEST_BODY_SUFFIXES = ("Request", "RequestOrder")

No model in this SDK currently ends with "RequestOrder" (all 17 request bodies end with "Request"). If "RequestOrder" was added prophylactically for a future model, a short inline comment would make the intent clear. If it's genuinely unused, removing it keeps the heuristic crisp and avoids puzzling future readers.


3. Test ids= missing — noisy pytest -v output (nit)

@pytest.mark.parametrize(
    "name,cls",
    [(n, c) for n, c in _exported_model_classes() if not _is_request_body(n)],
)

Without ids=, pytest auto-generates IDs from the class objects, producing output like test_response_models_use_extra_allow[name0-cls0]. Adding ids=[n for n, _ in ...] would give test_response_models_use_extra_allow[Market], making failures immediately legible. Same applies to test_request_bodies_use_extra_forbid.


4. Enforcement scope limited to __all__ exports (low risk, worth noting)

_exported_model_classes() iterates kalshi.models.__all__. A response model added to a models/*.py file but not wired into __all__ would silently escape the policy check. Given the project's convention of exporting everything through __init__.py, this is a theoretical gap rather than a practical one — but documenting the assumption in a comment on _exported_model_classes would make the invariant explicit.


What's Done Well

  • Diff is minimal: 5 two-line additions + 2 line modifications. No scope creep.
  • BidAskDistribution / PriceDistribution correctly merge the new key into the existing model_config dict rather than overwriting it — populate_by_name is preserved.
  • CHANGELOG entry is precise and correctly placed under [Unreleased] → Changed.
  • The parametrized test design is the right approach; 103 new test cases for ~55 lines of test code is a good ratio.
  • No security or performance concerns — extra="allow" vs extra="ignore" has negligible overhead and no attack surface.

Summary

Approve with minor suggestions. The ids= nit and _REQUEST_BODY_SUFFIXES clarification are the most actionable items; the model_config placement in Page is the smallest but easiest to fix in the same pass.

@claude

claude Bot commented May 17, 2026

Copy link
Copy Markdown

Code Review — PR #143: uniform extra=\"allow\" on all response models

Overview

Clean, well-scoped fix for #114. The 5 outlier response models that were silently dropping unknown fields (Page, Orderbook, OrderbookLevel, BidAskDistribution, PriceDistribution) have been brought into line with the 80 sibling models. The new parametrized drift guard (test_model_extra_policy.py) is a meaningful addition — future model additions will fail immediately at CI if the wrong policy is used.


Code quality

✅ Strengths

  • Surgical changes only — no adjacent cleanup, exactly what CLAUDE.md calls for.
  • Audit table in the PR description makes the scope clear and verifiable.
  • Drift guard test is well-structured: parametrizes over __all__, no fixtures needed, failure message includes both the observed and expected value plus the issue number.
  • CHANGELOG entry is accurate and readable.

Issues

Minor: model_config key ordering is now inconsistent for the two updated models

BidAskDistribution and PriceDistribution end up with {"populate_by_name": True, "extra": "allow"} (new key appended). But Candlestick on line 237 and Market on line 138 use {"extra": "allow", "populate_by_name": True} (extra first).

# After this PR — inconsistent with siblings
model_config = {"populate_by_name": True, "extra": "allow"}  # BidAskDistribution, PriceDistribution

# Pre-existing style in the same file
model_config = {"extra": "allow", "populate_by_name": True}  # Candlestick (line 237), Market (line 138)

Suggestion: flip the key order to match the rest of the file.


Observation (non-blocking): naming-based request detection carries a silent assumption

_is_request_body() infers a model's role purely from its name suffix:

_REQUEST_BODY_SUFFIXES = ("Request", "RequestOrder")

This works for every current model, but there is no enforcement that a response model cannot be named FooRequest. A comment explaining that this relies on the project convention documented in CLAUDE.md ("request bodies end in Request/RequestOrder") would make the fragility visible to future editors without adding code.


Observation (non-blocking): WS envelope classes are outside the policy scope

kalshi/ws/models/orderbook_delta.py has OrderbookSnapshotMessage and OrderbookDeltaMessage without model_config. The test is correctly scoped to kalshi.models.__all__, so these are out of scope for this PR. Noting it for a potential follow-up: if the same extra="allow" policy should apply to kalshi.ws.models, a separate audit/test would be needed.


Nit: _exported_model_classes() is evaluated twice

The function is called once per @pytest.mark.parametrize decorator at module import time. Two calls over ~102 models is inconsequential, but if you ever want to clean it up: assign _ALL_MODELS = _exported_model_classes() at module level and reference it in both parametrize calls.


Test coverage

+103 tests from the new parametrized policy check. Happy path and failure path are both implicitly covered (the policy fix is what makes the response-model tests pass). No integration tests are needed here.


Summary

Approve with one suggestion: normalize the model_config key order in BidAskDistribution and PriceDistribution to {"extra": "allow", "populate_by_name": True} for consistency with the rest of markets.py. Everything else is correct and well-executed.

Per two-pass bot review on #143:

- Page model_config moved adjacent to fields (was between fields and
  @Property block — visual inconsistency with sibling models).
- BidAskDistribution + PriceDistribution model_config key order flipped
  to {"extra": ..., "populate_by_name": ...} matching Market and
  Candlestick siblings in the same file.
- test_model_extra_policy.py:
  * Added ids= to both parametrize decorators so pytest -v output reads
    test_response_models_use_extra_allow[Market] instead of [name0-cls0].
  * Cached _ALL_MODELS / _RESPONSE_MODELS / _REQUEST_MODELS at module
    scope (was evaluating _exported_model_classes() twice at import).
  * Added a comment explaining why _REQUEST_BODY_SUFFIXES needs both
    "Request" and "RequestOrder" — BatchCancelOrdersRequestOrder
    doesn't end in plain "Request" (bot was incorrect that "RequestOrder"
    was unused; this comment prevents the next reviewer from chasing it).
  * Added a docstring on _exported_model_classes noting the __all__
    scope assumption.

Skipped (justified):
- WS envelope classes (kalshi/ws/models/) are out of scope per the issue;
  separate audit if we want the policy applied there too.

Verify: tests/test_model_extra_policy.py 102 passed. ruff + mypy --strict
clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@TexasCoding

Copy link
Copy Markdown
Owner Author

Both reviews addressed in latest commit:

From bot #1:

  • 🟦 Page.model_config placement — moved adjacent to fields, no longer between fields and @property.
  • 🟦 _REQUEST_BODY_SUFFIXES includes "RequestOrder" — clarified. Actually load-bearing: BatchCancelOrdersRequestOrder doesn't end in plain Request. Added an inline comment so the next reader doesn't chase it.
  • 🟦 ids= addedpytest -v output now reads [Market] / [CreateRFQRequest] instead of [name0-cls0].
  • 🟦 __all__ scope assumption — added to _exported_model_classes() docstring.

From bot #2:

  • 🟦 Key ordering in BidAskDistribution / PriceDistribution — flipped to {"extra": ..., "populate_by_name": ...} matching Market and Candlestick in the same file.
  • 🟦 _exported_model_classes() evaluated twice — cached at module scope (_ALL_MODELS / _RESPONSE_MODELS / _REQUEST_MODELS).
  • WS envelope classes — acknowledged out of scope per the issue; separate audit if we want the same policy in kalshi/ws/models/.

Verify: tests/test_model_extra_policy.py 102 passed. ruff + mypy --strict clean.

@claude

claude Bot commented May 17, 2026

Copy link
Copy Markdown

Code Review — PR #143: Uniform extra=\"allow\" on response models

Overall: Clean, surgical PR that fixes a real forward-compatibility gap. The drift guard test is the most valuable addition here — it turns a one-time audit into a permanent policy. A few minor items below, none blocking.


What the PR Does

Five response models (Page, Orderbook, OrderbookLevel, BidAskDistribution, PriceDistribution) were silently falling through to Pydantic's default extra=\"ignore\" while the 80 sibling models used extra=\"allow\". This PR fixes the inconsistency and adds a parametrized test to enforce the policy on every future model at collection time.


Strengths

  • Surgical diff. Only the five outlier models are touched; nothing adjacent is disturbed. Exactly right per CLAUDE.md.
  • Drift guard is excellent. test_model_extra_policy.py parametrizes over the live kalshi.models.__all__ so any future model added with the wrong policy fails immediately at pytest collect time — no review required to catch it.
  • CHANGELOG entry is precise. Names the affected classes, explains the old behavior, and links the issue. Easy to parse when debugging a future regression.
  • Error messages in assertions are actionable. They tell you exactly which model is wrong, what value it has, and what it should have — no need to dig into the test to understand a failure.

Issues / Suggestions

1. model_config placement in Page is inconsistent (minor style)

# current (diff)
class Page(BaseModel, Generic[T]):
    items: list[T]
    cursor: str | None = None
    model_config = {"extra": "allow"}   # ← between fields and methods

    @property
    def has_next(self) -> bool:
        ...

In BidAskDistribution and PriceDistribution, model_config sits at the bottom of the field block, which is the existing codebase convention. Page puts it between fields and method definitions, which is a bit surprising in a class this long (it has to_dataframe, to_polars, __iter__, __len__, etc. below). The conventional Pydantic placement is at the top of the class, before field definitions — which also avoids mid-class surprises. Suggest moving it to the top or following the bottom-of-fields convention the other models use.

2. Naming heuristic could misclassify future sub-models (low risk, worth a comment)

_REQUEST_BODY_SUFFIXES = ("Request", "RequestOrder")

def _is_request_body(name: str) -> bool:
    return any(name.endswith(suffix) for suffix in _REQUEST_BODY_SUFFIXES)

This works perfectly for current models, but the convention is implicit. If a future developer adds a request-body sub-model that doesn't follow the *Request / *RequestOrder naming pattern (e.g., BatchOrderItem embedded inside a BatchCreateOrdersRequest), it will be misclassified as a response model and the test will enforce extra=\"allow\" on it — wrong policy for a request sub-model.

Suggest adding a short comment here explaining that the convention is load-bearing:

# Convention (CLAUDE.md §"Adding a new resource"): every model that is part of
# a request body must be named with a *Request or *RequestOrder suffix so this
# heuristic correctly classifies it. Sub-models of request bodies must follow
# the same convention or be added to an explicit exclusion list here.
_REQUEST_BODY_SUFFIXES = ("Request", "RequestOrder")

3. __all__ scope gap is acknowledged but not guarded (informational)

The docstring on _exported_model_classes() notes that models not wired into __all__ silently escape the check. This is acceptable given the "always export" project convention, but if the __all__ entry is ever accidentally omitted for a new model, the policy test gives no signal. No action required — just flagging it as a known blind spot.

4. Test count appears off by one (trivial)

PR description says "+103 from the new parametrized tests" but the audit table shows 85 response + 17 request = 102 parametrized test cases. Either the count is off or there is a 103rd test I am not seeing (possibly collected from a fixture). Negligible, but worth correcting in the PR description.


Verdict

Items 2 and 3 are worth addressing before merge; item 1 is purely cosmetic. Nothing here is a correctness or security issue. The core change is correct, the test is a real improvement, and the CHANGELOG is exemplary.

@TexasCoding TexasCoding merged commit 10879c0 into main May 17, 2026
4 checks passed
@TexasCoding TexasCoding deleted the fix/issue-114-uniform-extra-allow branch May 17, 2026 18:57
TexasCoding added a commit that referenced this pull request May 17, 2026
* 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>
TexasCoding added a commit that referenced this pull request May 19, 2026
… + WS payloads + flip warn\u2192fail (#161, #162, #163) (#168)

* feat(models): backfill v3.18.0 fields on communications + order_groups (#161)

PR3 of the response-side spec-drift hardening stack. 8 new optional
fields across 5 mid-tier response models. Eliminates 8 additive-drift
warning bullets and clears 5 test_additive_drift cases.

Fields added
------------

`RFQ` (+1): creator_subaccount
`Quote` (+3): creator_subaccount, rfq_creator_subaccount, post_only
`OrderGroup` (+1): exchange_index
`GetOrderGroupResponse` (+1): exchange_index
`CreateOrderGroupResponse` (+2): subaccount, exchange_index

Design decisions
----------------

- All new fields are `int | None = None` (or `bool | None` for post_only),
  matching the spec types exactly.
- post_only mirrors CreateQuoteRequest.post_only (already on request side
  from v2.1.0) — server echoes when the caller is the quote creator.
- subaccount/exchange_index on CreateOrderGroupResponse echoes the routing
  context the server resolved.
- All new fields appended at end of each model with `# v3.18.0 backfill
  (#161)` marker. Matches PR1/PR2 pattern.

Tests
-----

3 new test classes in tests/test_models.py (8 tests). Hoisted RFQ/Quote/
OrderGroup/* to module-level imports. Annotated _MINIMAL dict on
TestQuoteV3180Fields as `ClassVar[dict[str, str]]` per ruff RUF012.

Closes #161.

* feat(ws): backfill v0.14+ fields on WebSocket payloads (#162)

PR4 of the response-side spec-drift hardening stack. 33 new optional
fields across 11 WebSocket payload models. Clears all 11
test_ws_additive_drift cases.

Key themes
----------

1. **`*_ts_ms` Unix-ms timestamps** — spec promotes ms integer as the
   primary form (TickerPayload.ts_ms, OrderbookDeltaPayload.ts_ms,
   TradePayload.ts_ms, FillPayload.ts_ms, OrderGroupPayload.ts_ms,
   UserOrdersPayload.{created,last_updated,expiration}_ts_ms). Existing
   `ts: int|str|datetime` fields stay for back-compat. NO auto-conversion
   — surface both so latency-sensitive callers get the int form directly.

2. **Direction encoding** — outcome_side / book_side /
   taker_outcome_side / taker_book_side on FillPayload, TradePayload,
   UserOrdersPayload. Mirrors the REST-side backfill from PR1 (#159).
   Reuses SideLiteral / BookSideLiteral from kalshi.models.orders.

3. **MVE + RFQ context echoes** on Communications payloads —
   mve_collection_ticker, mve_selected_legs, contracts_fp,
   target_cost_dollars, yes_contracts_offered_fp,
   no_contracts_offered_fp, rfq_target_cost_dollars across
   RfqCreatedPayload, RfqDeletedPayload, QuoteCreatedPayload,
   QuoteAcceptedPayload.

Field counts per file
---------------------

ticker.py            +2: price, ts_ms
fill.py              +3: outcome_side, book_side, ts_ms
orderbook_delta.py   +1: ts_ms
trade.py             +3: taker_outcome_side, taker_book_side, ts_ms
user_orders.py       +6: outcome_side, book_side, self_trade_prevention_type,
                         created_ts_ms, last_updated_ts_ms, expiration_ts_ms
market_lifecycle.py  +4: additional_metadata, floor_strike,
                         price_level_structure, yes_sub_title
order_group.py       +1: ts_ms
communications.py   +13: across 4 payload classes (RFQ + Quote echoes)

Design decisions
----------------

- `ts_ms` is plain `int | None`, NOT datetime — spec gives ms since
  epoch as an integer. Do NOT auto-convert.
- `MarketLifecyclePayload.floor_strike` typed as `Decimal | None` (spec:
  `type: number, format: double`) — matches the REST Market.floor_strike
  precedent rather than the issue's `str | None` text (which contradicted
  the spec).
- `additional_metadata` typed as `dict[str, Any] | None` — spec says
  object with subproperties.
- `mve_selected_legs` typed as `list[dict[str, object]] | None` — matches
  CommunicationsMessage.msg's existing `dict[str, object]` pattern,
  avoids the `typing.Any` import.
- WS communications additions use SDK short names with `validation_alias`
  to match the existing per-payload pattern (e.g. `contracts` aliased to
  `contracts_fp`/`contracts`) — keeps sibling consistency.

Tests
-----

One new test class `TestWsV0140Backfill` in tests/ws/test_models.py with
11 tests — one per payload covering at least one new field. Defaults-to-
None paths are already exercised by the existing per-payload `*_minimal`
tests. Added `QuoteCreatedPayload` to the test_models imports.

Closes #162.

* test(infra): promote additive drift to hard-fail; register ErrorPayload; WS envelopes extra=allow (#163)

PR5 of the response-side spec-drift hardening stack. With backfills
from #159#162 landed, additive-drift warnings are at zero. Flip
`warnings.warn(...)` → `pytest.fail(...)` so future spec syncs that
introduce unmodelled fields fail CI instead of slipping through five
rounds of review (the v2.1.0 `Balance.balance_dollars` failure mode).

Also closes two ROADMAP carry-overs:

1. Register `kalshi.ws.models.base.ErrorPayload` in `WS_CONTRACT_MAP`
   (spec name: `errorResponsePayload`). Clears the WS completeness
   warning so it can be promoted.

2. Apply `model_config = {"extra": "allow"}` to every WS envelope and
   helper class (14 classes across 9 files). PR #143 covered payloads;
   this completes the WS surface. Test
   `test_ws_models_use_extra_allow` (new) iterates every BaseModel in
   `kalshi.ws.models.*` and pins the policy.

Changes
-------

tests/test_contracts.py:
  - Flip 3 warning sites to `pytest.fail`:
    - TestSpecDrift.test_additive_drift
    - TestWsSpecDrift.test_ws_additive_drift
    - TestWsSpecDrift.test_ws_contract_map_completeness
  - REST `test_contract_map_completeness` stays warn-only (the issue
    expected it to pass, but 42 pre-existing sub-models / V2 family
    classes / internal containers remain unmapped — out of scope for
    this PR; tracked for a follow-up).
  - Required-drift and required-ws-drift stay as warnings (issue
    explicitly defers as separate ≈204-entry policy decision).
  - Update module docstring to reflect new FAIL semantics.

kalshi/_contract_map.py:
  - Add ContractEntry for ErrorPayload → errorResponsePayload.
  - Drop `ErrorPayload` from the intentionally-excluded comment.

kalshi/ws/models/*.py:
  - base.py: add extra=allow on SubscriptionInfo, ErrorPayload,
    SubscribedMessage, UnsubscribedMessage, ErrorMessage.
  - 8 per-channel files: add extra=allow on every *Message envelope
    plus SelectedMarket helper in multivariate.py.

tests/test_model_extra_policy.py:
  - Add `test_ws_models_use_extra_allow` parameterized across every
    BaseModel discovered in `kalshi.ws.models.*` modules. Pins the
    policy so future model additions that forget the config fail CI.

CHANGELOG.md:
  - New Unreleased section summarizing the full drift hardening stack
    (#159#163).

Verification
------------

uv run pytest tests/ --ignore=tests/integration -q
  -> 2023 passed, 35 warnings (all required-drift, kept warn-only).
     Up from 1965 baseline (+58 new tests: PR3 +8, PR4 +11, PR5 +38
     extra-policy entries + +1 net for hoisted imports/refactors).
uv run mypy kalshi/                            -> Success: no issues
uv run ruff check .                            -> All checks passed!

Smoke-tested both flips:
  - Delete Market.exchange_index locally
    -> test_additive_drift[Market] now FAILS with pytest.fail
       (was a UserWarning). Exit 1, message: "Additive drift in
       kalshi.models.markets.Market: Spec field 'exchange_index' has
       no SDK mapping".
  - Delete ErrorPayload from WS_CONTRACT_MAP locally
    -> test_ws_contract_map_completeness FAILS with pytest.fail.
       Exit 1, message: "WS payload models without contract map
       entries: kalshi.ws.models.base.ErrorPayload".
Both reverted after smoke test.

Closes #163.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Audit response models for consistent extra= policy

1 participant