diff --git a/narwhals/dataframe.py b/narwhals/dataframe.py index 5ba416c62b..bd602efd52 100644 --- a/narwhals/dataframe.py +++ b/narwhals/dataframe.py @@ -2361,6 +2361,8 @@ class LazyFrame(BaseFrame[LazyFrameT]): ``` """ + _version: ClassVar[Version] = Version.MAIN + @property def _compliant(self) -> CompliantLazyFrame[Any, LazyFrameT, Self]: return self._compliant_frame diff --git a/narwhals/stable/__init__.py b/narwhals/stable/__init__.py index 60bc872a56..1793b48d21 100644 --- a/narwhals/stable/__init__.py +++ b/narwhals/stable/__init__.py @@ -1,5 +1,5 @@ from __future__ import annotations -from narwhals.stable import v1 +from narwhals.stable import v1, v2 -__all__ = ["v1"] +__all__ = ["v1", "v2"] diff --git a/narwhals/stable/v1/__init__.py b/narwhals/stable/v1/__init__.py index 6ea63c2d85..eca46e0563 100644 --- a/narwhals/stable/v1/__init__.py +++ b/narwhals/stable/v1/__init__.py @@ -258,6 +258,8 @@ def _l1_norm(self) -> Self: class LazyFrame(NwLazyFrame[IntoLazyFrameT]): + _version = Version.V1 + @inherit_doc(NwLazyFrame) def __init__(self, df: Any, *, level: Literal["full", "lazy", "interchange"]) -> None: assert df._version is Version.V1 # noqa: S101 diff --git a/narwhals/stable/v2/__init__.py b/narwhals/stable/v2/__init__.py index 3319489a87..67136cdc21 100644 --- a/narwhals/stable/v2/__init__.py +++ b/narwhals/stable/v2/__init__.py @@ -241,6 +241,8 @@ def is_unique(self) -> Series[Any]: class LazyFrame(NwLazyFrame[IntoLazyFrameT]): + _version = Version.V2 + @inherit_doc(NwLazyFrame) def __init__(self, df: Any, *, level: Literal["full", "lazy", "interchange"]) -> None: assert df._version is Version.V2 # noqa: S101 diff --git a/narwhals/translate.py b/narwhals/translate.py index 7d59b63dfa..9452111bdd 100644 --- a/narwhals/translate.py +++ b/narwhals/translate.py @@ -282,9 +282,25 @@ def _from_native_impl( # noqa: C901, PLR0911, PLR0912, PLR0915 # Early returns if isinstance(native_object, (DataFrame, LazyFrame)) and not series_only: - return native_object + if native_object._version is version: + return native_object + + real_native_object = native_object.to_native() + return ( + version.namespace.from_native_object(real_native_object) + .compliant.from_native(real_native_object) + .to_narwhals() + ) if isinstance(native_object, Series) and (series_only or allow_series): - return native_object + if native_object._version is version: + return native_object + + real_native_object = native_object.to_native() + return ( + version.namespace.from_native_object(real_native_object) + .compliant.from_native(real_native_object) + .to_narwhals() + ) if series_only: if allow_series is False: diff --git a/tests/translate/from_native_test.py b/tests/translate/from_native_test.py index daa626dafe..5f155370f7 100644 --- a/tests/translate/from_native_test.py +++ b/tests/translate/from_native_test.py @@ -206,12 +206,14 @@ def test_pandas_like_validate() -> None: @pytest.mark.skipif(lf_pl is None, reason="polars not found") -def test_init_already_narwhals() -> None: - df = nw.from_native(pl.DataFrame({"a": [1, 2, 3]})) - result = nw.from_native(df) +def test_init_already_narwhals_stable() -> None: + from narwhals.stable import v1 as nw_v1 + + df = nw_v1.from_native(pl.DataFrame({"a": [1, 2, 3]})) + result = nw_v1.from_native(df) assert result is df s = df["a"] - result_s = nw.from_native(s, allow_series=True) + result_s = nw_v1.from_native(s, allow_series=True) assert result_s is s @@ -225,6 +227,38 @@ def test_init_already_narwhals_unstable() -> None: assert result_s is s +@pytest.mark.skipif(lf_pl is None, reason="polars not found") +def test_init_already_narwhals_unstable_to_stable() -> None: + from narwhals.stable import v1 as nw_v1 + + native = pl.DataFrame({"a": [1, 2, 3]}) + + unstable_df = nw.from_native(native) + stablified_df = nw_v1.from_native(unstable_df) + assert isinstance(stablified_df, nw_v1.DataFrame) + + s = native["a"] + unstable_s = nw.from_native(s, allow_series=True) + stablified_s = nw_v1.from_native(unstable_s, allow_series=True) + assert isinstance(stablified_s, nw_v1.Series) + + +@pytest.mark.skipif(lf_pl is None, reason="polars not found") +def test_init_already_narwhals_stable_to_unstable() -> None: + from narwhals.stable import v1 as nw_v1 + + native = pl.DataFrame({"a": [1, 2, 3]}) + + stable_df = nw_v1.from_native(native) + unstablified_df = nw.from_native(stable_df) + assert isinstance(unstablified_df, nw.DataFrame) + + s = native["a"] + stable_s = nw_v1.from_native(s, allow_series=True) + unstablified_s = nw.from_native(stable_s, allow_series=True) + assert isinstance(unstablified_s, nw.Series) + + @pytest.mark.skipif(df_pd is None, reason="pandas not found") def test_series_only_dask() -> None: pytest.importorskip("dask") diff --git a/tests/v1_test.py b/tests/v1_test.py index 330211162f..de1c8b5243 100644 --- a/tests/v1_test.py +++ b/tests/v1_test.py @@ -621,7 +621,7 @@ def test_lazyframe_recursive_v1() -> None: pl_frame = pl.DataFrame({"a": [1, 2, 3]}).lazy() nw_frame = nw_v1.from_native(pl_frame) - with pytest.raises(AttributeError): + with pytest.raises(AssertionError): nw_v1.LazyFrame(nw_frame, level="lazy") nw_frame_early_return = nw_v1.from_native(nw_frame)