diff --git a/narwhals/_compliant/expr.py b/narwhals/_compliant/expr.py index e48c773730..ffa9e39cbc 100644 --- a/narwhals/_compliant/expr.py +++ b/narwhals/_compliant/expr.py @@ -28,7 +28,8 @@ from narwhals._compliant.typing import EagerDataFrameT from narwhals._compliant.typing import EagerExprT from narwhals._compliant.typing import EagerSeriesT -from narwhals._compliant.typing import NativeExprT_co +from narwhals._compliant.typing import LazyExprT +from narwhals._compliant.typing import NativeExprT from narwhals._expression_parsing import evaluate_output_names_and_aliases from narwhals.dependencies import get_numpy from narwhals.dependencies import is_numpy_array @@ -868,9 +869,9 @@ def struct(self) -> EagerExprStructNamespace[Self]: return EagerExprStructNamespace(self) -class LazyExpr( - CompliantExpr[CompliantLazyFrameT, NativeExprT_co], - Protocol38[CompliantLazyFrameT, NativeExprT_co], +class LazyExpr( # type: ignore[misc] + CompliantExpr[CompliantLazyFrameT, NativeExprT], + Protocol38[CompliantLazyFrameT, NativeExprT], ): arg_min: not_implemented = not_implemented() arg_max: not_implemented = not_implemented() @@ -891,7 +892,12 @@ class LazyExpr( def _is_expr(cls, obj: Self | Any) -> TypeIs[Self]: return hasattr(obj, "__narwhals_expr__") - def _with_callable(self: Self, call: Callable[..., Any], /) -> Self: ... + def _with_callable(self, call: Callable[..., Any], /) -> Self: ... + def _with_alias_output_names(self, func: AliasNames | None, /) -> Self: ... + + @property + def name(self) -> LazyExprNameNamespace[Self]: + return LazyExprNameNamespace(self) class _ExprNamespace( # type: ignore[misc] @@ -909,6 +915,11 @@ def __init__(self, expr: EagerExprT, /) -> None: self._compliant_expr = expr +class LazyExprNamespace(_ExprNamespace[LazyExprT], Generic[LazyExprT]): + def __init__(self, expr: LazyExprT, /) -> None: + self._compliant_expr = expr + + class EagerExprCatNamespace( EagerExprNamespace[EagerExprT], CatNamespace[EagerExprT], Generic[EagerExprT] ): @@ -996,25 +1007,27 @@ def len(self) -> EagerExprT: return self.compliant._reuse_series_namespace("list", "len") -class EagerExprNameNamespace( - EagerExprNamespace[EagerExprT], NameNamespace[EagerExprT], Generic[EagerExprT] +class CompliantExprNameNamespace( # type: ignore[misc] + _ExprNamespace[CompliantExprT_co], + NameNamespace[CompliantExprT_co], + Protocol[CompliantExprT_co], ): - def keep(self) -> EagerExprT: + def keep(self) -> CompliantExprT_co: return self._from_callable(lambda name: name, alias=False) - def map(self, function: AliasName) -> EagerExprT: + def map(self, function: AliasName) -> CompliantExprT_co: return self._from_callable(function) - def prefix(self, prefix: str) -> EagerExprT: + def prefix(self, prefix: str) -> CompliantExprT_co: return self._from_callable(lambda name: f"{prefix}{name}") - def suffix(self, suffix: str) -> EagerExprT: + def suffix(self, suffix: str) -> CompliantExprT_co: return self._from_callable(lambda name: f"{name}{suffix}") - def to_lowercase(self) -> EagerExprT: + def to_lowercase(self) -> CompliantExprT_co: return self._from_callable(str.lower) - def to_uppercase(self) -> EagerExprT: + def to_uppercase(self) -> CompliantExprT_co: return self._from_callable(str.upper) @staticmethod @@ -1024,6 +1037,16 @@ def fn(output_names: Sequence[str], /) -> Sequence[str]: return fn + def _from_callable( + self, func: AliasName, /, *, alias: bool = True + ) -> CompliantExprT_co: ... + + +class EagerExprNameNamespace( + EagerExprNamespace[EagerExprT], + CompliantExprNameNamespace[EagerExprT], + Generic[EagerExprT], +): def _from_callable(self, func: AliasName, /, *, alias: bool = True) -> EagerExprT: expr = self.compliant return type(expr)( @@ -1042,6 +1065,17 @@ def _from_callable(self, func: AliasName, /, *, alias: bool = True) -> EagerExpr ) +class LazyExprNameNamespace( + LazyExprNamespace[LazyExprT], + CompliantExprNameNamespace[LazyExprT], + Generic[LazyExprT], +): + def _from_callable(self, func: AliasName, /, *, alias: bool = True) -> LazyExprT: + expr = self.compliant + output_names = self._alias_output_names(func) if alias else None + return expr._with_alias_output_names(output_names) + + class EagerExprStringNamespace( EagerExprNamespace[EagerExprT], StringNamespace[EagerExprT], Generic[EagerExprT] ): diff --git a/narwhals/_compliant/typing.py b/narwhals/_compliant/typing.py index 5b058f3c41..63e275ed76 100644 --- a/narwhals/_compliant/typing.py +++ b/narwhals/_compliant/typing.py @@ -102,6 +102,7 @@ # NOTE: `pyright` gives false (8) positives if this uses `EagerDataFrameAny`? EagerDataFrameT = TypeVar("EagerDataFrameT", bound="EagerDataFrame[Any, Any, Any]") +LazyExprT = TypeVar("LazyExprT", bound=LazyExprAny) LazyExprT_contra = TypeVar("LazyExprT_contra", bound=LazyExprAny, contravariant=True) AliasNames: TypeAlias = Callable[[Sequence[str]], Sequence[str]] diff --git a/narwhals/_dask/expr.py b/narwhals/_dask/expr.py index 6f0dead58f..244e978299 100644 --- a/narwhals/_dask/expr.py +++ b/narwhals/_dask/expr.py @@ -10,7 +10,6 @@ from narwhals._compliant import LazyExpr from narwhals._compliant.expr import DepthTrackingExpr from narwhals._dask.expr_dt import DaskExprDateTimeNamespace -from narwhals._dask.expr_name import DaskExprNameNamespace from narwhals._dask.expr_str import DaskExprStringNamespace from narwhals._dask.utils import add_row_index from narwhals._dask.utils import maybe_evaluate_expr @@ -25,6 +24,7 @@ from narwhals.utils import not_implemented if TYPE_CHECKING: + from narwhals._compliant.typing import AliasNames from narwhals._expression_parsing import ExprKind try: @@ -183,6 +183,18 @@ def func(df: DaskLazyFrame) -> list[dx.Series]: call_kwargs=call_kwargs, ) + def _with_alias_output_names(self, func: AliasNames | None, /) -> Self: + return type(self)( + call=self._call, + depth=self._depth, + function_name=self._function_name, + evaluate_output_names=self._evaluate_output_names, + alias_output_names=func, + backend_version=self._backend_version, + version=self._version, + call_kwargs=self._call_kwargs, + ) + def alias(self: Self, name: str) -> Self: def alias_output_names(names: Sequence[str]) -> Sequence[str]: if len(names) != 1: @@ -668,9 +680,5 @@ def str(self: Self) -> DaskExprStringNamespace: def dt(self: Self) -> DaskExprDateTimeNamespace: return DaskExprDateTimeNamespace(self) - @property - def name(self: Self) -> DaskExprNameNamespace: - return DaskExprNameNamespace(self) - list = not_implemented() # pyright: ignore[reportAssignmentType] struct = not_implemented() # pyright: ignore[reportAssignmentType] diff --git a/narwhals/_dask/expr_name.py b/narwhals/_dask/expr_name.py deleted file mode 100644 index 00c6e7df6e..0000000000 --- a/narwhals/_dask/expr_name.py +++ /dev/null @@ -1,68 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING -from typing import Callable -from typing import Sequence - -if TYPE_CHECKING: - from typing_extensions import Self - - from narwhals._dask.expr import DaskExpr - - -class DaskExprNameNamespace: - def __init__(self: Self, expr: DaskExpr) -> None: - self._compliant_expr = expr - - def keep(self: Self) -> DaskExpr: - return self._with_alias_output_names(alias_output_names=None) - - def map(self: Self, function: Callable[[str], str]) -> DaskExpr: - return self._with_alias_output_names( - alias_output_names=lambda output_names: [ - function(str(name)) for name in output_names - ], - ) - - def prefix(self: Self, prefix: str) -> DaskExpr: - return self._with_alias_output_names( - alias_output_names=lambda output_names: [ - f"{prefix}{output_name}" for output_name in output_names - ], - ) - - def suffix(self: Self, suffix: str) -> DaskExpr: - return self._with_alias_output_names( - alias_output_names=lambda output_names: [ - f"{output_name}{suffix}" for output_name in output_names - ] - ) - - def to_lowercase(self: Self) -> DaskExpr: - return self._with_alias_output_names( - alias_output_names=lambda output_names: [ - str(name).lower() for name in output_names - ], - ) - - def to_uppercase(self: Self) -> DaskExpr: - return self._with_alias_output_names( - alias_output_names=lambda output_names: [ - str(name).upper() for name in output_names - ] - ) - - def _with_alias_output_names( - self: Self, - alias_output_names: Callable[[Sequence[str]], Sequence[str]] | None, - ) -> DaskExpr: - return self._compliant_expr.__class__( - call=self._compliant_expr._call, - depth=self._compliant_expr._depth, - function_name=self._compliant_expr._function_name, - evaluate_output_names=self._compliant_expr._evaluate_output_names, - alias_output_names=alias_output_names, - backend_version=self._compliant_expr._backend_version, - version=self._compliant_expr._version, - call_kwargs=self._compliant_expr._call_kwargs, - ) diff --git a/narwhals/_duckdb/expr.py b/narwhals/_duckdb/expr.py index 93931fbe62..05ee4bd874 100644 --- a/narwhals/_duckdb/expr.py +++ b/narwhals/_duckdb/expr.py @@ -18,7 +18,6 @@ from narwhals._compliant import LazyExpr from narwhals._duckdb.expr_dt import DuckDBExprDateTimeNamespace from narwhals._duckdb.expr_list import DuckDBExprListNamespace -from narwhals._duckdb.expr_name import DuckDBExprNameNamespace from narwhals._duckdb.expr_str import DuckDBExprStringNamespace from narwhals._duckdb.expr_struct import DuckDBExprStructNamespace from narwhals._duckdb.utils import WindowInputs @@ -34,6 +33,7 @@ import duckdb from typing_extensions import Self + from narwhals._compliant.typing import AliasNames from narwhals._duckdb.dataframe import DuckDBLazyFrame from narwhals._duckdb.namespace import DuckDBNamespace from narwhals._duckdb.typing import WindowFunction @@ -235,6 +235,15 @@ def func(df: DuckDBLazyFrame) -> list[duckdb.Expression]: version=self._version, ) + def _with_alias_output_names(self, func: AliasNames | None, /) -> Self: + return type(self)( + call=self._call, + evaluate_output_names=self._evaluate_output_names, + alias_output_names=func, + backend_version=self._backend_version, + version=self._version, + ) + def _with_window_function( self: Self, window_function: WindowFunction, @@ -691,10 +700,6 @@ def str(self: Self) -> DuckDBExprStringNamespace: def dt(self: Self) -> DuckDBExprDateTimeNamespace: return DuckDBExprDateTimeNamespace(self) - @property - def name(self: Self) -> DuckDBExprNameNamespace: - return DuckDBExprNameNamespace(self) - @property def list(self: Self) -> DuckDBExprListNamespace: return DuckDBExprListNamespace(self) diff --git a/narwhals/_duckdb/expr_name.py b/narwhals/_duckdb/expr_name.py deleted file mode 100644 index 27895427e5..0000000000 --- a/narwhals/_duckdb/expr_name.py +++ /dev/null @@ -1,65 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING -from typing import Callable -from typing import Sequence - -if TYPE_CHECKING: - from typing_extensions import Self - - from narwhals._duckdb.expr import DuckDBExpr - - -class DuckDBExprNameNamespace: - def __init__(self: Self, expr: DuckDBExpr) -> None: - self._compliant_expr = expr - - def keep(self: Self) -> DuckDBExpr: - return self._with_alias_output_names(alias_output_names=None) - - def map(self: Self, function: Callable[[str], str]) -> DuckDBExpr: - return self._with_alias_output_names( - alias_output_names=lambda output_names: [ - function(name) for name in output_names - ], - ) - - def prefix(self: Self, prefix: str) -> DuckDBExpr: - return self._with_alias_output_names( - alias_output_names=lambda output_names: [ - f"{prefix}{output_name}" for output_name in output_names - ], - ) - - def suffix(self: Self, suffix: str) -> DuckDBExpr: - return self._with_alias_output_names( - alias_output_names=lambda output_names: [ - f"{output_name}{suffix}" for output_name in output_names - ] - ) - - def to_lowercase(self: Self) -> DuckDBExpr: - return self._with_alias_output_names( - alias_output_names=lambda output_names: [ - name.lower() for name in output_names - ], - ) - - def to_uppercase(self: Self) -> DuckDBExpr: - return self._with_alias_output_names( - alias_output_names=lambda output_names: [ - name.upper() for name in output_names - ] - ) - - def _with_alias_output_names( - self: Self, - alias_output_names: Callable[[Sequence[str]], Sequence[str]] | None, - ) -> DuckDBExpr: - return self._compliant_expr.__class__( - call=self._compliant_expr._call, - evaluate_output_names=self._compliant_expr._evaluate_output_names, - alias_output_names=alias_output_names, - backend_version=self._compliant_expr._backend_version, - version=self._compliant_expr._version, - ) diff --git a/narwhals/_spark_like/expr.py b/narwhals/_spark_like/expr.py index 2ed12db3ec..294a055f7c 100644 --- a/narwhals/_spark_like/expr.py +++ b/narwhals/_spark_like/expr.py @@ -12,7 +12,6 @@ from narwhals._expression_parsing import ExprKind from narwhals._spark_like.expr_dt import SparkLikeExprDateTimeNamespace from narwhals._spark_like.expr_list import SparkLikeExprListNamespace -from narwhals._spark_like.expr_name import SparkLikeExprNameNamespace from narwhals._spark_like.expr_str import SparkLikeExprStringNamespace from narwhals._spark_like.expr_struct import SparkLikeExprStructNamespace from narwhals._spark_like.utils import WindowInputs @@ -30,6 +29,7 @@ from sqlframe.base.window import Window from typing_extensions import Self + from narwhals._compliant.typing import AliasNames from narwhals._expression_parsing import ExprMetadata from narwhals._spark_like.dataframe import SparkLikeLazyFrame from narwhals._spark_like.namespace import SparkLikeNamespace @@ -274,6 +274,16 @@ def func(df: SparkLikeLazyFrame) -> list[Column]: implementation=self._implementation, ) + def _with_alias_output_names(self, func: AliasNames | None, /) -> Self: + return type(self)( + call=self._call, + evaluate_output_names=self._evaluate_output_names, + alias_output_names=func, + backend_version=self._backend_version, + version=self._version, + implementation=self._implementation, + ) + def __eq__(self: Self, other: SparkLikeExpr) -> Self: # type: ignore[override] return self._with_callable( lambda _input, other: _input.__eq__(other), other=other @@ -750,10 +760,6 @@ def rolling_std( def str(self: Self) -> SparkLikeExprStringNamespace: return SparkLikeExprStringNamespace(self) - @property - def name(self: Self) -> SparkLikeExprNameNamespace: - return SparkLikeExprNameNamespace(self) - @property def dt(self: Self) -> SparkLikeExprDateTimeNamespace: return SparkLikeExprDateTimeNamespace(self) diff --git a/narwhals/_spark_like/expr_name.py b/narwhals/_spark_like/expr_name.py deleted file mode 100644 index cada7e00ee..0000000000 --- a/narwhals/_spark_like/expr_name.py +++ /dev/null @@ -1,66 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING -from typing import Callable -from typing import Sequence - -if TYPE_CHECKING: - from typing_extensions import Self - - from narwhals._spark_like.expr import SparkLikeExpr - - -class SparkLikeExprNameNamespace: - def __init__(self: Self, expr: SparkLikeExpr) -> None: - self._compliant_expr = expr - - def keep(self: Self) -> SparkLikeExpr: - return self._with_alias_output_names(alias_output_names=None) - - def map(self: Self, function: Callable[[str], str]) -> SparkLikeExpr: - return self._with_alias_output_names( - alias_output_names=lambda output_names: [ - function(name) for name in output_names - ], - ) - - def prefix(self: Self, prefix: str) -> SparkLikeExpr: - return self._with_alias_output_names( - alias_output_names=lambda output_names: [ - f"{prefix}{output_name}" for output_name in output_names - ], - ) - - def suffix(self: Self, suffix: str) -> SparkLikeExpr: - return self._with_alias_output_names( - alias_output_names=lambda output_names: [ - f"{output_name}{suffix}" for output_name in output_names - ] - ) - - def to_lowercase(self: Self) -> SparkLikeExpr: - return self._with_alias_output_names( - alias_output_names=lambda output_names: [ - name.lower() for name in output_names - ], - ) - - def to_uppercase(self: Self) -> SparkLikeExpr: - return self._with_alias_output_names( - alias_output_names=lambda output_names: [ - name.upper() for name in output_names - ] - ) - - def _with_alias_output_names( - self: Self, - alias_output_names: Callable[[Sequence[str]], Sequence[str]] | None, - ) -> SparkLikeExpr: - return self._compliant_expr.__class__( - self._compliant_expr._call, - evaluate_output_names=self._compliant_expr._evaluate_output_names, - alias_output_names=alias_output_names, - backend_version=self._compliant_expr._backend_version, - version=self._compliant_expr._version, - implementation=self._compliant_expr._implementation, - )