Problem
v2.4 #223 added _post_json / _delete_with_body_json helpers for batch endpoints with the rationale: "skips the dict-walk pass httpx's json= does. Use this when a Pydantic model already produced the wire bytes via model_dump_json(...).encode()." V1 batch_create / batch_cancel were rewired through _build_batch_create_body / _build_batch_cancel_body returning bytes and POSTed via _post_json / _delete_with_body_json.
The corresponding V2 methods (batch_create_v2, batch_cancel_v2) still call request.model_dump(mode='json') to produce a dict and ship it through _post(json=...) / _delete_with_body(json=...), so every V2 batch pays the Pydantic-walk-to-dict + httpx-walk-to-bytes overhead twice — exactly the cost #223 measured and removed for V1. With up to 100 orders × ~6-10 Decimal fields per V2 entry, that's hundreds of redundant dict-walks per batch.
The V2 surface is the spec-recommended path for new code (V1 deprecation "no earlier than May 6, 2026" per README); leaving the perf regression on the recommended path is a regression of #223's own contract.
Evidence
kalshi/resources/orders.py:534-539 — V1 batch_create already on the fast path:
body = _build_batch_create_body(request, orders)
data = self._post_json(
"/portfolio/orders/batched", content=body, extra_headers=extra_headers
)
kalshi/resources/orders.py:866-886 — V2 batch_create / batch_cancel still on the slow path:
def batch_create_v2(
self, *, request: BatchCreateOrdersV2Request, extra_headers: dict[str, str] | None = None
) -> BatchCreateOrdersV2Response:
self._require_auth()
body = request.model_dump(exclude_none=True, by_alias=True, mode="json")
data = self._post(
"/portfolio/events/orders/batched", json=body, extra_headers=extra_headers
)
...
def batch_cancel_v2(...):
...
body = request.model_dump(exclude_none=True, by_alias=True, mode="json")
data = self._delete_with_body(
"/portfolio/events/orders/batched", json=body, extra_headers=extra_headers
)
Suggested fix
Add _build_batch_create_v2_body / _build_batch_cancel_v2_body helpers next to the existing V1 builders that call request.model_dump_json(exclude_none=True, by_alias=True).encode(). Route batch_create_v2 through _post_json(content=body, ...) and batch_cancel_v2 through _delete_with_body_json(content=body, ...). Mirror the changes on AsyncOrdersResource. Extend scripts/bench_request_hot_path.py (or add a new V2-specific bench) to lock in parity with V1.
Source
Round-3 independent audit (reviewer: resources_api).
Problem
v2.4 #223 added
_post_json/_delete_with_body_jsonhelpers for batch endpoints with the rationale: "skips the dict-walk passhttpx'sjson=does. Use this when a Pydantic model already produced the wire bytes viamodel_dump_json(...).encode()." V1batch_create/batch_cancelwere rewired through_build_batch_create_body/_build_batch_cancel_bodyreturningbytesand POSTed via_post_json/_delete_with_body_json.The corresponding V2 methods (
batch_create_v2,batch_cancel_v2) still callrequest.model_dump(mode='json')to produce a dict and ship it through_post(json=...)/_delete_with_body(json=...), so every V2 batch pays the Pydantic-walk-to-dict + httpx-walk-to-bytes overhead twice — exactly the cost #223 measured and removed for V1. With up to 100 orders × ~6-10Decimalfields per V2 entry, that's hundreds of redundant dict-walks per batch.The V2 surface is the spec-recommended path for new code (V1 deprecation "no earlier than May 6, 2026" per README); leaving the perf regression on the recommended path is a regression of #223's own contract.
Evidence
kalshi/resources/orders.py:534-539— V1 batch_create already on the fast path:kalshi/resources/orders.py:866-886— V2 batch_create / batch_cancel still on the slow path:CHANGELOG.mdv2.4 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 — "Batch order bodies serialized once. Resource layer routes batch_create/batch_cancel through new_post_json/_delete_with_body_jsonbytes helpers that usemodel_dump_json+httpx content=, skipping one full dict-walk per call."Suggested fix
Add
_build_batch_create_v2_body/_build_batch_cancel_v2_bodyhelpers next to the existing V1 builders that callrequest.model_dump_json(exclude_none=True, by_alias=True).encode(). Routebatch_create_v2through_post_json(content=body, ...)andbatch_cancel_v2through_delete_with_body_json(content=body, ...). Mirror the changes onAsyncOrdersResource. Extendscripts/bench_request_hot_path.py(or add a new V2-specific bench) to lock in parity with V1.Source
Round-3 independent audit (reviewer:
resources_api).