Skip to content

Commit 25bdb8f

Browse files
authored
fix: Update version when passing narwhals objects in from_native (#3515)
* add v2 to stable/__init__:__all__ * ref: test_init_already_narwhals duplicate test in tests/translate/from_native_test.py test_init_already_narwhals and test_init_already_narwhals_unstable have the exact same source code, when the former should verify behavior in a stable version. This function used to test this, but seemed to have been mistakenly edited. * fix: from_native updates a passed nw.DataFrame with the passed version from_native on a narwhals.{DataFrame,Series} would always return the same object even if the DataFrame._version and the version passed into from_native disagreed. Now, if the versions mismatch we create a new narwhals object with the appropriate version. If the versions match, we short cut and return the same object. * fix nw.LazyFrame missing _version ClassVar * add Series objects in to_stable/to_unstable tests * ignore var-annotated in tests/translate/from_native init_already to {stable,unstable}
1 parent b95a52d commit 25bdb8f

7 files changed

Lines changed: 65 additions & 9 deletions

File tree

narwhals/dataframe.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2361,6 +2361,8 @@ class LazyFrame(BaseFrame[LazyFrameT]):
23612361
```
23622362
"""
23632363

2364+
_version: ClassVar[Version] = Version.MAIN
2365+
23642366
@property
23652367
def _compliant(self) -> CompliantLazyFrame[Any, LazyFrameT, Self]:
23662368
return self._compliant_frame

narwhals/stable/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from __future__ import annotations
22

3-
from narwhals.stable import v1
3+
from narwhals.stable import v1, v2
44

5-
__all__ = ["v1"]
5+
__all__ = ["v1", "v2"]

narwhals/stable/v1/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,8 @@ def _l1_norm(self) -> Self:
258258

259259

260260
class LazyFrame(NwLazyFrame[IntoLazyFrameT]):
261+
_version = Version.V1
262+
261263
@inherit_doc(NwLazyFrame)
262264
def __init__(self, df: Any, *, level: Literal["full", "lazy", "interchange"]) -> None:
263265
assert df._version is Version.V1 # noqa: S101

narwhals/stable/v2/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,8 @@ def is_unique(self) -> Series[Any]:
241241

242242

243243
class LazyFrame(NwLazyFrame[IntoLazyFrameT]):
244+
_version = Version.V2
245+
244246
@inherit_doc(NwLazyFrame)
245247
def __init__(self, df: Any, *, level: Literal["full", "lazy", "interchange"]) -> None:
246248
assert df._version is Version.V2 # noqa: S101

narwhals/translate.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,9 +282,25 @@ def _from_native_impl( # noqa: C901, PLR0911, PLR0912, PLR0915
282282

283283
# Early returns
284284
if isinstance(native_object, (DataFrame, LazyFrame)) and not series_only:
285-
return native_object
285+
if native_object._version is version:
286+
return native_object
287+
288+
real_native_object = native_object.to_native()
289+
return (
290+
version.namespace.from_native_object(real_native_object)
291+
.compliant.from_native(real_native_object)
292+
.to_narwhals()
293+
)
286294
if isinstance(native_object, Series) and (series_only or allow_series):
287-
return native_object
295+
if native_object._version is version:
296+
return native_object
297+
298+
real_native_object = native_object.to_native()
299+
return (
300+
version.namespace.from_native_object(real_native_object)
301+
.compliant.from_native(real_native_object)
302+
.to_narwhals()
303+
)
288304

289305
if series_only:
290306
if allow_series is False:

tests/translate/from_native_test.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,12 +206,14 @@ def test_pandas_like_validate() -> None:
206206

207207

208208
@pytest.mark.skipif(lf_pl is None, reason="polars not found")
209-
def test_init_already_narwhals() -> None:
210-
df = nw.from_native(pl.DataFrame({"a": [1, 2, 3]}))
211-
result = nw.from_native(df)
209+
def test_init_already_narwhals_stable() -> None:
210+
from narwhals.stable import v1 as nw_v1
211+
212+
df = nw_v1.from_native(pl.DataFrame({"a": [1, 2, 3]}))
213+
result = nw_v1.from_native(df)
212214
assert result is df
213215
s = df["a"]
214-
result_s = nw.from_native(s, allow_series=True)
216+
result_s = nw_v1.from_native(s, allow_series=True)
215217
assert result_s is s
216218

217219

@@ -225,6 +227,38 @@ def test_init_already_narwhals_unstable() -> None:
225227
assert result_s is s
226228

227229

230+
@pytest.mark.skipif(lf_pl is None, reason="polars not found")
231+
def test_init_already_narwhals_unstable_to_stable() -> None:
232+
from narwhals.stable import v1 as nw_v1
233+
234+
native = pl.DataFrame({"a": [1, 2, 3]})
235+
236+
unstable_df = nw.from_native(native)
237+
stablified_df = nw_v1.from_native(unstable_df)
238+
assert isinstance(stablified_df, nw_v1.DataFrame)
239+
240+
s = native["a"]
241+
unstable_s = nw.from_native(s, allow_series=True)
242+
stablified_s = nw_v1.from_native(unstable_s, allow_series=True) # type: ignore[var-annotated]
243+
assert isinstance(stablified_s, nw_v1.Series)
244+
245+
246+
@pytest.mark.skipif(lf_pl is None, reason="polars not found")
247+
def test_init_already_narwhals_stable_to_unstable() -> None:
248+
from narwhals.stable import v1 as nw_v1
249+
250+
native = pl.DataFrame({"a": [1, 2, 3]})
251+
252+
stable_df = nw_v1.from_native(native)
253+
unstablified_df = nw.from_native(stable_df)
254+
assert isinstance(unstablified_df, nw.DataFrame)
255+
256+
s = native["a"]
257+
stable_s = nw_v1.from_native(s, allow_series=True) # type: ignore[var-annotated]
258+
unstablified_s = nw.from_native(stable_s, allow_series=True) # type: ignore[var-annotated]
259+
assert isinstance(unstablified_s, nw.Series)
260+
261+
228262
@pytest.mark.skipif(df_pd is None, reason="pandas not found")
229263
def test_series_only_dask() -> None:
230264
pytest.importorskip("dask")

tests/v1_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,7 @@ def test_lazyframe_recursive_v1() -> None:
621621

622622
pl_frame = pl.DataFrame({"a": [1, 2, 3]}).lazy()
623623
nw_frame = nw_v1.from_native(pl_frame)
624-
with pytest.raises(AttributeError):
624+
with pytest.raises(AssertionError):
625625
nw_v1.LazyFrame(nw_frame, level="lazy")
626626

627627
nw_frame_early_return = nw_v1.from_native(nw_frame)

0 commit comments

Comments
 (0)