Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ Core OpenAPI remains 3.34.0.

### Added

- **`events.get(..., exclude_markets=)`** (sync + async) — optional
boolean query param on `GET /events/{event_ticker}`. When true,
returns event metadata without loading child markets. Use
`GET /markets` with `event_ticker`, `limit`, and `cursor` for
bounded market pages. Cannot be combined with
`with_nested_markets=true` (API returns 400). Default `None` does
not emit the param; explicit `False` serializes as `false`.
(Closes #542).

- Perps **`markets.bulk_orderbooks(*, tickers)`** (sync + async) —
`GET /margin/markets/orderbooks`. Fetches up to 100 margin orderbooks
in one call. `tickers` is required (1–100) and serializes as a
Expand Down
7 changes: 6 additions & 1 deletion docs/resources/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ more markets that share a resolution.
| `list_all(...)` | walks `list` | no |
| `list_multivariate(...)` | `GET /events/multivariate` | no |
| `list_all_multivariate(...)` | walks `list_multivariate` | no |
| `get(event_ticker, *, with_nested_markets=False)` | `GET /events/{event_ticker}` | no |
| `get(event_ticker, *, with_nested_markets=False, exclude_markets=False)` | `GET /events/{event_ticker}` | no |
| `metadata(event_ticker)` | `GET /events/{event_ticker}/metadata` | no |
| `fee_changes(...)` | `GET /events/fee_changes` | no |
| `fee_changes_all(...)` | walks `fee_changes` | no |
Expand Down Expand Up @@ -59,6 +59,11 @@ for market in event.markets:
`with_nested_markets` defaults to `False` — you'll get the event metadata only
unless you opt in.

`exclude_markets=True` returns event metadata without loading child markets
(use `client.markets.list(event_ticker=..., limit=..., cursor=...)` for
bounded market pages). It cannot be combined with `with_nested_markets=True`
(the API returns 400). Default `None` omits the query param.

## Event metadata

```python
Expand Down
4 changes: 4 additions & 0 deletions kalshi/resources/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,12 @@ def get(
event_ticker: str,
*,
with_nested_markets: bool | None = None,
exclude_markets: bool | None = None,
extra_headers: dict[str, str] | None = None,
) -> Event:
params = _params(
with_nested_markets=_bool_param(with_nested_markets),
exclude_markets=_bool_param(exclude_markets),
)
data = self._get(
f"/events/{_seg(event_ticker, name='event_ticker')}",
Expand Down Expand Up @@ -384,10 +386,12 @@ async def get(
event_ticker: str,
*,
with_nested_markets: bool | None = None,
exclude_markets: bool | None = None,
extra_headers: dict[str, str] | None = None,
) -> Event:
params = _params(
with_nested_markets=_bool_param(with_nested_markets),
exclude_markets=_bool_param(exclude_markets),
)
data = await self._get(
f"/events/{_seg(event_ticker, name='event_ticker')}",
Expand Down
7 changes: 7 additions & 0 deletions specs/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,13 @@ paths:
description: Event ticker
schema:
type: string
- name: exclude_markets
in: query
description: Return event metadata without loading child markets. Use GET /markets with event_ticker, limit and cursor for bounded market pages. Cannot be combined with with_nested_markets=true.
required: false
schema:
type: boolean
default: false
- name: with_nested_markets
in: query
required: false
Expand Down
39 changes: 39 additions & 0 deletions tests/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,45 @@ def test_get_with_explicit_false_still_serializes(self, events: EventsResource)
events.get("EVT-1", with_nested_markets=False)
assert route.calls[0].request.url.params["with_nested_markets"] == "false"

@respx.mock
def test_get_exclude_markets_true(self, events: EventsResource) -> None:
route = respx.get("https://test.kalshi.com/trade-api/v2/events/EVT-1").mock(
return_value=httpx.Response(
200,
json={"event": event_dict(event_ticker="EVT-1", title="T", series_ticker="S")},
)
)
events.get("EVT-1", exclude_markets=True)
assert route.calls[0].request.url.params["exclude_markets"] == "true"

@respx.mock
def test_get_exclude_markets_default_does_not_send_param(
self, events: EventsResource
) -> None:
"""Default `exclude_markets=None` MUST NOT emit the query param (#542)."""
route = respx.get("https://test.kalshi.com/trade-api/v2/events/EVT-1").mock(
return_value=httpx.Response(
200,
json={"event": event_dict(event_ticker="EVT-1", title="T", series_ticker="S")},
)
)
events.get("EVT-1")
assert "exclude_markets" not in route.calls[0].request.url.params

@respx.mock
def test_get_exclude_markets_explicit_false_still_serializes(
self, events: EventsResource
) -> None:
"""Explicit `exclude_markets=False` MUST still emit `?exclude_markets=false`."""
route = respx.get("https://test.kalshi.com/trade-api/v2/events/EVT-1").mock(
return_value=httpx.Response(
200,
json={"event": event_dict(event_ticker="EVT-1", title="T", series_ticker="S")},
)
)
events.get("EVT-1", exclude_markets=False)
assert route.calls[0].request.url.params["exclude_markets"] == "false"

@respx.mock
def test_not_found(self, events: EventsResource) -> None:
respx.get("https://test.kalshi.com/trade-api/v2/events/FAKE").mock(
Expand Down
Loading