diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index ee5ba37ec..265442e9f 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -25,7 +25,7 @@ jobs: if: runner.os == 'Windows' run: powershell -c "irm https://astral.sh/uv/install.ps1 | iex" - name: install-reqs - run: uv pip install --upgrade tox virtualenv setuptools -r requirements-dev.txt --system + run: uv pip install --upgrade tox virtualenv setuptools -r requirements-dev.txt ibis-framework[duckdb] --system - name: show-deps run: uv pip freeze - name: Run pytest diff --git a/narwhals/dependencies.py b/narwhals/dependencies.py index 24b435858..2cd9f0983 100644 --- a/narwhals/dependencies.py +++ b/narwhals/dependencies.py @@ -83,79 +83,79 @@ def get_ibis() -> Any: def is_pandas_dataframe(df: Any) -> TypeGuard[pd.DataFrame]: """Check whether `df` is a pandas DataFrame without importing pandas.""" - return bool((pd := get_pandas()) is not None and isinstance(df, pd.DataFrame)) + return (pd := get_pandas()) is not None and isinstance(df, pd.DataFrame) def is_pandas_series(ser: Any) -> TypeGuard[pd.Series[Any]]: """Check whether `ser` is a pandas Series without importing pandas.""" - return bool((pd := get_pandas()) is not None and isinstance(ser, pd.Series)) + return (pd := get_pandas()) is not None and isinstance(ser, pd.Series) def is_modin_dataframe(df: Any) -> TypeGuard[mpd.DataFrame]: """Check whether `df` is a modin DataFrame without importing modin.""" - return bool((pd := get_modin()) is not None and isinstance(df, pd.DataFrame)) + return (pd := get_modin()) is not None and isinstance(df, pd.DataFrame) def is_modin_series(ser: Any) -> TypeGuard[mpd.Series]: """Check whether `ser` is a modin Series without importing modin.""" - return bool((pd := get_modin()) is not None and isinstance(ser, pd.Series)) + return (pd := get_modin()) is not None and isinstance(ser, pd.Series) def is_cudf_dataframe(df: Any) -> TypeGuard[cudf.DataFrame]: """Check whether `df` is a cudf DataFrame without importing cudf.""" - return bool((pd := get_cudf()) is not None and isinstance(df, pd.DataFrame)) + return (pd := get_cudf()) is not None and isinstance(df, pd.DataFrame) def is_cudf_series(ser: Any) -> TypeGuard[pd.Series[Any]]: """Check whether `ser` is a cudf Series without importing cudf.""" - return bool((pd := get_cudf()) is not None and isinstance(ser, pd.Series)) + return (pd := get_cudf()) is not None and isinstance(ser, pd.Series) def is_dask_dataframe(df: Any) -> TypeGuard[dd.DataFrame]: """Check whether `df` is a Dask DataFrame without importing Dask.""" - return bool((dd := get_dask_dataframe()) is not None and isinstance(df, dd.DataFrame)) + return (dd := get_dask_dataframe()) is not None and isinstance(df, dd.DataFrame) def is_duckdb_relation(df: Any) -> TypeGuard[duckdb.DuckDBPyRelation]: """Check whether `df` is a DuckDB Relation without importing DuckDB.""" - return bool( - (duckdb := get_duckdb()) is not None and isinstance(df, duckdb.DuckDBPyRelation) + return (duckdb := get_duckdb()) is not None and isinstance( + df, duckdb.DuckDBPyRelation ) def is_ibis_table(df: Any) -> TypeGuard[ibis.Table]: """Check whether `df` is a Ibis Table without importing Ibis.""" - return bool((ibis := get_ibis()) is not None and isinstance(df, ibis.Table)) + return (ibis := get_ibis()) is not None and isinstance(df, ibis.expr.types.Table) def is_polars_dataframe(df: Any) -> TypeGuard[pl.DataFrame]: """Check whether `df` is a Polars DataFrame without importing Polars.""" - return bool((pl := get_polars()) is not None and isinstance(df, pl.DataFrame)) + return (pl := get_polars()) is not None and isinstance(df, pl.DataFrame) def is_polars_lazyframe(df: Any) -> TypeGuard[pl.LazyFrame]: """Check whether `df` is a Polars LazyFrame without importing Polars.""" - return bool((pl := get_polars()) is not None and isinstance(df, pl.LazyFrame)) + return (pl := get_polars()) is not None and isinstance(df, pl.LazyFrame) def is_polars_series(ser: Any) -> TypeGuard[pl.Series]: """Check whether `ser` is a Polars Series without importing Polars.""" - return bool((pl := get_polars()) is not None and isinstance(ser, pl.Series)) + return (pl := get_polars()) is not None and isinstance(ser, pl.Series) def is_pyarrow_chunked_array(ser: Any) -> TypeGuard[pa.ChunkedArray]: """Check whether `ser` is a PyArrow ChunkedArray without importing PyArrow.""" - return bool((pa := get_pyarrow()) is not None and isinstance(ser, pa.ChunkedArray)) + return (pa := get_pyarrow()) is not None and isinstance(ser, pa.ChunkedArray) def is_pyarrow_table(df: Any) -> TypeGuard[pa.Table]: """Check whether `df` is a PyArrow Table without importing PyArrow.""" - return bool((pa := get_pyarrow()) is not None and isinstance(df, pa.Table)) + return (pa := get_pyarrow()) is not None and isinstance(df, pa.Table) def is_numpy_array(arr: Any) -> TypeGuard[np.ndarray]: """Check whether `arr` is a NumPy Array without importing NumPy.""" - return bool((np := get_numpy()) is not None and isinstance(arr, np.ndarray)) + return (np := get_numpy()) is not None and isinstance(arr, np.ndarray) def is_pandas_like_dataframe(df: Any) -> bool: diff --git a/tests/frame/arrow_c_stream_test.py b/tests/frame/arrow_c_stream_test.py index 7a3403f69..cb856adf9 100644 --- a/tests/frame/arrow_c_stream_test.py +++ b/tests/frame/arrow_c_stream_test.py @@ -10,6 +10,9 @@ @pytest.mark.skipif( parse_version(pl.__version__) < (1, 3), reason="too old for pycapsule in Polars" ) +@pytest.mark.skipif( + parse_version(pa.__version__) < (16, 0, 0), reason="too old for pycapsule in PyArrow" +) def test_arrow_c_stream_test() -> None: df = nw.from_native(pl.Series([1, 2, 3]).to_frame("a"), eager_only=True) result = pa.table(df) @@ -20,6 +23,9 @@ def test_arrow_c_stream_test() -> None: @pytest.mark.skipif( parse_version(pl.__version__) < (1, 3), reason="too old for pycapsule in Polars" ) +@pytest.mark.skipif( + parse_version(pa.__version__) < (16, 0, 0), reason="too old for pycapsule in PyArrow" +) def test_arrow_c_stream_test_invalid(monkeypatch: pytest.MonkeyPatch) -> None: # "poison" the dunder method to make sure it actually got called above monkeypatch.setattr( @@ -33,6 +39,9 @@ def test_arrow_c_stream_test_invalid(monkeypatch: pytest.MonkeyPatch) -> None: @pytest.mark.skipif( parse_version(pl.__version__) < (1, 3), reason="too old for pycapsule in Polars" ) +@pytest.mark.skipif( + parse_version(pa.__version__) < (16, 0, 0), reason="too old for pycapsule in PyArrow" +) def test_arrow_c_stream_test_fallback(monkeypatch: pytest.MonkeyPatch) -> None: # Check that fallback to PyArrow works monkeypatch.delattr("polars.DataFrame.__arrow_c_stream__") diff --git a/tests/frame/interchange_schema_test.py b/tests/frame/interchange_schema_test.py index a73ca6259..afec06831 100644 --- a/tests/frame/interchange_schema_test.py +++ b/tests/frame/interchange_schema_test.py @@ -8,6 +8,7 @@ import pytest import narwhals.stable.v1 as nw +from narwhals.utils import parse_version def test_interchange_schema() -> None: @@ -67,7 +68,10 @@ def test_interchange_schema() -> None: assert df["a"].dtype == nw.Int64 -def test_interchange_schema_ibis() -> None: # pragma: no cover +@pytest.mark.filterwarnings("ignore:.*locale specific date formats") +def test_interchange_schema_ibis( + tmpdir: pytest.TempdirFactory, +) -> None: # pragma: no cover ibis = pytest.importorskip("ibis") df_pl = pl.DataFrame( { @@ -105,26 +109,49 @@ def test_interchange_schema_ibis() -> None: # pragma: no cover "o": pl.Boolean, }, ) - tbl = ibis.memtable(df_pl) + filepath = str(tmpdir / "file.parquet") # type: ignore[operator] + df_pl.write_parquet(filepath) + tbl = ibis.read_parquet(filepath) df = nw.from_native(tbl, eager_or_interchange_only=True) result = df.schema - expected = { - "a": nw.Int64, - "b": nw.Int32, - "c": nw.Int16, - "d": nw.Int8, - "e": nw.UInt64, - "f": nw.UInt32, - "g": nw.UInt16, - "h": nw.UInt8, - "i": nw.Float64, - "j": nw.Float32, - "k": nw.String, - "l": nw.String, - "m": nw.Date, - "n": nw.Datetime, - "o": nw.Boolean, - } + if parse_version(ibis.__version__) > (6, 0, 0): + expected = { + "a": nw.Int64, + "b": nw.Int32, + "c": nw.Int16, + "d": nw.Int8, + "e": nw.UInt64, + "f": nw.UInt32, + "g": nw.UInt16, + "h": nw.UInt8, + "i": nw.Float64, + "j": nw.Float32, + "k": nw.String, + "l": nw.String, + "m": nw.Date, + "n": nw.Datetime, + "o": nw.Boolean, + } + else: + # Old versions of Ibis would read the file in + # with different data types + expected = { + "a": nw.Int64, + "b": nw.Int32, + "c": nw.Int16, + "d": nw.Int32, + "e": nw.Int32, + "f": nw.Int32, + "g": nw.Int32, + "h": nw.Int32, + "i": nw.Float64, + "j": nw.Float64, + "k": nw.String, + "l": nw.String, + "m": nw.Date, + "n": nw.Datetime, + "o": nw.Boolean, + } assert result == expected assert df["a"].dtype == nw.Int64 diff --git a/tests/series_only/arrow_c_stream_test.py b/tests/series_only/arrow_c_stream_test.py index 9964d7408..9d2ebc8d0 100644 --- a/tests/series_only/arrow_c_stream_test.py +++ b/tests/series_only/arrow_c_stream_test.py @@ -10,6 +10,9 @@ @pytest.mark.skipif( parse_version(pl.__version__) < (1, 3), reason="too old for pycapsule in Polars" ) +@pytest.mark.skipif( + parse_version(pa.__version__) < (16, 0, 0), reason="too old for pycapsule in PyArrow" +) def test_arrow_c_stream_test() -> None: s = nw.from_native(pl.Series([1, 2, 3]), series_only=True) result = pa.chunked_array(s) @@ -20,6 +23,9 @@ def test_arrow_c_stream_test() -> None: @pytest.mark.skipif( parse_version(pl.__version__) < (1, 3), reason="too old for pycapsule in Polars" ) +@pytest.mark.skipif( + parse_version(pa.__version__) < (16, 0, 0), reason="too old for pycapsule in PyArrow" +) def test_arrow_c_stream_test_invalid(monkeypatch: pytest.MonkeyPatch) -> None: # "poison" the dunder method to make sure it actually got called above monkeypatch.setattr("narwhals.series.Series.__arrow_c_stream__", lambda *_: 1 / 0) @@ -31,6 +37,9 @@ def test_arrow_c_stream_test_invalid(monkeypatch: pytest.MonkeyPatch) -> None: @pytest.mark.skipif( parse_version(pl.__version__) < (1, 3), reason="too old for pycapsule in Polars" ) +@pytest.mark.skipif( + parse_version(pa.__version__) < (16, 0, 0), reason="too old for pycapsule in PyArrow" +) def test_arrow_c_stream_test_fallback(monkeypatch: pytest.MonkeyPatch) -> None: # Check that fallback to PyArrow works monkeypatch.delattr("polars.Series.__arrow_c_stream__")