Repository: narwhals-dev/narwhals

Issue:
[Enh]: Add support for DataFrame.write_parquet
### We would like to learn about your use case. For example, if this feature is needed  to adopt Narwhals in an open source project, could you please enter the link to it below?


_No response_

### Please describe the purpose of the new feature or describe the problem to solve.

I have a library which lets users ingest a pandas dataframe to a datalake. The dataframe provided to the lib by the user is converted to a parquet file (with pyarrow) before putting it in an ETL pipeline. I'd like to let users ingest polars dataframes as well via the same library and would like to use narwhals for the implementation, if possible.

### Suggest a solution if possible.

For polars, `nw.DataFrame.write_parquet` would be the same as `pl.DataFrame.write_parquet`.
For pandas, `nw.DataFrame.write_parquet` would be `pd.DataFrame.to_parquet`.

### If you have tried alternatives, please describe them below.

_No response_

### Would you want to open a pull request?

no

### Additional information that may help us understand your needs.

_No response_

Discussion:

Patch:
diff --git a/docs/api-reference/dataframe.md b/docs/api-reference/dataframe.md
index bb4f0f2d..a7f518d2 100644
--- a/docs/api-reference/dataframe.md
+++ b/docs/api-reference/dataframe.md
@@ -28,5 +28,6 @@
         - unique
         - with_columns
         - with_row_index
+        - write_parquet
       show_source: false
       show_bases: false
diff --git a/narwhals/_pandas_like/dataframe.py b/narwhals/_pandas_like/dataframe.py
index 73ba692b..f481b0c3 100644
--- a/narwhals/_pandas_like/dataframe.py
+++ b/narwhals/_pandas_like/dataframe.py
@@ -286,6 +286,9 @@ class PandasDataFrame:
             return self._dataframe._to_pandas()
         return self._dataframe.to_pandas()  # pragma: no cover
 
+    def write_parquet(self, file: Any) -> Any:
+        self._dataframe.to_parquet(file)
+
     # --- descriptive ---
     def is_duplicated(self: Self) -> PandasSeries:
         from narwhals._pandas_like.series import PandasSeries
diff --git a/narwhals/dataframe.py b/narwhals/dataframe.py
index d94eb9d6..91bc76ba 100644
--- a/narwhals/dataframe.py
+++ b/narwhals/dataframe.py
@@ -18,6 +18,9 @@ from narwhals.utils import parse_version
 from narwhals.utils import validate_same_library
 
 if TYPE_CHECKING:
+    from io import BytesIO
+    from pathlib import Path
+
     import numpy as np
     from typing_extensions import Self
 
@@ -277,6 +280,33 @@ class DataFrame(BaseFrame):
         """
         return self._dataframe.to_pandas()
 
+    def write_parquet(self, file: str | Path | BytesIO) -> Any:
+        """
+        Write dataframe to parquet file.
+
+        Examples:
+            Construct pandas and Polars DataFrames:
+
+            >>> import pandas as pd
+            >>> import polars as pl
+            >>> import narwhals as nw
+            >>> df = {"foo": [1, 2, 3], "bar": [6.0, 7.0, 8.0], "ham": ["a", "b", "c"]}
+            >>> df_pd = pd.DataFrame(df)
+            >>> df_pl = pl.DataFrame(df)
+
+            We define a library agnostic function:
+
+            >>> def func(df_any):
+            ...     df = nw.from_native(df_any)
+            ...     df.write_parquet('foo.parquet')
+
+            We can then pass either pandas or Polars to `func`:
+
+            >>> func(df_pd)  # doctest:+SKIP
+            >>> func(df_pl)  # doctest:+SKIP
+        """
+        self._dataframe.write_parquet(file)
+
     def to_numpy(self) -> Any:
         """
         Convert this DataFrame to a NumPy ndarray.


Test patch:
diff --git a/tests/frame/write_parquet_test.py b/tests/frame/write_parquet_test.py
new file mode 100644
index 00000000..7d4a71f3
--- /dev/null
+++ b/tests/frame/write_parquet_test.py
@@ -0,0 +1,19 @@
+from __future__ import annotations
+
+import os
+from typing import Any
+
+import pandas as pd
+import polars as pl
+import pytest
+
+import narwhals as nw
+
+data = {"a": [1, 2, 3]}
+
+
+@pytest.mark.parametrize("constructor", [pd.DataFrame, pl.DataFrame])
+def test_write_parquet(constructor: Any, tmpdir: pytest.TempdirFactory) -> None:
+    path = str(tmpdir / "foo.parquet")  # type: ignore[operator]
+    nw.from_native(constructor(data), eager_only=True).write_parquet(path)
+    assert os.path.exists(path)