fix(models): AmendOrderRequest Literal narrowing + V1 ge=0 strictness + create() overload fence#363
Conversation
Three integrity fixes closing the V1/V2 request-model parity gap and the orders.create() overload looseness. AmendOrderRequest.side and .action are narrowed to SideLiteral / ActionLiteral, mirroring the v2.5 #270 narrowing on CreateOrderRequest. A typo like side='yess' now fails at construction instead of being signed and 400'd by the server. The stale module-level comment implying request models are kept as bare str is removed; both V1 and V2 create paths plus amend are now narrowed. V1 CreateOrderRequest, AmendOrderRequest, DecreaseOrderRequest, and BatchCancelOrdersRequestOrder gain Field(default=None, ge=0) on subaccount and exchange_index, matching the V2 + communications + order_groups + subaccount-transfer surface that already enforced the lower bound. The two V2 exchange_index slots missed by #295 (CreateOrderV2Request, BatchCancelOrdersV2RequestOrder) are swept in the same pass. OrdersResource.create / AsyncOrdersResource.create kwarg overloads require action: ActionLiteral and count: int with no None and no default. Runtime already required them since v2.5 #242; this aligns the static contract so mypy --strict refuses the missing-arg shape that previously deferred to a runtime TypeError. Two existing auth-required-guard tests are updated to satisfy the tightened overload; the implementation signatures keep None defaults for overload dispatch and runtime error quality. Closes #312, #326, #350
Code Review — PR #363Overall: Solid spec-drift closure. The three fixes are well-scoped, the logic is correct, and the parametrized test suite gives good confidence. One linting bug and a few minor notes below. Bug: Missing blank lines before
|
| Severity | Item |
|---|---|
| Must fix | Missing two blank lines before TestIssue350 in test_client.py (ruff E302) |
| Nit | count=Decimal(1) noise in test_issue_312_amend_order_request_rejects_invalid_side |
| Nit | transport.close() at end of test without try/finally (harmless) |
| Optional | Add AsyncOrdersResource coverage in test_async_client.py for #350 parity |
Fix the blank-line issue and this is good to merge.
…teral narrowing The amend validation tests asserted the server-400 -> KalshiValidationError mapping path but used side='invalid'/'bad' as the trigger. After the W1-D AmendOrderRequest Literal narrowing (#312), those values now raise pydantic.ValidationError client-side before the mocked HTTP call, so the KalshiValidationError assertion never fires. Switch the trigger to a valid Literal (side='yes') so the request reaches the mocked 400 response and the SDK's server-error mapping is still exercised. The new pre-HTTP ValidationError path is already covered by the W1-D regression tests in tests/test_models.py (test_issue_312_amend_order_request_rejects_invalid_side and friends).
|
Round-2 review addressed in a1c6a21:
|
Code Review — PR #363OverviewThree tightly scoped model-layer integrity fixes: Literal narrowing on Issues1. Missing blank lines before new test class — will fail
|
|
Round-3 review addressed in 26ccd32:
|
Code Review — PR #363OverviewThree focused, spec-aligned model hardening fixes. The scope is well-defined, the changes are surgical, and the test coverage is thorough. No architectural concerns. What the PR does
Code QualityPositive:
Issues found: 1. Redundant inner import of In def test_issue_326_v1_subaccount_ge_zero(...) -> None:
import importlib
from pydantic import ValidationError # ← redundant; already at module levelThe inner import is harmless but inconsistent — the module-level import is sufficient. 2. Test intent drift in The original tests in Type SafetyThe The overload change (dropping Test Coverage
SummaryThis is a clean, well-scoped PR. The one actionable nit is the redundant |
…sue326V1SubaccountGeZero
|
Round-4 review addressed in 32eec67: removed redundant inline ValidationError import in TestIssue326V1SubaccountGeZero (already at module scope per round-3). |
Code Review — PR #363OverviewThree well-scoped model-layer integrity fixes: What's Good
Issues1. Missing async runtime test for #350 (minor gap)
The fix in @pytest.mark.asyncio
async def test_issue_350_async_orders_create_overload_requires_action_count(
self, test_auth: KalshiAuth
) -> None:
...
with pytest.raises(TypeError, match=r"action"):
await resource.create( # type: ignore[call-overload]
ticker="TEST",
side="yes",
count=1,
)2.
|
Summary
Three model-layer integrity fixes that close drift between V1 and V2 order
request schemas and tighten the public
orders.create()overload so thestatic contract matches the runtime guard. The headline behavioral break
mirrors v2.5 #270 lineage:
AmendOrderRequest.side/.actionno longeraccept arbitrary strings.
Issues closed
strafter v2.5 narrowed CreateOrderRequest #312 —AmendOrderRequest.side/.actionnarrowed toSideLiteral/ActionLiteral, matching the v2.5 Polish bundle: model small items (AwareDatetime in WS, V1 Literal, default=None, timestamp typing, Decimal NaN) #270 narrowing onCreateOrderRequest. The stale module-level "leave asstrto remaintolerant of spec drift" comment is removed; both V1 and V2 create paths
(and now amend) are narrowed.
CreateOrderRequest,AmendOrderRequest,DecreaseOrderRequest, andBatchCancelOrdersRequestOrdergainField(default=None, ge=0)onsubaccountandexchange_index. Sweepsthe two V2
exchange_indexslots missed by bug:int-typed request fields silently coercebool(subaccount routing, transfers, counts) #295(
CreateOrderV2Request,BatchCancelOrdersV2RequestOrder) for paritywith
subaccount, communications, order_groups, and thesubaccount-transfer body.
OrdersResource.create/AsyncOrdersResource.createkwarg overloads require
action: ActionLiteralandcount: int(no
None, no default). Runtime requirement has been there sincev2.5 orders.create() silently defaults count=1 and action="buy" on the kwarg path #242; this lines up the type-system contract so mypy --strict
rejects the missing-arg shape that previously deferred to a runtime
TypeError.Behavioral changes
AmendOrderRequest.side/.actionnow reject non-Literal strings(AmendOrderRequest.side/.action still bare
strafter v2.5 narrowed CreateOrderRequest #312). A typo likeside="yess"raisesValidationErroratconstruction time instead of being signed by the SDK and 400'd by the
server. This is the same fail-fast story v2.5 Polish bundle: model small items (AwareDatetime in WS, V1 Literal, default=None, timestamp typing, Decimal NaN) #270 told for
CreateOrderRequest; minimal surprise expected (no caller intentionallyamends with a non-spec value), but it is a strict tightening.
subaccount/exchange_indexreject negatives (V1 order request models miss ge=0 on subaccount/exchange_index that V2 and other models enforce #326).Previously bare
StrictInt | None; nowField(default=None, ge=0).Mirrors V2 + every other request model in the SDK.
orders.create(...)kwarg overload requiresactionandcount(orders.create type overload still permits omitting required action/count #350). Static-only change: mypy --strict refuses
client.orders.create(ticker="T", side="yes")against the kwarg form.The
request=CreateOrderRequest(...)overload is unaffected.Tests
tests/test_models.py::TestIssue312AmendOrderRequestLiteralNarrowing—invalid-side / invalid-action rejected; valid
Literalvalues accepted.tests/test_models.py::TestIssue326V1SubaccountGeZero— parametrizedacross all 10 newly-constrained fields (8 V1 + 2 V2-exchange_index
slots): negative values raise
ValidationError, zero remains valid(primary subaccount / first shard).
tests/test_client.py::TestIssue350OrdersCreateOverloadRequiresActionCount— runtime
TypeErrorwhenactionorcountis omitted; explicit# type: ignore[call-overload]markers also act as the static fence(under
warn_unused_ignores/--strictthese markers would themselvesfail if the overload were re-loosened).
resource.create(ticker="TEST", side="yes")callsites intests/test_client.pyandtests/test_async_client.py(the auth-required guards) updated to satisfy the tightened overload.
EXCLUSIONS / contract drift
None. The existing
tests/_contract_support.py::EXCLUSIONSentries forAmendOrderRequest(cent-form prices,count/count_fpserialization)are unrelated to the enum narrowing.
test_contracts.pypasses unchanged— the OpenAPI spec defines
side/actionas enums, so narrowing theSDK model to
Literal[...]moves the SDK closer to the spec, not away.Source
Round-3 independent audit closure plan, wave W1 (HIGH-severity integrity
fixes), PR W1-D.