diff --git a/narwhals/_duckdb/dataframe.py b/narwhals/_duckdb/dataframe.py index 82ac6d41b..5bd7af153 100644 --- a/narwhals/_duckdb/dataframe.py +++ b/narwhals/_duckdb/dataframe.py @@ -90,6 +90,22 @@ def __getitem__(self, item: str) -> DuckDBInterchangeSeries: self._native_frame.select(item), dtypes=self._dtypes ) + def select( + self: Self, + *exprs: Any, + **named_exprs: Any, + ) -> Self: + if named_exprs or not all(isinstance(x, str) for x in exprs): # pragma: no cover + msg = ( + "`select`-ing not by name is not supported for DuckDB backend.\n\n" + "If you would like to see this kind of object better supported in " + "Narwhals, please open a feature request " + "at https://github.com/narwhals-dev/narwhals/issues." + ) + raise NotImplementedError(msg) + + return self._from_native_frame(self._native_frame.select(*exprs)) + def __getattr__(self, attr: str) -> Any: if attr == "schema": return { @@ -120,3 +136,6 @@ def to_pandas(self: Self) -> pd.DataFrame: def to_arrow(self: Self) -> pa.Table: return self._native_frame.arrow() + + def _from_native_frame(self: Self, df: Any) -> Self: + return self.__class__(df, dtypes=self._dtypes) diff --git a/narwhals/_ibis/dataframe.py b/narwhals/_ibis/dataframe.py index a9c3a49fa..c8a665db0 100644 --- a/narwhals/_ibis/dataframe.py +++ b/narwhals/_ibis/dataframe.py @@ -85,6 +85,24 @@ def to_pandas(self: Self) -> pd.DataFrame: def to_arrow(self: Self) -> pa.Table: return self._native_frame.to_pyarrow() + def select( + self: Self, + *exprs: Any, + **named_exprs: Any, + ) -> Self: + if named_exprs or not all(isinstance(x, str) for x in exprs): # pragma: no cover + msg = ( + "`select`-ing not by name is not supported for Ibis backend.\n\n" + "If you would like to see this kind of object better supported in " + "Narwhals, please open a feature request " + "at https://github.com/narwhals-dev/narwhals/issues." + ) + raise NotImplementedError(msg) + + import ibis.selectors as s + + return self._from_native_frame(self._native_frame.select(s.cols(*exprs))) + def __getattr__(self, attr: str) -> Any: if attr == "schema": return { @@ -98,3 +116,6 @@ def __getattr__(self, attr: str) -> Any: "at https://github.com/narwhals-dev/narwhals/issues." ) raise NotImplementedError(msg) + + def _from_native_frame(self: Self, df: Any) -> Self: + return self.__class__(df, dtypes=self._dtypes) diff --git a/tests/frame/interchange_select_test.py b/tests/frame/interchange_select_test.py new file mode 100644 index 000000000..e124735f7 --- /dev/null +++ b/tests/frame/interchange_select_test.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +import duckdb +import polars as pl +import pytest + +import narwhals.stable.v1 as nw + +data = {"a": [1, 2, 3], "b": [4.0, 5.0, 6.1], "z": ["x", "y", "z"]} + + +def test_interchange() -> None: + df_pl = pl.DataFrame(data) + df = nw.from_native(df_pl.__dataframe__(), eager_or_interchange_only=True) + with pytest.raises( + NotImplementedError, + match="Attribute select is not supported for metadata-only dataframes", + ): + df.select("a", "z") + + +def test_interchange_ibis( + tmpdir: pytest.TempdirFactory, +) -> None: # pragma: no cover + ibis = pytest.importorskip("ibis") + df_pl = pl.DataFrame(data) + + 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) + + out_cols = df.select("a", "z").schema.names() + + assert out_cols == ["a", "z"] + + +def test_interchange_duckdb() -> None: + df_pl = pl.DataFrame(data) # noqa: F841 + rel = duckdb.sql("select * from df_pl") + df = nw.from_native(rel, eager_or_interchange_only=True) + + out_cols = df.select("a", "z").schema.names() + + assert out_cols == ["a", "z"]