Skip to content

Commit 035976b

Browse files
committed
feat: Define CompliantSeries._to_expr
Resolves: (#2149 (comment)) Doing this properly surfaced lots of issues - `Polars*` classes are *similar* to `Compliant*` - Not close enough to support typing - Lots of stuff is behind `__getattr__` - We don't need `PolarsExpr` to do a lot of the work `CompliantExpr` does - `nw.Series._compliant_series` is still a big PR away - Repeat of (#2119) - Last big hurdle to get (#2104 (comment))
1 parent 7cd3e2d commit 035976b

File tree

9 files changed

+53
-34
lines changed

9 files changed

+53
-34
lines changed

narwhals/_compliant/series.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
if TYPE_CHECKING:
1010
from typing_extensions import Self
1111

12+
from narwhals._compliant.expr import CompliantExpr # noqa: F401
13+
from narwhals._compliant.expr import EagerExpr
14+
from narwhals._compliant.namespace import CompliantNamespace # noqa: F401
15+
from narwhals._compliant.namespace import EagerNamespace
1216
from narwhals.dtypes import DType
1317
from narwhals.typing import NativeSeries
1418
from narwhals.utils import Implementation
@@ -27,6 +31,8 @@ def dtype(self) -> DType: ...
2731
def name(self) -> str: ...
2832
def __narwhals_series__(self) -> CompliantSeries: ...
2933
def alias(self, name: str) -> Self: ...
34+
def __narwhals_namespace__(self) -> Any: ... # CompliantNamespace[Any, Self]: ...
35+
def _to_expr(self) -> Any: ... # CompliantExpr[Any, Self]: ...
3036

3137

3238
class EagerSeries(CompliantSeries, Protocol[NativeSeriesT_co]):
@@ -50,6 +56,11 @@ def _from_iterable(
5056
cls: type[Self], data: Iterable[Any], name: str, *, context: _FullContext
5157
) -> Self: ...
5258

59+
def __narwhals_namespace__(self) -> EagerNamespace[Any, Self]: ...
60+
61+
def _to_expr(self) -> EagerExpr[Any, Self]:
62+
return self.__narwhals_namespace__()._expr._from_series(self)
63+
5364
# TODO @dangotbanned: replacing `Namespace._create_compliant_series``
5465
# - All usage within `*Expr.map_batches`
5566
# - `PandasLikeExpr` uses that **once**

narwhals/_expression_parsing.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from narwhals.exceptions import LengthChangingExprError
1919
from narwhals.exceptions import ShapeError
2020
from narwhals.utils import is_compliant_expr
21-
from narwhals.utils import is_eager_namespace
2221

2322
if TYPE_CHECKING:
2423
from typing_extensions import Never
@@ -132,14 +131,9 @@ def extract_compliant(
132131
if isinstance(other, str) and not str_as_lit:
133132
return plx.col(other)
134133
if is_narwhals_series(other):
135-
if is_eager_namespace(plx):
136-
return plx._expr._from_series(other._compliant_series)
137-
return plx._create_expr_from_series(other._compliant_series) # type: ignore[attr-defined]
134+
return other._compliant_series._to_expr()
138135
if is_numpy_array(other):
139-
if is_eager_namespace(plx):
140-
return plx._expr._from_series(plx._create_compliant_series(other))
141-
series = plx._create_compliant_series(other) # type: ignore[attr-defined]
142-
return plx._create_expr_from_series(series) # type: ignore[attr-defined]
136+
return plx._create_compliant_series(other)._to_expr() # type: ignore[attr-defined]
143137
return other
144138

145139

narwhals/_pandas_like/series.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939

4040
from narwhals._arrow.typing import ArrowArray
4141
from narwhals._pandas_like.dataframe import PandasLikeDataFrame
42+
from narwhals._pandas_like.namespace import PandasLikeNamespace
4243
from narwhals.dtypes import DType
4344
from narwhals.typing import _1DArray
4445
from narwhals.typing import _AnyDArray
@@ -131,6 +132,13 @@ def __native_namespace__(self: Self) -> ModuleType:
131132
def __narwhals_series__(self: Self) -> Self:
132133
return self
133134

135+
def __narwhals_namespace__(self) -> PandasLikeNamespace:
136+
from narwhals._pandas_like.namespace import PandasLikeNamespace
137+
138+
return PandasLikeNamespace(
139+
self._implementation, self._backend_version, self._version
140+
)
141+
134142
@overload
135143
def __getitem__(self: Self, idx: int) -> Any: ...
136144

narwhals/_polars/expr.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ def _from_native_expr(self: Self, expr: pl.Expr) -> Self:
3838
expr, version=self._version, backend_version=self._backend_version
3939
)
4040

41+
@classmethod
42+
def _from_series(cls, series: Any) -> Self:
43+
return cls(
44+
series._native_series,
45+
version=series._version,
46+
backend_version=series._backend_version,
47+
)
48+
4149
def broadcast(self, kind: Literal[ExprKind.AGGREGATION, ExprKind.LITERAL]) -> Self:
4250
# Let Polars do its thing.
4351
return self

narwhals/_polars/namespace.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,19 +49,19 @@ def func(*args: Any, **kwargs: Any) -> Any:
4949

5050
return func
5151

52+
@property
53+
def _expr(self) -> type[PolarsExpr]:
54+
return PolarsExpr
55+
56+
@property
57+
def _series(self) -> type[PolarsSeries]:
58+
return PolarsSeries
59+
5260
def _create_compliant_series(self, value: Any) -> PolarsSeries:
5361
return PolarsSeries(
5462
pl.Series(value), backend_version=self._backend_version, version=self._version
5563
)
5664

57-
def _create_expr_from_series(self, value: Any) -> PolarsExpr:
58-
# Let Polars do its own thing.
59-
return PolarsExpr(
60-
value._native_series,
61-
version=self._version,
62-
backend_version=self._backend_version,
63-
)
64-
6565
def nth(self: Self, *indices: int) -> PolarsExpr:
6666
from narwhals._polars.expr import PolarsExpr
6767

narwhals/_polars/series.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
from typing_extensions import Self
2424

2525
from narwhals._polars.dataframe import PolarsDataFrame
26+
from narwhals._polars.expr import PolarsExpr
27+
from narwhals._polars.namespace import PolarsNamespace
2628
from narwhals.dtypes import DType
2729
from narwhals.typing import _1DArray
2830
from narwhals.utils import Version
@@ -47,6 +49,13 @@ def __init__(
4749
def __repr__(self: Self) -> str: # pragma: no cover
4850
return "PolarsSeries"
4951

52+
def __narwhals_namespace__(self) -> PolarsNamespace:
53+
from narwhals._polars.namespace import PolarsNamespace
54+
55+
return PolarsNamespace(
56+
backend_version=self._backend_version, version=self._version
57+
)
58+
5059
def __narwhals_series__(self: Self) -> Self:
5160
return self
5261

@@ -90,6 +99,9 @@ def _from_native_object(
9099
# scalar
91100
return series
92101

102+
def _to_expr(self) -> PolarsExpr:
103+
return self.__narwhals_namespace__()._expr._from_series(self)
104+
93105
def __getattr__(self: Self, attr: str) -> Any:
94106
if attr == "as_py": # pragma: no cover
95107
raise AttributeError

narwhals/dataframe.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
from narwhals.utils import find_stacklevel
3434
from narwhals.utils import flatten
3535
from narwhals.utils import generate_repr
36-
from narwhals.utils import is_eager_namespace
3736
from narwhals.utils import is_sequence_but_not_str
3837
from narwhals.utils import issue_deprecation_warning
3938
from narwhals.utils import parse_version
@@ -430,9 +429,7 @@ def _extract_compliant(self: Self, arg: Any) -> Any:
430429
if isinstance(arg, BaseFrame):
431430
return arg._compliant_frame
432431
if isinstance(arg, Series):
433-
if is_eager_namespace(plx):
434-
return plx._expr._from_series(arg._compliant_series)
435-
return plx._create_expr_from_series(arg._compliant_series)
432+
return arg._compliant_series._to_expr()
436433
if isinstance(arg, Expr):
437434
return arg._to_compliant_expr(self.__narwhals_namespace__())
438435
if isinstance(arg, str):
@@ -446,9 +443,7 @@ def _extract_compliant(self: Self, arg: Any) -> Any:
446443
)
447444
raise TypeError(msg)
448445
if is_numpy_array(arg):
449-
if is_eager_namespace(plx):
450-
return plx._expr._from_series(plx._create_compliant_series(arg))
451-
return plx._create_expr_from_series(plx._create_compliant_series(arg))
446+
return plx._create_compliant_series(arg)._to_expr()
452447
raise InvalidIntoExprError.from_invalid_type(type(arg))
453448

454449
@property

narwhals/series.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ def __init__(
7676
) -> None:
7777
self._level: Literal["full", "lazy", "interchange"] = level
7878
if hasattr(series, "__narwhals_series__"):
79+
# TODO @dangotbanned: Repeat (#2119) for `CompliantSeries` to support typing
80+
# morally: `CompliantSeries`
7981
self._compliant_series = series.__narwhals_series__()
8082
else: # pragma: no cover
8183
msg = f"Expected Polars Series or an object which implements `__narwhals_series__`, got: {type(series)}."

narwhals/utils.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,6 @@
5656
from narwhals._compliant import CompliantFrameT
5757
from narwhals._compliant import CompliantSeriesOrNativeExprT_co
5858
from narwhals._compliant import CompliantSeriesT_co
59-
from narwhals._compliant import EagerDataFrameT
60-
from narwhals._compliant import EagerNamespace
61-
from narwhals._compliant import EagerSeriesT
6259
from narwhals.dataframe import DataFrame
6360
from narwhals.dataframe import LazyFrame
6461
from narwhals.dtypes import DType
@@ -1402,14 +1399,6 @@ def is_compliant_expr(
14021399
return hasattr(obj, "__narwhals_expr__")
14031400

14041401

1405-
# NOTE: Temporary - just to introduce a path for the (Arrow|PandasLike) WIP
1406-
def is_eager_namespace(
1407-
obj: EagerNamespace[EagerDataFrameT, EagerSeriesT] | Any,
1408-
) -> TypeIs[EagerNamespace[EagerDataFrameT, EagerSeriesT]]:
1409-
return type(obj).__name__ in {"ArrowNamespace", "PandasLikeNamespace"}
1410-
# return all(hasattr(obj, name) for name in ("selectors", "_expr", "_series")) # noqa: ERA001
1411-
1412-
14131402
def has_native_namespace(obj: Any) -> TypeIs[SupportsNativeNamespace]:
14141403
return hasattr(obj, "__native_namespace__")
14151404

0 commit comments

Comments
 (0)