Source code for fabrictools.powerquery.number

"""Power Query ``Number.*`` column expressions."""

from __future__ import annotations

from typing import Union

from pyspark.sql import Column
from pyspark.sql import functions as F

from fabrictools.powerquery.text import Text

__all__ = ["Number"]


def _from_text_expr(expr: Union[Column, str]) -> Column:
    """Internal: parse text to nullable double (``fxToNumber`` rules)."""
    t0 = Text.From(expr)
    t1 = Text.Select(t0, list("0123456789,.-"))

    empty = (t1 == F.lit("")) | (t1 == F.lit("-"))
    has_comma = t1.contains(",")
    has_dot = t1.contains(".")
    comma_count = F.length(t1) - F.length(F.regexp_replace(t1, ",", ""))

    us_thousands = (has_comma & has_dot) | (comma_count > F.lit(1))
    us_stripped = F.regexp_replace(t1, ",", "")
    fr_normalized = F.regexp_replace(t1, ",", ".")

    parsed = (
        F.when(empty, F.lit(None).cast("double"))
        .when(us_thousands, us_stripped.cast("double"))
        .when(has_comma, fr_normalized.cast("double"))
        .when(has_dot, t1.cast("double"))
        .otherwise(t1.cast("double"))
    )
    return parsed


[docs] class Number: """Namespace for Power Query ``Number.*`` functions."""
[docs] @staticmethod def FromText(expr: Union[Column, str]) -> Column: """Convert text to a nullable number with US/FR decimal rules (Power Query ``Number.FromText``). Implements the invoicing script ``fxToNumber`` logic: strips non-numeric characters, detects thousand separators vs decimal comma/point, and parses accordingly. :param expr: Spark column or string literal. :type expr: ~pyspark.sql.Column | str :returns: Nullable ``double`` column expression. :rtype: ~pyspark.sql.Column .. rubric:: Example >>> from fabrictools import Table, Number # doctest: +SKIP >>> df = Table.TransformColumns(df, [ # doctest: +SKIP ... ("Total Invoice amount without VAT", Number.FromText), ... ]) """ return _from_text_expr(expr)