From bf9cb140796fd4d58d6cd65db3178ce4f035dde1 Mon Sep 17 00:00:00 2001 From: Marco Edward Gorelli Date: Sat, 2 Nov 2024 11:56:18 +0000 Subject: [PATCH] fix: np.datetime64 scalar with ns resolution was become int instead of datetime in to_py_scalar (#1304) * fix: np.datetime64 scalar with ns resolution was become int instead of datetime in to_py_scalar * coverage * coverage --- narwhals/translate.py | 7 ++++++ tests/translate/to_py_scalar_test.py | 35 +++++++++++++--------------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/narwhals/translate.py b/narwhals/translate.py index 075f9b3a1..65cc44226 100644 --- a/narwhals/translate.py +++ b/narwhals/translate.py @@ -857,6 +857,13 @@ def to_py_scalar(scalar_like: Any) -> Any: return scalar_like np = get_numpy() + if ( + np + and isinstance(scalar_like, np.datetime64) + and scalar_like.dtype == "datetime64[ns]" + ): + return datetime(1970, 1, 1) + timedelta(microseconds=scalar_like.item() // 1000) + if np and np.isscalar(scalar_like) and hasattr(scalar_like, "item"): return scalar_like.item() diff --git a/tests/translate/to_py_scalar_test.py b/tests/translate/to_py_scalar_test.py index 3519b5e87..b13ba009e 100644 --- a/tests/translate/to_py_scalar_test.py +++ b/tests/translate/to_py_scalar_test.py @@ -2,7 +2,6 @@ from datetime import datetime from datetime import timedelta -from typing import TYPE_CHECKING from typing import Any import numpy as np @@ -11,9 +10,7 @@ import narwhals.stable.v1 as nw from narwhals.dependencies import get_cudf - -if TYPE_CHECKING: - from tests.utils import ConstructorEager +from tests.utils import PANDAS_VERSION @pytest.mark.parametrize( @@ -28,31 +25,31 @@ (b"a", b"a"), (datetime(2021, 1, 1), datetime(2021, 1, 1)), (timedelta(days=1), timedelta(days=1)), + (pd.Timestamp("2020-01-01"), datetime(2020, 1, 1)), + (pd.Timedelta(days=3), timedelta(days=3)), + (np.datetime64("2020-01-01", "s"), datetime(2020, 1, 1)), + (np.datetime64("2020-01-01", "ms"), datetime(2020, 1, 1)), + (np.datetime64("2020-01-01", "us"), datetime(2020, 1, 1)), + (np.datetime64("2020-01-01", "ns"), datetime(2020, 1, 1)), ], ) def test_to_py_scalar( - constructor_eager: ConstructorEager, input_value: Any, expected: Any, - request: pytest.FixtureRequest, ) -> None: - if isinstance(input_value, bytes) and "cudf" in str(constructor_eager): - request.applymarker(pytest.mark.xfail) - df = nw.from_native(constructor_eager({"a": [input_value]})) - output = nw.to_py_scalar(df["a"].item(0)) - if expected == 1 and constructor_eager.__name__.startswith("pandas"): + output = nw.to_py_scalar(input_value) + if expected == 1: assert not isinstance(output, np.int64) - elif isinstance(expected, datetime) and constructor_eager.__name__.startswith( - "pandas" - ): - assert not isinstance(output, pd.Timestamp) - elif isinstance(expected, timedelta) and constructor_eager.__name__.startswith( - "pandas" - ): - assert not isinstance(output, pd.Timedelta) assert output == expected +@pytest.mark.skipif( + PANDAS_VERSION < (1,), reason="there was a (better?) time when there was no pd.NA" +) +def test_na_to_py_scalar() -> None: + assert nw.to_py_scalar(pd.NA) is None + + @pytest.mark.parametrize( "input_value", [np.array([1, 2]), [1, 2, 3], {"a": [1, 2, 3]}],