From 6d4c5986a2bccc740ec9e7fefbeea0e3da935b39 Mon Sep 17 00:00:00 2001 From: MarcoGorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Tue, 21 Apr 2026 12:12:48 +0100 Subject: [PATCH 1/7] run pyrefly on tests --- narwhals/_polars/dataframe.py | 4 ++- narwhals/_polars/expr.py | 6 +++-- narwhals/_polars/namespace.py | 9 ++++--- pyproject.toml | 16 +++++++++++ tests/conftest.py | 2 +- tests/dependencies/is_into_dataframe_test.py | 2 +- tests/dependencies/is_into_series_test.py | 2 +- tests/dtypes/pandas_extension_dtypes_test.py | 4 +-- tests/expr_and_series/lit_test.py | 2 +- tests/frame/collect_test.py | 2 +- tests/frame/join_test.py | 4 +-- tests/frame/schema_test.py | 2 +- tests/implementation_test.py | 28 ++++++++++---------- tests/namespace_test.py | 12 ++++----- tests/translate/from_native_test.py | 2 +- tests/typing_compat_test.py | 3 +-- tests/utils_test.py | 2 +- tests/v1_test.py | 2 +- tests/v2_test.py | 2 +- 19 files changed, 64 insertions(+), 42 deletions(-) diff --git a/narwhals/_polars/dataframe.py b/narwhals/_polars/dataframe.py index 0848967e9a..2974a948c7 100644 --- a/narwhals/_polars/dataframe.py +++ b/narwhals/_polars/dataframe.py @@ -5,7 +5,6 @@ import polars as pl -from narwhals._polars.namespace import PolarsNamespace from narwhals._polars.series import PolarsSeries from narwhals._polars.utils import ( FROM_DICTS_ACCEPTS_MAPPINGS, @@ -44,6 +43,7 @@ from narwhals._compliant.typing import CompliantDataFrameAny, CompliantLazyFrameAny from narwhals._polars.expr import PolarsExpr from narwhals._polars.group_by import PolarsGroupBy, PolarsLazyGroupBy + from narwhals._polars.namespace import PolarsNamespace from narwhals._spark_like.utils import SparkSession from narwhals._translate import IntoArrowTable from narwhals._typing import _EagerAllowedImpl, _LazyAllowedImpl @@ -153,6 +153,8 @@ def columns(self) -> list[str]: return self.native.columns def __narwhals_namespace__(self) -> PolarsNamespace: + from narwhals._polars.namespace import PolarsNamespace + return PolarsNamespace(version=self._version) def __native_namespace__(self) -> ModuleType: diff --git a/narwhals/_polars/expr.py b/narwhals/_polars/expr.py index 1e2b546014..03f4109277 100644 --- a/narwhals/_polars/expr.py +++ b/narwhals/_polars/expr.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, ClassVar, cast +from typing import TYPE_CHECKING, Any, Callable, ClassVar import polars as pl @@ -41,6 +41,7 @@ class PolarsExpr: _evaluate_output_names: Any _alias_output_names: Any __call__: Any + _opt_metadata: ExprMetadata | None @classmethod def _from_series(cls, series: PolarsSeries) -> Self: @@ -89,7 +90,7 @@ def broadcast(self) -> Self: @property def _metadata(self) -> ExprMetadata: assert self._opt_metadata is not None # noqa: S101 - return cast("ExprMetadata", self._opt_metadata) + return self._opt_metadata def __getattr__(self, attr: str) -> Any: def func(*args: Any, **kwargs: Any) -> Any: @@ -347,6 +348,7 @@ def struct(self) -> PolarsExprStructNamespace: arg_min: Method[Self] arg_true: Method[Self] ceil: Method[Self] + clip: Method[Self] count: Method[Self] cos: Method[Self] cum_max: Method[Self] diff --git a/narwhals/_polars/namespace.py b/narwhals/_polars/namespace.py index eca964bbdb..b621d9f9aa 100644 --- a/narwhals/_polars/namespace.py +++ b/narwhals/_polars/namespace.py @@ -5,6 +5,8 @@ import polars as pl +from narwhals._compliant.namespace import CompliantNamespace +from narwhals._polars.dataframe import PolarsDataFrame from narwhals._polars.expr import PolarsExpr from narwhals._polars.series import PolarsSeries from narwhals._polars.utils import extract_args_kwargs, narwhals_to_native_dtype @@ -19,13 +21,13 @@ from typing_extensions import TypeIs from narwhals._compliant import CompliantSelectorNamespace - from narwhals._polars.dataframe import Method, PolarsDataFrame, PolarsLazyFrame + from narwhals._polars.dataframe import Method, PolarsLazyFrame from narwhals._polars.typing import FrameT from narwhals._utils import Version, _LimitedContext from narwhals.typing import Into1DArray, IntoDType, IntoSchema, TimeUnit, _2DArray -class PolarsNamespace: +class PolarsNamespace(CompliantNamespace[PolarsDataFrame, PolarsExpr]): all: Method[PolarsExpr] coalesce: Method[PolarsExpr] col: Method[PolarsExpr] @@ -130,7 +132,8 @@ def any_horizontal(self, *exprs: PolarsExpr, ignore_nulls: bool) -> PolarsExpr: it = (expr.fill_null(False) for expr in exprs) if ignore_nulls else iter(exprs) return self._expr(pl.any_horizontal(*(expr.native for expr in it)), self._version) - def concat( + # Type "PolarsDataFrame | PolarsLazyFrame" is not assignable to type "PolarsDataFrame" + def concat( # type: ignore[override] self, items: Iterable[FrameT], *, diff --git a/pyproject.toml b/pyproject.toml index 5ce6195d45..4157a7d766 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -392,3 +392,19 @@ ignore = [ "../../../**/Lib", # stdlib "../../../**/typeshed*" # typeshed-fallback ] + +[tool.pyrefly] +project-includes = ["tests"] +ignore-missing-imports = [ + "cudf.*", + "cupy.*", + "dask.*", + "dask_expr.*", + "ibis.*", + "joblib.*", + "modin.*", + "numpy.*", + "pyspark.*", + "sklearn.*", + "sqlparse.*", +] diff --git a/tests/conftest.py b/tests/conftest.py index 3e80bcdff4..3fc3e91fa9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -244,7 +244,7 @@ def ibis_lazy_constructor(obj: Data) -> ibis.Table: # pragma: no cover "cudf": cudf_constructor, "polars[eager]": polars_eager_constructor, } -LAZY_CONSTRUCTORS: dict[str, ConstructorLazy] = { +LAZY_CONSTRUCTORS: dict[str, ConstructorLazy] = { # pyrefly: ignore[bad-assignment] "dask": dask_lazy_p2_constructor, "polars[lazy]": polars_lazy_constructor, "duckdb": duckdb_lazy_constructor, diff --git a/tests/dependencies/is_into_dataframe_test.py b/tests/dependencies/is_into_dataframe_test.py index 71978f04be..32b2251ad6 100644 --- a/tests/dependencies/is_into_dataframe_test.py +++ b/tests/dependencies/is_into_dataframe_test.py @@ -52,6 +52,6 @@ def test_is_into_dataframe_other() -> None: pytest.importorskip("numpy") import numpy as np - assert is_into_dataframe(DictDataFrame(DATA)) + assert is_into_dataframe(DictDataFrame(DATA)) # pyrefly: ignore[bad-specialization] assert not is_into_dataframe(np.array([[1, 4], [2, 5], [3, 6]])) assert not is_into_dataframe(DATA) diff --git a/tests/dependencies/is_into_series_test.py b/tests/dependencies/is_into_series_test.py index 2d064835bc..8aa895343b 100644 --- a/tests/dependencies/is_into_series_test.py +++ b/tests/dependencies/is_into_series_test.py @@ -49,6 +49,6 @@ def test_is_into_series() -> None: pytest.importorskip("numpy") import numpy as np - assert is_into_series(ListBackedSeries("a", [1, 4, 2])) + assert is_into_series(ListBackedSeries("a", [1, 4, 2])) # pyrefly: ignore[bad-specialization] assert not is_into_series(np.array([1, 2, 3])) assert not is_into_series([1, 2, 3]) diff --git a/tests/dtypes/pandas_extension_dtypes_test.py b/tests/dtypes/pandas_extension_dtypes_test.py index bef74d75c8..43f06b49fa 100644 --- a/tests/dtypes/pandas_extension_dtypes_test.py +++ b/tests/dtypes/pandas_extension_dtypes_test.py @@ -25,7 +25,7 @@ class CustomInt16Dtype(pd.api.extensions.ExtensionDtype): # pragma: no cover @classmethod def construct_array_type(cls) -> type[pd.api.extensions.ExtensionArray]: # type: ignore[valid-type] - return CustomInt16Array + return CustomInt16Array # pyrefly: ignore[bad-return] class CustomInt16Array(pd.api.extensions.ExtensionArray): # pragma: no cover @@ -56,7 +56,7 @@ class CustomInt32Dtype(pd.api.extensions.ExtensionDtype): # pragma: no cover @classmethod def construct_array_type(cls) -> type[pd.api.extensions.ExtensionArray]: # type: ignore[valid-type] - return CustomInt32Array + return CustomInt32Array # pyrefly: ignore[bad-return] def __hash__(self) -> int: return hash(self.name) diff --git a/tests/expr_and_series/lit_test.py b/tests/expr_and_series/lit_test.py index 188292c0d1..7fab0ce2ec 100644 --- a/tests/expr_and_series/lit_test.py +++ b/tests/expr_and_series/lit_test.py @@ -46,7 +46,7 @@ def test_lit_error(constructor: Constructor) -> None: with pytest.raises( ValueError, match="numpy arrays are not supported as literal values" ): - _ = df.with_columns(nw.lit(np.array([1, 2])).alias("lit")) # pyright: ignore[reportArgumentType] + _ = df.with_columns(nw.lit(np.array([1, 2])).alias("lit")) # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] def test_lit_out_name(constructor: Constructor) -> None: diff --git a/tests/frame/collect_test.py b/tests/frame/collect_test.py index 66ea38e979..42d01d0a6f 100644 --- a/tests/frame/collect_test.py +++ b/tests/frame/collect_test.py @@ -149,7 +149,7 @@ def test_collect_with_kwargs(constructor: Constructor) -> None: if POLARS_VERSION > (1, 29, 0) else {"no_optimization": True} ) - collect_kwargs = { + collect_kwargs: dict[nw.Implementation, Any] = { nw.Implementation.POLARS: pl_kwargs, nw.Implementation.DASK: {"optimize_graph": False}, nw.Implementation.PYARROW: {}, diff --git a/tests/frame/join_test.py b/tests/frame/join_test.py index 42d52adafc..dc47ffb425 100644 --- a/tests/frame/join_test.py +++ b/tests/frame/join_test.py @@ -797,10 +797,10 @@ def test_join_duplicate_column_names( else: exception = nw.exceptions.DuplicateError if isinstance(df, nw.LazyFrame): - with pytest.raises(exception): + with pytest.raises(exception): # pyrefly: ignore[unbound-name] df.join(df, on=["a"]).join(df, on=["a"]).collect() else: - with pytest.raises(exception): + with pytest.raises(exception): # pyrefly: ignore[unbound-name] df.join(df, on=["a"]).join(df, on=["a"]) diff --git a/tests/frame/schema_test.py b/tests/frame/schema_test.py index d90916b029..a4ee5d36a3 100644 --- a/tests/frame/schema_test.py +++ b/tests/frame/schema_test.py @@ -149,7 +149,7 @@ def test_dtypes() -> None: }, ) df_from_pl = nw.from_native(df_pl, eager_only=True) - expected = { + expected: dict[str, type[nw.dtypes.DType]] = { "a": nw.Int64, "b": nw.Int32, "c": nw.Int16, diff --git a/tests/implementation_test.py b/tests/implementation_test.py index d15a569a52..4c12706f9e 100644 --- a/tests/implementation_test.py +++ b/tests/implementation_test.py @@ -162,7 +162,7 @@ def test_pandas_typing(native: pd.DataFrame) -> None: # [False Positive] any_df.lazy(ldf_impl) # [True Negative] - any_df.lazy(ser_impl) # pyright: ignore[reportArgumentType] + any_df.lazy(ser_impl) # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] # [True Positive] any_ldf.collect(df_impl) any_ldf.collect(ldf_impl) @@ -170,7 +170,7 @@ def test_pandas_typing(native: pd.DataFrame) -> None: assert_type(df_impl, _PandasImpl) # NOTE: Would require adding overloads to `DataFrame.lazy` - assert_type(ldf_impl, _PandasImpl) # pyright: ignore[reportAssertTypeFailure] + assert_type(ldf_impl, _PandasImpl) # pyright: ignore[reportAssertTypeFailure] # pyrefly: ignore[assert-type] assert_type(ser_impl, _PandasImpl) def test_arrow_typing(native: pa.Table) -> None: @@ -187,7 +187,7 @@ def test_arrow_typing(native: pa.Table) -> None: # [False Positive] any_df.lazy(ldf_impl) # [True Negative] - any_df.lazy(ser_impl) # pyright: ignore[reportArgumentType] + any_df.lazy(ser_impl) # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] # [True Positive] any_ldf.collect(df_impl) any_ldf.collect(ldf_impl) @@ -195,7 +195,7 @@ def test_arrow_typing(native: pa.Table) -> None: assert_type(df_impl, _ArrowImpl) # NOTE: Would require adding overloads to `DataFrame.lazy` - assert_type(ldf_impl, _ArrowImpl) # pyright: ignore[reportAssertTypeFailure] + assert_type(ldf_impl, _ArrowImpl) # pyright: ignore[reportAssertTypeFailure] # pyrefly: ignore[assert-type] assert_type(ser_impl, _ArrowImpl) def test_duckdb_typing(native: duckdb.DuckDBPyRelation) -> None: @@ -218,7 +218,7 @@ def test_sqlframe_typing(native: BaseDataFrame[Any, Any, Any, Any, Any]) -> None # [True Positive] any_df.lazy(ldf_impl) # [True Negative] - any_ldf.collect(ldf_impl) # pyright: ignore[reportArgumentType] + any_ldf.collect(ldf_impl) # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] assert_type(ldf.implementation, _SQLFrameImpl) @@ -232,7 +232,7 @@ def test_ibis_typing(native: ibis.Table) -> None: # [True Negative] any_ldf.collect(ldf_impl) # pyright: ignore[reportArgumentType] - assert_type(ldf.implementation, _IbisImpl) + assert_type(ldf.implementation, _IbisImpl) # pyrefly: ignore[assert-type] (todo) def test_dask_typing(native: dd.DataFrame) -> None: ldf = nw.from_native(native) @@ -242,7 +242,7 @@ def test_dask_typing(native: dd.DataFrame) -> None: # [True Positive] any_df.lazy(ldf_impl) # [True Negative] - any_ldf.collect(ldf_impl) # pyright: ignore[reportArgumentType] + any_ldf.collect(ldf_impl) # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] assert_type(ldf.implementation, _DaskImpl) @@ -255,10 +255,10 @@ def test_modin_typing(native: mpd.DataFrame) -> None: ser_impl = ser.implementation # [True Negative] - any_df.lazy(df_impl) # pyright: ignore[reportArgumentType] - any_df.lazy(ser_impl) # pyright: ignore[reportArgumentType] - any_ldf.collect(df_impl) # pyright: ignore[reportArgumentType] - any_ldf.collect(ser_impl) # pyright: ignore[reportArgumentType] + any_df.lazy(df_impl) # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] + any_df.lazy(df_impl) # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] + any_ldf.collect(df_impl) # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] + any_ldf.collect(df_impl) # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] assert_type(df_impl, _ModinImpl) assert_type(ser_impl, _ModinImpl) @@ -276,9 +276,9 @@ def test_any_typing() -> None: any_ldf.collect(ldf_impl) any_ldf.collect(ser_impl) - assert_type(df_impl, _EagerAllowedImpl) # pyright: ignore[reportAssertTypeFailure] - assert_type(ldf_impl, _LazyAllowedImpl) # pyright: ignore[reportAssertTypeFailure] - assert_type(ser_impl, _EagerAllowedImpl) # pyright: ignore[reportAssertTypeFailure] + assert_type(df_impl, _EagerAllowedImpl) # pyright: ignore[reportAssertTypeFailure] # pyrefly: ignore[assert-type] + assert_type(ldf_impl, _LazyAllowedImpl) # pyright: ignore[reportAssertTypeFailure] # pyrefly: ignore[assert-type] + assert_type(ser_impl, _EagerAllowedImpl) # pyright: ignore[reportAssertTypeFailure] # pyrefly: ignore[assert-type] # Fallback, matches the first overload `_PolarsImpl` assert_type(df_impl, _PolarsImpl) assert_type(ldf_impl, _PolarsImpl) diff --git a/tests/namespace_test.py b/tests/namespace_test.py index e94a6690c1..91b46117a3 100644 --- a/tests/namespace_test.py +++ b/tests/namespace_test.py @@ -188,7 +188,7 @@ def test_namespace_is_native() -> None: native_2 = pl.DataFrame({"a": unrelated}) maybe_native: list[pl.Series | list[int]] = [native_1, unrelated] - always_native = list["pl.DataFrame | pl.Series"]((native_2, native_1)) + always_native = list["pl.DataFrame | pl.Series"]((native_2, native_1)) # pyrefly: ignore[not-a-type] https://github.com/facebook/pyrefly/issues/3193 never_native = [unrelated, 50] expected_maybe = [True, False] @@ -210,7 +210,7 @@ def test_namespace_is_native() -> None: # NOTE: We can't spell intersections *yet* (https://github.com/python/typing/issues/213) # Would be: # ` | | `` - assert_type(unrelated, "Never") # pyright: ignore[reportAssertTypeFailure] + assert_type(unrelated, "Never") # pyright: ignore[reportAssertTypeFailure] # pyrefly: ignore[assert-type] else: assert_type(unrelated, "list[int]") @@ -227,14 +227,14 @@ def test_namespace_is_native() -> None: assert_type(native_2, "Never") always_item = always_native[1] - assert_type(always_item, "pl.DataFrame | pl.Series") + assert_type(always_item, "pl.DataFrame | pl.Series") # pyrefly: ignore[assert-type] (todo) if ns.is_native(always_item): - assert_type(always_item, "pl.DataFrame | pl.Series") + assert_type(always_item, "pl.DataFrame | pl.Series") # pyrefly: ignore[assert-type] (todo) if ns._dataframe._is_native(always_item): assert_type(always_item, "pl.DataFrame") elif ns._series._is_native(always_item): assert_type(always_item, "pl.Series") else: - assert_type(always_item, "Never") + assert_type(always_item, "pl.DataFrame | pl.Series") # pyrefly: ignore[assert-type] (todo) else: - assert_type(always_item, "Never") + assert_type(always_item, "pl.DataFrame | pl.Series") # pyrefly: ignore[assert-type] (todo) diff --git a/tests/translate/from_native_test.py b/tests/translate/from_native_test.py index 8d076699c0..9f87b5220b 100644 --- a/tests/translate/from_native_test.py +++ b/tests/translate/from_native_test.py @@ -297,7 +297,7 @@ def test_series_only_sqlframe() -> None: # pragma: no cover df = sqlframe_pyspark_lazy_constructor(data) with pytest.raises(TypeError, match="Cannot only use `series_only`"): - nw.from_native(df, series_only=True) # pyright: ignore[reportArgumentType, reportCallIssue] + nw.from_native(df, series_only=True) # pyright: ignore[reportArgumentType, reportCallIssue] # pyrefly: ignore[no-matching-overload] @pytest.mark.parametrize( diff --git a/tests/typing_compat_test.py b/tests/typing_compat_test.py index ffb026e8dc..4910b31a8d 100644 --- a/tests/typing_compat_test.py +++ b/tests/typing_compat_test.py @@ -17,8 +17,7 @@ def test_assert_never() -> None: ) some: Literal["a"] = "a" if some != "a": - assigned = "b" - assert_never(assigned) + assert_never(some) else: assigned = some if not TYPE_CHECKING: diff --git a/tests/utils_test.py b/tests/utils_test.py index c8be4131fb..862d5c3c01 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -416,7 +416,7 @@ def str(self) -> PolarsExprStringNamespace: # type: ignore[override] pl_expr = cast("PolarsExpr", self) return PolarsExprStringNamespace(pl_expr) - dt = not_implemented() + dt: Any = not_implemented() # NOTE: Typing is happy w/ double property @property diff --git a/tests/v1_test.py b/tests/v1_test.py index 9882c4ed15..8619b9c3c0 100644 --- a/tests/v1_test.py +++ b/tests/v1_test.py @@ -206,7 +206,7 @@ def test_concat() -> None: assert_equal_data(result, expected) assert isinstance(result, nw_v1.DataFrame) if TYPE_CHECKING: - assert_type(result, nw_v1.DataFrame[Any]) + assert_type(result, nw_v1.DataFrame[Any]) # pyrefly: ignore[assert-type] def test_to_dict() -> None: diff --git a/tests/v2_test.py b/tests/v2_test.py index 7a1903425c..522c709e79 100644 --- a/tests/v2_test.py +++ b/tests/v2_test.py @@ -197,7 +197,7 @@ def test_concat() -> None: assert_equal_data(result, expected) assert isinstance(result, nw_v2.DataFrame) if TYPE_CHECKING: - assert_type(result, nw_v2.DataFrame[Any]) + assert_type(result, nw_v2.DataFrame[Any]) # pyrefly: ignore[assert-type] def test_to_dict_as_series() -> None: From 0e6594423dcbacddae4a909c34bb3e9cf8ed4205 Mon Sep 17 00:00:00 2001 From: MarcoGorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Wed, 22 Apr 2026 11:03:58 +0100 Subject: [PATCH 2/7] wip --- Makefile | 1 + narwhals/_polars/dataframe.py | 4 +--- narwhals/_polars/expr.py | 6 ++---- narwhals/_polars/namespace.py | 9 +++------ pyproject.toml | 1 + tests/implementation_test.py | 4 ++-- tests/namespace_test.py | 8 +++++--- tests/v2_test.py | 1 + 8 files changed, 16 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index f9eae4f19d..2087291f0a 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,7 @@ typing: ## Run typing checks $(VENV_BIN)/uv pip install "pyarrow<24" $(VENV_BIN)/uv run --no-sync pyright $(VENV_BIN)/uv run --no-sync mypy + $(VENV_BIN)/uv run --no-sync pyrefly check .PHONY: docs-serve docs-serve: # Build and serve the docs locally diff --git a/narwhals/_polars/dataframe.py b/narwhals/_polars/dataframe.py index 2974a948c7..0848967e9a 100644 --- a/narwhals/_polars/dataframe.py +++ b/narwhals/_polars/dataframe.py @@ -5,6 +5,7 @@ import polars as pl +from narwhals._polars.namespace import PolarsNamespace from narwhals._polars.series import PolarsSeries from narwhals._polars.utils import ( FROM_DICTS_ACCEPTS_MAPPINGS, @@ -43,7 +44,6 @@ from narwhals._compliant.typing import CompliantDataFrameAny, CompliantLazyFrameAny from narwhals._polars.expr import PolarsExpr from narwhals._polars.group_by import PolarsGroupBy, PolarsLazyGroupBy - from narwhals._polars.namespace import PolarsNamespace from narwhals._spark_like.utils import SparkSession from narwhals._translate import IntoArrowTable from narwhals._typing import _EagerAllowedImpl, _LazyAllowedImpl @@ -153,8 +153,6 @@ def columns(self) -> list[str]: return self.native.columns def __narwhals_namespace__(self) -> PolarsNamespace: - from narwhals._polars.namespace import PolarsNamespace - return PolarsNamespace(version=self._version) def __native_namespace__(self) -> ModuleType: diff --git a/narwhals/_polars/expr.py b/narwhals/_polars/expr.py index 03f4109277..1e2b546014 100644 --- a/narwhals/_polars/expr.py +++ b/narwhals/_polars/expr.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, ClassVar +from typing import TYPE_CHECKING, Any, Callable, ClassVar, cast import polars as pl @@ -41,7 +41,6 @@ class PolarsExpr: _evaluate_output_names: Any _alias_output_names: Any __call__: Any - _opt_metadata: ExprMetadata | None @classmethod def _from_series(cls, series: PolarsSeries) -> Self: @@ -90,7 +89,7 @@ def broadcast(self) -> Self: @property def _metadata(self) -> ExprMetadata: assert self._opt_metadata is not None # noqa: S101 - return self._opt_metadata + return cast("ExprMetadata", self._opt_metadata) def __getattr__(self, attr: str) -> Any: def func(*args: Any, **kwargs: Any) -> Any: @@ -348,7 +347,6 @@ def struct(self) -> PolarsExprStructNamespace: arg_min: Method[Self] arg_true: Method[Self] ceil: Method[Self] - clip: Method[Self] count: Method[Self] cos: Method[Self] cum_max: Method[Self] diff --git a/narwhals/_polars/namespace.py b/narwhals/_polars/namespace.py index b621d9f9aa..eca964bbdb 100644 --- a/narwhals/_polars/namespace.py +++ b/narwhals/_polars/namespace.py @@ -5,8 +5,6 @@ import polars as pl -from narwhals._compliant.namespace import CompliantNamespace -from narwhals._polars.dataframe import PolarsDataFrame from narwhals._polars.expr import PolarsExpr from narwhals._polars.series import PolarsSeries from narwhals._polars.utils import extract_args_kwargs, narwhals_to_native_dtype @@ -21,13 +19,13 @@ from typing_extensions import TypeIs from narwhals._compliant import CompliantSelectorNamespace - from narwhals._polars.dataframe import Method, PolarsLazyFrame + from narwhals._polars.dataframe import Method, PolarsDataFrame, PolarsLazyFrame from narwhals._polars.typing import FrameT from narwhals._utils import Version, _LimitedContext from narwhals.typing import Into1DArray, IntoDType, IntoSchema, TimeUnit, _2DArray -class PolarsNamespace(CompliantNamespace[PolarsDataFrame, PolarsExpr]): +class PolarsNamespace: all: Method[PolarsExpr] coalesce: Method[PolarsExpr] col: Method[PolarsExpr] @@ -132,8 +130,7 @@ def any_horizontal(self, *exprs: PolarsExpr, ignore_nulls: bool) -> PolarsExpr: it = (expr.fill_null(False) for expr in exprs) if ignore_nulls else iter(exprs) return self._expr(pl.any_horizontal(*(expr.native for expr in it)), self._version) - # Type "PolarsDataFrame | PolarsLazyFrame" is not assignable to type "PolarsDataFrame" - def concat( # type: ignore[override] + def concat( self, items: Iterable[FrameT], *, diff --git a/pyproject.toml b/pyproject.toml index 4157a7d766..00b899c6f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,6 +80,7 @@ typing = [ # keep some of these pinned and bump periodically so there's fewer s "typing_extensions", "mypy~=1.15.0", "pyright", + "pyrefly", "pyarrow-stubs==19.2", "narwhals[dask]", "sqlframe", diff --git a/tests/implementation_test.py b/tests/implementation_test.py index 4c12706f9e..2075612d14 100644 --- a/tests/implementation_test.py +++ b/tests/implementation_test.py @@ -256,9 +256,9 @@ def test_modin_typing(native: mpd.DataFrame) -> None: # [True Negative] any_df.lazy(df_impl) # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] - any_df.lazy(df_impl) # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] - any_ldf.collect(df_impl) # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] + any_df.lazy(ser_impl) # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] any_ldf.collect(df_impl) # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] + any_ldf.collect(ser_impl) # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] assert_type(df_impl, _ModinImpl) assert_type(ser_impl, _ModinImpl) diff --git a/tests/namespace_test.py b/tests/namespace_test.py index 91b46117a3..34d0d60204 100644 --- a/tests/namespace_test.py +++ b/tests/namespace_test.py @@ -91,7 +91,8 @@ def test_namespace_from_backend_typing(backend: _EagerAllowed) -> None: if TYPE_CHECKING: assert_type( namespace, - "Namespace[PolarsNamespace] | Namespace[PandasLikeNamespace] | Namespace[ArrowNamespace]", + # pyrefly: `PolarsNamespace` is not assignable to upper bound `CompliantNamespace` of type variable `CompliantNamespaceT_co` + "Namespace[PolarsNamespace] | Namespace[PandasLikeNamespace] | Namespace[ArrowNamespace]", # pyrefly: ignore[bad-specialization] ) assert repr(namespace) in { "Namespace[PolarsNamespace]", @@ -227,6 +228,7 @@ def test_namespace_is_native() -> None: assert_type(native_2, "Never") always_item = always_native[1] + # pyrefly: `always_item` is `Unknown` assert_type(always_item, "pl.DataFrame | pl.Series") # pyrefly: ignore[assert-type] (todo) if ns.is_native(always_item): assert_type(always_item, "pl.DataFrame | pl.Series") # pyrefly: ignore[assert-type] (todo) @@ -235,6 +237,6 @@ def test_namespace_is_native() -> None: elif ns._series._is_native(always_item): assert_type(always_item, "pl.Series") else: - assert_type(always_item, "pl.DataFrame | pl.Series") # pyrefly: ignore[assert-type] (todo) + assert_type(always_item, "Never") # pyrefly: ignore[assert-type] (todo) else: - assert_type(always_item, "pl.DataFrame | pl.Series") # pyrefly: ignore[assert-type] (todo) + assert_type(always_item, "Never") # pyrefly: ignore[assert-type] (todo) diff --git a/tests/v2_test.py b/tests/v2_test.py index 522c709e79..91f8717a16 100644 --- a/tests/v2_test.py +++ b/tests/v2_test.py @@ -197,6 +197,7 @@ def test_concat() -> None: assert_equal_data(result, expected) assert isinstance(result, nw_v2.DataFrame) if TYPE_CHECKING: + # pyrefly reveals `result` to be DataFrame[Table] assert_type(result, nw_v2.DataFrame[Any]) # pyrefly: ignore[assert-type] From 8703713265106b2ddb38ce4555ee4c38e480cc5a Mon Sep 17 00:00:00 2001 From: MarcoGorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Wed, 22 Apr 2026 11:25:30 +0100 Subject: [PATCH 3/7] pyspark fix --- pyproject.toml | 2 -- tests/utils.py | 3 ++- tests/v1_test.py | 1 + tests/v2_test.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 00b899c6f3..73dca8e7df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -401,10 +401,8 @@ ignore-missing-imports = [ "cupy.*", "dask.*", "dask_expr.*", - "ibis.*", "joblib.*", "modin.*", - "numpy.*", "pyspark.*", "sklearn.*", "sqlparse.*", diff --git a/tests/utils.py b/tests/utils.py index 4d01223b2a..b9fa613bca 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -194,7 +194,8 @@ def pyspark_session() -> SparkSession: # pragma: no cover else builder.master("local[1]").config("spark.ui.enabled", "false") ) return ( - builder.config("spark.default.parallelism", "1") + # Don't remove pyrefly-ignore, needed in CI when pyspark is installed. + builder.config("spark.default.parallelism", "1") # pyrefly: ignore[bad-return] .config("spark.sql.shuffle.partitions", "2") .config("spark.sql.session.timeZone", "UTC") .getOrCreate() diff --git a/tests/v1_test.py b/tests/v1_test.py index 8619b9c3c0..dc7eb0efff 100644 --- a/tests/v1_test.py +++ b/tests/v1_test.py @@ -206,6 +206,7 @@ def test_concat() -> None: assert_equal_data(result, expected) assert isinstance(result, nw_v1.DataFrame) if TYPE_CHECKING: + # pyrefly: `result` is `DataFrame[Table]` assert_type(result, nw_v1.DataFrame[Any]) # pyrefly: ignore[assert-type] diff --git a/tests/v2_test.py b/tests/v2_test.py index 91f8717a16..eb7eb8e4f5 100644 --- a/tests/v2_test.py +++ b/tests/v2_test.py @@ -197,7 +197,7 @@ def test_concat() -> None: assert_equal_data(result, expected) assert isinstance(result, nw_v2.DataFrame) if TYPE_CHECKING: - # pyrefly reveals `result` to be DataFrame[Table] + # pyrefly: `result` is `DataFrame[Table]` assert_type(result, nw_v2.DataFrame[Any]) # pyrefly: ignore[assert-type] From caf439e5096722326963b125739679c8e2aa0c6b Mon Sep 17 00:00:00 2001 From: MarcoGorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Wed, 22 Apr 2026 12:52:54 +0100 Subject: [PATCH 4/7] link to pyrefly issue for assert never --- tests/typing_compat_test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/typing_compat_test.py b/tests/typing_compat_test.py index 4910b31a8d..35146595d4 100644 --- a/tests/typing_compat_test.py +++ b/tests/typing_compat_test.py @@ -17,7 +17,8 @@ def test_assert_never() -> None: ) some: Literal["a"] = "a" if some != "a": - assert_never(some) + assigned = "b" + assert_never(assigned) # pyrefly: ignore[bad-argument-type] https://github.com/facebook/pyrefly/issues/3202 else: assigned = some if not TYPE_CHECKING: From e29b6260ee5d324d1ee5a9968caee13a2e3ddb86 Mon Sep 17 00:00:00 2001 From: MarcoGorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Fri, 1 May 2026 10:16:03 +0100 Subject: [PATCH 5/7] wip --- tests/utils.py | 2 +- tests/v1_test.py | 2 +- tests/v2_test.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/utils.py b/tests/utils.py index b9fa613bca..939d93f101 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -195,7 +195,7 @@ def pyspark_session() -> SparkSession: # pragma: no cover ) return ( # Don't remove pyrefly-ignore, needed in CI when pyspark is installed. - builder.config("spark.default.parallelism", "1") # pyrefly: ignore[bad-return] + builder.config("spark.default.parallelism", "1") .config("spark.sql.shuffle.partitions", "2") .config("spark.sql.session.timeZone", "UTC") .getOrCreate() diff --git a/tests/v1_test.py b/tests/v1_test.py index dc7eb0efff..72175fc733 100644 --- a/tests/v1_test.py +++ b/tests/v1_test.py @@ -207,7 +207,7 @@ def test_concat() -> None: assert isinstance(result, nw_v1.DataFrame) if TYPE_CHECKING: # pyrefly: `result` is `DataFrame[Table]` - assert_type(result, nw_v1.DataFrame[Any]) # pyrefly: ignore[assert-type] + assert_type(result, nw_v1.DataFrame[Any]) def test_to_dict() -> None: diff --git a/tests/v2_test.py b/tests/v2_test.py index eb7eb8e4f5..0082fdbfa7 100644 --- a/tests/v2_test.py +++ b/tests/v2_test.py @@ -198,7 +198,7 @@ def test_concat() -> None: assert isinstance(result, nw_v2.DataFrame) if TYPE_CHECKING: # pyrefly: `result` is `DataFrame[Table]` - assert_type(result, nw_v2.DataFrame[Any]) # pyrefly: ignore[assert-type] + assert_type(result, nw_v2.DataFrame[Any]) def test_to_dict_as_series() -> None: From 082542f2b078a80922abd33879fbae60596a4521 Mon Sep 17 00:00:00 2001 From: MarcoGorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Fri, 1 May 2026 10:41:42 +0100 Subject: [PATCH 6/7] fixup --- tests/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/utils.py b/tests/utils.py index 939d93f101..b9fa613bca 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -195,7 +195,7 @@ def pyspark_session() -> SparkSession: # pragma: no cover ) return ( # Don't remove pyrefly-ignore, needed in CI when pyspark is installed. - builder.config("spark.default.parallelism", "1") + builder.config("spark.default.parallelism", "1") # pyrefly: ignore[bad-return] .config("spark.sql.shuffle.partitions", "2") .config("spark.sql.session.timeZone", "UTC") .getOrCreate() From 3ff372925e1c6873116bb0d264defd41a4ee1b9b Mon Sep 17 00:00:00 2001 From: MarcoGorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Fri, 1 May 2026 10:52:13 +0100 Subject: [PATCH 7/7] clean up --- tests/v1_test.py | 1 - tests/v2_test.py | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/v1_test.py b/tests/v1_test.py index 72175fc733..9882c4ed15 100644 --- a/tests/v1_test.py +++ b/tests/v1_test.py @@ -206,7 +206,6 @@ def test_concat() -> None: assert_equal_data(result, expected) assert isinstance(result, nw_v1.DataFrame) if TYPE_CHECKING: - # pyrefly: `result` is `DataFrame[Table]` assert_type(result, nw_v1.DataFrame[Any]) diff --git a/tests/v2_test.py b/tests/v2_test.py index 0082fdbfa7..7a1903425c 100644 --- a/tests/v2_test.py +++ b/tests/v2_test.py @@ -197,7 +197,6 @@ def test_concat() -> None: assert_equal_data(result, expected) assert isinstance(result, nw_v2.DataFrame) if TYPE_CHECKING: - # pyrefly: `result` is `DataFrame[Table]` assert_type(result, nw_v2.DataFrame[Any])