diff --git a/narwhals/_polars/dataframe.py b/narwhals/_polars/dataframe.py index 9653697863..94984acb73 100644 --- a/narwhals/_polars/dataframe.py +++ b/narwhals/_polars/dataframe.py @@ -338,7 +338,7 @@ def from_dicts( if not data: native = pl.DataFrame(schema=pl_schema) elif FROM_DICTS_ACCEPTS_MAPPINGS or isinstance(data[0], dict): - native = pl.from_dicts(data, pl_schema) # type: ignore[arg-type] + native = pl.from_dicts(data, pl_schema) else: # pragma: no cover columns = pl_schema or tuple(data[0]) native = pl.DataFrame( diff --git a/narwhals/_polars/utils.py b/narwhals/_polars/utils.py index 6417baafc4..397016b765 100644 --- a/narwhals/_polars/utils.py +++ b/narwhals/_polars/utils.py @@ -49,20 +49,23 @@ BACKEND_VERSION = Implementation.POLARS._backend_version() """Static backend version for `polars`.""" +SERIES_ACCEPTS_PD_INDEX: Final[bool] = BACKEND_VERSION >= (0, 20, 7) +"""`pl.Series(values: pd.Index)` fixed in https://github.com/pola-rs/polars/pull/14087""" + SERIES_RESPECTS_DTYPE: Final[bool] = BACKEND_VERSION >= (0, 20, 26) """`pl.Series(dtype=...)` fixed in https://github.com/pola-rs/polars/pull/15962 Includes `SERIES_ACCEPTS_PD_INDEX`. """ -SERIES_ACCEPTS_PD_INDEX: Final[bool] = BACKEND_VERSION >= (0, 20, 7) -"""`pl.Series(values: pd.Index)` fixed in https://github.com/pola-rs/polars/pull/14087""" +HAS_INT_128 = BACKEND_VERSION >= (1, 18, 0) +"""https://github.com/pola-rs/polars/pull/20232""" FROM_DICTS_ACCEPTS_MAPPINGS: Final[bool] = BACKEND_VERSION >= (1, 30, 0) -"""`pl.from_dicts(data: Iterable[Mapping[str, Any]])` since https://github.com/pola-rs/polars/pull/22638 +"""`pl.from_dicts(data: Iterable[Mapping[str, Any]])` since https://github.com/pola-rs/polars/pull/22638""" -Typing fix in https://github.com/pola-rs/polars/pull/24584 -""" +HAS_UINT_128 = BACKEND_VERSION >= (1, 34, 0) +"""https://github.com/pola-rs/polars/pull/24346""" @overload @@ -99,8 +102,7 @@ def native_to_narwhals_dtype( # noqa: C901, PLR0912 return dtypes.Float64() if dtype == pl.Float32: return dtypes.Float32() - if hasattr(pl, "Int128") and dtype == pl.Int128: # pragma: no cover - # Not available for Polars pre 1.8.0 + if HAS_INT_128 and dtype == pl.Int128: return dtypes.Int128() if dtype == pl.Int64: return dtypes.Int64() @@ -110,8 +112,7 @@ def native_to_narwhals_dtype( # noqa: C901, PLR0912 return dtypes.Int16() if dtype == pl.Int8: return dtypes.Int8() - if hasattr(pl, "UInt128") and dtype == pl.UInt128: # pyright: ignore[reportAttributeAccessIssue] # pragma: no cover - # Not available for Polars pre 1.8.0 + if HAS_UINT_128 and dtype == pl.UInt128: return dtypes.UInt128() if dtype == pl.UInt64: return dtypes.UInt64() @@ -169,6 +170,15 @@ def native_to_narwhals_dtype( # noqa: C901, PLR0912 dtypes = Version.MAIN.dtypes + + +def _version_dependent_dtypes() -> dict[type[DType], pl.DataType]: + if not HAS_INT_128: # pragma: no cover + return {} + nw_to_pl: dict[type[DType], pl.DataType] = {dtypes.Int128: pl.Int128()} + return nw_to_pl | {dtypes.UInt128: pl.UInt128()} if HAS_UINT_128 else nw_to_pl + + NW_TO_PL_DTYPES: Mapping[type[DType], pl.DataType] = { dtypes.Float64: pl.Float64(), dtypes.Float32: pl.Float32(), @@ -188,6 +198,7 @@ def native_to_narwhals_dtype( # noqa: C901, PLR0912 dtypes.UInt64: pl.UInt64(), dtypes.Object: pl.Object(), dtypes.Unknown: pl.Unknown(), + **_version_dependent_dtypes(), } UNSUPPORTED_DTYPES = (dtypes.Decimal,) @@ -199,9 +210,6 @@ def narwhals_to_native_dtype( # noqa: C901 base_type = dtype.base_type() if pl_type := NW_TO_PL_DTYPES.get(base_type): return pl_type - if dtype == dtypes.Int128 and hasattr(pl, "Int128"): - # Not available for Polars pre 1.8.0 - return pl.Int128() if isinstance_or_issubclass(dtype, dtypes.Enum): if version is Version.V1: msg = "Converting to Enum is not supported in narwhals.stable.v1" diff --git a/pyproject.toml b/pyproject.toml index 9ff5f6d501..dff47118e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ typing = [ # keep some of these pinned and bump periodically so there's fewer s "pyright", "pyarrow-stubs==19.2", "sqlframe", - "polars==1.32.2", + "polars==1.34.0", "uv", "narwhals[ibis]", ] diff --git a/tests/dtypes_test.py b/tests/dtypes_test.py index d353d59bcb..4ff9134c21 100644 --- a/tests/dtypes_test.py +++ b/tests/dtypes_test.py @@ -17,7 +17,7 @@ if TYPE_CHECKING: from collections.abc import Iterable - from narwhals.typing import IntoSeries, NonNestedDType + from narwhals.typing import IntoFrame, IntoSeries, NonNestedDType from tests.utils import Constructor, ConstructorPandasLike @@ -235,6 +235,10 @@ def test_pandas_fixed_offset_1302() -> None: pass +def from_native_collect_schema(native: IntoFrame) -> nw.Schema: + return nw.from_native(native).collect_schema() + + def test_huge_int() -> None: pytest.importorskip("duckdb") pytest.importorskip("polars") @@ -245,11 +249,18 @@ def test_huge_int() -> None: df = pl.DataFrame({"a": [1, 2, 3]}) if POLARS_VERSION >= (1, 18): - result = nw.from_native(df.select(pl.col("a").cast(pl.Int128))).collect_schema() + result = from_native_collect_schema(df.select(pl.col("a").cast(pl.Int128))) assert result["a"] == nw.Int128 + assert result.to_polars()["a"] == pl.Int128 else: # pragma: no cover # Int128 was not available yet pass + if POLARS_VERSION >= (1, 34): + result = from_native_collect_schema(df.select(pl.col("a").cast(pl.UInt128))) + assert result["a"] == nw.UInt128 + assert result.to_polars()["a"] == pl.UInt128 + else: # pragma: no cover + pass rel = duckdb.sql(""" select cast(a as int128) as a @@ -263,7 +274,7 @@ def test_huge_int() -> None: select cast(a as uint128) as a from df """) - result = nw.from_native(rel).collect_schema() + result = from_native_collect_schema(rel) assert result["a"] == nw.UInt128 # TODO(unassigned): once other libraries support Int128/UInt128,