Skip to content

Commit ce287a7

Browse files
authored
docs(migration): add v2.3→v2.4 section (batch shape, errors, passphrase, http2, extra_headers) (#277)
Closes #246
1 parent d59912b commit ce287a7

1 file changed

Lines changed: 121 additions & 0 deletions

File tree

docs/migration.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,126 @@
11
# Migration
22

3+
## v2.3 → v2.4
4+
5+
v2.4 ships one user-visible breaking change — the V1 batch order
6+
return shape (#194) — plus several additive surfaces: new typed
7+
exceptions, passphrase-protected PEMs, opt-in HTTP/2, and per-request
8+
`extra_headers`.
9+
10+
### Breaking: `batch_create` / `batch_cancel` return typed envelopes
11+
12+
Per #194, `orders.batch_create` previously returned `list[Order]` and
13+
crashed with `ValidationError` on the first failed leg;
14+
`orders.batch_cancel` returned `None` and discarded the per-leg
15+
`reduced_by_fp` counts the server actually sent. Both now return
16+
typed envelopes matching the V2 family — pair legs by
17+
`client_order_id` and check `entry.error` per leg.
18+
19+
```python
20+
# v2.3
21+
orders: list[Order] = client.orders.batch_create(request=req)
22+
for order in orders:
23+
print(order.order_id, order.status)
24+
25+
# v2.4
26+
from kalshi.models import BatchCreateOrdersResponse
27+
28+
resp: BatchCreateOrdersResponse = client.orders.batch_create(request=req)
29+
for entry in resp.orders:
30+
if entry.error is not None:
31+
logger.error("leg %s failed: %s", entry.client_order_id, entry.error)
32+
continue
33+
assert entry.order is not None
34+
print(entry.order.order_id, entry.order.status)
35+
```
36+
37+
`batch_cancel` follows the same shape: each `resp.orders[i]` exposes
38+
`order_id`, `reduced_by_fp` (the count of contracts that actually
39+
canceled — load-bearing for risk reconciliation), plus
40+
`order`/`error` for the affected leg.
41+
42+
### New typed exceptions
43+
44+
Per #201 / #204 / #226, three new subclasses of `KalshiError` join
45+
the existing hierarchy:
46+
47+
- `KalshiConflictError` — HTTP **409** (e.g. duplicate
48+
`client_order_id`).
49+
- `KalshiTimeoutError``httpx.TimeoutException` at the request
50+
level. **The server may or may not have processed the request.**
51+
POST/DELETE still never retry on this; reconcile by querying with
52+
the original `client_order_id` before issuing a fresh attempt.
53+
- `KalshiPoolExhaustedError``httpx.PoolTimeout`. The request
54+
never reached the wire and IS safe to retry on POST/DELETE.
55+
56+
422 now also routes to `KalshiValidationError` (was 400 only).
57+
58+
```python
59+
from kalshi import (
60+
KalshiConflictError,
61+
KalshiTimeoutError,
62+
KalshiPoolExhaustedError,
63+
)
64+
65+
try:
66+
client.orders.create(request=req)
67+
except KalshiConflictError:
68+
existing = client.orders.get_by_client_order_id(req.client_order_id)
69+
except KalshiPoolExhaustedError:
70+
# never touched the wire — safe to retry
71+
client.orders.create(request=req)
72+
except KalshiTimeoutError:
73+
# reconcile before retrying — server may have committed
74+
existing = client.orders.get_by_client_order_id(req.client_order_id)
75+
```
76+
77+
### Passphrase-protected PEMs
78+
79+
Per #217 / #233, `KalshiAuth.from_pem`, `from_key_path`, `from_env`,
80+
and `try_from_env` accept a `password=` kwarg (`str` / `bytes` / a
81+
zero-arg callable returning either — the callable form lets you
82+
defer fetching the secret from a vault until load-time). `from_env`
83+
/ `try_from_env` also read `KALSHI_PRIVATE_KEY_PASSPHRASE` from the
84+
environment; the explicit `password=` wins when both are set.
85+
Encrypted PEMs no longer have to be stripped to disk first.
86+
87+
```python
88+
auth = KalshiAuth.from_key_path(
89+
"your-key-id",
90+
"~/.kalshi/private_key.pem",
91+
password=lambda: vault.get_secret("kalshi-pem-pass"),
92+
)
93+
```
94+
95+
### Opt-in HTTP/2
96+
97+
Per #233, `KalshiConfig(http2=True)` enables HTTP/2 on the REST
98+
client (off by default for compat). The `h2` dependency is not
99+
installed by default; `KalshiConfig.__post_init__` fail-fasts with a
100+
clear message when `http2=True` is set without it:
101+
102+
```bash
103+
pip install 'kalshi-sdk[http2]'
104+
```
105+
106+
```python
107+
config = KalshiConfig(http2=True)
108+
client = KalshiClient(auth=auth, config=config)
109+
```
110+
111+
### Per-request `extra_headers` (transport-only, for now)
112+
113+
Per #220, `KalshiConfig.extra_headers` sets client-wide default
114+
headers, and `SyncTransport.request(..., extra_headers=...)` /
115+
`AsyncTransport.request(..., extra_headers=...)` merge per-call
116+
overrides on top. `KALSHI-ACCESS-*` signing headers always win, so
117+
callers cannot forge them via this surface. Resource-method plumbing
118+
— so e.g. `client.orders.create(..., extra_headers=...)` works
119+
directly — is tracked in #253 and not yet shipped in v2.4; only the
120+
transport-level entry point is public.
121+
122+
---
123+
3124
## v2.2 → v2.3
4125

5126
v2.3 is additive on the REST surface and **soft-breaking on the WebSocket

0 commit comments

Comments
 (0)