Skip to content

Public to_decimal() helper accepts NaN/Infinity despite docstring promising safety #325

Closed

Description

@TexasCoding

Problem

v2.5 #270 added an is_finite() guard to _coerce_decimal so a downstream NaN cannot ship the string "NaN" to a real-money endpoint. The sibling public helper to_decimal() — documented as the user-facing safe constructor — was not updated. to_decimal(float('nan')) returns Decimal('NaN') cleanly; to_decimal(float('inf')) returns Decimal('Infinity').

Users following the documented pattern of pre-coercing values with to_decimal() before passing them into model construction get a false sense of safety. Any code path that bypasses pydantic re-validation — direct Decimal arithmetic on to_decimal()'s output, then model_dump_json() on a hand-built dict, or pre-constructed Decimal values that match the field type and skip the BeforeValidator — ships "NaN" to the wire.

Evidence

  • kalshi/types.py:40-42 — _coerce_decimal rejects non-finite
if not result.is_finite():
    raise ValueError("Decimal must be finite (got NaN/Infinity)")
return result
  • kalshi/types.py:94-107 — to_decimal skips the guard
def to_decimal(value: int | float | str | Decimal) -> Decimal:
    """Convert a user-supplied price/count value to Decimal safely.
    ...
    """
    if isinstance(value, bool):
        raise TypeError(...)
    if isinstance(value, Decimal):
        return value
    return Decimal(str(value))
  • CHANGELOG.md:208 — #270 rejects NaN/Infinity at the boundary (only via _coerce_decimal).

Suggested fix

Replace the body of to_decimal() with a delegation to _coerce_decimal(value) so both code paths share the same is_finite() guard. Add a test_to_decimal_rejects_nan_inf regression test next to the existing TestToDecimal class.

Source

Round-3 independent audit (reviewer: models_contracts).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions