Skip to content

docs+polish: env-vars NameError, stale docs, limits default, WS perf, cancel idempotency, events default; populate_by_name on WS; AwareDatetime on REST - #234

Merged
TexasCoding merged 1 commit into
mainfrom
audit/w2c-polish-docs
May 21, 2026
Merged

TexasCoding merged 1 commit into
mainfrom
audit/w2c-polish-docs

Conversation

@TexasCoding

Copy link
Copy Markdown
Owner

Wave 2-C of audit umbrella. 7 P3 docs items + 2 P2 model items.

Changes

Closes #222
Refs #221 (W2-A lands items 1/2/5)
Refs #224

@claude

claude Bot commented May 21, 2026

Copy link
Copy Markdown

Code Review — PR #234 (Wave 2-C: docs polish + AwareDatetime + populate_by_name)

Overall this is a clean, well-scoped PR. The three code changes (events default, populate_by_name, AwareDatetime) are all correct. A few things worth calling out before merge.


Issues

1. Missing async test coverage for events.get default change

Two new tests were added to TestEventsGet (sync) but neither has an async counterpart in TestAsyncEventsGet:

  • test_get_with_default_does_not_send_with_nested_markets_param
  • test_get_with_explicit_false_still_serializes

Both the sync and async get() methods were changed (resources/events.py lines 155 and 258). The existing TestAsyncEventsGet class covers the with_nested_markets=True case — missing the None/False variants means the async path isn't regression-covered.

2. AwareDatetime is a subtle semi-breaking change — missing CHANGELOG entry

Any caller who constructs a REST response model directly with a naive datetime (e.g., in tests, fixtures, or application code that builds an Order(created_time=datetime.now()...)) will now get a ValidationError at runtime. The API wire path is unaffected (Kalshi always returns RFC3339+Z), but direct construction is a real use case.

This isn't necessarily wrong to ship, but it should be in CHANGELOG.md under a "Breaking" or "Changed" note since it's a validation tightening across 12 public models.

3. WeeklySchedule.start_time / end_time — verify API wire format

exchange.py's WeeklySchedule has start_time: AwareDatetime and end_time: AwareDatetime. The field names suggest these might encode time-of-day (e.g., trading hours) rather than full timestamps. If the Kalshi API ever returns a bare time string like "09:30:00" or a date-only string for these fields, Pydantic will raise ValidationError. Worth a quick spot-check against the live /exchange/schedule response or the OpenAPI spec.


Minor nits

from pydantic import ValidationError inside test methods (tests/test_models.py lines 506, 537 in the new block)

This matches the existing pattern in the file (same import style at lines 813, 690), so it's internally consistent — just noting it's unusual. Not a blocker.

Test fixture parametrization coverage for AwareDatetime

TestAwareDatetimeRejectsNaive only parametrizes over Order, Market, and Settlement. The change touches 12 modules. The three models chosen do demonstrate the principle, but communications, exchange, and historical models (which have the most timestamp-heavy payloads) aren't exercised. A light pass over the other fixture builders could catch a broken fixture_builder call before they surface in CI.


What's good

  • Docs: The env-vars dataclasses.replace fix is correct and the rewrite to KalshiConfig.demo(timeout=..., max_retries=5) is idiomatic. The idempotency callout blocks across four resource docs follow a consistent pattern and are accurate.
  • events.get default: Changing bool = False to bool | None = None so the param is omitted by default is the right call — sending ?with_nested_markets=false on every call was noise. The two existing sync tests (None omits, False serializes) are well-targeted.
  • populate_by_name=True: Correct fix for all WS payload models that use AliasChoices. Excluding orderbook_delta.py per W2-A scope is the right boundary.
  • WS Performance section: Actionable and technically accurate. The queue-sizing rule, overflow strategy table, and recv-loop threading notes are all correct. The orjson.dumps → bytes note is accurate.
  • Deleting AUDIT-resource-params.md: Right call to remove working-session artifacts from the published docs tree.

Summary

Two things to address before merge:

  1. Add async variants of the two new events.get tests to TestAsyncEventsGet.
  2. Add a CHANGELOG entry noting the AwareDatetime validation tightening as a breaking change for direct model construction (not for API parsing).

WeeklySchedule is worth a quick spec-check but is unlikely to be an issue in practice.

… default, WS perf section, cancel idempotency, events default; populate_by_name on WS models; AwareDatetime on REST

- docs/environment-variables.md: switch retry example to KalshiConfig.demo(timeout=...) so the snippet runs without an extra import.

- Remove stale docs/AUDIT-resource-params.md (v0.7.0-era) and drop from mkdocs not_in_nav.

- docs/migration.md: drop incorrect 'predecessor referenced in project README' clause.

- docs/configuration.md: limits default = None; add Performance cross-link to docs/websockets.md.

- docs/websockets.md: new Performance section covering msg rates, queue sizing, overflow strategy, orderbook_delta CPU cost, orjson loader (matches w2-a ws_json_loads/ws_json_dumps), single-threaded recv impl.

- docs/resources/{orders,communications,api-keys,order-groups}.md cancel/delete methods: document 404 idempotency contract with KalshiNotFoundError example.

- docs/concepts.md: new 'Datetime semantics' section explaining AwareDatetime on REST and bare datetime on WS.

- events.get (sync + async): default with_nested_markets=None (was False) so default calls no longer emit ?with_nested_markets=false.

- All kalshi/ws/models/* (except orderbook_delta.py, W2-A's scope): add populate_by_name=True to model_config so SDK-name kwargs work.

- kalshi/models/* (communications, events, exchange, historical, incentive_programs, markets, milestones, multivariate, orders, portfolio, series, structured_targets): datetime fields -> pydantic.AwareDatetime; rejects naive datetimes at construction.

Closes #222

Refs #221 (P2 items 3/4; W2-A finishes items 1/2/5)

Refs #224
@TexasCoding
TexasCoding force-pushed the audit/w2c-polish-docs branch from fc00aec to 4555610 Compare May 21, 2026 03:41
@claude

claude Bot commented May 21, 2026

Copy link
Copy Markdown

Code Review — PR #234: Wave 2-C docs/polish + AwareDatetime + populate_by_name

Overview

Wave 2-C of the audit umbrella (#224). Three bundled categories:

  1. P3 docs polish — NameError fix in env-vars example, stale audit doc deletion, limits default correction, WS performance guide, idempotency notes on four resource docs, concepts.md datetime semantics section.
  2. P2.3 WS model config — populate_by_name=True added to all WS payload model_config dicts.
  3. P2.4 datetime hardening — datetime → pydantic.AwareDatetime across all REST response model timestamp fields (12 modules).

Overall the PR is well-scoped and the intent is clear. A few issues below, two of which are worth addressing before merge.


Issues

🔴 Discrepancy: orderbook_delta.py is in the diff despite being explicitly excluded

The PR description states: populate_by_name=True added "except orderbook_delta.py — W2-A's scope." But the diff shows all four model_config dicts in kalshi/ws/models/orderbook_delta.py getting the flag. This is either:

  • A no-op if W2-A already landed it (safe, but the PR description is wrong), or
  • An unintentional addition if W2-A didn't (scope creep from W2-C's stated bounds).

Please verify and update the PR description accordingly.

🔴 Breaking change: AwareDatetime migration needs a CHANGELOG entry

The P2.4 section describes this as a model hardening item, but it is a breaking change for any user who constructs SDK models directly (e.g., in their own tests or application code). Before this PR:

Order(... created_time=datetime.now())   # worked

After:

Order(... created_time=datetime.now())   # ValidationError

This needs to appear in CHANGELOG.md with a migration note. The docs/concepts.md addition is good context but won't be seen during pip install --upgrade. Suggested placement: under a "Breaking changes" header in the current in-progress section, with the one-line fix (datetime.now(UTC) or datetime.now().replace(tzinfo=UTC)).

🟡 Behavior change: events.get() default also needs a CHANGELOG note

Changing with_nested_markets: bool = False → bool | None = None is behaviorally correct (let the server default apply rather than sending an explicit false), but it's observable: callers who previously got ?with_nested_markets=false on every request will now get no param. The tests correctly pin the new contract. A one-line entry in CHANGELOG.md under "Changes" is sufficient.

🟡 Missing async tests for the events.get() default change

The two new tests (test_get_with_default_does_not_send_with_nested_markets_param, test_get_with_explicit_false_still_serializes) only cover the sync EventsResource.get() path. AsyncEventsResource.get() has the same signature change and should have symmetric coverage.

🟡 WeeklySchedule.start_time / end_time as AwareDatetime — verify wire format

WeeklySchedule in exchange.py uses start_time and end_time, which are semantically time-of-day fields. AwareDatetime requires a full ISO-8601 datetime with timezone. If the Kalshi API returns a bare time string ("08:00:00Z") without a date component, Pydantic will reject it. Worth checking the live /exchange/schedule response before shipping — or at least adding a contract test fixture that reproduces what the server actually returns.

🟡 Test coverage gap for AwareDatetime — fixture builders in other test files

TestAwareDatetimeRejectsNaive validates the constraint against 3 models. More importantly: check whether any existing fixture builders in tests/_model_fixtures.py pass naive datetimes into the affected models. If they do, those tests will now silently pass a naive value and fail model construction. A quick grep -r "datetime(" tests/ for calls without tzinfo= or UTC would confirm whether the full suite is clean.


Positives

  • NameError fix is clean: replacing the two-step KalshiConfig.demo() / dataclasses.replace(...) pattern with direct constructor kwargs removes the dataclasses import and makes the example copy-paste correct.
  • WS Performance section is excellent — concrete message-rate table, the maxsize >= 2 × peak_burst_per_second rule of thumb, the DROP_OLDEST vs ERROR strategy rationale, and the on_error foot-gun warning are all immediately actionable. This will prevent real incidents.
  • Idempotency notes on orders/communications/api-keys/order-groups are exactly the kind of "gotcha" documentation that prevents production support tickets.
  • _SDK_NAME_PAYLOADS parametrize pattern for the WS populate_by_name tests is clean and easy to extend when new payload models are added.
  • with_nested_markets test pair — testing both "default omits param" and "explicit False still serializes" pins the behavioral contract precisely.
  • AwareDatetime tests — testing the positive case (RFC3339+Z parses and round-trips to UTC) alongside the negative case (naive raises ValidationError) is the right structure.

Summary

Two items to fix before merge: the orderbook_delta.py scope discrepancy and a CHANGELOG entry for the AwareDatetime breaking change. The async test gap and WeeklySchedule wire-format verification are the next priority. Everything else is polish.

@TexasCoding
TexasCoding merged commit 6fef3ff into main May 21, 2026
5 checks passed
@TexasCoding
TexasCoding deleted the audit/w2c-polish-docs branch May 21, 2026 10:19
TexasCoding added a commit that referenced this pull request May 21, 2026
…sTimestamp alias, Decimal NaN reject (#292)

* fix(ws): AwareDatetime in user_orders/communications payloads; document market_lifecycle seconds unit

WS payload models predated #234's REST AwareDatetime rollout, so they
accepted naive RFC3339 strings that REST callers couldn't. Align the
two by retyping the datetime fields on UserOrdersPayload (4 fields)
and the communications payloads (5 fields) to pydantic.AwareDatetime —
a string without an offset now raises ValidationError at parse rather
than silently producing a tz-unaware datetime that breaks arithmetic
against tz-aware peers in MarketPosition/Settlement.

MarketLifecyclePayload's *_ts fields are intentionally left as bare
int. The AsyncAPI spec example payload uses Unix **seconds** (e.g.
open_ts: 1694635200, 2023-09-13), not ms — so the lack of the _ts_ms
suffix carried by sibling channels is correct. Added a comment so
readers stop wondering.

* fix(models): V1 CreateOrderRequest Literals; document required-nullable response fields; UnixSecondsTimestamp alias

- V1 CreateOrderRequest tightens side/action/time_in_force/self_trade_prevention_type
  from bare str to the same Literal aliases V2 uses (SideLiteral, ActionLiteral,
  TimeInForceLiteral, SelfTradePreventionTypeLiteral). V1 keeps its yes/no side
  vocabulary (V2 is bid/ask). Caller passing side='foo' now fails at construction,
  matching V2 — closes the deprecation-window drift before V1 sunset (~May 2026).

- portfolio.MarketPosition / historical.Trade keep total_traded, position,
  count as 'T | None' without default=None. The OpenAPI spec marks the
  underlying wire keys (total_traded_dollars, position_fp, count_fp) as
  **required**, so omission must still raise ValidationError — only the
  observed JSON null is admitted. Added docstring notes explaining the
  intentional non-default so the audit doesn't re-flag them.

- types.UnixSecondsTimestamp: Annotated[int, …] alias for int64 Unix-seconds
  wire timestamps. Internal type-aid only (not re-exported via kalshi.__init__).
  Applied to Balance.updated_ts, Deposit.created_ts/finalized_ts,
  Withdrawal.created_ts/finalized_ts so the int-vs-AwareDatetime split is
  greppable when balances/positions are read side-by-side.

- Drift checker (tests/test_contracts.py) now classifies pydantic
  AwareDatetime/NaiveDatetime and ForwardRef('datetime') as kind='datetime',
  matching what those models actually validate at runtime.

* fix(types): reject non-finite Decimal at parse/serialize boundary

`f"{Decimal('NaN'):f}"` returns the literal string `"NaN"`, and
`_coerce_decimal` previously short-circuited on the `isinstance(value,
Decimal)` branch — a caller doing arithmetic that produced NaN and then
constructing a `CreateOrderRequest(yes_price=that_nan)` would ship
`"NaN"` straight to a real-money order placement endpoint.

`_coerce_decimal` now computes the Decimal first, then `is_finite()`-
guards it, raising `ValueError` on NaN/+Inf/-Inf. One branch protects
every DollarDecimal / FixedPointCount / MultiplierDecimal field at both
parse and serialize.

Closes #270
TexasCoding added a commit that referenced this pull request May 22, 2026
…e Literal (#331, #353) (#365)

`OrderbookDeltaPayload.ts` was missed by the v2.5 #270 WS AwareDatetime
sweep that closed the v2.4 #234 REST gap. Widen it to `AwareDatetime |
None` so naive RFC3339 strings on the highest-volume WS channel raise
`ValidationError` rather than silently passing as `str`, matching
`user_orders` and `communications`.

Narrow every WS message envelope's `type` field from bare `str` to its
channel-specific `Literal[...]` across `OrderbookSnapshotMessage`,
`OrderbookDeltaMessage`, `TickerMessage`, `FillMessage`,
`UserOrdersMessage`, `MarketLifecycleMessage`, and
`CommunicationsMessage`. The default-value form keeps no-arg
construction ergonomics but constructing an envelope with a mismatched
`type` string now raises `ValidationError`. This also unlocks
`pydantic.Discriminator("type")` for the dispatcher's union without a
follow-up wire-format change.

Five `tests/ws/test_models.py` fixtures sent `"type": "user_orders"`
(plural, channel name) where the wire `type` value per the dispatcher
table is `"user_order"` (singular). Those fixtures are corrected to
match the dispatch table that real frames go through.

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

Labels

None yet

Projects

None yet

1 participant