From f87ec466cf30a8580417ef7604c424ccdb278a97 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Sat, 6 Sep 2025 11:48:15 +0100 Subject: [PATCH 1/6] chore: enable reportIncompatibleMethodOverride in pyright --- narwhals/_arrow/series.py | 4 ++-- narwhals/_dask/expr.py | 20 ++++++-------------- narwhals/_duckdb/expr.py | 4 ++-- narwhals/_ibis/expr.py | 12 ++++++++---- narwhals/_ibis/expr_dt.py | 4 ++-- narwhals/_polars/dataframe.py | 4 ++-- narwhals/_spark_like/dataframe.py | 1 + narwhals/_spark_like/expr.py | 8 ++++---- narwhals/_sql/expr.py | 21 ++++++--------------- narwhals/dataframe.py | 14 +++++++------- narwhals/stable/v1/__init__.py | 4 ++-- narwhals/stable/v2/__init__.py | 4 ++-- pyproject.toml | 1 + 13 files changed, 45 insertions(+), 56 deletions(-) diff --git a/narwhals/_arrow/series.py b/narwhals/_arrow/series.py index eaae0353b3..ad35218148 100644 --- a/narwhals/_arrow/series.py +++ b/narwhals/_arrow/series.py @@ -373,12 +373,12 @@ def shift(self, n: int) -> Self: return self._with_native(self.native) return self._with_native(pa.concat_arrays(arrays)) - def std(self, ddof: int, *, _return_py_scalar: bool = True) -> float: + def std(self, *, ddof: int, _return_py_scalar: bool = True) -> float: return maybe_extract_py_scalar( pc.stddev(self.native, ddof=ddof), _return_py_scalar ) - def var(self, ddof: int, *, _return_py_scalar: bool = True) -> float: + def var(self, *, ddof: int, _return_py_scalar: bool = True) -> float: return maybe_extract_py_scalar( pc.variance(self.native, ddof=ddof), _return_py_scalar ) diff --git a/narwhals/_dask/expr.py b/narwhals/_dask/expr.py index 2ff2958d43..daf097ae1a 100644 --- a/narwhals/_dask/expr.py +++ b/narwhals/_dask/expr.py @@ -298,14 +298,14 @@ def min(self) -> Self: def max(self) -> Self: return self._with_callable(lambda expr: expr.max().to_series(), "max") - def std(self, ddof: int) -> Self: + def std(self, *, ddof: int) -> Self: return self._with_callable( lambda expr: expr.std(ddof=ddof).to_series(), "std", scalar_kwargs={"ddof": ddof}, ) - def var(self, ddof: int) -> Self: + def var(self, *, ddof: int) -> Self: return self._with_callable( lambda expr: expr.var(ddof=ddof).to_series(), "var", @@ -682,18 +682,10 @@ def str(self) -> DaskExprStringNamespace: def dt(self) -> DaskExprDateTimeNamespace: return DaskExprDateTimeNamespace(self) - arg_max: not_implemented = not_implemented() - arg_min: not_implemented = not_implemented() - arg_true: not_implemented = not_implemented() - ewm_mean: not_implemented = not_implemented() - gather_every: not_implemented = not_implemented() - head: not_implemented = not_implemented() - map_batches: not_implemented = not_implemented() - sample: not_implemented = not_implemented() - rank: not_implemented = not_implemented() - replace_strict: not_implemented = not_implemented() - sort: not_implemented = not_implemented() - tail: not_implemented = not_implemented() + ewm_mean = not_implemented() + map_batches = not_implemented() + rank = not_implemented() + replace_strict = not_implemented() # namespaces list: not_implemented = not_implemented() # type: ignore[assignment] diff --git a/narwhals/_duckdb/expr.py b/narwhals/_duckdb/expr.py index cc0112159d..cab9d51f22 100644 --- a/narwhals/_duckdb/expr.py +++ b/narwhals/_duckdb/expr.py @@ -188,7 +188,7 @@ def func(expr: Expression) -> Expression: def len(self) -> Self: return self._with_callable(lambda _expr: F("count")) - def std(self, ddof: int) -> Self: + def std(self, *, ddof: int) -> Self: if ddof == 0: return self._with_callable(lambda expr: F("stddev_pop", expr)) if ddof == 1: @@ -204,7 +204,7 @@ def _std(expr: Expression) -> Expression: return self._with_callable(_std) - def var(self, ddof: int) -> Self: + def var(self, *, ddof: int) -> Self: if ddof == 0: return self._with_callable(lambda expr: F("var_pop", expr)) if ddof == 1: diff --git a/narwhals/_ibis/expr.py b/narwhals/_ibis/expr.py index 17e372c3bb..90d8f42482 100644 --- a/narwhals/_ibis/expr.py +++ b/narwhals/_ibis/expr.py @@ -79,7 +79,7 @@ def _window_expression( self, expr: ir.Value, partition_by: Sequence[str | ir.Value] = (), - order_by: Sequence[str | ir.Column] = (), + order_by: Sequence[str | ir.Value] = (), rows_start: int | None = None, rows_end: int | None = None, *, @@ -96,7 +96,11 @@ def _window_expression( rows_between = {} window = ibis.window( group_by=partition_by, - order_by=self._sort(*order_by, descending=descending, nulls_last=nulls_last), + order_by=self._sort( + *cast("Sequence[ir.Column]", order_by), + descending=descending, + nulls_last=nulls_last, + ), **rows_between, ) return expr.over(window) @@ -217,7 +221,7 @@ def func(df: IbisLazyFrame) -> Sequence[ir.IntegerScalar]: version=self._version, ) - def std(self, ddof: int) -> Self: + def std(self, *, ddof: int) -> Self: def _std(expr: ir.NumericColumn, ddof: int) -> ir.Value: if ddof == 0: return expr.std(how="pop") @@ -230,7 +234,7 @@ def _std(expr: ir.NumericColumn, ddof: int) -> ir.Value: return self._with_callable(lambda expr: _std(expr, ddof)) - def var(self, ddof: int) -> Self: + def var(self, *, ddof: int) -> Self: def _var(expr: ir.NumericColumn, ddof: int) -> ir.Value: if ddof == 0: return expr.var(how="pop") diff --git a/narwhals/_ibis/expr_dt.py b/narwhals/_ibis/expr_dt.py index 3ee28f481d..7d98dba0df 100644 --- a/narwhals/_ibis/expr_dt.py +++ b/narwhals/_ibis/expr_dt.py @@ -58,8 +58,8 @@ def truncate(self, every: str) -> IbisExpr: fn = self._truncate(UNITS_DICT_TRUNCATE[unit]) return self.compliant._with_callable(fn) - def offset_by(self, every: str) -> IbisExpr: - interval = Interval.parse_no_constraints(every) + def offset_by(self, by: str) -> IbisExpr: + interval = Interval.parse_no_constraints(by) unit = interval.unit if unit in {"y", "q", "mo", "d", "ns"}: msg = f"Offsetting by {unit} is not yet supported for ibis." diff --git a/narwhals/_polars/dataframe.py b/narwhals/_polars/dataframe.py index 5a9fbbe670..328f99779e 100644 --- a/narwhals/_polars/dataframe.py +++ b/narwhals/_polars/dataframe.py @@ -181,7 +181,7 @@ def schema(self) -> dict[str, DType]: def join( self, - other: Self, + other: PolarsBaseFrame[NativePolarsFrame], *, how: JoinStrategy, left_on: Sequence[str] | None, @@ -568,7 +568,7 @@ def to_polars(self) -> pl.DataFrame: def join( self, - other: Self, + other: PolarsBaseFrame[pl.DataFrame], *, how: JoinStrategy, left_on: Sequence[str] | None, diff --git a/narwhals/_spark_like/dataframe.py b/narwhals/_spark_like/dataframe.py index eba3001e7b..cd673ecec4 100644 --- a/narwhals/_spark_like/dataframe.py +++ b/narwhals/_spark_like/dataframe.py @@ -385,6 +385,7 @@ def unique( def join( self, other: Self, + *, how: JoinStrategy, left_on: Sequence[str] | None, right_on: Sequence[str] | None, diff --git a/narwhals/_spark_like/expr.py b/narwhals/_spark_like/expr.py index 3b5d1c1653..13dd167fe5 100644 --- a/narwhals/_spark_like/expr.py +++ b/narwhals/_spark_like/expr.py @@ -264,7 +264,7 @@ def _null_count(expr: Column) -> Column: return self._with_callable(_null_count) - def std(self, ddof: int) -> Self: + def std(self, *, ddof: int) -> Self: F = self._F if ddof == 0: return self._with_callable(F.stddev_pop) @@ -277,7 +277,7 @@ def func(expr: Column) -> Column: return self._with_callable(func) - def var(self, ddof: int) -> Self: + def var(self, *, ddof: int) -> Self: F = self._F if ddof == 0: return self._with_callable(F.var_pop) @@ -305,9 +305,9 @@ def _is_finite(expr: Column) -> Column: return self._with_elementwise(_is_finite) - def is_in(self, values: Sequence[Any]) -> Self: + def is_in(self, other: Sequence[Any]) -> Self: def _is_in(expr: Column) -> Column: - return expr.isin(values) if values else self._F.lit(False) + return expr.isin(other) if other else self._F.lit(False) return self._with_elementwise(_is_in) diff --git a/narwhals/_sql/expr.py b/narwhals/_sql/expr.py index db6c5dff9d..da062bfb2d 100644 --- a/narwhals/_sql/expr.py +++ b/narwhals/_sql/expr.py @@ -784,18 +784,9 @@ def str(self) -> SQLExprStringNamespace[Self]: ... def dt(self) -> SQLExprDateTimeNamesSpace[Self]: ... # Not implemented - - arg_max: not_implemented = not_implemented() - arg_min: not_implemented = not_implemented() - arg_true: not_implemented = not_implemented() - cat: not_implemented = not_implemented() # type: ignore[assignment] - drop_nulls: not_implemented = not_implemented() - ewm_mean: not_implemented = not_implemented() - gather_every: not_implemented = not_implemented() - head: not_implemented = not_implemented() - map_batches: not_implemented = not_implemented() - replace_strict: not_implemented = not_implemented() - sort: not_implemented = not_implemented() - tail: not_implemented = not_implemented() - sample: not_implemented = not_implemented() - unique: not_implemented = not_implemented() + cat: Any = not_implemented() + drop_nulls: Any = not_implemented() + ewm_mean: Any = not_implemented() + map_batches: Any = not_implemented() + replace_strict: Any = not_implemented() + unique: Any = not_implemented() diff --git a/narwhals/dataframe.py b/narwhals/dataframe.py index d5015d5a69..9cc5382c78 100644 --- a/narwhals/dataframe.py +++ b/narwhals/dataframe.py @@ -284,7 +284,7 @@ def top_k( def join( self, - other: Self, + other: BaseFrame[_FrameT], on: str | list[str] | None, how: JoinStrategy, *, @@ -335,7 +335,7 @@ def gather_every(self, n: int, offset: int = 0) -> Self: def join_asof( self, - other: Self, + other: BaseFrame[_FrameT], *, left_on: str | None, right_on: str | None, @@ -1111,7 +1111,7 @@ def to_dict(self, *, as_series: Literal[True] = ...) -> dict[str, Series[Any]]: def to_dict(self, *, as_series: Literal[False]) -> dict[str, list[Any]]: ... @overload def to_dict( - self, *, as_series: bool + self, *, as_series: bool = True ) -> dict[str, Series[Any]] | dict[str, list[Any]]: ... def to_dict( self, *, as_series: bool = True @@ -1800,7 +1800,7 @@ def top_k( def join( self, - other: Self, + other: BaseFrame[DataFrameT], on: str | list[str] | None = None, how: JoinStrategy = "inner", *, @@ -1846,7 +1846,7 @@ def join( def join_asof( self, - other: Self, + other: BaseFrame[DataFrameT], *, left_on: str | None = None, right_on: str | None = None, @@ -3052,7 +3052,7 @@ def top_k( def join( self, - other: Self, + other: BaseFrame[LazyFrameT], on: str | list[str] | None = None, how: JoinStrategy = "inner", *, @@ -3107,7 +3107,7 @@ def join( def join_asof( self, - other: Self, + other: BaseFrame[LazyFrameT], *, left_on: str | None = None, right_on: str | None = None, diff --git a/narwhals/stable/v1/__init__.py b/narwhals/stable/v1/__init__.py index 923a387288..f9780fa4a9 100644 --- a/narwhals/stable/v1/__init__.py +++ b/narwhals/stable/v1/__init__.py @@ -203,9 +203,9 @@ def to_dict(self, *, as_series: Literal[True] = ...) -> dict[str, Series[Any]]: def to_dict(self, *, as_series: Literal[False]) -> dict[str, list[Any]]: ... @overload def to_dict( - self, *, as_series: bool + self, *, as_series: bool = True ) -> dict[str, Series[Any]] | dict[str, list[Any]]: ... - def to_dict( + def to_dict( # pyright: ignore[reportIncompatibleMethodOverride] self, *, as_series: bool = True ) -> dict[str, Series[Any]] | dict[str, list[Any]]: # Type checkers complain that `nw.Series` is not assignable to `nw.v1.stable.Series`. diff --git a/narwhals/stable/v2/__init__.py b/narwhals/stable/v2/__init__.py index e1963a4480..ada1ff20e8 100644 --- a/narwhals/stable/v2/__init__.py +++ b/narwhals/stable/v2/__init__.py @@ -198,9 +198,9 @@ def to_dict(self, *, as_series: Literal[True] = ...) -> dict[str, Series[Any]]: def to_dict(self, *, as_series: Literal[False]) -> dict[str, list[Any]]: ... @overload def to_dict( - self, *, as_series: bool + self, *, as_series: bool = True ) -> dict[str, Series[Any]] | dict[str, list[Any]]: ... - def to_dict( + def to_dict( # pyright: ignore[reportIncompatibleMethodOverride] self, *, as_series: bool = True ) -> dict[str, Series[Any]] | dict[str, list[Any]]: # Type checkers complain that `nw.Series` is not assignable to `nw.v2.stable.Series`. diff --git a/pyproject.toml b/pyproject.toml index 303e0a0bf4..3f72013ddf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -350,6 +350,7 @@ pythonPlatform = "All" # NOTE (`pyarrow-stubs` do unsafe `TypeAlias` and `TypeVar` imports) # pythonVersion = "3.9" reportMissingTypeArgument = "error" +reportIncompatibleMethodOverride = "error" reportMissingImports = "none" reportMissingModuleSource = "none" reportPrivateImportUsage = "none" From 1a8e7d66c8bf29ad4eca084ab8ba3adf76cfa255 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Sun, 7 Sep 2025 13:40:24 +0000 Subject: [PATCH 2/6] chore(suggestion): Try turning ibis issue into a positive? Part 1 of (https://github.com/narwhals-dev/narwhals/pull/3096#issuecomment-3263684697) Isolates the problematic typing and then reuses it in other places it causes issues --- narwhals/_ibis/dataframe.py | 26 +++++-------------- narwhals/_ibis/expr.py | 52 +++++++++++++++++++++---------------- narwhals/_ibis/utils.py | 23 ++++++++++++++-- 3 files changed, 57 insertions(+), 44 deletions(-) diff --git a/narwhals/_ibis/dataframe.py b/narwhals/_ibis/dataframe.py index e976d54729..455c3ab4ba 100644 --- a/narwhals/_ibis/dataframe.py +++ b/narwhals/_ibis/dataframe.py @@ -336,29 +336,17 @@ def unique( return self._with_native(self.native.distinct(on=subset)) def sort(self, *by: str, descending: bool | Sequence[bool], nulls_last: bool) -> Self: - if isinstance(descending, bool): - descending = [descending for _ in range(len(by))] + from narwhals._ibis.expr import IbisExpr - sort_cols: list[Any] = [] - - for i in range(len(by)): - direction_fn = ibis.desc if descending[i] else ibis.asc - col = direction_fn(by[i], nulls_first=not nulls_last) - sort_cols.append(col) - - return self._with_native(self.native.order_by(*sort_cols)) + cols = IbisExpr._sort(*by, descending=descending, nulls_last=nulls_last) + return self._with_native(self.native.order_by(*cols)) def top_k(self, k: int, *, by: Iterable[str], reverse: bool | Sequence[bool]) -> Self: - if isinstance(reverse, bool): - reverse = [reverse] * len(list(by)) - sort_cols = [] - - for is_reverse, by_col in zip_strict(reverse, by): - direction_fn = ibis.asc if is_reverse else ibis.desc - col = direction_fn(by_col, nulls_first=False) - sort_cols.append(cast("ir.Column", col)) + from narwhals._ibis.expr import IbisExpr - return self._with_native(self.native.order_by(*sort_cols).head(k)) + desc = not reverse if isinstance(reverse, bool) else [not el for el in reverse] + cols = IbisExpr._sort(*by, descending=desc, nulls_last=True) + return self._with_native(self.native.order_by(*cols).head(k)) def drop_nulls(self, subset: Sequence[str] | None) -> Self: subset_ = subset if subset is not None else self.columns diff --git a/narwhals/_ibis/expr.py b/narwhals/_ibis/expr.py index 90d8f42482..3471d3c831 100644 --- a/narwhals/_ibis/expr.py +++ b/narwhals/_ibis/expr.py @@ -1,7 +1,6 @@ from __future__ import annotations import operator -from functools import partial from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar, cast import ibis @@ -10,7 +9,17 @@ from narwhals._ibis.expr_list import IbisExprListNamespace from narwhals._ibis.expr_str import IbisExprStringNamespace from narwhals._ibis.expr_struct import IbisExprStructNamespace -from narwhals._ibis.utils import is_floating, lit, narwhals_to_native_dtype +from narwhals._ibis.utils import ( + IntoColumn, + asc_nulls_first, + asc_nulls_last, + desc_nulls_first, + desc_nulls_last, + extend_bool, + is_floating, + lit, + narwhals_to_native_dtype, +) from narwhals._sql.expr import SQLExpr from narwhals._utils import Implementation, Version, not_implemented, zip_strict @@ -79,7 +88,7 @@ def _window_expression( self, expr: ir.Value, partition_by: Sequence[str | ir.Value] = (), - order_by: Sequence[str | ir.Value] = (), + order_by: Sequence[IntoColumn] = (), rows_start: int | None = None, rows_end: int | None = None, *, @@ -94,13 +103,11 @@ def _window_expression( rows_between = {"preceding": -rows_start} else: rows_between = {} + desc = descending or False + last = nulls_last or False window = ibis.window( group_by=partition_by, - order_by=self._sort( - *cast("Sequence[ir.Column]", order_by), - descending=descending, - nulls_last=nulls_last, - ), + order_by=self._sort(*order_by, descending=desc, nulls_last=last), **rows_between, ) return expr.over(window) @@ -114,24 +121,23 @@ def broadcast(self, kind: Literal[ExprKind.AGGREGATION, ExprKind.LITERAL]) -> Se # Ibis does its own broadcasting. return self + @staticmethod def _sort( - self, - *cols: ir.Column | str, - descending: Sequence[bool] | None = None, - nulls_last: Sequence[bool] | None = None, + *cols: IntoColumn, + descending: Sequence[bool] | bool = False, + nulls_last: Sequence[bool] | bool = False, ) -> Iterator[ir.Column]: - descending = descending or [False] * len(cols) - nulls_last = nulls_last or [False] * len(cols) + n = len(cols) + descending = extend_bool(descending, n) + nulls_last = extend_bool(nulls_last, n) mapping = { - (False, False): partial(ibis.asc, nulls_first=True), - (False, True): partial(ibis.asc, nulls_first=False), - (True, False): partial(ibis.desc, nulls_first=True), - (True, True): partial(ibis.desc, nulls_first=False), + (False, False): asc_nulls_first, + (False, True): asc_nulls_last, + (True, False): desc_nulls_first, + (True, True): desc_nulls_last, } - yield from ( - cast("ir.Column", mapping[(_desc, _nulls_last)](col)) - for col, _desc, _nulls_last in zip_strict(cols, descending, nulls_last) - ) + for col, _desc, _nulls_last in zip_strict(cols, descending, nulls_last): + yield mapping[(_desc, _nulls_last)](col) @classmethod def from_column_names( @@ -293,7 +299,7 @@ def is_unique(self) -> Self: def rank(self, method: RankMethod, *, descending: bool) -> Self: def _rank(expr: ir.Column) -> ir.Value: - order_by = next(self._sort(expr, descending=[descending], nulls_last=[True])) + order_by = next(self._sort(expr, descending=descending, nulls_last=True)) window = ibis.window(order_by=order_by) if method == "dense": diff --git a/narwhals/_ibis/utils.py b/narwhals/_ibis/utils.py index a8b5bfa833..3aa84c776c 100644 --- a/narwhals/_ibis/utils.py +++ b/narwhals/_ibis/utils.py @@ -1,6 +1,6 @@ from __future__ import annotations -from functools import lru_cache +from functools import lru_cache, partial from typing import TYPE_CHECKING, Any, Literal, cast, overload import ibis @@ -9,7 +9,7 @@ from narwhals._utils import Version, isinstance_or_issubclass if TYPE_CHECKING: - from collections.abc import Mapping + from collections.abc import Callable, Iterable, Mapping, Sequence from datetime import timedelta import ibis.expr.types as ir @@ -23,6 +23,8 @@ from narwhals.dtypes import DType from narwhals.typing import IntoDType, PythonLiteral +IntoColumn: TypeAlias = "str | ir.Value | ir.Column" +SortFn: TypeAlias = "Callable[[IntoColumn], ir.Column]" Incomplete: TypeAlias = Any """Marker for upstream issues.""" @@ -45,6 +47,23 @@ def lit(value: Any, dtype: Any | None = None) -> Incomplete: return literal(value, dtype) +asc_nulls_first = cast("SortFn", partial(ibis.asc, nulls_first=True)) +asc_nulls_last = cast("SortFn", partial(ibis.asc, nulls_first=False)) +desc_nulls_first = cast("SortFn", partial(ibis.desc, nulls_first=True)) +desc_nulls_last = cast("SortFn", partial(ibis.desc, nulls_first=False)) + + +def extend_bool( + value: bool | Iterable[bool], # noqa: FBT001 + n_match: int, +) -> Sequence[bool]: + """Ensure the given bool or sequence of bools is the correct length. + + Stolen from https://github.com/pola-rs/polars/blob/b8bfb07a4a37a8d449d6d1841e345817431142df/py-polars/polars/_utils/various.py#L580-L594 + """ + return [value] * n_match if isinstance(value, bool) else list(value) + + BucketUnit: TypeAlias = Literal[ "years", "quarters", From 9cda7ba6c5eec6e947a5e73fd42beadb588e6861 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Sun, 7 Sep 2025 14:29:43 +0000 Subject: [PATCH 3/6] chore(typing): Ignore with context Seems to appear at random and vanish Error message is nonsense --- narwhals/_compliant/expr.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/narwhals/_compliant/expr.py b/narwhals/_compliant/expr.py index f224ef82e9..159892117d 100644 --- a/narwhals/_compliant/expr.py +++ b/narwhals/_compliant/expr.py @@ -168,8 +168,14 @@ class DepthTrackingExpr( _depth: int _function_name: str + # NOTE: pyright bug? + # Method "from_column_names" overrides class "CompliantExpr" in an incompatible manner + # Parameter 2 type mismatch: base parameter is type "EvalNames[CompliantFrameT@DepthTrackingExpr]", override parameter is type "EvalNames[CompliantFrameT@DepthTrackingExpr]" + # Type "EvalNames[CompliantFrameT@DepthTrackingExpr]" is not assignable to type "EvalNames[CompliantFrameT@DepthTrackingExpr]" + # Parameter 1: type "CompliantFrameT@DepthTrackingExpr" is incompatible with type "CompliantFrameT@DepthTrackingExpr" + # Type "CompliantFrameT@DepthTrackingExpr" is not assignable to type "CompliantFrameT@DepthTrackingExpr" @classmethod - def from_column_names( + def from_column_names( # pyright: ignore[reportIncompatibleMethodOverride] cls: type[Self], evaluate_column_names: EvalNames[CompliantFrameT], /, From 2174359c10c045b2deee8d69b38777c8c47db25b Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Sun, 7 Sep 2025 14:40:45 +0000 Subject: [PATCH 4/6] refactor: Move common `not_implemented` up --- narwhals/_compliant/expr.py | 13 ++++++++++++- narwhals/_dask/expr.py | 4 ---- narwhals/_sql/expr.py | 4 ---- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/narwhals/_compliant/expr.py b/narwhals/_compliant/expr.py index 159892117d..310209cd21 100644 --- a/narwhals/_compliant/expr.py +++ b/narwhals/_compliant/expr.py @@ -28,7 +28,12 @@ LazyExprT, NativeExprT, ) -from narwhals._utils import _StoresCompliant, qualified_type_name, zip_strict +from narwhals._utils import ( + _StoresCompliant, + not_implemented, + qualified_type_name, + zip_strict, +) from narwhals.dependencies import is_numpy_array, is_numpy_scalar if TYPE_CHECKING: @@ -905,6 +910,12 @@ def fn(names: Sequence[str]) -> Sequence[str]: def name(self) -> LazyExprNameNamespace[Self]: return LazyExprNameNamespace(self) + ewm_mean: not_implemented = not_implemented() # pyright: ignore[reportIncompatibleMethodOverride] + map_batches: not_implemented = not_implemented() # pyright: ignore[reportIncompatibleMethodOverride] + replace_strict: not_implemented = not_implemented() # pyright: ignore[reportIncompatibleMethodOverride] + + cat: not_implemented = not_implemented() # type: ignore[assignment] + class _ExprNamespace( # type: ignore[misc] _StoresCompliant[CompliantExprT_co], Protocol[CompliantExprT_co] diff --git a/narwhals/_dask/expr.py b/narwhals/_dask/expr.py index daf097ae1a..db636bb504 100644 --- a/narwhals/_dask/expr.py +++ b/narwhals/_dask/expr.py @@ -682,12 +682,8 @@ def str(self) -> DaskExprStringNamespace: def dt(self) -> DaskExprDateTimeNamespace: return DaskExprDateTimeNamespace(self) - ewm_mean = not_implemented() - map_batches = not_implemented() rank = not_implemented() - replace_strict = not_implemented() # namespaces list: not_implemented = not_implemented() # type: ignore[assignment] - cat: not_implemented = not_implemented() # type: ignore[assignment] struct: not_implemented = not_implemented() # type: ignore[assignment] diff --git a/narwhals/_sql/expr.py b/narwhals/_sql/expr.py index da062bfb2d..0e8115f1a4 100644 --- a/narwhals/_sql/expr.py +++ b/narwhals/_sql/expr.py @@ -784,9 +784,5 @@ def str(self) -> SQLExprStringNamespace[Self]: ... def dt(self) -> SQLExprDateTimeNamesSpace[Self]: ... # Not implemented - cat: Any = not_implemented() drop_nulls: Any = not_implemented() - ewm_mean: Any = not_implemented() - map_batches: Any = not_implemented() - replace_strict: Any = not_implemented() unique: Any = not_implemented() From dc4a596f8fbfb685b8a55b3c0b96db2fc375e49d Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Sun, 7 Sep 2025 14:47:33 +0000 Subject: [PATCH 5/6] refactor(typing): Do this ignore instead actually --- narwhals/_compliant/expr.py | 6 +++--- narwhals/_sql/expr.py | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/narwhals/_compliant/expr.py b/narwhals/_compliant/expr.py index 310209cd21..55ed2d3e47 100644 --- a/narwhals/_compliant/expr.py +++ b/narwhals/_compliant/expr.py @@ -910,9 +910,9 @@ def fn(names: Sequence[str]) -> Sequence[str]: def name(self) -> LazyExprNameNamespace[Self]: return LazyExprNameNamespace(self) - ewm_mean: not_implemented = not_implemented() # pyright: ignore[reportIncompatibleMethodOverride] - map_batches: not_implemented = not_implemented() # pyright: ignore[reportIncompatibleMethodOverride] - replace_strict: not_implemented = not_implemented() # pyright: ignore[reportIncompatibleMethodOverride] + ewm_mean = not_implemented() # type: ignore[misc] + map_batches = not_implemented() # type: ignore[misc] + replace_strict = not_implemented() # type: ignore[misc] cat: not_implemented = not_implemented() # type: ignore[assignment] diff --git a/narwhals/_sql/expr.py b/narwhals/_sql/expr.py index 0e8115f1a4..f427b73f40 100644 --- a/narwhals/_sql/expr.py +++ b/narwhals/_sql/expr.py @@ -783,6 +783,5 @@ def str(self) -> SQLExprStringNamespace[Self]: ... @property def dt(self) -> SQLExprDateTimeNamesSpace[Self]: ... - # Not implemented - drop_nulls: Any = not_implemented() - unique: Any = not_implemented() + drop_nulls = not_implemented() # type: ignore[misc] + unique = not_implemented() # type: ignore[misc] From bc5934027d3e250e76bdbfd5d02c99433dbc6f6b Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Sun, 7 Sep 2025 14:57:40 +0000 Subject: [PATCH 6/6] be reasonable https://github.com/narwhals-dev/narwhals/pull/3096#discussion_r2328699978 --- narwhals/dataframe.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/narwhals/dataframe.py b/narwhals/dataframe.py index 9cc5382c78..5b2752e029 100644 --- a/narwhals/dataframe.py +++ b/narwhals/dataframe.py @@ -91,6 +91,7 @@ ) PS = ParamSpec("PS") + Incomplete: TypeAlias = Any _FrameT = TypeVar("_FrameT", bound="IntoFrame") LazyFrameT = TypeVar("LazyFrameT", bound="IntoLazyFrame") @@ -284,7 +285,7 @@ def top_k( def join( self, - other: BaseFrame[_FrameT], + other: Incomplete, on: str | list[str] | None, how: JoinStrategy, *, @@ -335,7 +336,7 @@ def gather_every(self, n: int, offset: int = 0) -> Self: def join_asof( self, - other: BaseFrame[_FrameT], + other: Incomplete, *, left_on: str | None, right_on: str | None, @@ -1800,7 +1801,7 @@ def top_k( def join( self, - other: BaseFrame[DataFrameT], + other: Self, on: str | list[str] | None = None, how: JoinStrategy = "inner", *, @@ -1846,7 +1847,7 @@ def join( def join_asof( self, - other: BaseFrame[DataFrameT], + other: Self, *, left_on: str | None = None, right_on: str | None = None, @@ -3052,7 +3053,7 @@ def top_k( def join( self, - other: BaseFrame[LazyFrameT], + other: Self, on: str | list[str] | None = None, how: JoinStrategy = "inner", *, @@ -3107,7 +3108,7 @@ def join( def join_asof( self, - other: BaseFrame[LazyFrameT], + other: Self, *, left_on: str | None = None, right_on: str | None = None,