Skip to content

Commit 501733b

Browse files
authored
chore: Add Compliant*.to_narwhals (#2461)
1 parent ea1f5f0 commit 501733b

File tree

16 files changed

+206
-214
lines changed

16 files changed

+206
-214
lines changed

narwhals/_arrow/dataframe.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@
5151
from narwhals._arrow.typing import ChunkedArrayAny
5252
from narwhals._arrow.typing import Mask # type: ignore[attr-defined]
5353
from narwhals._arrow.typing import Order # type: ignore[attr-defined]
54+
from narwhals._compliant.typing import CompliantDataFrameAny
55+
from narwhals._compliant.typing import CompliantLazyFrameAny
5456
from narwhals._translate import IntoArrowTable
5557
from narwhals.dtypes import DType
5658
from narwhals.schema import Schema
57-
from narwhals.typing import CompliantDataFrame
58-
from narwhals.typing import CompliantLazyFrame
5959
from narwhals.typing import JoinStrategy
6060
from narwhals.typing import SizedMultiIndexSelector
6161
from narwhals.typing import SizedMultiNameSelector
@@ -539,9 +539,7 @@ def tail(self, n: int) -> Self:
539539
else:
540540
return self._with_native(df.slice(abs(n)), validate_column_names=False)
541541

542-
def lazy(
543-
self, *, backend: Implementation | None = None
544-
) -> CompliantLazyFrame[Any, Any]:
542+
def lazy(self, *, backend: Implementation | None = None) -> CompliantLazyFrameAny:
545543
if backend is None:
546544
return self
547545
elif backend is Implementation.DUCKDB:
@@ -579,10 +577,8 @@ def lazy(
579577
raise AssertionError # pragma: no cover
580578

581579
def collect(
582-
self,
583-
backend: Implementation | None,
584-
**kwargs: Any,
585-
) -> CompliantDataFrame[Any, Any, Any]:
580+
self, backend: Implementation | None, **kwargs: Any
581+
) -> CompliantDataFrameAny:
586582
if backend is Implementation.PYARROW or backend is None:
587583
from narwhals._arrow.dataframe import ArrowDataFrame
588584

narwhals/_compliant/dataframe.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
from typing import TypeVar
1313
from typing import overload
1414

15+
from narwhals._compliant.typing import CompliantDataFrameAny
1516
from narwhals._compliant.typing import CompliantExprT_contra
17+
from narwhals._compliant.typing import CompliantLazyFrameAny
1618
from narwhals._compliant.typing import CompliantSeriesT
1719
from narwhals._compliant.typing import EagerExprT
1820
from narwhals._compliant.typing import EagerSeriesT
@@ -22,6 +24,8 @@
2224
from narwhals._translate import DictConvertible
2325
from narwhals._translate import FromNative
2426
from narwhals._translate import NumpyConvertible
27+
from narwhals._translate import ToNarwhals
28+
from narwhals._translate import ToNarwhalsT_co
2529
from narwhals.utils import Version
2630
from narwhals.utils import _StoresNative
2731
from narwhals.utils import deprecated
@@ -47,6 +51,7 @@
4751
from narwhals._compliant.group_by import DataFrameGroupBy
4852
from narwhals._compliant.namespace import EagerNamespace
4953
from narwhals._translate import IntoArrowTable
54+
from narwhals.dataframe import DataFrame
5055
from narwhals.dtypes import DType
5156
from narwhals.schema import Schema
5257
from narwhals.typing import AsofJoinStrategy
@@ -81,8 +86,9 @@ class CompliantDataFrame(
8186
ArrowConvertible["pa.Table", "IntoArrowTable"],
8287
_StoresNative[NativeFrameT],
8388
FromNative[NativeFrameT],
89+
ToNarwhals[ToNarwhalsT_co],
8490
Sized,
85-
Protocol[CompliantSeriesT, CompliantExprT_contra, NativeFrameT],
91+
Protocol[CompliantSeriesT, CompliantExprT_contra, NativeFrameT, ToNarwhalsT_co],
8692
):
8793
_native_frame: NativeFrameT
8894
_implementation: Implementation
@@ -113,6 +119,7 @@ def from_numpy(
113119
context: _FullContext,
114120
schema: Mapping[str, DType] | Schema | Sequence[str] | None,
115121
) -> Self: ...
122+
116123
def __array__(self, dtype: Any, *, copy: bool | None) -> _2DArray: ...
117124
def __getitem__(
118125
self,
@@ -148,7 +155,7 @@ def shape(self) -> tuple[int, int]: ...
148155
def clone(self) -> Self: ...
149156
def collect(
150157
self, backend: Implementation | None, **kwargs: Any
151-
) -> CompliantDataFrame[Any, Any, Any]: ...
158+
) -> CompliantDataFrameAny: ...
152159
def collect_schema(self) -> Mapping[str, DType]: ...
153160
def drop(self, columns: Sequence[str], *, strict: bool) -> Self: ...
154161
def drop_nulls(self, subset: Sequence[str] | None) -> Self: ...
@@ -190,7 +197,7 @@ def join_asof(
190197
strategy: AsofJoinStrategy,
191198
suffix: str,
192199
) -> Self: ...
193-
def lazy(self, *, backend: Implementation | None) -> CompliantLazyFrame[Any, Any]: ...
200+
def lazy(self, *, backend: Implementation | None) -> CompliantLazyFrameAny: ...
194201
def pivot(
195202
self,
196203
on: Sequence[str],
@@ -260,7 +267,8 @@ def _evaluate_aliases(self, *exprs: CompliantExprT_contra) -> list[str]:
260267
class CompliantLazyFrame(
261268
_StoresNative[NativeFrameT],
262269
FromNative[NativeFrameT],
263-
Protocol[CompliantExprT_contra, NativeFrameT],
270+
ToNarwhals[ToNarwhalsT_co],
271+
Protocol[CompliantExprT_contra, NativeFrameT, ToNarwhalsT_co],
264272
):
265273
_native_frame: NativeFrameT
266274
_implementation: Implementation
@@ -297,7 +305,7 @@ def schema(self) -> Mapping[str, DType]: ...
297305
def _iter_columns(self) -> Iterator[Any]: ...
298306
def collect(
299307
self, backend: Implementation | None, **kwargs: Any
300-
) -> CompliantDataFrame[Any, Any, Any]: ...
308+
) -> CompliantDataFrameAny: ...
301309
def collect_schema(self) -> Mapping[str, DType]: ...
302310
def drop(self, columns: Sequence[str], *, strict: bool) -> Self: ...
303311
def drop_nulls(self, subset: Sequence[str] | None) -> Self: ...
@@ -364,14 +372,17 @@ def _evaluate_aliases(self, *exprs: CompliantExprT_contra) -> list[str]:
364372

365373

366374
class EagerDataFrame(
367-
CompliantDataFrame[EagerSeriesT, EagerExprT, NativeFrameT],
368-
CompliantLazyFrame[EagerExprT, NativeFrameT],
375+
CompliantDataFrame[EagerSeriesT, EagerExprT, NativeFrameT, "DataFrame[NativeFrameT]"],
376+
CompliantLazyFrame[EagerExprT, NativeFrameT, "DataFrame[NativeFrameT]"],
369377
Protocol[EagerSeriesT, EagerExprT, NativeFrameT, NativeSeriesT],
370378
):
371379
def __narwhals_namespace__(
372380
self,
373381
) -> EagerNamespace[Self, EagerSeriesT, EagerExprT, NativeFrameT, NativeSeriesT]: ...
374382

383+
def to_narwhals(self) -> DataFrame[NativeFrameT]:
384+
return self._version.dataframe(self, level="full")
385+
375386
def _evaluate_expr(self, expr: EagerExprT, /) -> EagerSeriesT:
376387
"""Evaluate `expr` and ensure it has a **single** output."""
377388
result: Sequence[EagerSeriesT] = expr(self)

narwhals/_compliant/namespace.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,14 @@ def when(
148148
def from_native(self, data: NativeFrameT, /) -> EagerDataFrameT: ...
149149
@overload
150150
def from_native(self, data: NativeSeriesT, /) -> EagerSeriesT: ...
151+
# TODO @dangotbanned: Align `PandasLike` typing with `_namespace`, then drop this `@overload`
152+
# - Using the guards there introduces `_NativeModin`, `_NativeCuDF`
153+
# - These types haven't been integrated into the backend
154+
# - Most of the `pandas` stuff is still untyped
155+
@overload
156+
def from_native(
157+
self, data: NativeFrameT | NativeSeriesT | Any, /
158+
) -> EagerDataFrameT | EagerSeriesT: ...
151159
def from_native(
152160
self, data: NativeFrameT | NativeSeriesT | Any, /
153161
) -> EagerDataFrameT | EagerSeriesT:

narwhals/_compliant/series.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from narwhals._translate import FromIterable
2222
from narwhals._translate import FromNative
2323
from narwhals._translate import NumpyConvertible
24+
from narwhals._translate import ToNarwhals
2425
from narwhals.utils import _StoresCompliant
2526
from narwhals.utils import _StoresNative
2627
from narwhals.utils import is_compliant_series
@@ -41,6 +42,7 @@
4142
from narwhals._compliant.namespace import CompliantNamespace
4243
from narwhals._compliant.namespace import EagerNamespace
4344
from narwhals.dtypes import DType
45+
from narwhals.series import Series
4446
from narwhals.typing import ClosedInterval
4547
from narwhals.typing import FillNullStrategy
4648
from narwhals.typing import Into1DArray
@@ -64,6 +66,7 @@ class CompliantSeries(
6466
NumpyConvertible["_1DArray", "Into1DArray"],
6567
FromIterable,
6668
FromNative[NativeSeriesT],
69+
ToNarwhals["Series[NativeSeriesT]"],
6770
Protocol[NativeSeriesT],
6871
):
6972
_implementation: Implementation
@@ -105,6 +108,8 @@ def from_iterable(
105108
name: str = "",
106109
dtype: DType | type[DType] | None = None,
107110
) -> Self: ...
111+
def to_narwhals(self) -> Series[NativeSeriesT]:
112+
return self._version.series(self, level="full")
108113

109114
# Operators
110115
def __add__(self, other: Any) -> Self: ...
@@ -180,7 +185,7 @@ def hist(
180185
*,
181186
bin_count: int | None,
182187
include_breakpoint: bool,
183-
) -> CompliantDataFrame[Self, Any, Any]: ...
188+
) -> CompliantDataFrame[Self, Any, Any, Any]: ...
184189
def head(self, n: int) -> Self: ...
185190
def is_between(
186191
self, lower_bound: Any, upper_bound: Any, closed: ClosedInterval
@@ -262,8 +267,8 @@ def tail(self, n: int) -> Self: ...
262267
def to_arrow(self) -> pa.Array[Any]: ...
263268
def to_dummies(
264269
self, *, separator: str, drop_first: bool
265-
) -> CompliantDataFrame[Self, Any, Any]: ...
266-
def to_frame(self) -> CompliantDataFrame[Self, Any, Any]: ...
270+
) -> CompliantDataFrame[Self, Any, Any, Any]: ...
271+
def to_frame(self) -> CompliantDataFrame[Self, Any, Any, Any]: ...
267272
def to_list(self) -> list[Any]: ...
268273
def to_pandas(self) -> pd.Series[Any]: ...
269274
def to_polars(self) -> pl.Series: ...
@@ -275,7 +280,7 @@ def value_counts(
275280
parallel: bool,
276281
name: str | None,
277282
normalize: bool,
278-
) -> CompliantDataFrame[Self, Any, Any]: ...
283+
) -> CompliantDataFrame[Self, Any, Any, Any]: ...
279284
def var(self, *, ddof: int) -> float: ...
280285
def zip_with(self, mask: Any, other: Any) -> Self: ...
281286

narwhals/_compliant/typing.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@
4040
CompliantExprAny: TypeAlias = "CompliantExpr[Any, Any]"
4141
CompliantSeriesAny: TypeAlias = "CompliantSeries[Any]"
4242
CompliantSeriesOrNativeExprAny: TypeAlias = "CompliantSeriesAny | NativeExpr"
43-
CompliantDataFrameAny: TypeAlias = "CompliantDataFrame[Any, Any, Any]"
44-
CompliantLazyFrameAny: TypeAlias = "CompliantLazyFrame[Any, Any]"
43+
CompliantDataFrameAny: TypeAlias = "CompliantDataFrame[Any, Any, Any, Any]"
44+
CompliantLazyFrameAny: TypeAlias = "CompliantLazyFrame[Any, Any, Any]"
4545
CompliantFrameAny: TypeAlias = "CompliantDataFrameAny | CompliantLazyFrameAny"
4646
CompliantNamespaceAny: TypeAlias = "CompliantNamespace[Any, Any]"
4747

narwhals/_dask/dataframe.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
from narwhals._dask.utils import evaluate_exprs
1414
from narwhals._pandas_like.utils import native_to_narwhals_dtype
1515
from narwhals._pandas_like.utils import select_columns_by_name
16-
from narwhals.typing import CompliantDataFrame
1716
from narwhals.typing import CompliantLazyFrame
1817
from narwhals.utils import Implementation
1918
from narwhals.utils import _remap_full_join_keys
@@ -32,9 +31,11 @@
3231
from typing_extensions import Self
3332
from typing_extensions import TypeIs
3433

34+
from narwhals._compliant.typing import CompliantDataFrameAny
3535
from narwhals._dask.expr import DaskExpr
3636
from narwhals._dask.group_by import DaskLazyGroupBy
3737
from narwhals._dask.namespace import DaskNamespace
38+
from narwhals.dataframe import LazyFrame
3839
from narwhals.dtypes import DType
3940
from narwhals.typing import AsofJoinStrategy
4041
from narwhals.typing import JoinStrategy
@@ -43,7 +44,9 @@
4344
from narwhals.utils import _FullContext
4445

4546

46-
class DaskLazyFrame(CompliantLazyFrame["DaskExpr", "dd.DataFrame"]):
47+
class DaskLazyFrame(
48+
CompliantLazyFrame["DaskExpr", "dd.DataFrame", "LazyFrame[dd.DataFrame]"]
49+
):
4750
def __init__(
4851
self,
4952
native_dataframe: dd.DataFrame,
@@ -69,6 +72,9 @@ def from_native(cls, data: dd.DataFrame, /, *, context: _FullContext) -> Self:
6972
data, backend_version=context._backend_version, version=context._version
7073
)
7174

75+
def to_narwhals(self) -> LazyFrame[dd.DataFrame]:
76+
return self._version.lazyframe(self, level="lazy")
77+
7278
def __native_namespace__(self) -> ModuleType:
7379
if self._implementation is Implementation.DASK:
7480
return self._implementation.to_native_namespace()
@@ -103,10 +109,8 @@ def with_columns(self, *exprs: DaskExpr) -> Self:
103109
return self._with_native(self.native.assign(**dict(new_series)))
104110

105111
def collect(
106-
self,
107-
backend: Implementation | None,
108-
**kwargs: Any,
109-
) -> CompliantDataFrame[Any, Any, Any]:
112+
self, backend: Implementation | None, **kwargs: Any
113+
) -> CompliantDataFrameAny:
110114
result = self.native.compute(**kwargs)
111115

112116
if backend is None or backend is Implementation.PANDAS:

narwhals/_duckdb/dataframe.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
from narwhals.dependencies import get_duckdb
2222
from narwhals.exceptions import ColumnNotFoundError
2323
from narwhals.exceptions import InvalidOperationError
24-
from narwhals.typing import CompliantDataFrame
2524
from narwhals.typing import CompliantLazyFrame
2625
from narwhals.utils import Implementation
2726
from narwhals.utils import Version
@@ -39,11 +38,14 @@
3938
from typing_extensions import Self
4039
from typing_extensions import TypeIs
4140

41+
from narwhals._compliant.typing import CompliantDataFrameAny
4242
from narwhals._duckdb.expr import DuckDBExpr
4343
from narwhals._duckdb.group_by import DuckDBGroupBy
4444
from narwhals._duckdb.namespace import DuckDBNamespace
4545
from narwhals._duckdb.series import DuckDBInterchangeSeries
46+
from narwhals.dataframe import LazyFrame
4647
from narwhals.dtypes import DType
48+
from narwhals.stable.v1 import DataFrame as DataFrameV1
4749
from narwhals.typing import AsofJoinStrategy
4850
from narwhals.typing import JoinStrategy
4951
from narwhals.typing import LazyUniqueKeepStrategy
@@ -53,7 +55,13 @@
5355
from duckdb import SQLExpression # type: ignore[attr-defined, unused-ignore]
5456

5557

56-
class DuckDBLazyFrame(CompliantLazyFrame["DuckDBExpr", "duckdb.DuckDBPyRelation"]):
58+
class DuckDBLazyFrame(
59+
CompliantLazyFrame[
60+
"DuckDBExpr",
61+
"duckdb.DuckDBPyRelation",
62+
"LazyFrame[duckdb.DuckDBPyRelation] | DataFrameV1[duckdb.DuckDBPyRelation]",
63+
]
64+
):
5765
_implementation = Implementation.DUCKDB
5866

5967
def __init__(
@@ -82,6 +90,16 @@ def from_native(
8290
data, backend_version=context._backend_version, version=context._version
8391
)
8492

93+
def to_narwhals(
94+
self, *args: Any, **kwds: Any
95+
) -> LazyFrame[duckdb.DuckDBPyRelation] | DataFrameV1[duckdb.DuckDBPyRelation]:
96+
if self._version is Version.MAIN:
97+
return self._version.lazyframe(self, level="lazy")
98+
99+
from narwhals.stable.v1 import DataFrame as DataFrameV1
100+
101+
return DataFrameV1(self, level="interchange") # type: ignore[no-any-return]
102+
85103
def __narwhals_dataframe__(self) -> Self: # pragma: no cover
86104
# Keep around for backcompat.
87105
if self._version is not Version.V1:
@@ -112,10 +130,8 @@ def _iter_columns(self) -> Iterator[duckdb.Expression]:
112130
yield col(name)
113131

114132
def collect(
115-
self,
116-
backend: ModuleType | Implementation | str | None,
117-
**kwargs: Any,
118-
) -> CompliantDataFrame[Any, Any, Any]:
133+
self, backend: ModuleType | Implementation | str | None, **kwargs: Any
134+
) -> CompliantDataFrameAny:
119135
if backend is None or backend is Implementation.PYARROW:
120136
import pyarrow as pa # ignore-banned-import
121137

narwhals/_pandas_like/dataframe.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,15 @@
5050
from typing_extensions import TypeAlias
5151
from typing_extensions import TypeIs
5252

53+
from narwhals._compliant.typing import CompliantDataFrameAny
54+
from narwhals._compliant.typing import CompliantLazyFrameAny
5355
from narwhals._pandas_like.expr import PandasLikeExpr
5456
from narwhals._pandas_like.group_by import PandasLikeGroupBy
5557
from narwhals._pandas_like.namespace import PandasLikeNamespace
5658
from narwhals._translate import IntoArrowTable
5759
from narwhals.dtypes import DType
5860
from narwhals.schema import Schema
5961
from narwhals.typing import AsofJoinStrategy
60-
from narwhals.typing import CompliantDataFrame
61-
from narwhals.typing import CompliantLazyFrame
6262
from narwhals.typing import DTypeBackend
6363
from narwhals.typing import JoinStrategy
6464
from narwhals.typing import PivotAgg
@@ -518,10 +518,8 @@ def sort(
518518

519519
# --- convert ---
520520
def collect(
521-
self,
522-
backend: Implementation | None,
523-
**kwargs: Any,
524-
) -> CompliantDataFrame[Any, Any, Any]:
521+
self, backend: Implementation | None, **kwargs: Any
522+
) -> CompliantDataFrameAny:
525523
if backend is None:
526524
return PandasLikeDataFrame(
527525
self.native,
@@ -773,9 +771,7 @@ def unique(
773771
)
774772

775773
# --- lazy-only ---
776-
def lazy(
777-
self, *, backend: Implementation | None = None
778-
) -> CompliantLazyFrame[Any, Any]:
774+
def lazy(self, *, backend: Implementation | None = None) -> CompliantLazyFrameAny:
779775
from narwhals.utils import parse_version
780776

781777
pandas_df = self.to_pandas()

0 commit comments

Comments
 (0)