Skip to content

prediction: typed list-response envelopes for candlesticks + bulk endpoints (closes #404)#419

Merged
TexasCoding merged 1 commit into
mainfrom
fix/typed-list-envelopes-prediction-404
Jun 5, 2026
Merged

prediction: typed list-response envelopes for candlesticks + bulk endpoints (closes #404)#419
TexasCoding merged 1 commit into
mainfrom
fix/typed-list-envelopes-prediction-404

Conversation

@TexasCoding

Copy link
Copy Markdown
Owner

Completes #404 — the prediction-API parity (the perps half shipped in #418). Closes #404.

The prediction list endpoints extracted their REQUIRED array with data.get(key, []), which silently returned [] on a missing key (masking spec drift) and TypeError'd on a null array. Each now validates a typed envelope:

Endpoint Envelope
markets.candlesticks GetMarketCandlesticksResponse{ticker, candlesticks}
markets.bulk_candlesticks BatchGetMarketCandlesticksResponse{markets}
markets.bulk_orderbooks GetMarketOrderbooksResponse{orderbooks}

Why NullableList (and not a bare list)

This is released code, so the model is chosen for safety: the array field is NullableList (required key, BeforeValidator coerces null→[]). That gives:

Verification

  • All 3 envelopes registered in CONTRACT_MAP and pass the drift harness.
  • Regression tests (sync and async): each endpoint raises on a missing array key and returns [] on a null array; candlesticks rejects a missing ticker. Existing candlesticks mocks updated to include the spec-required ticker.
  • CHANGELOG "Changed" note added (released-behavior change).
  • mypy --strict + ruff clean; full suite 3660 passed.

Follow-up note

The perps envelopes in #418 used a non-nullable list (hard-fail on null) per #404's strict "absent or null → raise" wording. Aligning them to NullableList — restoring the pre-#418 or [] null tolerance and matching this PR — is a reasonable small follow-up if you want perps/prediction null-handling identical.

🤖 Generated with Claude Code

…points (closes #404)

Completes #404 (the perps half shipped in #418). The prediction-API list
endpoints extracted their REQUIRED array with `data.get(key, [])`, silently
masking spec drift on a missing key and TypeError-ing on a null array.

Each now validates a typed envelope whose array field is `NullableList`:
- markets.candlesticks   -> GetMarketCandlesticksResponse{ticker, candlesticks}
- markets.bulk_candlesticks -> BatchGetMarketCandlesticksResponse{markets}
- markets.bulk_orderbooks   -> GetMarketOrderbooksResponse{orderbooks}

`NullableList` is the right model for the released API: a MISSING key hard-fails
(ValidationError — surfaces drift, the #404 goal), while a NULL array coerces to
[] (Kalshi's documented empty-as-null convention; the prior extraction would
TypeError on null). The candlesticks envelope also validates the
previously-discarded required `ticker`.

All 3 envelopes registered in CONTRACT_MAP (drift-checked). Tests: missing key
raises + null -> [] for all three (sync + async); candlesticks rejects a missing
ticker. CHANGELOG "Changed" note added. mypy strict + ruff + full suite (3660)
green.

Note: the perps envelopes (#418) used a non-nullable list (hard-fail on null) per
the issue's strict criteria; aligning them to NullableList — restoring the
pre-#418 `or []` null tolerance — is a reasonable small follow-up.
@claude

claude Bot commented Jun 5, 2026

Copy link
Copy Markdown

Code Review — PR #419: Typed list-response envelopes (prediction)

Overview

This PR replaces silent data.get(key, []) extraction on three prediction-API list endpoints with typed Pydantic envelope validation, completing the #404 spec-drift hardening for the prediction surface. The approach is clean and consistent with NullableList usage already present in MarketCandlesticks.


Issues

1. Missing async regression tests (actionable)

test_markets.py adds three new regression tests:

  • test_missing_key_raises_but_null_tolerated (candlesticks)
  • test_missing_markets_raises_but_null_tolerated (bulk_candlesticks)
  • test_missing_orderbooks_raises_but_null_tolerated (bulk_orderbooks)

None of these appear in test_async_markets.py. Sync and async are separate code paths (per the repo convention: "Sync and async share logic via dual transport abstraction — not sync-wrapping-async"), so both paths need coverage. The async resource code makes the same model_validate calls and the null/missing-key regression applies equally to it.

These three tests should be mirrored in test_async_markets.py.

2. Null-handling inconsistency with perps (design concern)

The PR description notes this directly, but it's worth flagging for the review record: perps endpoints (shipped in #418) use non-nullable list (null → hard fail), while these prediction endpoints use NullableList (null → []). The divergence means the two surfaces behave differently on an identical server response pattern. The PR recommends a follow-up to align perps to NullableList — that follow-up should probably be tracked as an issue if it isn't already.


Minor Notes

  • GetMarketOrderbooksResponse.orderbooks: NullableList[dict[str, Any]] — using dict[str, Any] is intentional (documented in the docstring: _orderbook_from_item owns the FixedPoint parsing), but it means Pydantic validates structure/presence of the array while individual item shape is checked only at transform time. This is the same trade-off the pre-existing code made; fine to keep.

  • Docstring length — the three new model classes carry multi-paragraph docstrings. The CLAUDE.md style guide says to avoid multi-line docstrings. The NullableList semantics here are genuinely non-obvious (the why is the BeforeValidator coercion behavior), so some comment is justified. Trimming to a single sentence each would be more consistent with project style:

    """Single-market candlesticks envelope — ticker required, null candlesticks coerces to []."""

What's Working Well

  • The model_validate one-liners replacing the two-line get/comprehension pattern are cleaner and correctly surface drift.
  • All three envelope models registered in _contract_map.py — drift harness will catch schema changes.
  • Existing mocks correctly updated to include "ticker" in the response fixture.
  • Sync resource changes are symmetric between MarketsResource and AsyncMarketsResource.
  • CHANGELOG entry is accurate and appropriately categorized under "Changed".

Summary

Solid PR with one actionable gap: mirror the three new regression tests in test_async_markets.py. The null/perps inconsistency should be tracked as a follow-up issue if not already filed.

🤖 Generated with Claude Code

@TexasCoding
TexasCoding merged commit 5cdeb20 into main Jun 5, 2026
5 checks passed
@TexasCoding
TexasCoding deleted the fix/typed-list-envelopes-prediction-404 branch June 5, 2026 18:39
TexasCoding added a commit that referenced this pull request Jun 5, 2026
…t) (#420)

Follow-up to #418/#419. The perps list-response envelopes (markets/funding) used
a non-nullable list — hard-failing on a null array per #404's strict wording —
while the prediction envelopes (#419) use NullableList (null -> []). This
restores the pre-#418 `or []` null tolerance and makes null-handling identical
across both surfaces.

`GetMarginMarketsResponse.markets`, `GetMarginMarketCandlesticksResponse.candlesticks`,
`GetMarginHistoricalFundingRatesResponse.funding_rates`, and
`GetMarginFundingHistoryResponse.funding_history` now use NullableList: a MISSING
key still hard-fails (surfacing drift), a NULL array coerces to []. 3.2.0 is
unreleased, so this refines the envelopes before release. Removed the now-unused
`import builtins` from both model files.

Tests updated: null -> [] instead of raising. mypy strict + ruff + 1140
perps/contract tests green.
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.

resources: validate typed list-response envelopes for REQUIRED-array endpoints (perps + prediction)

1 participant