diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e532315..e18a89e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,31 @@ All notable changes to kalshi-sdk will be documented in this file. +## 3.0.1 — 2026-05-26 + +OpenAPI spec sync from v3.18.0 → v3.19.0 (`#383`). Single additive +request-side constraint on `GET /structured_targets`: the `ids` query +param gained `maxItems: 2000`. Mirrored at the SDK boundary so an +oversize filter fails fast with a clear `ValueError` instead of paying +a network round trip for a 400. No endpoint, channel, or response-model +changes; regenerated `kalshi/_generated/models.py` is byte-identical to +the v3.18.0 output. + +### Changed + +- `StructuredTargetsResource.list` / `list_all` and their async + counterparts now raise `ValueError("ids accepts at most 2000 entries + per spec ...")` when `ids` exceeds 2000 entries. The 2000 boundary + is inclusive (matches spec `maxItems`). Mirrors the existing + `live_data.batch` (`milestone_ids`, max 100) and + `markets.bulk_*` (`tickers`, max 100) precedent. + +### Internal + +- `specs/openapi.yaml` snapshot bumped (sha256 + `5eaeca6bb64b2ff0aa4f63f9e13381da5a8f6d8f9b34328408499a0503a3085d`). +- README + `docs/index.md` banners bumped to "OpenAPI v3.19.0". + ## 3.0.0 — 2026-05-22 Public-API rename release. Three breaking-rename issues (`#348`, `#349`, `#351`) diff --git a/README.md b/README.md index c86af541..ef30949a 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ A professional, spec-first Python SDK for the [Kalshi](https://kalshi.com) predi [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Type checked: mypy strict](https://img.shields.io/badge/mypy-strict-blue.svg)](https://mypy.readthedocs.io/) -- **Full coverage** of the Kalshi REST API (98 operations across 19 resources, OpenAPI v3.18.0) and WebSocket API (11 typed `subscribe_*` channels + 2 escape-hatch). +- **Full coverage** of the Kalshi REST API (98 operations across 19 resources, OpenAPI v3.19.0) and WebSocket API (11 typed `subscribe_*` channels + 2 escape-hatch). - **V2 event-market orders**: `create_v2` / `amend_v2` / `decrease_v2` / `cancel_v2` plus batched variants on `/portfolio/events/orders/*`. Legacy `/portfolio/orders` keeps working — deprecated no earlier than May 6, 2026. - **Funding & cost introspection**: `portfolio.deposits()`, `portfolio.withdrawals()`, `account.endpoint_costs()`. - **Sync and async** clients sharing one transport — no thread-pool wrapping. diff --git a/docs/index.md b/docs/index.md index 58735527..e5e0347f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -3,7 +3,7 @@ A professional, spec-first Python SDK for the [Kalshi](https://kalshi.com) prediction markets API. -- **Full REST coverage** — 98 operations across 19 resources (OpenAPI v3.18.0), +- **Full REST coverage** — 98 operations across 19 resources (OpenAPI v3.19.0), every kwarg drift-tested against the spec. - **V2 event-market orders** — new `create_v2` / `amend_v2` / `decrease_v2` / `cancel_v2` family on `/portfolio/events/orders/*`. Legacy `/portfolio/orders` diff --git a/kalshi/__init__.py b/kalshi/__init__.py index ad42e438..e24b5e16 100644 --- a/kalshi/__init__.py +++ b/kalshi/__init__.py @@ -351,4 +351,4 @@ "Withdrawal", ] -__version__ = "2.6.0" +__version__ = "3.0.1" diff --git a/kalshi/resources/structured_targets.py b/kalshi/resources/structured_targets.py index 1febb760..3d0dec38 100644 --- a/kalshi/resources/structured_targets.py +++ b/kalshi/resources/structured_targets.py @@ -19,6 +19,18 @@ _validate_max_pages, ) +# Spec v3.19.0 added ``maxItems: 2000`` to the ``ids`` query param on +# ``GET /structured_targets``. Mirror it at the SDK boundary so an oversize +# filter fails fast with a clear ``ValueError`` instead of a 400 round trip. +_MAX_IDS = 2000 + + +def _validate_ids(ids: builtins.list[str] | None) -> None: + if ids is not None and len(ids) > _MAX_IDS: + raise ValueError( + f"ids accepts at most {_MAX_IDS} entries per spec (got {len(ids)})" + ) + class StructuredTargetsResource(SyncResource): """Sync structured targets API. @@ -39,6 +51,7 @@ def list( cursor: str | None = None, extra_headers: dict[str, str] | None = None, ) -> Page[StructuredTarget]: + _validate_ids(ids) _validate_limit(page_size, hi=2000, name="page_size") params = _params( ids=ids, @@ -66,6 +79,7 @@ def list_all( extra_headers: dict[str, str] | None = None, ) -> Iterator[StructuredTarget]: _validate_max_pages(max_pages) + _validate_ids(ids) _validate_limit(page_size, hi=2000, name="page_size") params = _params( ids=ids, @@ -105,6 +119,7 @@ async def list( cursor: str | None = None, extra_headers: dict[str, str] | None = None, ) -> Page[StructuredTarget]: + _validate_ids(ids) _validate_limit(page_size, hi=2000, name="page_size") params = _params( ids=ids, @@ -133,6 +148,7 @@ def list_all( ) -> AsyncIterator[StructuredTarget]: """Returns an async iterator — use ``async for``.""" _validate_max_pages(max_pages) + _validate_ids(ids) _validate_limit(page_size, hi=2000, name="page_size") params = _params( ids=ids, diff --git a/pyproject.toml b/pyproject.toml index 1855952e..ba457b23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "kalshi-sdk" -version = "3.0.0" +version = "3.0.1" description = "A professional Python SDK for the Kalshi prediction markets API" readme = "README.md" license = { text = "MIT" } diff --git a/specs/openapi.yaml b/specs/openapi.yaml index 12cbf60b..96499407 100644 --- a/specs/openapi.yaml +++ b/specs/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: Kalshi Trade API Manual Endpoints - version: 3.18.0 + version: 3.19.0 description: >- Manually defined OpenAPI spec for endpoints being migrated to spec-first approach @@ -2909,6 +2909,7 @@ paths: required: false schema: type: array + maxItems: 2000 items: type: string style: form diff --git a/tests/test_structured_targets.py b/tests/test_structured_targets.py index 27fdaede..146979eb 100644 --- a/tests/test_structured_targets.py +++ b/tests/test_structured_targets.py @@ -226,3 +226,46 @@ def test_type_alias_round_trip(self) -> None: {"target_type": "basketball_player"}, ) assert from_sdk.target_type == "basketball_player" + +class TestIdsCap: + """Spec v3.19.0 added ``maxItems: 2000`` on the ``ids`` query param. + + The SDK mirrors this client-side so callers see a clear ``ValueError`` + instead of paying a network round trip for a 400. + """ + + def test_list_rejects_oversize_ids( + self, resource: StructuredTargetsResource, + ) -> None: + with pytest.raises(ValueError, match=r"at most 2000.*got 2001"): + resource.list(ids=[f"uuid-{i}" for i in range(2001)]) + + def test_list_all_rejects_oversize_ids( + self, resource: StructuredTargetsResource, + ) -> None: + with pytest.raises(ValueError, match=r"at most 2000.*got 2001"): + resource.list_all(ids=[f"uuid-{i}" for i in range(2001)]) + + @respx.mock + def test_list_allows_boundary(self, resource: StructuredTargetsResource) -> None: + respx.get("https://test.kalshi.com/trade-api/v2/structured_targets").mock( + return_value=httpx.Response(200, json={"structured_targets": []}) + ) + # 2000 is the spec cap — boundary must succeed. + page = resource.list(ids=[f"uuid-{i}" for i in range(2000)]) + assert page.items == [] + + @pytest.mark.asyncio + async def test_async_list_rejects_oversize_ids( + self, async_resource: AsyncStructuredTargetsResource, + ) -> None: + with pytest.raises(ValueError, match=r"at most 2000.*got 2001"): + await async_resource.list(ids=[f"uuid-{i}" for i in range(2001)]) + + def test_async_list_all_rejects_oversize_ids( + self, async_resource: AsyncStructuredTargetsResource, + ) -> None: + # ``list_all`` is a plain ``def`` returning an AsyncIterator so the + # validator fires synchronously at call time, not on first iteration. + with pytest.raises(ValueError, match=r"at most 2000.*got 2001"): + async_resource.list_all(ids=[f"uuid-{i}" for i in range(2001)])