Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c55949f
Pin Polars<1.43
Matt711 Jun 25, 2026
c82d12c
Merge branch 'main' into imp/polars/bump-polars-1.43
Matt711 Jun 29, 2026
5bc2314
compat IR
Matt711 Jun 29, 2026
e6887c6
only polars CI
Matt711 Jun 29, 2026
cbaf750
Merge branch 'main' into imp/polars/bump-polars-1.43
Matt711 Jun 29, 2026
f95b02b
Merge branch 'main' into imp/polars/bump-polars-1.43
Matt711 Jun 29, 2026
5bb9a56
fix xpass'ing tests and implemented is_sorted boolean expr
Matt711 Jun 29, 2026
950fe2a
fallback for hive-partitioned scans, skip upstream polars tests
Matt711 Jun 29, 2026
5f9aaf7
Merge branch 'main' into imp/polars/bump-polars-1.43
Matt711 Jun 29, 2026
93434d6
unpin polars version in pre-commit and a test for boolean is_sorted
Matt711 Jun 29, 2026
1ad3e5f
code coverage :(
Matt711 Jun 30, 2026
6c041a4
temp test all versions
Matt711 Jun 30, 2026
585c4b0
Merge branch 'main' into imp/polars/bump-polars-1.43
Matt711 Jun 30, 2026
7bb624f
add dyn pred comment and revert polars only CI
Matt711 Jun 30, 2026
0564da0
xfail more upstream polars tests
Matt711 Jun 30, 2026
cb31390
Merge branch 'main' into imp/polars/bump-polars-1.43
Matt711 Jun 30, 2026
363ebe2
fix incorrect DeprecationWarning failure in in xfail set, and use Col…
Matt711 Jun 30, 2026
966a510
Merge branch 'main' into imp/polars/bump-polars-1.43
Matt711 Jun 30, 2026
67ee448
Merge branch 'main' into imp/polars/bump-polars-1.43
Matt711 Jul 1, 2026
9741a1d
update xfails
Matt711 Jul 2, 2026
7c60952
Merge branch 'main' into imp/polars/bump-polars-1.43
Matt711 Jul 2, 2026
909017a
skip tests
Matt711 Jul 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ repos:
hooks:
- id: mypy
additional_dependencies: [
"polars>=1.35,<1.39",
"polars>=1.35,<1.43",
"numpy>=1.26",
"pyarrow-stubs>=19.0",
"pyarrow>=19.0.0,<24.0.0", # https://github.com/rapidsai/cudf/issues/22229
Expand Down
8 changes: 6 additions & 2 deletions ci/test_narwhals.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/bash
# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION.
# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

set -euo pipefail
Expand Down Expand Up @@ -122,6 +122,9 @@ PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 \
# test_dtypes: narwhals' dtype mapping changed with polars 1.40 (reports Object where the test expects Int8).
# test_namespace_len[polars[lazy]]: len() row count lost in zero-column streaming chunks
# (https://github.com/rapidsai/cudf/issues/21428).
# test_explode_*[polars[lazy]-*]: polars 1.42 emits a DeprecationWarning when explode() is called

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't found time TODO #19594 yet. Maybe I'll put an agent on it

# without empty_as_null=True; narwhals 2.16.0 doesn't pass the kwarg yet, so filterwarnings=error
# turns this into a failure. Remove once narwhals is updated.
TESTS_THAT_NEED_NARWHALS_FIX_FOR_CUDF_POLARS=" \
test_dtypes or \
test_namespace_len[polars[lazy]] or \
Expand All @@ -131,7 +134,8 @@ test_to_datetime_tz_aware[polars[lazy]-None] or \
test_truncate[polars[lazy]-1ns-expected0] or \
test_truncate_multiples[polars[lazy]-2ns-expected0] or \
((test_nested_structures and polars and lazy) and (value0 or value1 or value3 or value4 or value6 or value7)) or \
(test_series_from_iterable and pandas) \
(test_series_from_iterable and pandas) or \
(test_explode and polars and lazy) \
"

rapids-logger "Run narwhals tests for cuDF Polars"
Expand Down
2 changes: 1 addition & 1 deletion conda/environments/all_cuda-129_arch-aarch64.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ dependencies:
- packaging
- pandas>=3.0.0,<3.0.4a0
- pandoc
- polars>=1.35,<1.42
- polars>=1.35,<1.43
- pre-commit
- pyarrow>=19.0.0,<24
- pytables
Expand Down
2 changes: 1 addition & 1 deletion conda/environments/all_cuda-129_arch-x86_64.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ dependencies:
- packaging
- pandas>=3.0.0,<3.0.4a0
- pandoc
- polars>=1.35,<1.42
- polars>=1.35,<1.43
- pre-commit
- pyarrow>=19.0.0,<24
- pytables
Expand Down
2 changes: 1 addition & 1 deletion conda/environments/all_cuda-133_arch-aarch64.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ dependencies:
- packaging
- pandas>=3.0.0,<3.0.4a0
- pandoc
- polars>=1.35,<1.42
- polars>=1.35,<1.43
- pre-commit
- pyarrow>=19.0.0,<24
- pytables
Expand Down
2 changes: 1 addition & 1 deletion conda/environments/all_cuda-133_arch-x86_64.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ dependencies:
- packaging
- pandas>=3.0.0,<3.0.4a0
- pandoc
- polars>=1.35,<1.42
- polars>=1.35,<1.43
- pre-commit
- pyarrow>=19.0.0,<24
- pytables
Expand Down
4 changes: 2 additions & 2 deletions conda/recipes/cudf-polars/recipe.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: Copyright (c) 2018-2026, NVIDIA CORPORATION.
# SPDX-FileCopyrightText: Copyright (c) 2018-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
schema_version: 1

Expand Down Expand Up @@ -38,7 +38,7 @@ requirements:
- pylibcudf =${{ version }}
- rapidsmpf =${{ minor_version }}
- cudf-streaming =${{ version }}
- polars>=1.35,<1.42
- polars>=1.35,<1.43
- packaging
- ${{ pin_compatible("cuda-version", upper_bound="x", lower_bound="x") }}
- if: cuda_major == "12"
Expand Down
8 changes: 6 additions & 2 deletions dependencies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ files:
test_cudf_polars_compat:
output: none
matrix:
polars_compat_version: ["1.35", "1.36", "1.37", "1.38", "1.39", "1.40", "1.41"]
polars_compat_version: ["1.35", "1.36", "1.37", "1.38", "1.39", "1.40", "1.41", "1.42"]
includes:
- test_cudf_polars_compat
channels:
Expand Down Expand Up @@ -967,7 +967,7 @@ dependencies:
# 'nvidia-ml-py' provides the 'pynvml' module
- &nvidia_ml_py nvidia-ml-py>=12
- packaging
- polars>=1.35,<1.42
- polars>=1.35,<1.43
- typing_extensions>=4.0.0
run_cudf_polars_dask:
common:
Expand Down Expand Up @@ -1741,5 +1741,9 @@ dependencies:
polars_compat_version: "1.41"
packages:
- polars==1.41.*
- matrix:
polars_compat_version: "1.42"
packages:
- polars==1.42.*
- matrix:
packages:
2 changes: 1 addition & 1 deletion python/cudf_polars/cudf_polars/containers/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def __init__(
self.stream = stream

def __arrow_c_array__(
self, requested_schema: None = None
self, requested_schema: object | None = None
) -> tuple[CapsuleType, CapsuleType]:
return self.obj._to_schema(self.metadata), self.obj._to_host_array(
stream=self.stream
Expand Down
22 changes: 22 additions & 0 deletions python/cudf_polars/cudf_polars/dsl/expressions/boolean.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class Name(IntEnum):
IsNotNan = auto()
IsNotNull = auto()
IsNull = auto()
IsSorted = auto()
IsUnique = auto()
Not = auto()

Expand Down Expand Up @@ -100,6 +101,7 @@ def __init__(
BooleanFunction.Name.IsDuplicated,
BooleanFunction.Name.IsFirstDistinct,
BooleanFunction.Name.IsLastDistinct,
BooleanFunction.Name.IsSorted,
BooleanFunction.Name.IsUnique,
)
if self.name in {
Expand Down Expand Up @@ -409,6 +411,26 @@ def do_evaluate(
),
dtype=self.dtype,
)
elif self.name is BooleanFunction.Name.IsSorted:
(column,) = columns
(descending, nulls_last) = self.options
order = (
plc.types.Order.DESCENDING if descending else plc.types.Order.ASCENDING
)
null_order = (
plc.types.NullOrder.AFTER if nulls_last else plc.types.NullOrder.BEFORE
)
bool_result: bool = column.check_sorted(
order=order, null_order=null_order, stream=df.stream
)
return Column(
plc.Column.from_scalar(
plc.Scalar.from_py(py_val=bool_result, stream=df.stream),
1,
stream=df.stream,
),
dtype=self.dtype,
)
elif self.name is BooleanFunction.Name.Not:
(column,) = columns
# Polars semantics:
Expand Down
29 changes: 24 additions & 5 deletions python/cudf_polars/cudf_polars/dsl/translate.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,15 @@
POLARS_VERSION_LT_139,
POLARS_VERSION_LT_140,
POLARS_VERSION_LT_141,
POLARS_VERSION_LT_142,
)

if TYPE_CHECKING:
from collections.abc import Generator

from polars import GPUEngine

from cudf_polars.typing import NodeTraverser
from cudf_polars.typing import NodeTraverser, Slice as Zlice

__all__ = ["Translator", "translate_named_expr"]

Expand Down Expand Up @@ -164,7 +165,7 @@ def translate_ir(self, *, n: int | None = None) -> ir.IR:
# IR is versioned with major.minor, minor is bumped for backwards
# compatible changes (e.g. adding new nodes), major is bumped for
# incompatible changes (e.g. renaming nodes).
if (version := self.visitor.version()) >= (13, 1):
if (version := self.visitor.version()) >= (14, 4):
e = NotImplementedError(
f"No support for polars IR {version=}"
) # pragma: no cover; no such version for now.
Expand Down Expand Up @@ -291,10 +292,16 @@ def _drop_dyn_pred_hints(
) -> expr.Expr | None:
try:
node = translator.visitor.view_expression(n)
except Exception as e:
except Exception as e: # pragma: no cover
if str(e) == "dynamic_pred":
return None
raise # pragma: no cover
raise
if (
not POLARS_VERSION_LT_142
and isinstance(node, plrs._expr_nodes.Function)
and node.function_data[0] == "dynamic_pred"
):
return None
if isinstance(node, plrs._expr_nodes.BinaryExpr) and node.op in (
plrs._expr_nodes.Operator.And,
plrs._expr_nodes.Operator.LogicalAnd,
Expand Down Expand Up @@ -410,6 +417,8 @@ def _(node: plrs._ir_nodes.Scan, translator: Translator, schema: Schema) -> ir.I
raise NotImplementedError(
"Iceberg format is not supported in cudf-polars. Furthermore, row-level deletions are not supported."
) # pragma: no cover
if not POLARS_VERSION_LT_142 and node.hive_parts is not None:
raise NotImplementedError("Hive-partitioned scans are not supported")
config_options = translator.config_options
parquet_options = config_options.parquet_options

Expand Down Expand Up @@ -657,7 +666,17 @@ def _(node: plrs._ir_nodes.Sort, translator: Translator, schema: Schema) -> ir.I
order, null_order = sorting.sort_order(
descending, nulls_last=nulls_last, num_keys=len(by)
)
return ir.Sort(schema, by, order, null_order, stable, node.slice, inp)
# TODO: use the DynamicPred hint from node.slice (offset, length,
# dynamic_pred_id). dynamic_pred_id is an int (u128 UUID) matching a
# DynamicPred Function node in an upstream scan. The sort actor could
# set a threshold predicate on it (e.g. col < max_seen) to prune rows
# at the source.
if not POLARS_VERSION_LT_142 and node.slice is not None:
offset, length, *_ = node.slice
zlice: Zlice | None = (offset, length)
else:
zlice = node.slice
return ir.Sort(schema, by, order, null_order, stable, zlice, inp)


@_translate_ir.register
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ def pytest_report_header(config: pytest.Config) -> str:
"tests/unit/io/test_iceberg.py::test_scan_iceberg_nested_column_cast_deletion_rename": "Iceberg column_mapping (schema evolution) not yet implemented in cudf-polars",
"tests/unit/io/test_iceberg.py::test_scan_iceberg_parquet_prefilter_with_column_mapping": "Iceberg column_mapping (schema evolution) not yet implemented in cudf-polars",
"tests/unit/io/test_iceberg.py::test_fill_missing_fields_with_identity_partition_values_nested": "Iceberg partition column injection not yet implemented in cudf-polars",
"tests/unit/io/test_iceberg.py::test_scan_iceberg_fast_count": "Iceberg support not yet implemented in cudf-polars",
"tests/unit/io/test_iceberg.py::test_scan_iceberg_fast_count[native]": "Iceberg fast count from metadata not yet supported in cudf-polars",
"tests/unit/io/test_iceberg.py::test_iceberg_filter_bool_26474": "Iceberg support not yet implemented in cudf-polars",
"tests/unit/io/test_io_plugin.py::test_defer_validate_false": "cudf-polars always validates the IO source schema, so validate_schema=False dtype mismatches are unsupported on GPU",
"tests/unit/io/test_io_plugin.py::test_datetime_io_predicate_pushdown_21790": "cudf-polars validates the IO source schema exactly and does not coerce datetime time units (us vs ns)",
Expand Down Expand Up @@ -248,6 +248,7 @@ def pytest_report_header(config: pytest.Config) -> str:
"tests/unit/sql/test_cast.py::test_cast_errors[values2-values::int1-conversion from `i64` to `i8` failed]": "Casting that raises not supported on GPU",
"tests/unit/sql/test_cast.py::test_cast_errors[values5-values::int4-conversion from `str` to `i32` failed]": "Cast raises, but error user receives is wrong",
"tests/unit/lazyframe/test_predicates.py::test_predicate_pushdown_split_pushable": "Casting that raises not supported on GPU",
"tests/unit/lazyframe/test_predicates.py::test_filter_contradiction_fallible_error_handling": "Casting that raises not supported on GPU",
"tests/unit/sql/test_miscellaneous.py::test_read_csv": "Incorrect handling of missing_is_null in read_csv",
"tests/unit/lazyframe/test_cse.py::test_cse_predicate_self_join[False]": "Debug output on stderr doesn't match, see https://github.com/rapidsai/cudf/issues/22967",
"tests/unit/lazyframe/test_cse.py::test_cse_predicate_self_join[True]": "Debug output on stderr doesn't match, see https://github.com/rapidsai/cudf/issues/22967",
Expand Down Expand Up @@ -344,6 +345,12 @@ def pytest_report_header(config: pytest.Config) -> str:
"tests/unit/io/test_scan.py::test_scan_metrics[False-parquet]": "Checks to IO metric logs specific to Polars CPU",
"tests/unit/io/test_scan.py::test_scan_metrics[False-csv]": "Checks to IO metric logs specific to Polars CPU",
"tests/unit/io/test_scan.py::test_scan_metrics[False-ndjson]": "Checks to IO metric logs specific to Polars CPU",
# polars 1.42 updated these tests to also assert deprecated_call and strict=True ShapeError
# in the same test function. The SPMD engine fails on the strict=True collect() inside a
# pytest.raises block because the DeprecationWarning from how='horizontal' propagates differently
# across engines. Skip both runs rather than xfail (which would XPASS on in-memory).
"tests/unit/lazyframe/test_predicates.py::test_hconcat_predicate": "polars 1.42: test uses deprecated how='horizontal' with strict=True in ways that behave differently across GPU engines",
"tests/unit/functions/test_union.py::test_union_lazyframe_horizontal": "polars 1.42: test uses deprecated how='horizontal' with strict=True in ways that behave differently across GPU engines",
}


Expand Down
3 changes: 2 additions & 1 deletion python/cudf_polars/cudf_polars/utils/versions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES.
# SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Version utilities so that cudf_polars supports a range of polars versions."""
Expand All @@ -18,6 +18,7 @@
POLARS_VERSION_LT_139 = POLARS_VERSION < parse("1.39.0")
POLARS_VERSION_LT_140 = POLARS_VERSION < parse("1.40.0")
POLARS_VERSION_LT_141 = POLARS_VERSION < parse("1.41.0")
POLARS_VERSION_LT_142 = POLARS_VERSION < parse("1.42.0")


def _ensure_polars_version() -> None:
Expand Down
2 changes: 1 addition & 1 deletion python/cudf_polars/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ dependencies = [
"cudf-streaming==26.8.*,>=0.0.0a0",
"nvidia-ml-py>=12",
"packaging",
"polars>=1.35,<1.42",
"polars>=1.35,<1.43",
"pylibcudf==26.8.*,>=0.0.0a0",
"rapidsmpf==26.8.*,>=0.0.0a0",
"typing_extensions>=4.0.0",
Expand Down
8 changes: 7 additions & 1 deletion python/cudf_polars/tests/dsl/test_serialization.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES.
# SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations
Expand All @@ -16,6 +16,7 @@
from cudf_polars.utils.versions import (
POLARS_VERSION_LT_138,
POLARS_VERSION_LT_141,
POLARS_VERSION_LT_142,
)


Expand Down Expand Up @@ -57,6 +58,9 @@ def test_from_polars_all_names(function):
if POLARS_VERSION_LT_141 and function == BooleanFunction:
# 'HasNulls' and 'IsEmpty' were added to polars' BooleanFunction in 1.41.
cudf_polars_names_set = cudf_polars_names_set - {"HasNulls", "IsEmpty"}
if POLARS_VERSION_LT_142 and function == BooleanFunction:
# 'IsSorted' was added to polars' BooleanFunction in 1.42.
cudf_polars_names_set = cudf_polars_names_set - {"IsSorted"}
assert polars_names_set == cudf_polars_names_set
names = function.Name
if function == StructFunction:
Expand All @@ -71,6 +75,8 @@ def test_from_polars_all_names(function):
BooleanFunction.Name.HasNulls,
BooleanFunction.Name.IsEmpty,
}
if POLARS_VERSION_LT_142 and function == BooleanFunction:
names = set(names) - {BooleanFunction.Name.IsSorted}
for name in names:
attr = getattr(polars_function, name.name)
assert function.Name.from_polars(attr) == name
Expand Down
32 changes: 31 additions & 1 deletion python/cudf_polars/tests/expressions/test_booleanfunction.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES.
# SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations

Expand All @@ -12,6 +12,7 @@
assert_gpu_result_equal,
assert_ir_translation_raises,
)
from cudf_polars.utils.versions import POLARS_VERSION_LT_142

if TYPE_CHECKING:
from collections.abc import Callable
Expand Down Expand Up @@ -294,6 +295,35 @@ def test_boolean_not_with_integers(engine: pl.GPUEngine, dtype, col):
assert_gpu_result_equal(q, engine=engine)


@pytest.mark.skipif(
POLARS_VERSION_LT_142, reason="IsSorted BooleanFunction was added in polars 1.42"
)
@pytest.mark.parametrize("descending", [False, True])
@pytest.mark.parametrize("nulls_last", [False, True])
def test_boolean_is_sorted(
engine: pl.GPUEngine, *, descending: bool, nulls_last: bool, has_nulls: bool
) -> None:
values: list[int | None] = [1, 2, 3, 4, 5]
if has_nulls:
values[2] = None

ldf = pl.LazyFrame(
{
"asc": pl.Series(values, dtype=pl.Int64()),
"desc": pl.Series(list(reversed(values)), dtype=pl.Int64()),
"unsorted": pl.Series([3, 1, None, 4, 2], dtype=pl.Int64()),
}
)

q = ldf.select(
pl.col("asc").is_sorted(descending=descending, nulls_last=nulls_last),
pl.col("desc").is_sorted(descending=descending, nulls_last=nulls_last),
pl.col("unsorted").is_sorted(descending=descending, nulls_last=nulls_last),
)

assert_gpu_result_equal(q, engine=engine)


@pytest.mark.parametrize(
"expr",
[
Expand Down
Loading
Loading