fix(orders): require count+action on create() kwarg path; reject bool in buy_max_cost#281
Conversation
Pre-#242 the kwarg-form `client.orders.create(ticker=..., side='yes')` silently defaulted missing `count` to 1 and missing `action` to 'buy'. For a real-money SDK that converted a missing-arg bug into a real 1-contract BUY fill — money risk. Drop the silent defaults on both sync and async kwarg paths and raise `TypeError` before any HTTP request when either is None. Mirror the spec at the model level by removing the `Decimal('1')` default on `CreateOrderRequest.count` (spec lists `count` as required); the `action` field was already required since #172. The `request=CreateOrderRequest(...)` overload path is unaffected: the model now declares both required so a fully-populated request still dispatches normally. BREAKING CHANGE: callers relying on `orders.create(ticker, side)` defaulting to 1-contract buy must now pass `count=` and `action=` explicitly (or build a `CreateOrderRequest`). This is technically breaking but money-risk-driven. Closes #242
Code Review — PR #281OverviewThis PR closes two money-risk issues: it removes the silent What's good
Issues / suggestions1. Missing
2. TypeError regex is fragile (minor) with pytest.raises(TypeError, match=r"count.*action"):This passes today because the message reads # Option A — test both words independently
with pytest.raises(TypeError) as exc_info:
client.orders.create(ticker="X", side="yes")
msg = str(exc_info.value)
assert "count" in msg and "action" in msg
# Option B — anchor on the actual required-field clause
match=r"`count`, and `action`"3. Verbose historical context in error messages The
This is helpful to SDK developers reading the code, but end users of the SDK (who read raise TypeError(
"create() requires `ticker`, `side`, `count`, and `action` "
"(or pass `request=...`). A missing `count` or `action` would "
"otherwise place a real order on the wire."
)4. Missing The PR description references #225 as having closed bool-rejection for No concerns
SummaryThe logic is correct and the test coverage is solid. Three items before merge:
The money-risk motivation is sound and the changes are well-scoped. Approve pending the label and optional test hardening. |
`buy_max_cost` is integer cents, but bool is an `int` subclass. `buy_max_cost=True` was slipping through as the integer 1 (= a 1 cent cap) — the exact failure mode #225 closed for `DollarDecimal` and `FixedPointCount`. A caller who accidentally passed a flag (e.g. `risk_check_passed`, `dry_run`) where cents were expected ended up placing a real order capped at $0.01 with no error. Prepend an `isinstance(v, bool)` rejection before the Decimal/float branches, mirroring the wording/style of the existing rejections. Audited the file: `buy_max_cost` is the only field with a Decimal/float guard, so no other validators need the same patch. Closes #243
45529f3 to
7b6dacd
Compare
Code Review — PR #281Overview: This PR hardens two money-risk vectors on
The intent is exactly right and the structural approach is sound. A few things worth tightening before merge: Bugs / CorrectnessNone found. The guard Historical context belongs in git history, not in codePer the project's own CLAUDE.md:
Several places violate this:
Issue numbers and "previously the SDK did X" history belongs in the commit message, not the docstring. The docstring should state what is currently required.
Same issue — this is changelog content, not API documentation.
raise TypeError(
"create() requires `ticker`, `side`, `count`, and `action` "
"(or pass `request=...`). Pre-#242 the SDK silently defaulted "
"missing `count` to 1 contract and missing `action` to \"buy\" — "
"that has been removed: a missing arg would otherwise translate "
"into a real 1-contract BUY on the wire."
)The part after the first sentence is changelog noise at runtime. A caller hitting this error doesn't need to know what the SDK used to do. Trim to: raise TypeError(
"create() requires `ticker`, `side`, `count`, and `action` "
"(or pass `request=...`)"
)Validator docstring and inline test comment: """Reject Decimal, float, and bool inputs on buy_max_cost.
… Bool is an ``int`` subclass, so ``True`` would otherwise
slip through as 1 (= 1 cent cap), the same class of bug #225
closed for ``DollarDecimal`` / ``FixedPointCount``. Reject at
the boundary (#243).# #242: `count` no longer defaults to Decimal("1") — it is required.
# A missing-arg bug would otherwise silently become a 1-contract BUY.
Minor
Imports inside test body ( Test coverageCoverage is good: missing-both, missing-count-only, missing-action-only, regression (explicit args work), SummaryThe logic is correct and the safety intent is the right call for a financial API. Main ask before merge: trim the historical/issue-reference prose out of the |
Closes #242
Closes #243
Part of #273 (post-v2.4 audit), Wave 1 money-risk batch.