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
14 changes: 14 additions & 0 deletions docs/basics/dataframe.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ To write a dataframe-agnostic function, the steps you'll want to follow are:

Note: if you need eager execution, make sure to pass `eager_only=True` to `nw.from_native`.

You can check if your dataframe's implementation supports eager evaluation using the `is_eager_allowed()` method:

```python
import narwhals as nw

df = nw.from_native(your_dataframe)
if df.implementation.is_eager_allowed():
# Safe to use eager_only=True
df_eager = nw.from_native(your_dataframe, eager_only=True)
else:
# Implementation only supports lazy evaluation
df_lazy = nw.from_native(your_dataframe)
```

2. Express your logic using the subset of the Polars API supported by Narwhals.
3. If you need to return a dataframe to the user in its original library, call `nw.to_native`.

Expand Down
19 changes: 19 additions & 0 deletions narwhals/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,25 @@ def is_sqlframe(self) -> bool:
"""
return self is Implementation.SQLFRAME # pragma: no cover

def is_eager_allowed(self) -> bool:
"""Return whether implementation supports eager evaluation.

Examples:
>>> import pandas as pd
>>> import narwhals as nw
>>> df_native = pd.DataFrame({"a": [1, 2, 3]})
>>> df = nw.from_native(df_native)
>>> df.implementation.is_eager_allowed()
True
"""
return self in {
Implementation.CUDF,
Implementation.MODIN,
Implementation.PANDAS,
Implementation.POLARS,
Implementation.PYARROW,
}

def _backend_version(self) -> tuple[int, ...]:
"""Returns backend version."""
return backend_version(self)
Expand Down
4 changes: 4 additions & 0 deletions narwhals/translate.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,10 @@ def from_native( # noqa: D417

- `False` (default): don't require `native_object` to be eager
- `True`: only convert to Narwhals if `native_object` is eager

To check if an implementation supports eager evaluation, use the
`.implementation.is_eager_allowed()` method on a Narwhals object.
Eager-compatible implementations include: pandas, Polars, PyArrow, cuDF, and Modin.
series_only: Whether to only allow Series

- `False` (default): don't require `native_object` to be a Series
Expand Down
32 changes: 32 additions & 0 deletions tests/implementation_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,38 @@ def test_implementation_new(member: str, value: str) -> None:
assert nw.Implementation(value) is getattr(nw.Implementation, member)


def test_is_eager_allowed() -> None:
"""Test that is_eager_allowed correctly identifies eager-compatible implementations."""
# Test eager-allowed implementations
assert nw.Implementation.PANDAS.is_eager_allowed()
assert nw.Implementation.POLARS.is_eager_allowed()
assert nw.Implementation.PYARROW.is_eager_allowed()
assert nw.Implementation.CUDF.is_eager_allowed()
assert nw.Implementation.MODIN.is_eager_allowed()

# Test non-eager implementations
assert not nw.Implementation.DASK.is_eager_allowed()
assert not nw.Implementation.DUCKDB.is_eager_allowed()
assert not nw.Implementation.IBIS.is_eager_allowed()
assert not nw.Implementation.PYSPARK.is_eager_allowed()
assert not nw.Implementation.PYSPARK_CONNECT.is_eager_allowed()
assert not nw.Implementation.SQLFRAME.is_eager_allowed()
assert not nw.Implementation.UNKNOWN.is_eager_allowed()


def test_is_eager_allowed_with_actual_dataframes() -> None:
"""Test is_eager_allowed with actual dataframe objects."""
# Test with pandas (if available)
pd = pytest.importorskip("pandas")
df_pandas = nw.from_native(pd.DataFrame({"a": [1, 2, 3]}))
assert df_pandas.implementation.is_eager_allowed()

# Test with polars (if available)
pl = pytest.importorskip("polars")
df_polars = nw.from_native(pl.DataFrame({"a": [1, 2, 3]}))
assert df_polars.implementation.is_eager_allowed()


_TYPING_ONLY_TESTS = "_"
"""Exhaustive checks for overload matching native -> implementation.

Expand Down