fix: spec drift — Klear Bearer auth (breaking) + cfbenchmarks WS + account/perps additions (closes #443)#444
Conversation
…count/perps additions (closes #443) Re-vendor the upstream OpenAPI (3.20.0) and AsyncAPI specs and reconcile the SDK + contract-drift suites. The vendored specs had drifted again since the issue was filed, so the actual diff is broader than the issue summary. BREAKING: the Klear (SCM) API dropped POST /log_in and switched to a static Bearer token. KlearClient/AsyncKlearClient now take admin_user_id + access_token (or from_env via KALSHI_KLEAR_ADMIN_USER_ID / KALSHI_KLEAR_ACCESS_TOKEN); KlearAuth is a Bearer-credential holder; login()/is_authenticated/the auth resource and LogInRequest/LogInResponse are removed. Header injected per-request by the Klear resource base. Added (additive spec surface): - cfbenchmarks_value WS channel (subscribe_cfbenchmarks_value + 5 models + dispatch/params/update_subscription plumbing) - AccountResource.upgrade() (POST /account/api_usage_level/upgrade) - AccountApiLimits.grants + ApiUsageLevelGrant model - MarginAccountResource.api_limits() (GET /account/limits/perps) - MarginMarket leverage_estimates + notional-value fields; MarginMarketCandlestick and ticker WS payload notional-value fields Other: subaccount prose 1-32 -> 1-63 (doc-only); version 3.3.0 -> 4.0.0. Verified: ruff + mypy strict clean; full suite 4221 passed (the 4 from_env failures are pre-existing test-pollution that pass in isolation, unrelated). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Code Review — PR #444: Spec drift reconciliation (v4.0.0)Overall this is a solid, well-structured PR. The Klear auth migration is clean, the new models are consistent with existing patterns, and test coverage is broad. A few things worth addressing before merge. Issues1.
|
… (PR #444) Address PR review feedback: - upgrade(): document that json={} forces Content-Type for the spec's bodyless POST, mirroring the existing subaccounts.create precedent; fix the "bodyless"/"sends json={}" test comment; note the absent request_body_schema on the METHOD_ENDPOINT_MAP entry. - CFBenchmarksAvgData.value: justify DollarDecimal (BRTI/ETHUSD_RTI are USD-denominated reference rates; exact Decimal, no float drift). - CFBenchmarksValuePayload.data: point callers to json.loads(msg.data). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks for the review — addressed in 6a01a2d. Point-by-point: 1 & 2 — 3 — 4 — 5 — env-var pollution. Filed as #445 with the confirmed root cause: |
Code ReviewGood overall — the breaking change is well-scoped, the additive surface follows existing patterns, and the PR description is thorough. A few items worth addressing before merge. Breaking change — Klear Bearer auth ✅The migration is clean:
One concern: def _with_auth(self, extra_headers: dict[str, str] | None) -> dict[str, str]:
merged = dict(extra_headers) if extra_headers else {}
# Caller-supplied Authorization is intentionally replaced by Bearer token
merged["Authorization"] = self._klear_auth.authorization_header()
return merged
|
) test_from_env_demo_flag / test_from_env_base_url_override set KALSHI_PRIVATE_KEY but never dropped an ambient KALSHI_PRIVATE_KEY_PATH, so when the integration conftest's session-scoped _bridge_env_vars fixture bridged a developer's KALSHI_DEMO_PRIVATE_KEY_PATH into the session, from_env() saw both and raised "Both KALSHI_PRIVATE_KEY and KALSHI_PRIVATE_KEY_PATH are set". Add the missing delenv("KALSHI_PRIVATE_KEY_PATH") in both sync + async tests, matching the already-hermetic sibling tests. Full suite now 4225 passed, 0 failed (was 4221 passed / 4 pre-existing flakes). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Follow-up on the second review + the env-flake:
Test coverage (it's in the diff). The branch does include the test changes — e.g. Env-var pollution → fixed here (closes #445). Root-caused and fixed in |
Code Review — PR #444: Spec drift (Klear Bearer auth + cfbenchmarks WS + account/perps additions)Overall this is a well-executed spec-reconciliation PR. The architecture is sound, the test strategy is correct, and the docs are thorough. A few things worth addressing before merge: 🔴 Potential runtime breakage: required fields in
|
… index_ids (PR #444) Address third review (minor items): - KlearAuth: reject whitespace-only (and None) admin_user_id/access_token — a blank token is truthy but yields a malformed "Bearer : " header. Add whitespace test cases. - klear resources _base: document that only _get/_post are overridden because the Klear surface is GET/POST-only; a future PUT/DELETE endpoint must add its override (else requests go out unauthenticated). - ws channels update_subscription: comment that the index_ids truthy check intentionally omits an empty list (consistent with market_tickers/market_ids; server requires >=1 id, error code 24). Candlestick/ticker notional fields stay required: the perps spec marks them required and TestPerpsSpecDrift hard-fails on required-but-optional, and the ticker payload's sibling fields (volume/open_interest) are already required. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks — addressed the three minor items in 🔴 Required notional fields on candlestick / ticker — keeping required (intentional). The SDK mirrors each schema's own
So the asymmetry the review noticed is the spec's, faithfully mirrored. (Your second review also called non-nullable "appropriate for historical records" on the candlestick, which lines up.) 🟡 🟡 KlearAuth whitespace creds. Fixed — now rejects whitespace-only (and None-safe), with new test cases. 🟡 Nit (PERPS_SCM_BODY_MODEL_MAP): no map-non-empty assertion exists, and Suite still 4225 passed / 0 failed; ruff + mypy strict clean. |
Code Review — PR #444: Spec drift reconciliation + Klear Bearer authThis is a well-executed PR. The breaking change is correctly scoped, the auth migration is sound, test coverage is thorough across the new surfaces, and all major project conventions are followed (forbid models, retry guard, DollarDecimal prices, no-builtin-list shadowing). Below are the actionable findings — one real bug, two test gaps, and a few minor notes. Bug: whitespace-padded credentials survive
|
| Severity | Finding |
|---|---|
| Bug | KlearAuth stores whitespace-padded credentials verbatim → malformed Bearer header |
| Medium | update_subscription(index_ids=...) post-subscribe actions untested |
| Low | Missing test case for " admin-id " credential (padded, non-whitespace-only) |
| Nit | from_env() duplication between sync/async clients |
The Klear auth migration and the additive surfaces (cfbenchmarks WS, account upgrade, perps notional fields) are solid. Fix the whitespace bug and plug the update_subscription test gap before merging.
…ndex_ids (PR #444) Address fourth review: - KlearAuth: strip admin_user_id/access_token before storage (None-safe via `(x or "").strip()`), so a padded value like " id " can't produce a malformed "Bearer id : tok " header. Add a padded-credential test. - ws update_subscription: add unit tests for the cfbenchmarks index_ids path (subscribe_indices sends index_ids; indexlist omits it; empty list dropped). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Both substantive items fixed in 🐞 Padded credentials (bug). Good catch — admin_user_id = (admin_user_id or "").strip()
access_token = (access_token or "").strip()
if not admin_user_id or not access_token:
raise ValueError(...)Added 🧪 Nits (no action, agreed): Suite green; ruff + mypy strict clean. |
Code Review — PR #444: Spec drift reconciliation + Klear Bearer authOverall this is a well-executed, broad reconciliation. The test suite passes at 4221 and the diff is clean. Below are findings sorted by severity. Bugs / Correctness
# kalshi/perps/models/markets.py (current)
volume_notional_value: DollarDecimal = Field(...)
open_interest_notional_value: DollarDecimal = Field(...)
# safer, consistent with MarginMarket
volume_notional_value: DollarDecimal | None = Field(default=None, ...)
open_interest_notional_value: DollarDecimal | None = Field(default=None, ...)Same concern applies to the three Security
Code Quality / Conventions
def _build_auth_headers(auth: KlearAuth, extra: dict[str, str] | None) -> dict[str, str]:
merged = dict(extra) if extra else {}
merged["Authorization"] = auth.authorization_header()
return mergedNot blocking, but it's a three-line helper that's copy-pasted exactly.
Test Coverage
Minor / Polish
Summary
The breaking auth change is well-motivated (upstream removed the endpoint), the version bump to 4.0.0 is correct per semver, and the changelog entry is clear. The required-vs-optional concern on candlestick/ticker notional fields is the main thing I'd want resolved before merge. 🤖 Generated with Claude Code |
…l test gaps (PR #444) Address fifth review: - MarginTickerPayload notional fields -> optional (| None). They're newly added on a *live* streaming channel; a server omitting them mid-rollout shouldn't break the whole ticker. WS payload-drift checks name coverage (not required), and REST MarginMarket already models them optional. MarginMarketCandlestick stays required (spec lists them required + perps response-drift hard-fails on required->optional; inherent to a settled historical record). - Dedup the verbatim _with_auth into a module-level _with_klear_auth helper (used by both sync + async bases). - Clarify the KlearAuth None-coercion comment; document the subscribe_cfbenchmarks_value union-return discrimination idiom. Test gaps filled: - ticker parses with notional fields absent (optional); - candlestick raises when a notional field is missing (required); - KlearClient.from_env with exactly one credential missing raises; - subscribe_cfbenchmarks_value(index_ids=["all"]) passthrough. Full suite 4233 passed, 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Addressed in
Code quality — Security — KlearAuth Union return idiom. Test gaps filled: ticker-absent (above), candlestick-missing-raises (above), Minor (no action):
Full suite: 4233 passed, 0 failed; ruff + mypy strict clean. |
Code Review — PR #444TL;DR: Well-executed, high-confidence PR. The Klear Bearer migration is clean and complete; all the additive work (cfbenchmarks WS, account/perps additions) follows established conventions. A few minor items worth addressing before merge, but no blockers. OverviewThis PR reconciles the SDK against the latest upstream OpenAPI/AsyncAPI specs (as of 2026-06-09). It includes:
Code QualityKlear Bearer auth migration
Perps notional fields on
Potential Issues / Suggestions1. grants: NullableList[ApiUsageLevelGrant]This has no default, which means it's a required field in Pydantic — validation will fail for any server response that omits The async test correctly exercises the 2. In if not resolved_admin or not resolved_token:
raise ValueError(...)If 3.
4. async def update_subscription(self, client_id: int, action: str, ...) -> None:
5. Pre-existing The PR notes 4 pre-existing Test Coverage
Gap: No test for Security
Minor
Summary
One actionable item before merge: add a test for 🤖 Generated with Claude Code |
|
Thanks — this one needs no code change; the one "actionable" item is already covered (looks like a partial-diff view, same as a couple earlier passes).
Notes (no action, with reasons):
Full suite: 4233 passed, 0 failed; ruff + mypy strict clean. |
Spec drift reconciliation (closes #443)
Re-vendored the upstream specs and reconciled the hand-written SDK + contract-drift suites. The vendored specs had drifted again since the issue was filed (2026-06-08), so this PR resolves the current upstream state, which is broader than the issue's summary:
baf3bf09…760df935…+ /account/api_usage_level/upgrade,AccountApiLimits.grants8ee211e1…25e85ecd…+ cfbenchmarks_valuechannel4e133710…4336b33d…+ /account/limits/perps, notional/leverage fields1dd27956…1dd27956…✓d8c0d9c4…d8c0d9c4…✓Upstream removed
POST /log_inand switched the Klear API from cookie-session to a static Bearer token (Authorization: Bearer <admin_user_id>:<access_token>).KlearClient/AsyncKlearClientnow requireadmin_user_id+access_tokenat construction (orfrom_env()→KALSHI_KLEAR_ADMIN_USER_ID/KALSHI_KLEAR_ACCESS_TOKEN).KlearAuth(admin_user_id, access_token)is now a Bearer-credential holder; the header is injected per-request by the Klear resource base.KlearClient.login(),KlearClient.is_authenticated,client.auth,LogInRequest,LogInResponse,AuthResource.Because this breaks a public surface, the version is bumped 3.3.0 → 4.0.0 (semver major). Happy to re-target the release if you'd rather batch it.
Added (additive)
cfbenchmarks_valueWS channel —subscribe_cfbenchmarks_value(index_ids=[...])+CFBenchmarks*models + dispatch/param/update_subscriptionplumbing + error-code notes 23–25.AccountResource.upgrade()—POST /account/api_usage_level/upgrade.AccountApiLimits.grants+ newApiUsageLevelGrantmodel.MarginAccountResource.api_limits()—GET /account/limits/perps(reusesAccountApiLimits).MarginMarket,MarginMarketCandlestick, and the margin ticker WS payload.Verification
ruff check .✅ ·mypy kalshi/(strict) ✅ — no issues in 157 filestest_from_env_*failures (client/async_client) appear only in the full run due to env-var test-pollution — they pass in isolation and were already failing onmainbefore this PR. Left out of scope; happy to file a separate issue.🤖 Generated with Claude Code