Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
0a95012
chore(typing): Add alias to identify holes
dangotbanned Feb 28, 2025
b752965
feat(typing): Add all methods used in `expr.py`
dangotbanned Feb 28, 2025
2b87d80
fix(typing): `map_batches` return type
dangotbanned Feb 28, 2025
44f3c6f
feat(typing): add `Expr` namespaces
dangotbanned Feb 28, 2025
01ea1b3
refactor: mark `ewm_mean` as unstable
dangotbanned Feb 28, 2025
26b967f
fix: start adding `NotImplementedError`s
dangotbanned Feb 28, 2025
39d6ced
fix(typing): add missing `[type-var]`
dangotbanned Feb 28, 2025
4ca309c
fix: permit `over(keys=...)` widening
dangotbanned Feb 28, 2025
0a93329
feat: replace boilerplate w/ `not_implemented` marker
dangotbanned Feb 28, 2025
6a138dd
refactor: replace the other ones
dangotbanned Feb 28, 2025
6b57795
feat(typing): mark all missing `DaskExpr`
dangotbanned Feb 28, 2025
7622439
feat(typing): mark all missing `DuckDBExpr`
dangotbanned Feb 28, 2025
ccd68f5
feat(typing): mark all missing `SparkLikeExpr`
dangotbanned Feb 28, 2025
a1a1461
fix(typing): resolve more out-of-sync signatures
dangotbanned Feb 28, 2025
f8ef0c7
ignore pretty reasonable warning
dangotbanned Feb 28, 2025
b2c19e7
fix(typing): remove defaults from protocol
dangotbanned Feb 28, 2025
6131455
test: add `test_not_implemented`
dangotbanned Feb 28, 2025
50bcef0
feat: kinda resolve the `property` case
dangotbanned Feb 28, 2025
427c8d9
docs: add "Returns"
dangotbanned Feb 28, 2025
fc02399
test: skip `duckdb` if not available
dangotbanned Feb 28, 2025
31f3506
fix: unbreak doctest
dangotbanned Feb 28, 2025
4c6b639
Merge branch 'main' into compliant-expr-spec
dangotbanned Mar 1, 2025
e599caa
Merge branch 'main' into compliant-expr-spec
dangotbanned Mar 1, 2025
85a691c
Merge remote-tracking branch 'upstream/main' into compliant-expr-spec
dangotbanned Mar 2, 2025
d3bc414
refactor: Convert `DaskExpr.replace_strict` into `not_implemented`
dangotbanned Mar 2, 2025
4804ab4
Merge remote-tracking branch 'upstream/main' into compliant-expr-spec
dangotbanned Mar 2, 2025
d8e32c0
feat(DRAFT): Add descriptor version `not_implemented_alt`
dangotbanned Mar 3, 2025
85df827
typo
dangotbanned Mar 3, 2025
88a073e
test: coverage for `@classmethod`-like?
dangotbanned Mar 3, 2025
6784a12
feat(DRAFT): Improved `@property` support
dangotbanned Mar 3, 2025
68b37d8
feat(DRAFT): Add a second option for `@property`
dangotbanned Mar 3, 2025
50a9376
fix: coverage, fix alias
dangotbanned Mar 3, 2025
f64b466
Merge remote-tracking branch 'upstream/main' into compliant-expr-spec
dangotbanned Mar 3, 2025
fc0071c
Merge remote-tracking branch 'upstream/main' into compliant-expr-spec
dangotbanned Mar 4, 2025
990ee7b
docs(DRAFT): Add IDE benefit demo video
dangotbanned Mar 4, 2025
f125b97
refactor: Fully replace original `not_implemented` callable
dangotbanned Mar 4, 2025
a076349
chore(typing): ignore pyright
dangotbanned Mar 4, 2025
6928c1e
docs: Handle `not_implemented` in `generate_backend_completeness`
dangotbanned Mar 4, 2025
fae840f
Merge remote-tracking branch 'upstream/main' into compliant-expr-spec
dangotbanned Mar 4, 2025
b4caa96
Merge branch 'main' into compliant-expr-spec
dangotbanned Mar 4, 2025
a124302
chore: todo -> to-done
dangotbanned Mar 4, 2025
7eab760
Merge branch 'compliant-expr-spec' of https://github.com/narwhals-dev…
dangotbanned Mar 4, 2025
233ba14
style: tidy up whitespace
dangotbanned Mar 4, 2025
3b24293
Merge branch 'main' into compliant-expr-spec
dangotbanned Mar 4, 2025
7400a22
refactor: move `@deprecated` to `utils`
dangotbanned Mar 4, 2025
400bbe4
docs: Finalize `unstable`, `not_implemented`
dangotbanned Mar 5, 2025
885c16b
docs: add missing import
dangotbanned Mar 5, 2025
7962cbb
revert: remove video
dangotbanned Mar 5, 2025
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
19 changes: 14 additions & 5 deletions narwhals/_arrow/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import Any
from typing import Callable
from typing import Literal
from typing import Mapping
from typing import Sequence

from narwhals._arrow.expr_cat import ArrowExprCatNamespace
Expand All @@ -21,6 +22,7 @@
from narwhals.exceptions import ColumnNotFoundError
from narwhals.typing import CompliantExpr
from narwhals.utils import Implementation
from narwhals.utils import not_implemented

if TYPE_CHECKING:
from typing_extensions import Self
Expand Down Expand Up @@ -262,7 +264,7 @@ def var(self: Self, ddof: int) -> Self:
def skew(self: Self) -> Self:
return reuse_series_implementation(self, "skew", returns_scalar=True)

def cast(self: Self, dtype: DType) -> Self:
def cast(self: Self, dtype: DType | type[DType]) -> Self:
return reuse_series_implementation(self, "cast", dtype=dtype)

def abs(self: Self) -> Self:
Expand Down Expand Up @@ -385,7 +387,11 @@ def unique(self: Self) -> Self:
return reuse_series_implementation(self, "unique", maintain_order=False)

def replace_strict(
self: Self, old: Sequence[Any], new: Sequence[Any], *, return_dtype: DType | None
self: Self,
old: Sequence[Any] | Mapping[Any, Any],
new: Sequence[Any],
*,
return_dtype: DType | type[DType] | None,
) -> Self:
return reuse_series_implementation(
self, "replace_strict", old=old, new=new, return_dtype=return_dtype
Expand Down Expand Up @@ -417,7 +423,7 @@ def clip(self: Self, lower_bound: Any | None, upper_bound: Any | None) -> Self:
self, "clip", lower_bound=lower_bound, upper_bound=upper_bound
)

def over(self: Self, keys: list[str], kind: ExprKind) -> Self:
def over(self: Self, keys: Sequence[str], kind: ExprKind) -> Self:
if not is_scalar_like(kind):
msg = "Only aggregation or literal operations are supported in `over` context for PyArrow."
raise NotImplementedError(msg)
Expand All @@ -434,8 +440,9 @@ def func(df: ArrowDataFrame) -> list[ArrowSeries]:
raise NotImplementedError(msg)

tmp = df.group_by(*keys, drop_null_keys=False).agg(self)
on = list(keys)
tmp = df.simple_select(*keys).join(
tmp, how="left", left_on=keys, right_on=keys, suffix="_right"
tmp, how="left", left_on=on, right_on=on, suffix="_right"
)
return [tmp[alias] for alias in aliases]

Expand All @@ -455,7 +462,7 @@ def mode(self: Self) -> Self:
def map_batches(
self: Self,
function: Callable[[Any], Any],
return_dtype: DType | None,
return_dtype: DType | type[DType] | None,
) -> Self:
def func(df: ArrowDataFrame) -> list[ArrowSeries]:
input_series_list = self._call(df)
Expand Down Expand Up @@ -579,6 +586,8 @@ def rank(
self, "rank", method=method, descending=descending
)

ewm_mean = not_implemented()

@property
def dt(self: Self) -> ArrowExprDateTimeNamespace:
return ArrowExprDateTimeNamespace(self)
Expand Down
2 changes: 1 addition & 1 deletion narwhals/_arrow/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ def is_null(self: Self) -> Self:
def is_nan(self: Self) -> Self:
return self._from_native_series(pc.is_nan(self._native_series))

def cast(self: Self, dtype: DType) -> Self:
def cast(self: Self, dtype: DType | type[DType]) -> Self:
ser = self._native_series
data_type = narwhals_to_native_dtype(dtype, self._version)
return self._from_native_series(pc.cast(ser, data_type))
Expand Down
30 changes: 23 additions & 7 deletions narwhals/_dask/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from narwhals.typing import CompliantExpr
from narwhals.utils import Implementation
from narwhals.utils import generate_temporary_column_name
from narwhals.utils import not_implemented

if TYPE_CHECKING:
try:
Expand Down Expand Up @@ -392,12 +393,6 @@ def unique(self: Self) -> Self:
def drop_nulls(self: Self) -> Self:
return self._from_call(lambda _input: _input.dropna(), "drop_nulls")

def replace_strict(
self: Self, old: Sequence[Any], new: Sequence[Any], *, return_dtype: DType | None
) -> Self:
msg = "`replace_strict` is not yet supported for Dask expressions"
raise NotImplementedError(msg)

def abs(self: Self) -> Self:
return self._from_call(lambda _input: _input.abs(), "abs")

Expand Down Expand Up @@ -538,7 +533,7 @@ def null_count(self: Self) -> Self:
lambda _input: _input.isna().sum().to_series(), "null_count"
)

def over(self: Self, keys: list[str], kind: ExprKind) -> Self:
def over(self: Self, keys: Sequence[str], kind: ExprKind) -> Self:
# pandas is a required dependency of dask so it's safe to import this
from narwhals._pandas_like.group_by import AGGREGATIONS_TO_PANDAS_EQUIVALENT

Expand Down Expand Up @@ -607,3 +602,24 @@ def dt(self: Self) -> DaskExprDateTimeNamespace:
@property
def name(self: Self) -> DaskExprNameNamespace:
return DaskExprNameNamespace(self)

arg_min = not_implemented()
arg_max = not_implemented()
arg_true = not_implemented()
head = not_implemented()
tail = not_implemented()
mode = not_implemented()
sort = not_implemented()
rank = not_implemented()
sample = not_implemented()
map_batches = not_implemented()
ewm_mean = not_implemented()
rolling_sum = not_implemented()
rolling_mean = not_implemented()
rolling_var = not_implemented()
rolling_std = not_implemented()
gather_every = not_implemented()
replace_strict = not_implemented()

cat = not_implemented() # pyright: ignore[reportAssignmentType]
list = not_implemented() # pyright: ignore[reportAssignmentType]
34 changes: 34 additions & 0 deletions narwhals/_duckdb/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from narwhals._expression_parsing import ExprKind
from narwhals.typing import CompliantExpr
from narwhals.utils import Implementation
from narwhals.utils import not_implemented

if TYPE_CHECKING:
import duckdb
Expand Down Expand Up @@ -482,3 +483,36 @@ def name(self: Self) -> DuckDBExprNameNamespace:
@property
def list(self: Self) -> DuckDBExprListNamespace:
return DuckDBExprListNamespace(self)

arg_min = not_implemented()
arg_max = not_implemented()
arg_true = not_implemented()
head = not_implemented()
tail = not_implemented()
mode = not_implemented()
sort = not_implemented()
rank = not_implemented()
sample = not_implemented()
map_batches = not_implemented()
ewm_mean = not_implemented()
rolling_sum = not_implemented()
rolling_mean = not_implemented()
rolling_var = not_implemented()
rolling_std = not_implemented()
gather_every = not_implemented()
drop_nulls = not_implemented()
diff = not_implemented()
unique = not_implemented()
shift = not_implemented()
is_unique = not_implemented()
is_first_distinct = not_implemented()
is_last_distinct = not_implemented()
cum_sum = not_implemented()
cum_count = not_implemented()
cum_min = not_implemented()
cum_max = not_implemented()
cum_prod = not_implemented()
replace_strict = not_implemented()
over = not_implemented()

cat = not_implemented() # pyright: ignore[reportAssignmentType]
11 changes: 8 additions & 3 deletions narwhals/_pandas_like/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import Any
from typing import Callable
from typing import Literal
from typing import Mapping
from typing import Sequence

from narwhals._expression_parsing import ExprKind
Expand Down Expand Up @@ -386,7 +387,11 @@ def drop_nulls(self: Self) -> Self:
return reuse_series_implementation(self, "drop_nulls")

def replace_strict(
self: Self, old: Sequence[Any], new: Sequence[Any], *, return_dtype: DType | None
self: Self,
old: Sequence[Any] | Mapping[Any, Any],
new: Sequence[Any],
*,
return_dtype: DType | type[DType] | None,
) -> Self:
return reuse_series_implementation(
self, "replace_strict", old=old, new=new, return_dtype=return_dtype
Expand Down Expand Up @@ -452,7 +457,7 @@ def alias_output_names(names: Sequence[str]) -> Sequence[str]:
call_kwargs=self._call_kwargs,
)

def over(self: Self, partition_by: list[str], kind: ExprKind) -> Self:
def over(self: Self, partition_by: Sequence[str], kind: ExprKind) -> Self:
if not is_elementary_expression(self):
msg = (
"Only elementary expressions are supported for `.over` in pandas-like backends.\n\n"
Expand Down Expand Up @@ -564,7 +569,7 @@ def mode(self: Self) -> Self:
def map_batches(
self: Self,
function: Callable[[Any], Any],
return_dtype: DType | None,
return_dtype: DType | type[DType] | None,
) -> Self:
def func(df: PandasLikeDataFrame) -> list[PandasLikeSeries]:
input_series_list = self._call(df)
Expand Down
36 changes: 35 additions & 1 deletion narwhals/_spark_like/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from narwhals.dependencies import get_pyspark
from narwhals.typing import CompliantExpr
from narwhals.utils import Implementation
from narwhals.utils import not_implemented
from narwhals.utils import parse_version

if TYPE_CHECKING:
Expand Down Expand Up @@ -486,7 +487,7 @@ def _n_unique(_input: Column) -> Column:

return self._from_call(_n_unique, "n_unique")

def over(self: Self, keys: list[str], kind: ExprKind) -> Self:
def over(self: Self, keys: Sequence[str], kind: ExprKind) -> Self:
def func(df: SparkLikeLazyFrame) -> list[Column]:
return [expr.over(self._Window.partitionBy(*keys)) for expr in self._call(df)]

Expand Down Expand Up @@ -526,3 +527,36 @@ def dt(self: Self) -> SparkLikeExprDateTimeNamespace:
@property
def list(self: Self) -> SparkLikeExprListNamespace:
return SparkLikeExprListNamespace(self)

arg_min = not_implemented()
arg_max = not_implemented()
arg_true = not_implemented()
head = not_implemented()
tail = not_implemented()
mode = not_implemented()
sort = not_implemented()
rank = not_implemented()
sample = not_implemented()
map_batches = not_implemented()
ewm_mean = not_implemented()
rolling_sum = not_implemented()
rolling_mean = not_implemented()
rolling_var = not_implemented()
rolling_std = not_implemented()
gather_every = not_implemented()
drop_nulls = not_implemented()
diff = not_implemented()
unique = not_implemented()
shift = not_implemented()
is_first_distinct = not_implemented()
is_last_distinct = not_implemented()
cum_sum = not_implemented()
cum_count = not_implemented()
cum_min = not_implemented()
cum_max = not_implemented()
cum_prod = not_implemented()
replace_strict = not_implemented()
fill_null = not_implemented()
quantile = not_implemented()

cat = not_implemented() # pyright: ignore[reportAssignmentType]
9 changes: 7 additions & 2 deletions narwhals/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,18 @@
from typing_extensions import Concatenate
from typing_extensions import ParamSpec
from typing_extensions import Self
from typing_extensions import TypeAlias

from narwhals.dtypes import DType
from narwhals.typing import CompliantExpr
from narwhals.typing import CompliantNamespace
from narwhals.typing import IntoExpr

PS = ParamSpec("PS")
R = TypeVar("R")
_ToCompliant: TypeAlias = Callable[
[CompliantNamespace[Any, Any]], CompliantExpr[Any, Any]
]


class Expr:
Expand All @@ -47,7 +52,7 @@ def __init__(
metadata: ExprMetadata,
) -> None:
# callable from CompliantNamespace to CompliantExpr
self._to_compliant_expr = to_compliant_expr
self._to_compliant_expr: _ToCompliant = to_compliant_expr
self._metadata = metadata

def _from_callable(self, to_compliant_expr: Callable[[Any], Any]) -> Self:
Expand Down Expand Up @@ -608,7 +613,7 @@ def var(self: Self, *, ddof: int = 1) -> Self:

def map_batches(
self: Self,
function: Callable[[Any], Self],
function: Callable[[Any], CompliantExpr[Any, Any]],
return_dtype: DType | None = None,
) -> Self:
"""Apply a custom python function to a whole Series or sequence of Series.
Expand Down
2 changes: 1 addition & 1 deletion narwhals/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1490,7 +1490,7 @@ def func(plx: CompliantNamespace[Any, Any]) -> CompliantExpr[Any, Any]:
compliant_value = extract_compliant(plx, value, str_as_lit=False)
if is_scalar_like(kind) and is_compliant_expr(compliant_value):
compliant_value = compliant_value.broadcast(kind)
return compliant_expr.otherwise(compliant_value) # type: ignore[no-any-return]
return compliant_expr.otherwise(compliant_value) # type: ignore[attr-defined, no-any-return]

return Expr(
func,
Expand Down
Loading
Loading