fix(types): to_decimal rejects NaN/Inf; Page._columns handles None-first nullable nested; DollarDecimal bounds on requests#369
Conversation
to_decimal accepted NaN/Infinity despite docstring promising safety; now delegates to _coerce_decimal so the public helper and the DollarDecimal / FixedPointCount validators share the same is_finite() guard (#325). Page._columns peeked only col[0] to decide whether to dump nested BaseModel cells. An Optional[NestedModel] column whose first row is None (common across Market/Event/Series/Settlement nullable struct columns) skipped the dump pass and leaked raw BaseModel instances into pandas/polars, breaking the polars Struct dtype the docstring promises. Now probes the whole column for the first non-None BaseModel (#328). Request-side DollarDecimal price fields had no sign or tick bounds — Decimal('-0.65') or Decimal('0.12345') constructed cleanly and round-tripped to a server 400. Introduce an OrderPrice annotated alias (BeforeValidator coerce + AfterValidator non-negative + $0.0001 tick) applied to CreateOrderRequest / AmendOrderRequest yes_price/no_price and CreateOrderV2Request / AmendOrderV2Request price. Response-side fields (PnL/fee/settlement averages) keep bare DollarDecimal since negatives are legitimate there (#343). Closes #325, #328, #343
Code ReviewThree clean, targeted correctness fixes. The PR description is accurate and the test coverage is solid. A few observations below — all minor; nothing blocks merge. Fix 1 —
|
| Area | Verdict |
|---|---|
| Correctness | ✅ All three fixes are accurate |
| Wire compatibility | ✅ OrderPrice serializes identically to DollarDecimal |
| Test coverage | ✅ Happy path, negative path, edge cases covered |
| Style / conventions | Minor: extra blank line in test_types.py |
| Public API surface | Consider whether OrderPrice belongs in __init__.py |
Nothing here blocks merge. The extra blank line is the only ruff-flagged concern.
Summary
Three MEDIUM/LOW correctness fixes from the Round-3 audit closure plan (wave W2).
Closes a NaN/Inf hole in the public
to_decimalhelper, repairs aPage._columnscolumn-detection bug that broke nullable nested
Structinference under polars,and adds sign / tick bounds to request-side price fields so invalid inputs fail
at construction instead of round-tripping to a server 400.
Issues closed
to_decimal()acceptedNaN/Infinitydespite the docstring promising safety; now delegates to_coerce_decimalso both code paths share the existingis_finite()guard.Page._columnspeeked onlycol[0]to decide whether to dump nestedBaseModelcells; aNone-first nullable nested column skipped the dump pass and leaked rawBaseModelinstances. Now probes the whole column for the first non-NoneBaseModel.DollarDecimalprice fields had no sign or tick bounds. Introduces anOrderPricealias (sign +$0.0001tick) onCreateOrderRequest.yes_price/no_price,AmendOrderRequest.yes_price/no_price,CreateOrderV2Request.price,AmendOrderV2Request.price. Response-side fields keep bareDollarDecimal(negatives are legitimate for PnL / fees / settlements).Behavioral changes
to_decimal(float('nan'))/to_decimal(Decimal('Infinity'))/to_decimal("NaN")now raiseValueError("Decimal must be finite ...")instead of returning a non-finiteDecimal.CreateOrderRequest,AmendOrderRequest,CreateOrderV2Request,AmendOrderV2Requestnow reject negative prices and prices finer than the$0.0001tick at construction (ValidationError). Valid (zero or tick-aligned, non-negative) prices behave identically.Page.to_polars()/Page.to_dataframe()now correctly dump nested-model columns even when the first row's nested value isNone(visible as a polarsStructdtype rather than anobjectcolumn or aValueError).Tests
tests/test_types.py::TestIssue325ToDecimalRejectsNanInf—to_decimalrejectsNaN/Infinity/-InfinityfromDecimal/str/floatinputs; finiteDecimalidentity is preserved.tests/test_models.py::TestIssue328PageColumnsNoneFirstNested—Page._columnsandPage.to_polars()over aNone-first nullable nested column produce the dumpeddictcell and the polarsStructdtype.tests/test_models.py::TestIssue343DollarDecimalRequestBounds— V1 and V2 create/amend requests reject negative prices and sub-tick precision; zero and tick-aligned values still construct; V2 response averages keep bareDollarDecimal(negatives accepted).Source
Round-3 independent audit closure plan, wave
W2.