diff --git a/src/narwhals/_interchange/dataframe.py b/src/narwhals/_interchange/dataframe.py index a07f1fad4c..ad42e0d535 100644 --- a/src/narwhals/_interchange/dataframe.py +++ b/src/narwhals/_interchange/dataframe.py @@ -3,7 +3,7 @@ import enum from typing import TYPE_CHECKING, Any, NoReturn -from narwhals._utils import Version, parse_version +from narwhals._utils import Version, _hasattr_static, parse_version if TYPE_CHECKING: import pandas as pd @@ -156,4 +156,4 @@ def select(self, *exprs: str) -> Self: # pragma: no cover def supports_dataframe_interchange(obj: Any) -> TypeIs[DataFrameLike]: - return hasattr(obj, "__dataframe__") + return _hasattr_static(obj, "__dataframe__") diff --git a/src/narwhals/dependencies.py b/src/narwhals/dependencies.py index bbc49e4f71..0fbaaded13 100644 --- a/src/narwhals/dependencies.py +++ b/src/narwhals/dependencies.py @@ -128,26 +128,41 @@ def get_sqlframe() -> Any: return sys.modules.get("sqlframe", None) -def _warn_if_narwhals_df_or_lf(df: Any) -> None: - if is_narwhals_dataframe(df) or is_narwhals_lazyframe(df): +def _warn_if_narwhals_df_or_lf(frame: Any, /) -> None: + if is_narwhals_dataframe(frame) or is_narwhals_lazyframe(frame): + from narwhals._utils import qualified_type_name + + caller = sys._getframe(1).f_code.co_name 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())`?" + f"You passed a `{qualified_type_name(frame)}` to `{caller}`.\n\n" + f"Hint: Instead of e.g. `{caller}(frame)`, " + f"did you mean `{caller}(frame.to_native())`?" ) issue_warning(msg, UserWarning) -def _warn_if_narwhals_series(ser: Any) -> None: +def _warn_if_narwhals_series(ser: Any, /) -> None: if is_narwhals_series(ser): + from narwhals._utils import qualified_type_name + + caller = sys._getframe(1).f_code.co_name 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())`?" + f"You passed a `{qualified_type_name(ser)}` to `{caller}`.\n\n" + f"Hint: Instead of e.g. `{caller}(ser)`, " + f"did you mean `{caller}(ser.to_native())`?" ) issue_warning(msg, UserWarning) +def _is_pandas_dataframe(df: Any) -> TypeIs[pd.DataFrame]: + """Check whether `df` is a pandas DataFrame without importing pandas.""" + 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) + for module_name in IMPORT_HOOKS + ) + + def is_pandas_dataframe(df: Any) -> TypeIs[pd.DataFrame]: """Check whether `df` is a pandas DataFrame without importing pandas. @@ -155,9 +170,14 @@ def is_pandas_dataframe(df: Any) -> TypeIs[pd.DataFrame]: This method cannot be called on a Narwhals DataFrame/LazyFrame. """ _warn_if_narwhals_df_or_lf(df) - return ((pd := get_pandas()) is not None and isinstance(df, pd.DataFrame)) or any( + return _is_pandas_dataframe(df) + + +def _is_pandas_series(ser: Any) -> TypeIs[pd.Series[Any]]: + """Check whether `ser` is a pandas Series without importing pandas.""" + 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(df, mod.pandas.DataFrame) + and isinstance(ser, mod.pandas.Series) for module_name in IMPORT_HOOKS ) @@ -169,11 +189,7 @@ def is_pandas_series(ser: Any) -> TypeIs[pd.Series[Any]]: This method cannot be called on Narwhals Series. """ _warn_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) - for module_name in IMPORT_HOOKS - ) + return _is_pandas_series(ser) def is_pandas_index(index: Any) -> TypeIs[pd.Index[Any]]: @@ -185,6 +201,11 @@ 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.""" + return (mpd := get_modin()) is not None and isinstance(df, mpd.DataFrame) + + def is_modin_dataframe(df: Any) -> TypeIs[mpd.DataFrame]: """Check whether `df` is a modin DataFrame without importing modin. @@ -192,7 +213,12 @@ def is_modin_dataframe(df: Any) -> TypeIs[mpd.DataFrame]: This method cannot be called on a Narwhals DataFrame/LazyFrame. """ _warn_if_narwhals_df_or_lf(df) - return (mpd := get_modin()) is not None and isinstance(df, mpd.DataFrame) + return _is_modin_dataframe(df) + + +def _is_modin_series(ser: Any) -> TypeIs[mpd.Series]: + """Check whether `ser` is a modin Series without importing modin.""" + return (mpd := get_modin()) is not None and isinstance(ser, mpd.Series) def is_modin_series(ser: Any) -> TypeIs[mpd.Series]: @@ -202,7 +228,7 @@ def is_modin_series(ser: Any) -> TypeIs[mpd.Series]: This method cannot be called on Narwhals Series. """ _warn_if_narwhals_series(ser) - return (mpd := get_modin()) is not None and isinstance(ser, mpd.Series) + return _is_modin_series(ser) def is_modin_index(index: Any) -> TypeIs[mpd.Index[Any]]: # pragma: no cover @@ -210,6 +236,11 @@ def is_modin_index(index: Any) -> TypeIs[mpd.Index[Any]]: # pragma: no cover return (mpd := get_modin()) is not None and isinstance(index, mpd.Index) +def _is_cudf_dataframe(df: Any) -> TypeIs[cudf.DataFrame]: + """Check whether `df` is a cudf DataFrame without importing cudf.""" + return (cudf := get_cudf()) is not None and isinstance(df, cudf.DataFrame) + + def is_cudf_dataframe(df: Any) -> TypeIs[cudf.DataFrame]: """Check whether `df` is a cudf DataFrame without importing cudf. @@ -217,7 +248,12 @@ def is_cudf_dataframe(df: Any) -> TypeIs[cudf.DataFrame]: This method cannot be called on a Narwhals DataFrame/LazyFrame. """ _warn_if_narwhals_df_or_lf(df) - return (cudf := get_cudf()) is not None and isinstance(df, cudf.DataFrame) + return _is_cudf_dataframe(df) + + +def _is_cudf_series(ser: Any) -> TypeIs[cudf.Series[Any]]: + """Check whether `ser` is a cudf Series without importing cudf.""" + return (cudf := get_cudf()) is not None and isinstance(ser, cudf.Series) def is_cudf_series(ser: Any) -> TypeIs[cudf.Series[Any]]: @@ -227,7 +263,7 @@ def is_cudf_series(ser: Any) -> TypeIs[cudf.Series[Any]]: This method cannot be called on Narwhals Series. """ _warn_if_narwhals_series(ser) - return (cudf := get_cudf()) is not None and isinstance(ser, cudf.Series) + return _is_cudf_series(ser) def is_cudf_index(index: Any) -> TypeIs[cudf.Index]: @@ -245,6 +281,11 @@ def is_cupy_scalar(obj: Any) -> bool: ) # pragma: no cover +def _is_dask_dataframe(df: Any) -> TypeIs[dd.DataFrame]: + """Check whether `df` is a Dask DataFrame without importing Dask.""" + return (dd := get_dask_dataframe()) is not None and isinstance(df, dd.DataFrame) + + def is_dask_dataframe(df: Any) -> TypeIs[dd.DataFrame]: """Check whether `df` is a Dask DataFrame without importing Dask. @@ -252,7 +293,14 @@ def is_dask_dataframe(df: Any) -> TypeIs[dd.DataFrame]: This method cannot be called on a Narwhals DataFrame/LazyFrame. """ _warn_if_narwhals_df_or_lf(df) - return (dd := get_dask_dataframe()) is not None and isinstance(df, dd.DataFrame) + return _is_dask_dataframe(df) + + +def _is_duckdb_relation(df: Any) -> TypeIs[duckdb.DuckDBPyRelation]: + """Check whether `df` is a DuckDB Relation without importing DuckDB.""" + return (duckdb := get_duckdb()) is not None and isinstance( + df, duckdb.DuckDBPyRelation + ) def is_duckdb_relation(df: Any) -> TypeIs[duckdb.DuckDBPyRelation]: @@ -262,9 +310,12 @@ def is_duckdb_relation(df: Any) -> TypeIs[duckdb.DuckDBPyRelation]: This method cannot be called on Narwhals DataFrame/LazyFrame. """ _warn_if_narwhals_df_or_lf(df) - return (duckdb := get_duckdb()) is not None and isinstance( - df, duckdb.DuckDBPyRelation - ) + return _is_duckdb_relation(df) + + +def _is_ibis_table(df: Any) -> TypeIs[ibis.Table]: + """Check whether `df` is a Ibis Table without importing Ibis.""" + return (ibis := get_ibis()) is not None and isinstance(df, ibis.expr.types.Table) def is_ibis_table(df: Any) -> TypeIs[ibis.Table]: @@ -274,7 +325,12 @@ def is_ibis_table(df: Any) -> TypeIs[ibis.Table]: This method cannot be called on Narwhals DataFrame/LazyFrame. """ _warn_if_narwhals_df_or_lf(df) - return (ibis := get_ibis()) is not None and isinstance(df, ibis.expr.types.Table) + return _is_ibis_table(df) + + +def _is_polars_dataframe(df: Any) -> TypeIs[pl.DataFrame]: + """Check whether `df` is a Polars DataFrame without importing Polars.""" + return (pl := get_polars()) is not None and isinstance(df, pl.DataFrame) def is_polars_dataframe(df: Any) -> TypeIs[pl.DataFrame]: @@ -284,7 +340,12 @@ def is_polars_dataframe(df: Any) -> TypeIs[pl.DataFrame]: This method cannot be called on a Narwhals DataFrame/LazyFrame. """ _warn_if_narwhals_df_or_lf(df) - return (pl := get_polars()) is not None and isinstance(df, pl.DataFrame) + return _is_polars_dataframe(df) + + +def _is_polars_lazyframe(df: Any) -> TypeIs[pl.LazyFrame]: + """Check whether `df` is a Polars LazyFrame without importing Polars.""" + return (pl := get_polars()) is not None and isinstance(df, pl.LazyFrame) def is_polars_lazyframe(df: Any) -> TypeIs[pl.LazyFrame]: @@ -294,7 +355,12 @@ def is_polars_lazyframe(df: Any) -> TypeIs[pl.LazyFrame]: This method cannot be called on Narwhals DataFrame/LazyFrame. """ _warn_if_narwhals_df_or_lf(df) - return (pl := get_polars()) is not None and isinstance(df, pl.LazyFrame) + return _is_polars_lazyframe(df) + + +def _is_polars_series(ser: Any) -> TypeIs[pl.Series]: + """Check whether `ser` is a Polars Series without importing Polars.""" + return (pl := get_polars()) is not None and isinstance(ser, pl.Series) def is_polars_series(ser: Any) -> TypeIs[pl.Series]: @@ -304,7 +370,7 @@ def is_polars_series(ser: Any) -> TypeIs[pl.Series]: This method cannot be called on Narwhals Series. """ _warn_if_narwhals_series(ser) - return (pl := get_polars()) is not None and isinstance(ser, pl.Series) + return _is_polars_series(ser) def is_polars_schema(obj: Any) -> TypeIs[pl.Schema]: @@ -318,6 +384,11 @@ def is_polars_data_type(obj: Any) -> TypeIs[pl.DataType]: return bool(pl := get_polars()) and isinstance(obj, pl.DataType) +def _is_pyarrow_chunked_array(ser: Any) -> TypeIs[pa.ChunkedArray[Any]]: + """Check whether `ser` is a PyArrow ChunkedArray without importing PyArrow.""" + return (pa := get_pyarrow()) is not None and isinstance(ser, pa.ChunkedArray) + + def is_pyarrow_chunked_array(ser: Any) -> TypeIs[pa.ChunkedArray[Any]]: """Check whether `ser` is a PyArrow ChunkedArray without importing PyArrow. @@ -325,17 +396,22 @@ def is_pyarrow_chunked_array(ser: Any) -> TypeIs[pa.ChunkedArray[Any]]: This method cannot be called on Narwhals Series. """ _warn_if_narwhals_series(ser) - return (pa := get_pyarrow()) is not None and isinstance(ser, pa.ChunkedArray) + return _is_pyarrow_chunked_array(ser) + + +def _is_pyarrow_table(df: Any) -> TypeIs[pa.Table]: + """Check whether `df` is a PyArrow Table without importing PyArrow.""" + return (pa := get_pyarrow()) is not None and isinstance(df, pa.Table) def is_pyarrow_table(df: Any) -> TypeIs[pa.Table]: """Check whether `df` is a PyArrow Table without importing PyArrow. Warning: - This method cannot be called on Narwhals DataFrame/LazyFrame. + This method cannot be called on a Narwhals DataFrame/LazyFrame. """ _warn_if_narwhals_df_or_lf(df) - return (pa := get_pyarrow()) is not None and isinstance(df, pa.Table) + return _is_pyarrow_table(df) def is_pyarrow_scalar(obj: Any) -> TypeIs[pa.Scalar[Any]]: @@ -350,26 +426,26 @@ def is_pyarrow_data_type(obj: Any) -> TypeIs[pa.DataType]: return bool(pa := get_pyarrow()) and isinstance(obj, pa.DataType) -def is_pyspark_dataframe(df: Any) -> TypeIs[pyspark_sql.DataFrame]: - """Check whether `df` is a PySpark DataFrame without importing PySpark. - - Warning: - This method cannot be called on a Narwhals DataFrame/LazyFrame. - """ - _warn_if_narwhals_df_or_lf(df) +def _is_pyspark_dataframe(df: Any) -> TypeIs[pyspark_sql.DataFrame]: + """Check whether `df` is a PySpark DataFrame without importing PySpark.""" 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. +def is_pyspark_dataframe(df: Any) -> TypeIs[pyspark_sql.DataFrame]: + """Check whether `df` is a PySpark DataFrame without importing PySpark. Warning: This method cannot be called on a Narwhals DataFrame/LazyFrame. """ _warn_if_narwhals_df_or_lf(df) + return _is_pyspark_dataframe(df) + + +def _is_pyspark_connect_dataframe(df: Any) -> TypeIs[PySparkConnectDataFrame]: + """Check whether `df` is a PySpark Connect DataFrame without importing PySpark.""" if get_pyspark_connect() is not None: # pragma: no cover try: from pyspark.sql.connect.dataframe import DataFrame @@ -379,13 +455,18 @@ def is_pyspark_connect_dataframe(df: Any) -> TypeIs[PySparkConnectDataFrame]: return False -def is_sqlframe_dataframe(df: Any) -> TypeIs[SQLFrameDataFrame]: - """Check whether `df` is a SQLFrame DataFrame without importing SQLFrame. +def is_pyspark_connect_dataframe(df: Any) -> TypeIs[PySparkConnectDataFrame]: + """Check whether `df` is a PySpark Connect DataFrame without importing PySpark. Warning: This method cannot be called on a Narwhals DataFrame/LazyFrame. """ _warn_if_narwhals_df_or_lf(df) + return _is_pyspark_connect_dataframe(df) + + +def _is_sqlframe_dataframe(df: Any) -> TypeIs[SQLFrameDataFrame]: + """Check whether `df` is a SQLFrame DataFrame without importing SQLFrame.""" if get_sqlframe() is not None: from sqlframe.base.dataframe import BaseDataFrame @@ -393,6 +474,16 @@ def is_sqlframe_dataframe(df: Any) -> TypeIs[SQLFrameDataFrame]: return False # pragma: no cover +def is_sqlframe_dataframe(df: Any) -> TypeIs[SQLFrameDataFrame]: + """Check whether `df` is a SQLFrame DataFrame without importing SQLFrame. + + Warning: + This method cannot be called on a Narwhals DataFrame/LazyFrame. + """ + _warn_if_narwhals_df_or_lf(df) + return _is_sqlframe_dataframe(df) + + def is_numpy_array(arr: Any | _NDArray[_ShapeT]) -> TypeIs[_NDArray[_ShapeT]]: """Check whether `arr` is a NumPy Array without importing NumPy.""" return (np := get_numpy()) is not None and isinstance(arr, np.ndarray) @@ -432,6 +523,14 @@ def is_numpy_scalar(scalar: Any) -> TypeGuard[_NumpyScalar]: return (np := get_numpy()) is not None and isinstance(scalar, np.generic) +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. + """ + return _is_pandas_dataframe(df) or _is_modin_dataframe(df) or _is_cudf_dataframe(df) + + def is_pandas_like_dataframe(df: Any) -> bool: """Check whether `df` is a pandas-like DataFrame without doing any imports. @@ -441,7 +540,15 @@ def is_pandas_like_dataframe(df: Any) -> bool: This method cannot be called on a Narwhals DataFrame/LazyFrame. """ _warn_if_narwhals_df_or_lf(df) - return is_pandas_dataframe(df) or is_modin_dataframe(df) or is_cudf_dataframe(df) + return _is_pandas_like_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. + """ + return _is_pandas_series(ser) or _is_modin_series(ser) or _is_cudf_series(ser) def is_pandas_like_series(ser: Any) -> bool: @@ -453,7 +560,7 @@ def is_pandas_like_series(ser: Any) -> bool: This method cannot be called on Narwhals Series. """ _warn_if_narwhals_series(ser) - return is_pandas_series(ser) or is_modin_series(ser) or is_cudf_series(ser) + return _is_pandas_like_series(ser) def is_pandas_like_index(index: Any) -> bool: @@ -482,6 +589,43 @@ def is_cudf_dtype( ) +def _is_into_native_series(obj: Any | IntoSeriesT) -> TypeIs[IntoSeriesT]: + from narwhals._utils import _hasattr_static + + return ( + _hasattr_static(obj, "__narwhals_series__") + or _is_polars_series(obj) + or _is_pyarrow_chunked_array(obj) + or _is_pandas_like_series(obj) + ) + + +def _is_into_native_dataframe(obj: Any | IntoDataFrameT) -> TypeIs[IntoDataFrameT]: + from narwhals._utils import _hasattr_static + + return ( + _hasattr_static(obj, "__narwhals_dataframe__") + or _is_polars_dataframe(obj) + or _is_pyarrow_table(obj) + or _is_pandas_like_dataframe(obj) + ) + + +def _is_into_native_lazyframe(obj: Any | IntoLazyFrameT) -> TypeIs[IntoLazyFrameT]: + from narwhals._utils import _hasattr_static + + return ( + _hasattr_static(obj, "__narwhals_lazyframe__") + or _is_polars_lazyframe(obj) + or _is_dask_dataframe(obj) + or _is_duckdb_relation(obj) + or _is_ibis_table(obj) + or _is_pyspark_dataframe(obj) + or _is_pyspark_connect_dataframe(obj) + or _is_sqlframe_dataframe(obj) + ) + + def is_into_series(native_series: Any | IntoSeriesT) -> TypeIs[IntoSeriesT]: """Check whether `native_series` can be converted to a Narwhals Series. @@ -507,13 +651,7 @@ def is_into_series(native_series: Any | IntoSeriesT) -> TypeIs[IntoSeriesT]: """ from narwhals.series import Series - return ( - isinstance(native_series, Series) - or hasattr(native_series, "__narwhals_series__") - or is_polars_series(native_series) - or is_pyarrow_chunked_array(native_series) - or is_pandas_like_series(native_series) - ) + return isinstance(native_series, Series) or _is_into_native_series(native_series) def is_into_dataframe(native_dataframe: Any | IntoDataFrameT) -> TypeIs[IntoDataFrameT]: @@ -541,12 +679,38 @@ def is_into_dataframe(native_dataframe: Any | IntoDataFrameT) -> TypeIs[IntoData """ from narwhals.dataframe import DataFrame - return ( - isinstance(native_dataframe, DataFrame) - or hasattr(native_dataframe, "__narwhals_dataframe__") - or is_polars_dataframe(native_dataframe) - or is_pyarrow_table(native_dataframe) - or is_pandas_like_dataframe(native_dataframe) + return isinstance(native_dataframe, DataFrame) or _is_into_native_dataframe( + native_dataframe + ) + + +def is_into_lazyframe(native_lazyframe: Any | IntoLazyFrameT) -> TypeIs[IntoLazyFrameT]: + """Check whether `native_lazyframe` can be converted to a Narwhals LazyFrame. + + Arguments: + native_lazyframe: The object to check. + + Examples: + >>> import pandas as pd + >>> import polars as pl + >>> import numpy as np + >>> from narwhals.dependencies import is_into_lazyframe + + >>> df_pd = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}) + >>> lf_pl = pl.LazyFrame({"a": [1, 2, 3], "b": [4, 5, 6]}) + >>> np_arr = np.array([[1, 4], [2, 5], [3, 6]]) + + >>> is_into_lazyframe(df_pd) + False + >>> is_into_lazyframe(lf_pl) + True + >>> is_into_lazyframe(np_arr) + False + """ + from narwhals.dataframe import LazyFrame + + return isinstance(native_lazyframe, LazyFrame) or _is_into_native_lazyframe( + native_lazyframe ) @@ -602,19 +766,30 @@ def is_narwhals_series_bool( __all__ = [ "get_cudf", + "get_dask", + "get_dask_dataframe", + "get_duckdb", "get_ibis", "get_modin", "get_numpy", "get_pandas", "get_polars", "get_pyarrow", + "get_pyspark", + "get_pyspark_connect", + "get_pyspark_sql", + "get_sqlframe", "is_cudf_dataframe", + "is_cudf_index", "is_cudf_series", "is_dask_dataframe", + "is_duckdb_relation", "is_ibis_table", "is_into_dataframe", + "is_into_lazyframe", "is_into_series", "is_modin_dataframe", + "is_modin_index", "is_modin_series", "is_narwhals_dataframe", "is_narwhals_lazyframe", @@ -623,6 +798,7 @@ def is_narwhals_series_bool( "is_pandas_dataframe", "is_pandas_index", "is_pandas_like_dataframe", + "is_pandas_like_index", "is_pandas_like_series", "is_pandas_series", "is_polars_dataframe", @@ -630,4 +806,7 @@ def is_narwhals_series_bool( "is_polars_series", "is_pyarrow_chunked_array", "is_pyarrow_table", + "is_pyspark_connect_dataframe", + "is_pyspark_dataframe", + "is_sqlframe_dataframe", ] diff --git a/src/narwhals/stable/v1/dependencies.py b/src/narwhals/stable/v1/dependencies.py index 2674f2890f..d479d4674b 100644 --- a/src/narwhals/stable/v1/dependencies.py +++ b/src/narwhals/stable/v1/dependencies.py @@ -1,143 +1,117 @@ from __future__ import annotations -import sys from typing import TYPE_CHECKING, Any if TYPE_CHECKING: - import cudf - import dask.dataframe as dd - import ibis - import modin.pandas as mpd - import pandas as pd - import polars as pl - import pyarrow as pa from typing_extensions import TypeIs + from narwhals.stable.v1.typing import IntoDataFrameT, IntoLazyFrameT, IntoSeriesT + from narwhals.dependencies import ( - IMPORT_HOOKS, + _is_cudf_dataframe as is_cudf_dataframe, + _is_cudf_series as is_cudf_series, + _is_dask_dataframe as is_dask_dataframe, + _is_duckdb_relation as is_duckdb_relation, + _is_ibis_table as is_ibis_table, + _is_into_native_dataframe, + _is_into_native_lazyframe, + _is_into_native_series, + _is_modin_dataframe as is_modin_dataframe, + _is_modin_series as is_modin_series, + _is_pandas_dataframe as is_pandas_dataframe, + _is_pandas_like_dataframe as is_pandas_like_dataframe, + _is_pandas_like_series as is_pandas_like_series, + _is_pandas_series as is_pandas_series, + _is_polars_dataframe as is_polars_dataframe, + _is_polars_lazyframe as is_polars_lazyframe, + _is_polars_series as is_polars_series, + _is_pyarrow_chunked_array as is_pyarrow_chunked_array, + _is_pyarrow_table as is_pyarrow_table, + _is_pyspark_connect_dataframe as is_pyspark_connect_dataframe, + _is_pyspark_dataframe as is_pyspark_dataframe, + _is_sqlframe_dataframe as is_sqlframe_dataframe, get_cudf, + get_dask, get_dask_dataframe, + get_duckdb, get_ibis, get_modin, get_numpy, get_pandas, get_polars, get_pyarrow, - is_into_dataframe, - is_into_series, + get_pyspark, + get_pyspark_connect, + get_pyspark_sql, + get_sqlframe, + is_cudf_index, + is_modin_index, is_narwhals_dataframe, is_narwhals_lazyframe, is_narwhals_series, is_numpy_array, is_pandas_index, + is_pandas_like_index, ) -def is_pandas_dataframe(df: Any) -> TypeIs[pd.DataFrame]: - """Check whether `df` is a pandas DataFrame without importing pandas.""" - 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) - for module_name in IMPORT_HOOKS - ) - +# TODO(Unassigned): For duckdb and ibis backends: +# * is_into_dataframe(native_frame) returns False +# * nw_v1.from_native(native_frame) returns a narwhals.stable.v1.DataFrame +# * Therefore is_into_dataframe(nw_v1.from_native(native_frame)) returns True +# See discussion https://github.com/narwhals-dev/narwhals/pull/3613#discussion_r3288440039 +def is_into_dataframe(native_dataframe: Any | IntoDataFrameT) -> TypeIs[IntoDataFrameT]: + """Check whether `native_dataframe` can be converted to a narwhals.stable.v1.DataFrame.""" + from narwhals.stable.v1 import DataFrame -def is_pandas_series(ser: Any) -> TypeIs[pd.Series[Any]]: - """Check whether `ser` is a pandas Series without importing pandas.""" - 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) - for module_name in IMPORT_HOOKS + return isinstance(native_dataframe, DataFrame) or _is_into_native_dataframe( + native_dataframe ) -def is_modin_dataframe(df: Any) -> TypeIs[mpd.DataFrame]: - """Check whether `df` is a modin DataFrame without importing modin.""" - 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.""" - return (mpd := get_modin()) is not None and isinstance(ser, mpd.Series) - - -def is_cudf_dataframe(df: Any) -> TypeIs[cudf.DataFrame]: - """Check whether `df` is a cudf DataFrame without importing cudf.""" - 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.""" - return (cudf := get_cudf()) is not None and isinstance(ser, cudf.Series) - - -def is_dask_dataframe(df: Any) -> TypeIs[dd.DataFrame]: - """Check whether `df` is a Dask DataFrame without importing Dask.""" - return (dd := get_dask_dataframe()) is not None and isinstance(df, dd.DataFrame) - - -def is_ibis_table(df: Any) -> TypeIs[ibis.Table]: - """Check whether `df` is a Ibis Table without importing Ibis.""" - return (ibis := get_ibis()) is not None and isinstance(df, ibis.expr.types.Table) +def is_into_lazyframe(native_lazyframe: Any | IntoLazyFrameT) -> TypeIs[IntoLazyFrameT]: + """Check whether `native_lazyframe` can be converted to a narwhals.stable.v1.LazyFrame.""" + from narwhals.stable.v1 import LazyFrame - -def is_polars_dataframe(df: Any) -> TypeIs[pl.DataFrame]: - """Check whether `df` is a Polars DataFrame without importing Polars.""" - 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.""" - 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.""" - 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.""" - 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.""" - return (pa := get_pyarrow()) is not None and isinstance(df, pa.Table) - - -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. - """ - return is_pandas_dataframe(df) or is_modin_dataframe(df) or is_cudf_dataframe(df) + return isinstance(native_lazyframe, LazyFrame) or _is_into_native_lazyframe( + native_lazyframe + ) -def is_pandas_like_series(ser: Any) -> bool: - """Check whether `ser` is a pandas-like Series without doing any imports. +def is_into_series(native_series: Any | IntoSeriesT) -> TypeIs[IntoSeriesT]: + """Check whether `native_series` can be converted to a narwhals.stable.v1.Series.""" + from narwhals.stable.v1 import Series - By "pandas-like", we mean: pandas, Modin, cuDF. - """ - return is_pandas_series(ser) or is_modin_series(ser) or is_cudf_series(ser) + return isinstance(native_series, Series) or _is_into_native_series(native_series) __all__ = [ "get_cudf", + "get_dask", + "get_dask_dataframe", + "get_duckdb", "get_ibis", "get_modin", "get_numpy", "get_pandas", "get_polars", "get_pyarrow", + "get_pyspark", + "get_pyspark_connect", + "get_pyspark_sql", + "get_sqlframe", "is_cudf_dataframe", + "is_cudf_index", "is_cudf_series", "is_dask_dataframe", + "is_duckdb_relation", "is_ibis_table", "is_into_dataframe", + "is_into_lazyframe", "is_into_series", "is_modin_dataframe", + "is_modin_index", "is_modin_series", "is_narwhals_dataframe", "is_narwhals_lazyframe", @@ -146,6 +120,7 @@ def is_pandas_like_series(ser: Any) -> bool: "is_pandas_dataframe", "is_pandas_index", "is_pandas_like_dataframe", + "is_pandas_like_index", "is_pandas_like_series", "is_pandas_series", "is_polars_dataframe", @@ -153,4 +128,7 @@ def is_pandas_like_series(ser: Any) -> bool: "is_polars_series", "is_pyarrow_chunked_array", "is_pyarrow_table", + "is_pyspark_connect_dataframe", + "is_pyspark_dataframe", + "is_sqlframe_dataframe", ] diff --git a/src/narwhals/stable/v2/dependencies.py b/src/narwhals/stable/v2/dependencies.py index 09dac96892..06f273a752 100644 --- a/src/narwhals/stable/v2/dependencies.py +++ b/src/narwhals/stable/v2/dependencies.py @@ -1,3 +1,128 @@ from __future__ import annotations -from narwhals.dependencies import * # noqa: F403 +from typing import TYPE_CHECKING, Any + +from narwhals.dependencies import ( + _is_into_native_dataframe, + _is_into_native_lazyframe, + _is_into_native_series, + get_cudf, + get_dask, + get_dask_dataframe, + get_duckdb, + get_ibis, + get_modin, + get_numpy, + get_pandas, + get_polars, + get_pyarrow, + get_pyspark, + get_pyspark_connect, + get_pyspark_sql, + get_sqlframe, + is_cudf_dataframe, + is_cudf_index, + is_cudf_series, + is_dask_dataframe, + is_duckdb_relation, + is_ibis_table, + is_modin_dataframe, + is_modin_index, + is_modin_series, + is_narwhals_dataframe, + is_narwhals_lazyframe, + is_narwhals_series, + is_numpy_array, + is_pandas_dataframe, + is_pandas_index, + is_pandas_like_dataframe, + is_pandas_like_index, + is_pandas_like_series, + is_pandas_series, + is_polars_dataframe, + is_polars_lazyframe, + is_polars_series, + is_pyarrow_chunked_array, + is_pyarrow_table, + is_pyspark_connect_dataframe, + is_pyspark_dataframe, + is_sqlframe_dataframe, +) + +if TYPE_CHECKING: + from typing_extensions import TypeIs + + from narwhals.stable.v2.typing import IntoDataFrameT, IntoLazyFrameT, IntoSeriesT + + +def is_into_dataframe(native_dataframe: Any | IntoDataFrameT) -> TypeIs[IntoDataFrameT]: + """Check whether `native_dataframe` can be converted to a narwhals.stable.v2.DataFrame.""" + from narwhals.stable.v2 import DataFrame + + return isinstance(native_dataframe, DataFrame) or _is_into_native_dataframe( + native_dataframe + ) + + +def is_into_lazyframe(native_lazyframe: Any | IntoLazyFrameT) -> TypeIs[IntoLazyFrameT]: + """Check whether `native_lazyframe` can be converted to a narwhals.stable.v2.LazyFrame.""" + from narwhals.stable.v2 import LazyFrame + + return isinstance(native_lazyframe, LazyFrame) or _is_into_native_lazyframe( + native_lazyframe + ) + + +def is_into_series(native_series: Any | IntoSeriesT) -> TypeIs[IntoSeriesT]: + """Check whether `native_series` can be converted to a narwhals.stable.v2.Series.""" + from narwhals.stable.v2 import Series + + return isinstance(native_series, Series) or _is_into_native_series(native_series) + + +__all__ = [ + "get_cudf", + "get_dask", + "get_dask_dataframe", + "get_duckdb", + "get_ibis", + "get_modin", + "get_numpy", + "get_pandas", + "get_polars", + "get_pyarrow", + "get_pyspark", + "get_pyspark_connect", + "get_pyspark_sql", + "get_sqlframe", + "is_cudf_dataframe", + "is_cudf_index", + "is_cudf_series", + "is_dask_dataframe", + "is_duckdb_relation", + "is_ibis_table", + "is_into_dataframe", + "is_into_lazyframe", + "is_into_series", + "is_modin_dataframe", + "is_modin_index", + "is_modin_series", + "is_narwhals_dataframe", + "is_narwhals_lazyframe", + "is_narwhals_series", + "is_numpy_array", + "is_pandas_dataframe", + "is_pandas_index", + "is_pandas_like_dataframe", + "is_pandas_like_index", + "is_pandas_like_series", + "is_pandas_series", + "is_polars_dataframe", + "is_polars_lazyframe", + "is_polars_series", + "is_pyarrow_chunked_array", + "is_pyarrow_table", + "is_pyspark_connect_dataframe", + "is_pyspark_dataframe", + "is_sqlframe_dataframe", +] diff --git a/tests/dependencies/conftest.py b/tests/dependencies/conftest.py new file mode 100644 index 0000000000..7448b04303 --- /dev/null +++ b/tests/dependencies/conftest.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +from typing import Any + +import pytest + + +class AlwaysHasAttr: + """An object whose attributes only exist dynamically, never statically. + + Every attribute access is fabricated on the fly via `__getattr__`, so + `hasattr` reports `True` for any name while [`inspect.getattr_static`] + (and therefore `_hasattr_static`) correctly reports `False`: + + obj = AlwaysHasAttr() + hasattr(obj, "i_dont_exist") # True (false positive) + _hasattr_static(obj, "i_dont_exist") # False (correct) + + Use it in tests to prove that code under test relies on `_hasattr_static` + rather than `hasattr` when probing unknown objects: any check that accepts + a `AlwaysHasAttr` instance is, by construction, using the unsafe path. + + [`inspect.getattr_static`]: https://docs.python.org/3/library/inspect.html#inspect.getattr_static + """ + + def __getattr__(self, name: str) -> Any: # pragma: no cover + return self + + +@pytest.fixture(scope="session") +def always_has_attr() -> AlwaysHasAttr: + """An object that triggers `hasattr` false positives for every attribute name.""" + return AlwaysHasAttr() diff --git a/tests/dependencies/is_into_dataframe_test.py b/tests/dependencies/is_into_dataframe_test.py index 32b2251ad6..4e31b71afd 100644 --- a/tests/dependencies/is_into_dataframe_test.py +++ b/tests/dependencies/is_into_dataframe_test.py @@ -5,53 +5,87 @@ import pytest import narwhals as nw -from narwhals.stable.v1.dependencies import is_into_dataframe +import narwhals.stable.v1 as nw_v1 +import narwhals.stable.v2 as nw_v2 +from narwhals.dependencies import is_into_dataframe +from narwhals.stable.v1.dependencies import is_into_dataframe as v1_is_into_dataframe +from narwhals.stable.v2.dependencies import is_into_dataframe as v2_is_into_dataframe if TYPE_CHECKING: from collections.abc import Mapping from typing_extensions import Self -DATA: dict[str, Any] = {"a": [1, 2, 3], "b": [4, 5, 6]} + from tests.dependencies.conftest import AlwaysHasAttr + from tests.utils import Constructor +EAGER_CONSTRUCTOR_NAMES = ("pandas", "modin", "cudf", "polars_eager", "pyarrow") +V1_INTO_DATAFRAMES = (*EAGER_CONSTRUCTOR_NAMES, "duckdb", "ibis") -class DictDataFrame: +data: dict[str, Any] = {"a": [1, 2, 3], "b": [4, 5, 6]} + + +class DictDataFrame: # pragma: no cover def __init__(self, data: Mapping[str, Any]) -> None: self._data = data - def __len__(self) -> int: # pragma: no cover - return len(next(iter(self._data.values()))) - - def __narwhals_dataframe__(self) -> Self: # pragma: no cover + def __narwhals_dataframe__(self) -> Self: return self + def __len__(self) -> int: + return len(next(iter(self._data.values()))) -def test_is_into_dataframe_pyarrow() -> None: - pytest.importorskip("pyarrow") - import pyarrow as pa + @property + def columns(self) -> Any: ... + def drop(self, *args: Any, **kwargs: Any) -> Any: ... + def join(self, *args: Any, **kwargs: Any) -> Any: ... - assert is_into_dataframe(pa.table(DATA)) +def test_is_into_dataframe(constructor: Constructor) -> None: + native_frame = constructor(data) + nw_frame = nw.from_native(native_frame) + nw_v1_frame = nw_v1.from_native(native_frame) + nw_v2_frame = nw_v2.from_native(native_frame) -def test_is_into_dataframe_polars() -> None: - pytest.importorskip("polars") - import polars as pl + result = any(x in str(constructor) for x in EAGER_CONSTRUCTOR_NAMES) + result_v1 = any(x in str(constructor) for x in V1_INTO_DATAFRAMES) - assert is_into_dataframe(pl.DataFrame(DATA)) + assert is_into_dataframe(native_frame) == result + assert v1_is_into_dataframe(native_frame) == result + assert v2_is_into_dataframe(native_frame) == result + assert is_into_dataframe(nw_frame) == result + assert not v1_is_into_dataframe(nw_frame) + assert not v2_is_into_dataframe(nw_frame) -def test_is_into_dataframe_pandas() -> None: - pytest.importorskip("pandas") - import pandas as pd + assert is_into_dataframe(nw_v1_frame) == result_v1 + assert v1_is_into_dataframe(nw_v1_frame) == result_v1 + assert v2_is_into_dataframe(nw_v1_frame) is False - assert is_into_dataframe(pd.DataFrame(DATA)) - assert is_into_dataframe(nw.from_native(pd.DataFrame(DATA))) + assert is_into_dataframe(nw_v2_frame) == result + assert v2_is_into_dataframe(nw_v2_frame) == result + assert v1_is_into_dataframe(nw_v2_frame) is False -def test_is_into_dataframe_other() -> None: +def test_is_into_dataframe_numpy() -> None: pytest.importorskip("numpy") import numpy as np - assert is_into_dataframe(DictDataFrame(DATA)) # pyrefly: ignore[bad-specialization] - assert not is_into_dataframe(np.array([[1, 4], [2, 5], [3, 6]])) - assert not is_into_dataframe(DATA) + arr = np.array([[1, 4], [2, 5], [3, 6]]) + assert not is_into_dataframe(arr) + assert not v1_is_into_dataframe(arr) + assert not v2_is_into_dataframe(arr) + + +def test_is_into_dataframe_other(always_has_attr: AlwaysHasAttr) -> None: + assert not is_into_dataframe(data) + assert not v1_is_into_dataframe(data) + assert not v2_is_into_dataframe(data) + + assert is_into_dataframe(DictDataFrame(data)) + assert v1_is_into_dataframe(DictDataFrame(data)) + assert v2_is_into_dataframe(DictDataFrame(data)) + + assert not is_into_dataframe(always_has_attr) + assert not v1_is_into_dataframe(always_has_attr) + assert not v2_is_into_dataframe(always_has_attr) diff --git a/tests/dependencies/is_into_lazyframe_test.py b/tests/dependencies/is_into_lazyframe_test.py new file mode 100644 index 0000000000..b9cabdea83 --- /dev/null +++ b/tests/dependencies/is_into_lazyframe_test.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +import pytest + +import narwhals as nw +import narwhals.stable.v1 as nw_v1 +import narwhals.stable.v2 as nw_v2 +from narwhals.dependencies import is_into_lazyframe +from narwhals.stable.v1.dependencies import is_into_lazyframe as v1_is_into_lazyframe +from narwhals.stable.v2.dependencies import is_into_lazyframe as v2_is_into_lazyframe + +if TYPE_CHECKING: + from collections.abc import Mapping + + from typing_extensions import Self + + from tests.dependencies.conftest import AlwaysHasAttr + from tests.utils import Constructor + +EAGER_CONSTRUCTOR_NAMES = ("pandas", "modin", "cudf", "polars_eager", "pyarrow") +V1_INTO_DATAFRAMES = (*EAGER_CONSTRUCTOR_NAMES, "duckdb", "ibis") + +data: dict[str, Any] = {"a": [1, 2, 3], "b": [4, 5, 6]} + + +class DictLazyFrame: # pragma: no cover + def __init__(self, data: Mapping[str, Any]) -> None: + self._data = data + + def __narwhals_lazyframe__(self) -> Self: + return self + + @property + def columns(self) -> Any: ... + def drop(self, *args: Any, **kwargs: Any) -> Any: ... + def explain(self, *args: Any, **kwargs: Any) -> Any: ... + def join(self, *args: Any, **kwargs: Any) -> Any: ... + + +def test_is_into_lazyframe(constructor: Constructor) -> None: + native_frame = constructor(data) + nw_frame = nw.from_native(native_frame) + nw_v1_frame = nw_v1.from_native(native_frame) + nw_v2_frame = nw_v2.from_native(native_frame) + + result = not any(x in str(constructor) for x in EAGER_CONSTRUCTOR_NAMES) + result_v1 = not any(x in str(constructor) for x in V1_INTO_DATAFRAMES) + + assert is_into_lazyframe(native_frame) == result + assert v1_is_into_lazyframe(native_frame) == result + assert v2_is_into_lazyframe(native_frame) == result + + assert is_into_lazyframe(nw_frame) == result + assert not v1_is_into_lazyframe(nw_frame) + assert not v2_is_into_lazyframe(nw_frame) + + assert is_into_lazyframe(nw_v1_frame) == result_v1 + assert v1_is_into_lazyframe(nw_v1_frame) == result_v1 + assert v2_is_into_lazyframe(nw_v1_frame) is False + + assert is_into_lazyframe(nw_v2_frame) == result + assert v2_is_into_lazyframe(nw_v2_frame) == result + assert v1_is_into_lazyframe(nw_v2_frame) is False + + +def test_is_into_lazyframe_numpy() -> None: + pytest.importorskip("numpy") + import numpy as np + + arr = np.array([[1, 4], [2, 5], [3, 6]]) + assert not is_into_lazyframe(arr) + assert not v1_is_into_lazyframe(arr) + assert not v2_is_into_lazyframe(arr) + + +def test_is_into_lazyframe_other(always_has_attr: AlwaysHasAttr) -> None: + assert not is_into_lazyframe(data) + assert not v1_is_into_lazyframe(data) + assert not v2_is_into_lazyframe(data) + + assert is_into_lazyframe(DictLazyFrame(data)) + assert v1_is_into_lazyframe(DictLazyFrame(data)) + assert v2_is_into_lazyframe(DictLazyFrame(data)) + + assert not is_into_lazyframe(always_has_attr) + assert not v1_is_into_lazyframe(always_has_attr) + assert not v2_is_into_lazyframe(always_has_attr) diff --git a/tests/dependencies/is_into_series_test.py b/tests/dependencies/is_into_series_test.py index 8aa895343b..06dc1fb8b1 100644 --- a/tests/dependencies/is_into_series_test.py +++ b/tests/dependencies/is_into_series_test.py @@ -5,50 +5,82 @@ import pytest import narwhals as nw -from narwhals.stable.v1.dependencies import is_into_series +import narwhals.stable.v1 as nw_v1 +import narwhals.stable.v2 as nw_v2 +from narwhals.dependencies import is_into_series +from narwhals.stable.v1.dependencies import is_into_series as v1_is_into_series +from narwhals.stable.v2.dependencies import is_into_series as v2_is_into_series if TYPE_CHECKING: from typing_extensions import Self + from tests.dependencies.conftest import AlwaysHasAttr + from tests.utils import ConstructorEager -class ListBackedSeries: +data: dict[str, Any] = {"a": [1, 2, 3], "b": [4, 5, 6]} + + +class ListBackedSeries: # pragma: no cover def __init__(self, name: str, data: list[Any]) -> None: self._data = data self._name = name - def __len__(self) -> int: # pragma: no cover + def __narwhals_series__(self) -> Self: + return self + + def __len__(self) -> int: return len(self._data) - def __narwhals_series__(self) -> Self: # pragma: no cover - return self + def __iter__(self) -> Any: ... + def filter(self, *args: Any, **kwargs: Any) -> Any: ... + def value_counts(self, *args: Any, **kwargs: Any) -> Any: ... + def unique(self, *args: Any, **kwargs: Any) -> Any: ... -def test_is_into_series_pyarrow() -> None: - pytest.importorskip("pyarrow") - import pyarrow as pa +def test_is_into_series(constructor_eager: ConstructorEager) -> None: + native_frame = constructor_eager(data) + nw_series = nw.from_native(native_frame)["a"] + nw_v1_series = nw_v1.from_native(native_frame)["a"] + nw_v2_series = nw_v2.from_native(native_frame)["a"] + native_series = nw_series.to_native() - assert is_into_series(pa.chunked_array([["a", "b"]])) + assert is_into_series(native_series) + assert is_into_series(nw_series) + assert is_into_series(nw_v1_series) + assert is_into_series(nw_v2_series) + assert v1_is_into_series(native_series) + assert not v1_is_into_series(nw_series) + assert v1_is_into_series(nw_v1_series) + assert not v1_is_into_series(nw_v2_series) -def test_is_into_series_polars() -> None: - pytest.importorskip("polars") - import polars as pl + assert v2_is_into_series(native_series) + assert not v2_is_into_series(nw_series) + assert not v2_is_into_series(nw_v1_series) + assert v2_is_into_series(nw_v2_series) - assert is_into_series(pl.Series([1, 2, 3])) +def test_is_into_series_numpy() -> None: + pytest.importorskip("numpy") + import numpy as np -def test_is_into_series_pandas() -> None: - pytest.importorskip("pandas") - import pandas as pd + arr = np.array([1, 2, 3]) + assert not is_into_series(arr) + assert not v1_is_into_series(arr) + assert not v2_is_into_series(arr) - assert is_into_series(pd.Series([1, 2, 3])) - assert is_into_series(nw.from_native(pd.Series([1, 2, 3]), series_only=True)) +def test_is_into_series_other(always_has_attr: AlwaysHasAttr) -> None: + values = [1, 4, 2] -def test_is_into_series() -> None: - pytest.importorskip("numpy") - import numpy as np + assert not is_into_series(values) + assert not v1_is_into_series(values) + assert not v2_is_into_series(values) + + assert is_into_series(ListBackedSeries("a", values)) + assert v1_is_into_series(ListBackedSeries("a", values)) + assert v2_is_into_series(ListBackedSeries("a", values)) - assert is_into_series(ListBackedSeries("a", [1, 4, 2])) # pyrefly: ignore[bad-specialization] - assert not is_into_series(np.array([1, 2, 3])) - assert not is_into_series([1, 2, 3]) + assert not is_into_series(always_has_attr) + assert not v1_is_into_series(always_has_attr) + assert not v2_is_into_series(always_has_attr)