Repository navigation
feat(resources): V1 batch typed responses (breaking); URL-encode paths; client-side limit validation - #229
Conversation
…s; client-side limit validation BREAKING: orders.batch_create now returns BatchCreateOrdersResponse (was list[Order]); orders.batch_cancel now returns BatchCancelOrdersResponse (was None). Mirrors V2 shape. Fixes partial-success crash and exposes per-leg reduced_by_fp. - Add _seg() helper: URL-encodes user-supplied path segments, rejects empty/whitespace and '.'/'..'. - Add _validate_limit() helper: enforces spec-correct bounds per endpoint. - Sweep applied across all resources (~20 path-segment sites, ~25 limit kwargs). Closes #194, Closes #211, Closes #214 Refs #224
Code Review — PR #229Wave 1-B audit hardening: batch typed responses, URL path encoding, limit validation Overall this is a solid, well-scoped PR. The three fixes each have clear motivation, the implementation is clean, and test coverage is genuinely thorough. A few items below warrant attention before merge. Issues1. Inconsistent Some callers rebind the result, others discard it: # Pattern A — rebind (orders.py, markets.py, historical.py, etc.)
limit = _validate_limit(limit, hi=1000)
return _params(limit=limit, ...)
# Pattern B — discard (incentive_programs.py, milestones.py, portfolio.py, subaccounts.py, structured_targets.py)
_validate_limit(limit, hi=10000)
return _params(limit=limit, ...) # limit unchanged either wayBoth are functionally correct today (the function raises on violation and returns the value unchanged on success), but Pattern B looks like the return value was accidentally ignored. A future maintainer who refactors 2. Async The sync 3. No CHANGELOG.md update visible in the diff The project updates 4.
5. The encoding is sound: Nits
What is done well
|
Hard-fail test_contract_map_completeness requires every Pydantic class in kalshi.models.* to have a ContractEntry. The four new V1 batch response models (added per #194) were missing entries.
Code Review — PR #229: Batch typed responses, URL-encoded paths, client-side limit validationOverall this is a well-structured PR. Three focused changes, each tied to a tracked issue, with tests for every new code path. Some observations below, ranging from one real concern to a handful of nits. Issue 1 —
|
…ated, event_ticker list helper (#290) * feat(multivariate): @deprecated decorators + lookback_seconds enum validation The spec marks GET /multivariate_event_collections/{ticker}/lookup, PUT .../lookup, and POST .../{ticker} (create_market) as 'deprecated: true' with the message 'This endpoint predates RFQs and should not be used for new integrations.' Decorate the three sync + three async methods with typing_extensions.deprecated so callers see a DeprecationWarning at runtime and a type-checker hint (PEP 702). Also reject lookback_seconds outside the spec enum {10, 60, 300, 3600} client-side via _validate_lookback_seconds — server returns 400 for any other value; failing before the GET mirrors the limit-bounds ergonomic fix landed in #229. Adds typing-extensions>=4.5 dep (3.12 lacks typing.deprecated which is 3.13+ only). Adds filterwarnings to silence the new DeprecationWarning in existing tests that intentionally exercise these endpoints; user callers still see it. Bundled in this commit: drop the stale '{event_ticker} but spec uses {ticker}' NOTE in tests/_contract_support.py — the SeriesResource rename it forecast is long done. Items 2, 3, 5 of #269 polish bundle. * feat(portfolio,fcm): positions_all() iterator for both resources Every other cursor-paginated endpoint on PortfolioResource (settlements, deposits, withdrawals) and FcmResource (orders) ships a *_all() iterator that wraps the cursor loop and exposes max_pages as a safety cap. positions() did not, so callers walking the full book had to hand-roll the loop and re-derive the _validate_max_pages guard. Add positions_all() to PortfolioResource + AsyncPortfolioResource and FcmResource + AsyncFcmResource, yielding MarketPosition. Reuses the shared _list_all paginator with 'market_positions' as the items key. EventPosition aggregates from the response envelope are intentionally not surfaced — see docstring rationale (page boundaries split aggregates arbitrarily so concatenating across pages would not reconstruct a meaningful event-level total). Register the four new methods in tests/_contract_support.py (MethodEndpointEntry + cursor Exclusion + _MAX_PAGES_FQNS) so the contract-drift suite covers them. Item 1 of #269 polish bundle. * feat(orders): widen event_ticker to list[str] | str | None + spec max-10 guard GET /portfolio/orders is the one route whose event_ticker param refs MultipleEventTickerQuery in the spec (openapi.yaml:3961-3967): comma- joined string, server max 10. The SDK accepted only str, forcing callers either to hand-join the list (and forget the max-10 cap until the server 400's) or to fall back to client-side filtering. Widen OrdersResource.list / list_all + AsyncOrdersResource.list / list_all kwarg to list[str] | str | None and run it through _join_tickers(..., max_items=10). _join_tickers grows a keyword-only max_items cap that rejects oversize lists before the wire (pre-joined strings opt out — caller owns wire format). Not widened on HistoricalResource.markets or FcmResource.orders despite the issue suggesting otherwise: their spec refs are SingleEventTickerQuery (openapi.yaml:3769, :3448) — passing multiple is undefined behaviour server-side. Item 4 stays narrow to /portfolio/orders. Item 4 of #269 polish bundle. Closes #269
Wave 1-B of the audit umbrella. 3 issues; resources API hardening. Contains a breaking change (#194) — folded into v2.4.0 per maintainer decision.
Changes
orders.batch_createnow returnsBatchCreateOrdersResponse(waslist[Order]);orders.batch_cancelnow returnsBatchCancelOrdersResponse(wasNone). Mirrors V2 shape. Fixes partial-success crash and exposes per-legreduced_by_fp._seg()helper URL-encodes user-supplied path segments and rejects empty / whitespace /./... Applied to ~40 sites across all resources._validate_limit()helper enforces spec-correct bounds per endpoint. Applied with ranges sourced fromspecs/openapi.yaml.Tests
8 new test classes; 658 tests pass across the in-scope test files; ruff + mypy --strict clean across 22 changed files.
Closes #194, Closes #211, Closes #214
Refs #224