Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions narwhals/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -2361,6 +2361,8 @@ class LazyFrame(BaseFrame[LazyFrameT]):
```
"""

_version: ClassVar[Version] = Version.MAIN
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hoping this has been an oversight and wasn't excluded for a strong reason. This change makes the DataFrame/LazyFrame api a bit more consistent internally.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah nice one, thanks @camriddell!

If you dig through the blame (I think) I added these on Series & DataFrame at the same time as the from_* constructors.

LazyFrame didn't get any new methods, so it slipped through 😅


@property
def _compliant(self) -> CompliantLazyFrame[Any, LazyFrameT, Self]:
return self._compliant_frame
Expand Down
4 changes: 2 additions & 2 deletions narwhals/stable/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from __future__ import annotations

from narwhals.stable import v1
from narwhals.stable import v1, v2

__all__ = ["v1"]
__all__ = ["v1", "v2"]
2 changes: 2 additions & 0 deletions narwhals/stable/v1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions narwhals/stable/v2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 18 additions & 2 deletions narwhals/translate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
42 changes: 38 additions & 4 deletions tests/translate/from_native_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion tests/v1_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Copy link
Member

@dangotbanned dangotbanned Mar 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, there may be a comment somewhere mentioning the error?

(Ignore me if this is too vague 😂)

nw_v1.LazyFrame(nw_frame, level="lazy")

nw_frame_early_return = nw_v1.from_native(nw_frame)
Expand Down
Loading