Skip to content

feat: raise informative error or warning when passing narwhals object to nw.dependencies.is_*_dataframe and nw.dependencies.is_*_series #1444

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Jun 15, 2025
Merged
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
4 changes: 3 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ repos:
# Run the formatter.
- id: ruff-format
# Run the linter.
- id: ruff
- id: ruff-check
args: [--fix]
- id: ruff
name: check-docstrings
Expand Down Expand Up @@ -36,6 +36,8 @@ repos:
narwhals/dataframe\.py|
# TODO: gradually enable
narwhals/dependencies\.py|
# TODO: gradually enable
narwhals/stable/v1/dependencies\.py|
# private, so less urgent to document too well
narwhals/_.*|
^utils/.*
Expand Down
13 changes: 6 additions & 7 deletions narwhals/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,8 @@
is_narwhals_series_int,
is_numpy_array_1d,
is_numpy_array_1d_int,
is_pandas_dataframe,
is_pandas_like_dataframe,
is_pandas_like_series,
is_pandas_series,
is_polars_series,
is_pyarrow_chunked_array,
)
Expand Down Expand Up @@ -653,12 +651,13 @@ def tupleify(arg: Any) -> Any:
def _is_iterable(arg: Any | Iterable[Any]) -> bool:
from narwhals.series import Series

if is_pandas_dataframe(arg) or is_pandas_series(arg):
msg = f"Expected Narwhals class or scalar, got: {qualified_type_name(arg)!r}. Perhaps you forgot a `nw.from_native` somewhere?"
raise TypeError(msg)
if (pl := get_polars()) is not None and isinstance(
arg, (pl.Series, pl.Expr, pl.DataFrame, pl.LazyFrame)
if (
(pd := get_pandas()) is not None and isinstance(arg, (pd.Series, pd.DataFrame))
) or (
(pl := get_polars()) is not None
and isinstance(arg, (pl.Series, pl.Expr, pl.DataFrame, pl.LazyFrame))
):
# Non-exhaustive check for common potential mistakes.
msg = (
f"Expected Narwhals class or scalar, got: {qualified_type_name(arg)!r}.\n\n"
"Hint: Perhaps you\n"
Expand Down
153 changes: 134 additions & 19 deletions narwhals/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
from narwhals.dataframe import DataFrame, LazyFrame
from narwhals.series import Series
from narwhals.typing import (
FrameT,
IntoDataFrameT,
IntoLazyFrameT,
IntoSeriesT,
_1DArray,
_1DArrayInt,
Expand Down Expand Up @@ -125,8 +125,33 @@ def get_sqlframe() -> Any:
return sys.modules.get("sqlframe", None)


def _raise_if_narwhals_df_or_lf(df: Any) -> None:
if is_narwhals_dataframe(df) or is_narwhals_lazyframe(df):
msg = (
f"You passed a `{type(df)}` to `is_pandas_dataframe`.\n\n"
"Hint: Instead of e.g. `is_pandas_dataframe(df)`, "
"did you mean `is_pandas_dataframe(df.to_native())`?"
)
raise TypeError(msg)


def _raise_if_narwhals_series(ser: Any) -> None:
if is_narwhals_series(ser):
msg = (
f"You passed a `{type(ser)}` to `is_pandas_series`.\n\n"
"Hint: Instead of e.g. `is_pandas_series(ser)`, "
"did you mean `is_pandas_series(ser.to_native())`?"
)
raise TypeError(msg)


def is_pandas_dataframe(df: Any) -> TypeIs[pd.DataFrame]:
"""Check whether `df` is a pandas DataFrame without importing pandas."""
"""Check whether `df` is a pandas DataFrame without importing pandas.

Warning:
This method cannot be called on a Narwhals DataFrame/LazyFrame.
"""
_raise_if_narwhals_df_or_lf(df)
return ((pd := get_pandas()) is not None and isinstance(df, pd.DataFrame)) or any(
(mod := sys.modules.get(module_name, None)) is not None
and isinstance(df, mod.pandas.DataFrame)
Expand All @@ -135,7 +160,12 @@ def is_pandas_dataframe(df: Any) -> TypeIs[pd.DataFrame]:


def is_pandas_series(ser: Any) -> TypeIs[pd.Series[Any]]:
"""Check whether `ser` is a pandas Series without importing pandas."""
"""Check whether `ser` is a pandas Series without importing pandas.

Warning:
This method cannot be called on Narwhals Series.
"""
_raise_if_narwhals_series(ser)
return ((pd := get_pandas()) is not None and isinstance(ser, pd.Series)) or any(
(mod := sys.modules.get(module_name, None)) is not None
and isinstance(ser, mod.pandas.Series)
Expand All @@ -153,12 +183,22 @@ def is_pandas_index(index: Any) -> TypeIs[pd.Index[Any]]:


def is_modin_dataframe(df: Any) -> TypeIs[mpd.DataFrame]:
"""Check whether `df` is a modin DataFrame without importing modin."""
"""Check whether `df` is a modin DataFrame without importing modin.

Warning:
This method cannot be called on a Narwhals DataFrame/LazyFrame.
"""
_raise_if_narwhals_df_or_lf(df)
return (mpd := get_modin()) is not None and isinstance(df, mpd.DataFrame)


def is_modin_series(ser: Any) -> TypeIs[mpd.Series]:
"""Check whether `ser` is a modin Series without importing modin."""
"""Check whether `ser` is a modin Series without importing modin.

Warning:
This method cannot be called on Narwhals Series.
"""
_raise_if_narwhals_series(ser)
return (mpd := get_modin()) is not None and isinstance(ser, mpd.Series)


Expand All @@ -168,12 +208,22 @@ def is_modin_index(index: Any) -> TypeIs[mpd.Index[Any]]: # pragma: no cover


def is_cudf_dataframe(df: Any) -> TypeIs[cudf.DataFrame]:
"""Check whether `df` is a cudf DataFrame without importing cudf."""
"""Check whether `df` is a cudf DataFrame without importing cudf.

Warning:
This method cannot be called on a Narwhals DataFrame/LazyFrame.
"""
_raise_if_narwhals_df_or_lf(df)
return (cudf := get_cudf()) is not None and isinstance(df, cudf.DataFrame)


def is_cudf_series(ser: Any) -> TypeIs[cudf.Series[Any]]:
"""Check whether `ser` is a cudf Series without importing cudf."""
"""Check whether `ser` is a cudf Series without importing cudf.

Warning:
This method cannot be called on Narwhals Series.
"""
_raise_if_narwhals_series(ser)
return (cudf := get_cudf()) is not None and isinstance(ser, cudf.Series)


Expand All @@ -193,44 +243,84 @@ def is_cupy_scalar(obj: Any) -> bool:


def is_dask_dataframe(df: Any) -> TypeIs[dd.DataFrame]:
"""Check whether `df` is a Dask DataFrame without importing Dask."""
"""Check whether `df` is a Dask DataFrame without importing Dask.

Warning:
This method cannot be called on a Narwhals DataFrame/LazyFrame.
"""
_raise_if_narwhals_df_or_lf(df)
return (dd := get_dask_dataframe()) is not None and isinstance(df, dd.DataFrame)


def is_duckdb_relation(df: Any) -> TypeIs[duckdb.DuckDBPyRelation]:
"""Check whether `df` is a DuckDB Relation without importing DuckDB."""
"""Check whether `df` is a DuckDB Relation without importing DuckDB.

Warning:
This method cannot be called on Narwhals DataFrame/LazyFrame.
"""
_raise_if_narwhals_df_or_lf(df)
return (duckdb := get_duckdb()) is not None and isinstance(
df, duckdb.DuckDBPyRelation
)


def is_ibis_table(df: Any) -> TypeIs[ibis.Table]:
"""Check whether `df` is a Ibis Table without importing Ibis."""
"""Check whether `df` is a Ibis Table without importing Ibis.

Warning:
This method cannot be called on Narwhals DataFrame/LazyFrame.
"""
_raise_if_narwhals_df_or_lf(df)
return (ibis := get_ibis()) is not None and isinstance(df, ibis.expr.types.Table)


def is_polars_dataframe(df: Any) -> TypeIs[pl.DataFrame]:
"""Check whether `df` is a Polars DataFrame without importing Polars."""
"""Check whether `df` is a Polars DataFrame without importing Polars.

Warning:
This method cannot be called on a Narwhals DataFrame/LazyFrame.
"""
_raise_if_narwhals_df_or_lf(df)
return (pl := get_polars()) is not None and isinstance(df, pl.DataFrame)


def is_polars_lazyframe(df: Any) -> TypeIs[pl.LazyFrame]:
"""Check whether `df` is a Polars LazyFrame without importing Polars."""
"""Check whether `df` is a Polars LazyFrame without importing Polars.

Warning:
This method cannot be called on Narwhals DataFrame/LazyFrame.
"""
_raise_if_narwhals_df_or_lf(df)
return (pl := get_polars()) is not None and isinstance(df, pl.LazyFrame)


def is_polars_series(ser: Any) -> TypeIs[pl.Series]:
"""Check whether `ser` is a Polars Series without importing Polars."""
"""Check whether `ser` is a Polars Series without importing Polars.

Warning:
This method cannot be called on Narwhals Series.
"""
_raise_if_narwhals_series(ser)
return (pl := get_polars()) is not None and isinstance(ser, pl.Series)


def is_pyarrow_chunked_array(ser: Any) -> TypeIs[pa.ChunkedArray[Any]]:
"""Check whether `ser` is a PyArrow ChunkedArray without importing PyArrow."""
"""Check whether `ser` is a PyArrow ChunkedArray without importing PyArrow.

Warning:
This method cannot be called on Narwhals Series.
"""
_raise_if_narwhals_series(ser)
return (pa := get_pyarrow()) is not None and isinstance(ser, pa.ChunkedArray)


def is_pyarrow_table(df: Any) -> TypeIs[pa.Table]:
"""Check whether `df` is a PyArrow Table without importing PyArrow."""
"""Check whether `df` is a PyArrow Table without importing PyArrow.

Warning:
This method cannot be called on Narwhals DataFrame/LazyFrame.
"""
_raise_if_narwhals_df_or_lf(df)
return (pa := get_pyarrow()) is not None and isinstance(df, pa.Table)


Expand All @@ -239,15 +329,25 @@ def is_pyarrow_scalar(obj: Any) -> TypeIs[pa.Scalar[Any]]:


def is_pyspark_dataframe(df: Any) -> TypeIs[pyspark_sql.DataFrame]:
"""Check whether `df` is a PySpark DataFrame without importing PySpark."""
"""Check whether `df` is a PySpark DataFrame without importing PySpark.

Warning:
This method cannot be called on a Narwhals DataFrame/LazyFrame.
"""
_raise_if_narwhals_df_or_lf(df)
return bool(
(pyspark_sql := get_pyspark_sql()) is not None
and isinstance(df, pyspark_sql.DataFrame)
)


def is_pyspark_connect_dataframe(df: Any) -> TypeIs[PySparkConnectDataFrame]:
"""Check whether `df` is a PySpark Connect DataFrame without importing PySpark."""
"""Check whether `df` is a PySpark Connect DataFrame without importing PySpark.

Warning:
This method cannot be called on a Narwhals DataFrame/LazyFrame.
"""
_raise_if_narwhals_df_or_lf(df)
if get_pyspark_connect() is not None: # pragma: no cover
try:
from pyspark.sql.connect.dataframe import DataFrame
Expand All @@ -258,7 +358,12 @@ def is_pyspark_connect_dataframe(df: Any) -> TypeIs[PySparkConnectDataFrame]:


def is_sqlframe_dataframe(df: Any) -> TypeIs[SQLFrameDataFrame]:
"""Check whether `df` is a SQLFrame DataFrame without importing SQLFrame."""
"""Check whether `df` is a SQLFrame DataFrame without importing SQLFrame.

Warning:
This method cannot be called on a Narwhals DataFrame/LazyFrame.
"""
_raise_if_narwhals_df_or_lf(df)
if get_sqlframe() is not None:
from sqlframe.base.dataframe import BaseDataFrame

Expand Down Expand Up @@ -301,15 +406,23 @@ def is_pandas_like_dataframe(df: Any) -> bool:
"""Check whether `df` is a pandas-like DataFrame without doing any imports.

By "pandas-like", we mean: pandas, Modin, cuDF.

Warning:
This method cannot be called on a Narwhals DataFrame/LazyFrame.
"""
_raise_if_narwhals_df_or_lf(df)
return is_pandas_dataframe(df) or is_modin_dataframe(df) or is_cudf_dataframe(df)


def is_pandas_like_series(ser: Any) -> bool:
"""Check whether `ser` is a pandas-like Series without doing any imports.

By "pandas-like", we mean: pandas, Modin, cuDF.

Warning:
This method cannot be called on Narwhals Series.
"""
_raise_if_narwhals_series(ser)
return is_pandas_series(ser) or is_modin_series(ser) or is_cudf_series(ser)


Expand Down Expand Up @@ -411,7 +524,9 @@ def is_narwhals_dataframe(
return isinstance(df, DataFrame)


def is_narwhals_lazyframe(lf: Any | LazyFrame[FrameT]) -> TypeIs[LazyFrame[FrameT]]:
def is_narwhals_lazyframe(
lf: Any | LazyFrame[IntoLazyFrameT],
) -> TypeIs[LazyFrame[IntoLazyFrameT]]:
"""Check whether `lf` is a Narwhals LazyFrame.

This is useful if you expect a user to pass in a Narwhals
Expand Down
Loading
Loading