From f31a971d5dd54a6ba6c1702f90bb33174ced3343 Mon Sep 17 00:00:00 2001 From: Jeff West Date: Wed, 20 May 2026 18:49:17 -0500 Subject: [PATCH 1/5] =?UTF-8?q?docs:=20pre-release=20audit=20sweep=20?= =?UTF-8?q?=E2=80=94=20accuracy,=20gaps,=20and=20staleness=20fixes=20(#179?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #179. Pre-release docs audit before cutting the next release. Findings compiled from a six-way parallel agent audit of disjoint doc-file partitions, triaged, ground-truthed against the actual code/spec, and applied: ROADMAP.md (#179 core): * "Open trackers" section dropped. #45 (json={} workaround) and #53 (nested $ref drift) are both closed; #106 was a PR (not an issue) whose remaining sub-items all shipped post-v2.2.0. * "Next milestone" carry-overs that landed (MessageQueue maxlen via #173, _coerce_decimal via #174, WS UX foot-guns via #175/#176/#177, CONTRACT_MAP completeness via #181) removed. * Added an "Unreleased (post-v2.2.0)" bullet at the top of "Shipped" pointing at #173–#178 and #183. docs/resources/multivariate.md: * Quick-reference table claimed lookup_history hits `GET /multivariate_event_collections/{ticker}/lookup_history`. Actual code (kalshi/resources/multivariate.py:233) uses `/lookup` with a `lookback_seconds` query param. Table updated. * "Recent lookup history" example called `hist.lookups` on the return value, but lookup_history returns `list[LookupPoint]` directly. Example now iterates the list and prints real LookupPoint fields. docs/index.md: * "85 endpoints" claim corrected to "98 operations" against current specs/openapi.yaml. * Sync/async parity bullet now explicitly notes the one exception: WebSocket access is async-only (via AsyncKalshiClient.ws). * Reconnection bullet mentions the new resubscribe-window stash. docs/websockets.md: * New "Resubscribe-window frame stashing" subsection under Reconnection, documenting the #176 mechanism, stash_maxlen bound, per-(sid, cycle) WARNING dedup, and the per-sub-failure drop case. CHANGELOG had claimed docs were updated for #176; they weren't. docs/authentication.md: * New "Async RSA-PSS sign offload" subsection documenting KalshiAuth.sign_request_async() (#178), the dedicated 2-worker ThreadPoolExecutor lifecycle, why it's separate from asyncio's default pool, and the close() chain through KalshiClient. docs/configuration.md: * New "Lifecycle" section explaining KalshiClient.close() semantics — tears down both the httpx client and the sign executor — with a cross-link to the new authentication.md subsection. docs/resources/events.md: * Note documenting the #183 server-omission handling: Event.product_metadata typed `dict[str, Any] | None`, and EventMetadata.market_details typed `NullableList[MarketMetadata]`. Cross-references the EXCLUSIONS map / server_omits_despite_required. README.md: * WS quickstart now uses the package-level `from kalshi.ws import KalshiWebSocket` instead of the deeper `kalshi.ws.client` path (matches docs/websockets.md style). * Channel list clarifies that 11 of the 13 channels have dedicated subscribe_* methods; the remaining two (control_frames, root) are reachable through the generic subscribe(channel) escape hatch. Findings not actioned (already-correct text or low-value style): * docs/environment-variables.md precedence — current wording was already accurate; agent #14 misread. * docs/configuration.md retry_base_delay/retry_max_delay table rows — already present at lines 37–38; agent #14 misread. * docs/types.md MoneyAmount — does not exist; was a phantom in my briefing. * docs/resources/orders.md batch_cancel positional vs kwarg — current positional call is correct; style nit only. * mkdocs.yml not_in_nav — is standard MkDocs config, agent was wrong. * Various low-severity wording tweaks (callback comments, float warnings, migration heading clarifications) — scope control. Verification: `mkdocs build --strict` clean (zero warnings on internal links or missing pages). `ruff check .` clean. `mypy kalshi/` strict clean. Pure-docs change, no code touched per `git diff --stat`. --- CHANGELOG.md | 34 ++++++++++++++++++++++++ README.md | 12 +++++---- ROADMAP.md | 47 ++++++++++++++++------------------ docs/authentication.md | 24 +++++++++++++++++ docs/configuration.md | 17 ++++++++++++ docs/index.md | 8 +++--- docs/resources/events.md | 13 ++++++++++ docs/resources/multivariate.md | 8 +++--- docs/websockets.md | 24 +++++++++++++++++ 9 files changed, 150 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2329e1b..851dcc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,40 @@ All notable changes to kalshi-sdk will be documented in this file. ## Unreleased +### Pre-release docs audit (#179) + +Release-prep sweep across all doc surfaces — README, mkdocs site, ROADMAP, +per-resource pages, and public-API docstrings. Findings compiled from a +six-way parallel audit of disjoint file partitions, then triaged: + +- **`ROADMAP.md`** — "Open trackers" section dropped; `#45`, `#53` are + closed and `#106` was a PR (not an issue) whose remaining sub-items all + shipped in this batch. "Next milestone" carry-overs that landed + (`MessageQueue` `maxlen`, `_coerce_decimal`, WS UX foot-guns, + CONTRACT_MAP completeness via `#181`) removed. Closes `#179`. +- **`docs/resources/multivariate.md`** — fixed wrong endpoint path on + `lookup_history` (was `/lookup_history`, actual is `/lookup` with a + `lookback_seconds` query param) and a broken example that called + `hist.lookups` on a list return. +- **`docs/index.md`** — REST coverage updated from "85 endpoints" to "98 + operations" against current spec; sync/async parity claim explicitly + notes WebSocket is async-only. +- **`docs/websockets.md`** — new "Resubscribe-window frame stashing" + subsection documenting the `#176` mechanism, `stash_maxlen` bound, and + overflow logging. +- **`docs/authentication.md`** — new "Async RSA-PSS sign offload" + subsection documenting `KalshiAuth.sign_request_async()` (`#178`) plus + the dedicated `ThreadPoolExecutor` lifecycle. +- **`docs/configuration.md`** — new "Lifecycle" section documenting + `client.close()` semantics and cross-linking the sign-executor teardown. +- **`docs/resources/events.md`** — note documenting `Event.product_metadata` + and `EventMetadata.market_details` server-omission handling from `#183`. +- **`README.md`** — WS quickstart uses the package-level import + (`from kalshi.ws import KalshiWebSocket`) instead of the deeper + `kalshi.ws.client`; channel list clarifies that 11 of the 13 channels + have dedicated `subscribe_*` methods and the remaining two ride the + generic `subscribe()` escape hatch. + ### WS resubscribe-window frame stashing (#176) Fixes silent message loss during reconnect bursts on high-volume channels. diff --git a/README.md b/README.md index 237954b..8488ce1 100644 --- a/README.md +++ b/README.md @@ -176,7 +176,7 @@ request model. See [V2 orders docs](https://texascoding.github.io/kalshi-python- ```python import asyncio from kalshi import KalshiAuth, KalshiConfig -from kalshi.ws.client import KalshiWebSocket +from kalshi.ws import KalshiWebSocket async def main() -> None: auth = KalshiAuth.from_key_path("your-key-id", "~/.kalshi/private_key.pem") @@ -191,10 +191,12 @@ async def main() -> None: asyncio.run(main()) ``` -Available channels (13): `ticker`, `trade`, `orderbook_delta`, `fill`, -`market_positions`, `user_orders`, `order_group_updates`, -`market_lifecycle_v2`, `multivariate`, `multivariate_market_lifecycle`, -`communications`, `control_frames`, `root`. +Available channels (13): 11 have dedicated `subscribe_*` methods — `ticker`, +`trade`, `orderbook_delta`, `fill`, `market_positions`, `user_orders`, +`order_group_updates`, `market_lifecycle_v2`, `multivariate`, +`multivariate_market_lifecycle`, `communications`. The remaining two +(`control_frames`, `root`) are reachable through the generic +`subscribe(channel, ...)` escape hatch. ## Error handling diff --git a/ROADMAP.md b/ROADMAP.md index 8c99886..26d0621 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -4,6 +4,17 @@ See `CHANGELOG.md` for full release history. +- **Unreleased (post-v2.2.0)** — WS reliability + auth polish batch: + WS resubscribe-window frame stashing (`#176`), `run_forever(stop_event=...)` + cooperative shutdown (`#177`), `run_forever()` raises on missing subscription + instead of silently returning (`#175`), `MessageQueue` `maxlen` defense-in-depth + (`#173`), `_to_decimal_*` consolidation into `_coerce_decimal` (`#174`), + async RSA-PSS sign offload via dedicated `ThreadPoolExecutor` (`#178`), + first two `server_omits_despite_required` exclusions for + `Event.product_metadata` and `EventMetadata.market_details` (`#183`). All + closed the open items previously tracked under #106's "Wave 5 polish backlog" + umbrella; nothing remains. + - **v2.2.0 (2026-05-19)** — response-side spec drift hardening (`#157`). 65 new optional fields backfilled across 16 REST + WS response models for OpenAPI v3.18.0 / AsyncAPI v0.14 (`Market`, `Order`, `Fill`, @@ -44,38 +55,24 @@ See `CHANGELOG.md` for full release history. ## Open trackers -- [#106](https://github.com/TexasCoding/kalshi-python-sdk/issues/106) — - Wave 5 polish backlog umbrella. 5 items landed via #141; remaining - sub-items (e.g. `MessageQueue.maxlen` defense-in-depth, RSA-sign via - executor, `run_forever` foot-gun) are opportunistic. -- [#45](https://github.com/TexasCoding/kalshi-python-sdk/issues/45) — - verify `json={}` workaround under prod credentials. Blocked on - prod-key access. -- [#53](https://github.com/TexasCoding/kalshi-python-sdk/issues/53) — - resolve nested `$ref` pointers in body-schema drift check. Spec - currently has no nested refs; implement when one lands. +None. The previous entries here (#45, #53, #106) are all closed: +`#45` (json={} workaround) resolved, `#53` (nested $ref drift) marked +not-applicable against the current spec, `#106` was a PR (not an issue) whose +remaining sub-items all shipped in the post-v2.2.0 batch above. ## Next milestone -Not scoped. Carry-overs from v2.1 and v2.0 audit backlog: +Not scoped. Open candidates from the v2.0/v2.1 audit backlog: -- **`MessageQueue._buffer = collections.deque(maxlen=maxsize+1)`** for - defense-in-depth (#106 F-P-15 / F-R-02). -- **`_to_decimal_dollars` / `_to_decimal_fp` consolidation** (#106 - F-N-09 + the follow-up noted in #140). -- **WS UX foot-guns**: auto-start recv loop when no `subscribe_*` was - called, resubscribe-time data-frame stashing (#106 F-P-16 / F-R-13). -- **Map remaining REST sub-models / V2 family / internal containers** - (~42 entries) into `CONTRACT_MAP` and promote - `test_contract_map_completeness` (REST) to hard-fail. WS side already - hard-fails as of v2.2.0; the REST side stays warn-only until the - mapping work lands. - **Required-but-Optional drift policy decision** (~204 entries on `test_required_drift` / `test_ws_required_drift`). Currently warn-only; - promote to fail behind either an allowlist or a tightening pass that - drops `None` defaults on fields the server reliably sends. + promote to fail behind either an allowlist or a tightening pass that drops + `None` defaults on fields the server reliably sends. +- **Continued nightly-integration `server_omits_despite_required` triage.** + `#183` was the first batch; the next nightly run against demo will catch + the next set as they surface. -Pick from `gh issue list` and the deferred items in `#106` opportunistically. +Pick from `gh issue list` opportunistically. ## Execution conventions (carried from v2.0) diff --git a/docs/authentication.md b/docs/authentication.md index 65ba5cb..0ab6625 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -198,4 +198,28 @@ headers = auth.sign_request("GET", "/trade-api/v2/exchange/status") # "KALSHI-ACCESS-TIMESTAMP": ...} ``` +### Async RSA-PSS sign offload + +`KalshiAuth.sign_request_async(method, path, timestamp_ms=None)` is the +coroutine version of `sign_request()`. It offloads the RSA-PSS signing +(typically 1–10 ms on a 2048-bit key) onto a **dedicated** +`ThreadPoolExecutor(max_workers=2)` lazy-initialised on first use, so signs +don't queue behind `loop.getaddrinfo` / file I/O / other `to_thread()` work +on a busy event loop: + +```python +headers = await auth.sign_request_async("GET", "/trade-api/v2/exchange/status") +``` + +The async REST transport (`AsyncTransport.request`) and async WebSocket +connect (`ConnectionManager._build_auth_headers`) both use this path +automatically — relevant during reconnect storms where cold DNS resolution +(5–50 ms) would otherwise dominate the sign cost. The sync `sign_request` +API is unchanged for sync-transport callers. + +`KalshiAuth.close()` shuts the executor down; `KalshiClient.close()` and +`AsyncKalshiClient.close()` chain into it, so you only need to call it +directly if you construct `KalshiAuth` standalone (e.g. for the WebSocket +with no REST client alongside). + See the [API reference](reference.md) for the full surface. diff --git a/docs/configuration.md b/docs/configuration.md index 2d37726..1b5b9c2 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -141,6 +141,23 @@ with KalshiClient(auth=auth) as client: `auth=` and the credential kwargs (`key_id` / `private_key_path` / `private_key`) are mutually exclusive — supply one or the other. +## Lifecycle + +`KalshiClient` (and `AsyncKalshiClient`) is a context manager; the `with` +block tears down the underlying `httpx` client and a small dedicated +`ThreadPoolExecutor` used for offloading RSA-PSS signing on the async path +(see [Authentication](authentication.md#async-rsa-pss-sign-offload)). If +you keep a client as a long-lived attribute, call `client.close()` +explicitly when shutting down to release both pools deterministically. + +```python +client = KalshiClient.from_env() +try: + ... +finally: + client.close() +``` + ## Reference ::: kalshi.config.KalshiConfig diff --git a/docs/index.md b/docs/index.md index 127bbb6..5873552 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** — 85 endpoints across 19 resources (OpenAPI v3.18.0), +- **Full REST coverage** — 98 operations across 19 resources (OpenAPI v3.18.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` @@ -11,10 +11,12 @@ markets API. - **Funding + cost introspection** — `portfolio.deposits()`, `portfolio.withdrawals()`, `account.endpoint_costs()`. - **Full WebSocket coverage** — 13 channels with sequence-gap detection, automatic - reconnection, backpressure strategies, and an in-memory orderbook builder. + reconnection (with resubscribe-window frame stashing for high-volume channels), + backpressure strategies, and an in-memory orderbook builder. Async-only — + access via `AsyncKalshiClient.ws`. - **Sync and async parity** — `KalshiClient` and `AsyncKalshiClient` share one transport implementation; method names, kwargs, return types, and error behavior - are identical. + are identical. The one exception is WebSocket access, which is async-only. - **Typed end-to-end** — Pydantic v2 models, `Literal` types for enums, `mypy --strict` clean. Request bodies use `extra="forbid"` so phantom kwargs fail fast. diff --git a/docs/resources/events.md b/docs/resources/events.md index 812e5ee..7adc5cb 100644 --- a/docs/resources/events.md +++ b/docs/resources/events.md @@ -60,6 +60,19 @@ print(md.tags, md.category) `EventMetadata` carries tags, categories, and other non-trading attributes. +!!! note "Server omissions on optional-shaped fields" + Two `EventMetadata`-adjacent fields are typed as nullable to absorb live + server behavior: + + - `Event.product_metadata` is typed `dict[str, Any] | None`. The OpenAPI + spec marks it `required`, but the live demo server omits the key on + most events. Defaults to `None`. + - `EventMetadata.market_details` uses `NullableList[MarketMetadata]`, + which coerces a JSON `null` payload to `[]`. The spec contract (key + present) is still enforced; callers always see a list. + + Both are tracked under `server_omits_despite_required` in the SDK's + EXCLUSIONS map. ## Reference ::: kalshi.resources.events.EventsResource diff --git a/docs/resources/multivariate.md b/docs/resources/multivariate.md index 1e06d4d..39ae865 100644 --- a/docs/resources/multivariate.md +++ b/docs/resources/multivariate.md @@ -16,7 +16,7 @@ Public listing, auth-required minting. Attribute name on the client: | `get(collection_ticker)` | `GET /multivariate_event_collections/{ticker}` | no | | `create_market(collection_ticker, *, selected_markets, with_market_payload=False)` | `POST /multivariate_event_collections/{ticker}` | yes | | `lookup_tickers(collection_ticker, *, selected_markets)` | `PUT /multivariate_event_collections/{ticker}/lookup` | no | -| `lookup_history(collection_ticker, *, lookback_seconds)` | `GET /multivariate_event_collections/{ticker}/lookup_history` | no | +| `lookup_history(collection_ticker, *, lookback_seconds)` | `GET /multivariate_event_collections/{ticker}/lookup` (with `lookback_seconds` query param) | no | ## List collections @@ -73,12 +73,12 @@ if resp.market is not None: ## Recent lookup history ```python -hist = client.multivariate_collections.lookup_history( +history = client.multivariate_collections.lookup_history( "KXWEATHER-SPORTS-COMBO", lookback_seconds=3600, ) -for entry in hist.lookups: - print(entry.market_ticker, entry.created_ts) +for point in history: + print(point.last_queried_ts, point.market_ticker, point.event_ticker) ``` ## Reference diff --git a/docs/websockets.md b/docs/websockets.md index 35a4b5a..4b937a9 100644 --- a/docs/websockets.md +++ b/docs/websockets.md @@ -281,6 +281,30 @@ On a successful reconnect: 4. Active iterators keep yielding — they reference the durable client-side ids, not the server `sid`s. +### Resubscribe-window frame stashing + +Between the moment `resubscribe_all` clears the `sid → client_id` map (to +prevent stale-sid mis-routing on per-sub failures) and the moment the new +sids land in the wait-for-subscribe-response handler, the server can already +send data frames on the freshly-assigned sids. Without buffering, those +frames have no destination yet and would be silently dropped. Under burst +reconnects on high-volume channels (`ticker`, `trade`, `fill`), this could +lose tens of messages per reconnect. + +`SubscriptionManager` stashes those frames in a per-sid bounded +`collections.deque(maxlen=stash_maxlen)` for the duration of +`resubscribe_all`. After resubscribe completes, `_handle_reconnect` drains +the stash through the normal dispatch path so the seq tracker advances, +orderbook state applies, and iterator consumers receive them in arrival +order. + +`stash_maxlen` defaults to `1000` per sid. On overflow, oldest evicts (deque +semantics) and a single WARNING per `(sid, replay-cycle)` is logged so the +caller notices congestion. Worst-case memory is bounded at +`stash_maxlen × len(active_subs) × avg_frame_size`. Frames whose sid never +gets re-mapped (a per-sub failure during resubscribe) are dropped on drain +with a debug log — there's no consumer to deliver them to. + If `ws_max_retries` is exhausted, the receive loop pushes sentinels to all active queues (so `async for` terminates cleanly) and exits. The connection state ends at `CLOSED`. From 252ce06f2c38d5421167eab9317a8363452c0d3e Mon Sep 17 00:00:00 2001 From: Jeff West Date: Wed, 20 May 2026 18:52:41 -0500 Subject: [PATCH 2/5] fixup: round-1 bot review on PR #188 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bot review found two real items plus one I caught while fixing: 1. CLAUDE.md still said "85 endpoints" — updated to "98 operations" to match docs/index.md. 2. README.md line 14 also said "85 endpoints" (bot missed this one) — updated to "98 operations" for consistency. 3. docs/resources/events.md was missing a blank line between the admonition block closing the "Server omissions" note and the next "## Reference" heading. mkdocs --strict passed before (it's not a hard error), but the bot is correct that it's fragile markdown. Added the blank line. The bot's "recount vs API expansion" observation was not actioned — adding a "this is a recount" disclaimer would actually be misleading, since some operations were added (v2 family, deposits/withdrawals, endpoint_costs) and some are just from a tighter count. The previous "85 endpoints" claim was inaccurate prior; "98 operations" against the current spec is verified-accurate. No disclaimer needed. mkdocs --strict still clean. --- CLAUDE.md | 2 +- README.md | 2 +- docs/resources/events.md | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2286d35..d337511 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -122,7 +122,7 @@ tests/ ## API Reference -- OpenAPI spec: https://docs.kalshi.com/openapi.yaml (v3.18.0, 85 endpoints) +- OpenAPI spec: https://docs.kalshi.com/openapi.yaml (v3.18.0, 98 operations) - AsyncAPI spec: https://docs.kalshi.com/asyncapi.yaml (13 WebSocket channels) - Base URL: https://api.elections.kalshi.com/trade-api/v2 - Demo URL: https://demo-api.kalshi.co/trade-api/v2 diff --git a/README.md b/README.md index 8488ce1..58f7147 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 (85 endpoints across 19 resources, OpenAPI v3.18.0) and WebSocket API (13 channels). +- **Full coverage** of the Kalshi REST API (98 operations across 19 resources, OpenAPI v3.18.0) and WebSocket API (13 channels). - **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/resources/events.md b/docs/resources/events.md index 7adc5cb..2e50ba4 100644 --- a/docs/resources/events.md +++ b/docs/resources/events.md @@ -73,6 +73,7 @@ print(md.tags, md.category) Both are tracked under `server_omits_despite_required` in the SDK's EXCLUSIONS map. + ## Reference ::: kalshi.resources.events.EventsResource From 701b0a2abdca4f732d265e979b09843786fdb104 Mon Sep 17 00:00:00 2001 From: Jeff West Date: Wed, 20 May 2026 18:59:33 -0500 Subject: [PATCH 3/5] fixup: round-2 bot review on PR #188 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bot round-2 found one item to action and one based on a misreading of convention. Actioned: * ROADMAP.md "Open trackers" trimmed from four lines of tombstone prose ("None. The previous entries here (#45, #53, #106) are all closed: ... ") down to "None." The why-they-closed history will rot by the next release; anyone curious can run `gh issue view 45` or check git history. Not actioned: * Bot suggested changing CHANGELOG.md "Pre-release docs audit (#179)" to "(#188)" on the claim that "every other CHANGELOG entry uses the PR number." This is backwards. Existing CHANGELOG entries (#175, #176, #177, #178, #183) are all *issue* numbers; the PRs that closed them were #185, #187, #186, #182, #184 respectively. Verified via `gh issue view --json closedByPullRequestsReferences`. The convention is "(#)" — using #179 is correct. mkdocs --strict still clean. --- ROADMAP.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 26d0621..74dedf3 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -55,10 +55,7 @@ See `CHANGELOG.md` for full release history. ## Open trackers -None. The previous entries here (#45, #53, #106) are all closed: -`#45` (json={} workaround) resolved, `#53` (nested $ref drift) marked -not-applicable against the current spec, `#106` was a PR (not an issue) whose -remaining sub-items all shipped in the post-v2.2.0 batch above. +None. ## Next milestone From b4c10e48b6d6546d4d9e3598305ae9f01532e499 Mon Sep 17 00:00:00 2001 From: Jeff West Date: Wed, 20 May 2026 19:01:43 -0500 Subject: [PATCH 4/5] fixup: async parity on Lifecycle example per PR #188 round-3 review --- docs/configuration.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index 1b5b9c2..7767ff2 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -158,6 +158,14 @@ finally: client.close() ``` +`AsyncKalshiClient` follows the same shape — prefer `async with` so the +async `close()` chains the executor teardown on exit: + +```python +async with AsyncKalshiClient.from_env() as client: + ... +``` + ## Reference ::: kalshi.config.KalshiConfig From 0b4ad5966f915635992adcf79c0ff2ae53b5bcb2 Mon Sep 17 00:00:00 2001 From: Jeff West Date: Wed, 20 May 2026 19:07:16 -0500 Subject: [PATCH 5/5] fixup: round-4/5 bot suggestions on PR #188 Round 4: stash WARNING dedup wording aligned to set[int] impl ('once per sid per resubscribe cycle' instead of '(sid, replay-cycle)' tuple-implying phrasing). Round 5: NullableList[MarketMetadata] replaced with plain-English coercion behavior in events.md (NullableList is internal type). Also clarified stash_maxlen=1000 is not user-configurable on KalshiWebSocket. --- docs/resources/events.md | 7 ++++--- docs/websockets.md | 9 ++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/resources/events.md b/docs/resources/events.md index 2e50ba4..9374b4b 100644 --- a/docs/resources/events.md +++ b/docs/resources/events.md @@ -67,9 +67,10 @@ print(md.tags, md.category) - `Event.product_metadata` is typed `dict[str, Any] | None`. The OpenAPI spec marks it `required`, but the live demo server omits the key on most events. Defaults to `None`. - - `EventMetadata.market_details` uses `NullableList[MarketMetadata]`, - which coerces a JSON `null` payload to `[]`. The spec contract (key - present) is still enforced; callers always see a list. + - `EventMetadata.market_details: list[MarketMetadata]` — when the live + server sends JSON `null`, the SDK coerces it to `[]` so callers + always see a list. The spec contract (key present) is still + enforced. Both are tracked under `server_omits_despite_required` in the SDK's EXCLUSIONS map. diff --git a/docs/websockets.md b/docs/websockets.md index 4b937a9..f85dfd7 100644 --- a/docs/websockets.md +++ b/docs/websockets.md @@ -298,9 +298,12 @@ the stash through the normal dispatch path so the seq tracker advances, orderbook state applies, and iterator consumers receive them in arrival order. -`stash_maxlen` defaults to `1000` per sid. On overflow, oldest evicts (deque -semantics) and a single WARNING per `(sid, replay-cycle)` is logged so the -caller notices congestion. Worst-case memory is bounded at +The stash is bounded by an internal `stash_maxlen=1000` per sid — generous +enough for normal market-burst reconnects, low enough to bound memory if +resubscribe stalls (not user-configurable on `KalshiWebSocket`). On +overflow, oldest evicts (deque semantics) and a WARNING fires **once per +sid per resubscribe cycle** so the caller notices congestion without log +spam. Worst-case memory is bounded at `stash_maxlen × len(active_subs) × avg_frame_size`. Frames whose sid never gets re-mapped (a per-sub failure during resubscribe) are dropped on drain with a debug log — there's no consumer to deliver them to.