Skip to content

Consolidate _to_decimal_dollars and _to_decimal_fp — byte-identical helpers #174

Description

@TexasCoding

From #106 F-N-09 and the follow-up note in #140. Severity: trivial cleanup.

Code

kalshi/types.py:13-27 and kalshi/types.py:47-60 define two functions whose bodies are byte-identical apart from the docstring:

def _to_decimal_dollars(value: Any) -> Decimal:
    if isinstance(value, Decimal):
        return value
    if isinstance(value, (int, float)):
        return Decimal(str(value))
    if isinstance(value, str):
        return Decimal(value)
    raise TypeError(f"Cannot convert {type(value).__name__} to Decimal")

def _to_decimal_fp(value: Any) -> Decimal:
    # ... identical body ...

Both are used as BeforeValidator on the public DollarDecimal and FixedPointCount Annotated aliases.

Fix

Collapse into a single private _coerce_decimal(value: Any) -> Decimal and wire both Annotated aliases to it. The public DollarDecimal and FixedPointCount types stay; only the underlying helper is shared.

def _coerce_decimal(value: Any) -> Decimal:
    if isinstance(value, Decimal):
        return value
    if isinstance(value, (int, float)):
        return Decimal(str(value))
    if isinstance(value, str):
        return Decimal(value)
    raise TypeError(f"Cannot convert {type(value).__name__} to Decimal")

DollarDecimal = Annotated[
    Decimal,
    BeforeValidator(_coerce_decimal),
    PlainSerializer(_decimal_to_str, return_type=str),
]
FixedPointCount = Annotated[
    Decimal,
    BeforeValidator(_coerce_decimal),
    PlainSerializer(_decimal_to_str, return_type=str),
]

Note: the resulting Annotated aliases become structurally equivalent. Pydantic distinguishes types by identity of the Annotated form, not by the validator function, so this is safe at runtime — but worth confirming no test relies on _to_decimal_dollars is not _to_decimal_fp or on distinguishing the two aliases via get_type_hints.

Acceptance

  • One private function instead of two in kalshi/types.py.
  • All of pytest tests/ passes unchanged.
  • mypy --strict clean.
  • ruff check clean.

Metadata

Metadata

Assignees

No one assigned

    Labels

    polishCode-quality and DX improvements; non-functional

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions