Skip to content

Polish bundle: perf + testing (cursor guard O(1), model_dump_json bytes, bench scripts, from_env precedence test, integration env isolation, KalshiAuth.close terminality) #223

Description

@TexasCoding

Summary

Bundle of small perf / testing polish items.

1. Cursor-loop guard memory grows unbounded

_list_all builds a set[str] of every cursor seen and never bounds it. A 10M-row enumeration with ~1KB cursors retains ~10MB purely for loop detection. Since the guard only needs to detect immediate cycles (server bug pattern), retaining the last cursor is sufficient.

Fix: Replace seen_cursors: set[str] = set() with last_cursor: str | None = None and check equality only against the previous page's cursor. O(1) memory, same protection against the realistic bug shape (server replays last page).

2. model_dump(mode='json') then httpx re-serializes

Every POST/DELETE-with-body builds the body via request.model_dump(exclude_none=True, by_alias=True, mode='json') (walks model, runs Decimal serializer, returns dict). That dict is then handed to httpx as json=body, which serializes the same dict to bytes again. For batch_create_orders (up to 100 orders × ~10 Decimal fields each) this is two full passes.

Fix: Add _post_json / _delete_with_body_json helpers in resource base that accept bytes (request.model_dump_json(...)) and pass content=body, headers={'Content-Type': 'application/json'} to httpx. Use from batch order paths first. Keep dict-based _post for retrofit-friendliness.

3. No performance regression benchmarks

scripts/bench_sign_offload.py is the only perf harness. No bench for: per-request allocations on SyncTransport.request, recv-loop throughput (frames/sec), OrderbookManager.apply_delta cost vs book depth, Decimal coercion cost across wide models. Regressions like the discarded-snapshot waste (separate high issue) are invisible to CI.

Fix: Add:

  • scripts/bench_ws_recv.py — synthetic snapshot+delta frames through _process_frame, reports frames/sec + GC counts.
  • scripts/bench_orderbook_delta.py — parameterized by book depth (10, 100, 500, 2000).
  • scripts/bench_request_hot_path.py — mock transport, measures requests/sec for sync + async.

Wire into nightly CI that compares against a checked-in baseline JSON (gating optional; emitting numbers is the minimum).

4. from_env precedence (KALSHI_PRIVATE_KEY vs KALSHI_PRIVATE_KEY_PATH) untested

from_env() checks KALSHI_PRIVATE_KEY first, falls back to KALSHI_PRIVATE_KEY_PATH. Existing tests only set one or the other, so neither pins the precedence when BOTH are set (common in CI environments).

Fix: Add test_pem_takes_precedence_over_path: monkeypatch both env vars to different keys, call from_env(), assert the loaded public key matches the PEM source. ~12 lines.

5. tests/integration/conftest.py mutates os.environ at import time

_bridge_env_vars() unconditionally writes KALSHI_KEY_ID / KALSHI_PRIVATE_KEY_PATH / KALSHI_DEMO into process env at conftest import time. Imported whenever pytest collects under tests/integration/, including under pytest tests/. Unit tests that read env without monkeypatch override silently see bridged demo credentials.

Fix: Move _bridge_env_vars() into a session-scoped autouse fixture using monkeypatch.setenv (test-scoped restoration), OR wrap in pytest_sessionstart / pytest_sessionfinish hooks that snapshot+restore.

6. KalshiAuth.close() reusability surprise

KalshiAuth.close() calls self._sign_executor.shutdown(wait=False) and sets _sign_executor = None. The lazy-init in _get_sign_executor then sees None and silently creates a NEW executor. close() is not actually terminal — a subsequent sign_request_async() spins up a fresh ThreadPoolExecutor and the close was effectively a no-op for resource lifetime.

Fix: Track self._closed: bool = False. Set in close(). Have _get_sign_executor raise RuntimeError("KalshiAuth has been closed; create a new instance") when _closed is True. Pairs with the "shared-auth ownership" medium issue.

Severity & category

Bundle of low items, performance + testing + polish.

Metadata

Metadata

Assignees

No one assigned

    Labels

    performancePerformance / hot-path concernpolishCode-quality and DX improvements; non-functionaltestingTest coverage

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions