Repository: narwhals-dev/narwhals
Pull request #2216 (2025-03-15 13:54:44)

chore: Deprecate `native_namespace` in favour of `backend` in `read_parquet`
## What type of PR is this? (check all applicable)

- [ ] 💾 Refactor
- [ ] ✨ Feature
- [ ] 🐛 Bug Fix
- [ ] 🔧 Optimization
- [ ] 📝 Documentation
- [ ] ✅ Test
- [x] 🐳 Other

## Related issues

- Related issue #1888

## Checklist

- [x] Code follows style guide (ruff)
- [x] Tests added
- [x] Documented the changes

## If you have comments or can explain your changes, please do so below



Patch:
diff --git a/narwhals/functions.py b/narwhals/functions.py
index e399233b..9157026c 100644
--- a/narwhals/functions.py
+++ b/narwhals/functions.py
@@ -445,11 +445,13 @@ def _from_dict_impl(
     return from_native(native_frame, eager_only=True)
 
 
+@deprecate_native_namespace(warn_version="1.31.0", required=True)
 def from_numpy(
     data: _2DArray,
     schema: Mapping[str, DType] | Schema | Sequence[str] | None = None,
     *,
-    native_namespace: ModuleType,
+    backend: ModuleType | Implementation | str | None = None,
+    native_namespace: ModuleType | None = None,  # noqa: ARG001
 ) -> DataFrame[Any]:
     """Construct a DataFrame from a NumPy ndarray.
 
@@ -462,8 +464,21 @@ def from_numpy(
     Arguments:
         data: Two-dimensional data represented as a NumPy ndarray.
         schema: The DataFrame schema as Schema, dict of {name: type}, or a sequence of str.
+        backend: specifies which eager backend instantiate to.
+
+            `backend` can be specified in various ways:
+
+            - As `Implementation.<BACKEND>` with `BACKEND` being `PANDAS`, `PYARROW`,
+                `POLARS`, `MODIN` or `CUDF`.
+            - As a string: `"pandas"`, `"pyarrow"`, `"polars"`, `"modin"` or `"cudf"`.
+            - Directly as a module `pandas`, `pyarrow`, `polars`, `modin` or `cudf`.
         native_namespace: The native library to use for DataFrame creation.
 
+            **Deprecated** (v1.31.0):
+                Please use `backend` instead. Note that `native_namespace` is still available
+                (and won't emit a deprecation warning) if you use `narwhals.stable.v1`,
+                see [perfect backwards compatibility policy](../backcompat.md/).
+
     Returns:
         A new DataFrame.
 
@@ -474,7 +489,7 @@ def from_numpy(
         >>>
         >>> arr = np.array([[5, 2, 1], [1, 4, 3]])
         >>> schema = {"c": nw.Int16(), "d": nw.Float32(), "e": nw.Int8()}
-        >>> nw.from_numpy(arr, schema=schema, native_namespace=pa)
+        >>> nw.from_numpy(arr, schema=schema, backend="pyarrow")
         ┌──────────────────┐
         |Narwhals DataFrame|
         |------------------|
@@ -488,17 +503,21 @@ def from_numpy(
         |  e: [[1,3]]      |
         └──────────────────┘
     """
-    return _from_numpy_impl(data, schema, native_namespace=native_namespace)
+    backend = cast("ModuleType | Implementation | str", backend)
+    return _from_numpy_impl(data, schema, backend=backend)
 
 
 def _from_numpy_impl(
     data: _2DArray,
     schema: Mapping[str, DType] | Schema | Sequence[str] | None = None,
     *,
-    native_namespace: ModuleType,
+    backend: ModuleType | Implementation | str,
 ) -> DataFrame[Any]:
     from narwhals.schema import Schema
 
+    implementation = Implementation.from_backend(backend)
+    native_namespace = implementation.to_native_namespace()
+
     if not is_numpy_array_2d(data):
         msg = "`from_numpy` only accepts 2D numpy arrays"
         raise ValueError(msg)
@@ -886,17 +905,31 @@ def _scan_csv_impl(
     return from_native(native_frame).lazy()
 
 
+@deprecate_native_namespace(warn_version="1.31.0", required=True)
 def read_parquet(
     source: str,
     *,
-    native_namespace: ModuleType,
+    backend: ModuleType | Implementation | str | None = None,
+    native_namespace: ModuleType | None = None,  # noqa: ARG001
     **kwargs: Any,
 ) -> DataFrame[Any]:
     """Read into a DataFrame from a parquet file.
 
     Arguments:
         source: Path to a file.
+        backend: The eager backend for DataFrame creation.
+            `backend` can be specified in various ways:
+
+            - As `Implementation.<BACKEND>` with `BACKEND` being `PANDAS`, `PYARROW`,
+                `POLARS`, `MODIN` or `CUDF`.
+            - As a string: `"pandas"`, `"pyarrow"`, `"polars"`, `"modin"` or `"cudf"`.
+            - Directly as a module `pandas`, `pyarrow`, `polars`, `modin` or `cudf`.
         native_namespace: The native library to use for DataFrame creation.
+
+            **Deprecated** (v1.31.0):
+                Please use `backend` instead. Note that `native_namespace` is still available
+                (and won't emit a deprecation warning) if you use `narwhals.stable.v1`,
+                see [perfect backwards compatibility policy](../backcompat.md/).
         kwargs: Extra keyword arguments which are passed to the native parquet reader.
             For example, you could use
             `nw.read_parquet('file.parquet', native_namespace=pd, engine='pyarrow')`.
@@ -908,7 +941,7 @@ def read_parquet(
         >>> import pyarrow as pa
         >>> import narwhals as nw
         >>>
-        >>> nw.read_parquet("file.parquet", native_namespace=pa)  # doctest:+SKIP
+        >>> nw.read_parquet("file.parquet", backend="pyarrow")  # doctest:+SKIP
         ┌──────────────────┐
         |Narwhals DataFrame|
         |------------------|
@@ -920,13 +953,15 @@ def read_parquet(
         |c: [[0.2,0.1]]    |
         └──────────────────┘
     """
-    return _read_parquet_impl(source, native_namespace=native_namespace, **kwargs)
+    backend = cast("ModuleType | Implementation | str", backend)
+    return _read_parquet_impl(source, backend=backend, **kwargs)
 
 
 def _read_parquet_impl(
-    source: str, *, native_namespace: ModuleType, **kwargs: Any
+    source: str, *, backend: ModuleType | Implementation | str, **kwargs: Any
 ) -> DataFrame[Any]:
-    implementation = Implementation.from_native_namespace(native_namespace)
+    implementation = Implementation.from_backend(backend)
+    native_namespace = implementation.to_native_namespace()
     native_frame: NativeFrame
     if implementation in {
         Implementation.POLARS,
diff --git a/narwhals/stable/v1/__init__.py b/narwhals/stable/v1/__init__.py
index 0a04ee63..00dcc7c1 100644
--- a/narwhals/stable/v1/__init__.py
+++ b/narwhals/stable/v1/__init__.py
@@ -2280,11 +2280,13 @@ def from_dict(
     )
 
 
+@deprecate_native_namespace(required=True)
 def from_numpy(
     data: _2DArray,
     schema: Mapping[str, DType] | Schema | Sequence[str] | None = None,
     *,
-    native_namespace: ModuleType,
+    backend: ModuleType | Implementation | str | None = None,
+    native_namespace: ModuleType | None = None,  # noqa: ARG001
 ) -> DataFrame[Any]:
     """Construct a DataFrame from a NumPy ndarray.
 
@@ -2297,12 +2299,26 @@ def from_numpy(
     Arguments:
         data: Two-dimensional data represented as a NumPy ndarray.
         schema: The DataFrame schema as Schema, dict of {name: type}, or a sequence of str.
+        backend: specifies which eager backend instantiate to.
+
+            `backend` can be specified in various ways:
+
+            - As `Implementation.<BACKEND>` with `BACKEND` being `PANDAS`, `PYARROW`,
+                `POLARS`, `MODIN` or `CUDF`.
+            - As a string: `"pandas"`, `"pyarrow"`, `"polars"`, `"modin"` or `"cudf"`.
+            - Directly as a module `pandas`, `pyarrow`, `polars`, `modin` or `cudf`.
         native_namespace: The native library to use for DataFrame creation.
 
+            **Deprecated** (v1.31.0):
+                Please use `backend` instead. Note that `native_namespace` is still available
+                (and won't emit a deprecation warning) if you use `narwhals.stable.v1`,
+                see [perfect backwards compatibility policy](../backcompat.md/).
+
     Returns:
         A new DataFrame.
     """
-    return _stableify(_from_numpy_impl(data, schema, native_namespace=native_namespace))  # type: ignore[no-any-return]
+    backend = cast("ModuleType | Implementation | str", backend)
+    return _stableify(_from_numpy_impl(data, schema, backend=backend))  # type: ignore[no-any-return]
 
 
 @deprecate_native_namespace(required=True)
@@ -2366,14 +2382,31 @@ def scan_csv(
     )
 
 
+@deprecate_native_namespace(required=True)
 def read_parquet(
-    source: str, *, native_namespace: ModuleType, **kwargs: Any
+    source: str,
+    *,
+    backend: ModuleType | Implementation | str | None = None,
+    native_namespace: ModuleType | None = None,  # noqa: ARG001
+    **kwargs: Any,
 ) -> DataFrame[Any]:
     """Read into a DataFrame from a parquet file.
 
     Arguments:
         source: Path to a file.
+        backend: The eager backend for DataFrame creation.
+            `backend` can be specified in various ways:
+
+            - As `Implementation.<BACKEND>` with `BACKEND` being `PANDAS`, `PYARROW`,
+                `POLARS`, `MODIN` or `CUDF`.
+            - As a string: `"pandas"`, `"pyarrow"`, `"polars"`, `"modin"` or `"cudf"`.
+            - Directly as a module `pandas`, `pyarrow`, `polars`, `modin` or `cudf`.
         native_namespace: The native library to use for DataFrame creation.
+
+            **Deprecated** (v1.31.0):
+                Please use `backend` instead. Note that `native_namespace` is still available
+                (and won't emit a deprecation warning) if you use `narwhals.stable.v1`,
+                see [perfect backwards compatibility policy](../backcompat.md/).
         kwargs: Extra keyword arguments which are passed to the native parquet reader.
             For example, you could use
             `nw.read_parquet('file.parquet', native_namespace=pd, engine='pyarrow')`.
@@ -2381,8 +2414,9 @@ def read_parquet(
     Returns:
         DataFrame.
     """
+    backend = cast("ModuleType | Implementation | str", backend)
     return _stableify(  # type: ignore[no-any-return]
-        _read_parquet_impl(source, native_namespace=native_namespace, **kwargs)
+        _read_parquet_impl(source, backend=backend, **kwargs)
     )
 
 


Test patch:
diff --git a/tests/from_numpy_test.py b/tests/from_numpy_test.py
index 62ac0930..3d23cef9 100644
--- a/tests/from_numpy_test.py
+++ b/tests/from_numpy_test.py
@@ -1,5 +1,8 @@
 from __future__ import annotations
 
+from typing import TYPE_CHECKING
+from typing import cast
+
 import numpy as np
 import pytest
 
@@ -8,8 +11,11 @@ import narwhals.stable.v1 as nw_v1
 from tests.utils import Constructor
 from tests.utils import assert_equal_data
 
+if TYPE_CHECKING:
+    from narwhals.typing import _2DArray
+
 data = {"a": [1, 2, 3], "b": [4, 5, 6]}
-arr = np.array([[5, 2, 0, 1], [1, 4, 7, 8], [1, 2, 3, 9]])
+arr: _2DArray = cast("_2DArray", np.array([[5, 2, 0, 1], [1, 4, 7, 8], [1, 2, 3, 9]]))
 expected = {
     "column_0": [5, 1, 1],
     "column_1": [2, 4, 2],
@@ -22,8 +28,8 @@ def test_from_numpy(constructor: Constructor, request: pytest.FixtureRequest) ->
     if "dask" in str(constructor) or "pyspark" in str(constructor):
         request.applymarker(pytest.mark.xfail)
     df = nw.from_native(constructor(data))
-    native_namespace = nw.get_native_namespace(df)
-    result = nw.from_numpy(arr, native_namespace=native_namespace)  # pyright: ignore[reportArgumentType]
+    backend = nw.get_native_namespace(df)
+    result = nw.from_numpy(arr, backend=backend)
     assert_equal_data(result, expected)
     assert isinstance(result, nw.DataFrame)
 
@@ -40,12 +46,8 @@ def test_from_numpy_schema_dict(
         "f": nw_v1.Float64(),
     }
     df = nw_v1.from_native(constructor(data))
-    native_namespace = nw_v1.get_native_namespace(df)
-    result = nw_v1.from_numpy(
-        arr,  # pyright: ignore[reportArgumentType]
-        native_namespace=native_namespace,
-        schema=schema,
-    )
+    backend = nw_v1.get_native_namespace(df)
+    result = nw_v1.from_numpy(arr, backend=backend, schema=schema)
     assert result.collect_schema() == schema
 
 
@@ -56,12 +58,8 @@ def test_from_numpy_schema_list(
         request.applymarker(pytest.mark.xfail)
     schema = ["c", "d", "e", "f"]
     df = nw_v1.from_native(constructor(data))
-    native_namespace = nw_v1.get_native_namespace(df)
-    result = nw_v1.from_numpy(
-        arr,  # pyright: ignore[reportArgumentType]
-        native_namespace=native_namespace,
-        schema=schema,
-    )
+    backend = nw_v1.get_native_namespace(df)
+    result = nw_v1.from_numpy(arr, backend=backend, schema=schema)
     assert result.columns == schema
 
 
@@ -71,25 +69,25 @@ def test_from_numpy_schema_notvalid(
     if "dask" in str(constructor) or "pyspark" in str(constructor):
         request.applymarker(pytest.mark.xfail)
     df = nw.from_native(constructor(data))
-    native_namespace = nw_v1.get_native_namespace(df)
+    backend = nw_v1.get_native_namespace(df)
     with pytest.raises(
         TypeError, match="`schema` is expected to be one of the following types"
     ):
-        nw.from_numpy(arr, schema="a", native_namespace=native_namespace)  # pyright: ignore[reportArgumentType]
+        nw.from_numpy(arr, schema=5, backend=backend)  # type: ignore[arg-type]
 
 
 def test_from_numpy_v1(constructor: Constructor, request: pytest.FixtureRequest) -> None:
     if "dask" in str(constructor) or "pyspark" in str(constructor):
         request.applymarker(pytest.mark.xfail)
     df = nw_v1.from_native(constructor(data))
-    native_namespace = nw_v1.get_native_namespace(df)
-    result = nw_v1.from_numpy(arr, native_namespace=native_namespace)  # pyright: ignore[reportArgumentType]
+    backend = nw_v1.get_native_namespace(df)
+    result = nw_v1.from_numpy(arr, backend=backend)
     assert_equal_data(result, expected)
     assert isinstance(result, nw_v1.DataFrame)
 
 
 def test_from_numpy_not2d(constructor: Constructor) -> None:
     df = nw.from_native(constructor(data))
-    native_namespace = nw_v1.get_native_namespace(df)
+    backend = nw_v1.get_native_namespace(df)
     with pytest.raises(ValueError, match="`from_numpy` only accepts 2D numpy arrays"):
-        nw.from_numpy(np.array([0]), native_namespace=native_namespace)  # pyright: ignore[reportArgumentType]
+        nw.from_numpy(np.array([0]), backend=backend)  # pyright: ignore[reportArgumentType]
diff --git a/tests/read_scan_test.py b/tests/read_scan_test.py
index 1f72250b..0a6481e0 100644
--- a/tests/read_scan_test.py
+++ b/tests/read_scan_test.py
@@ -118,8 +118,8 @@ def test_read_parquet(
     filepath = str(tmpdir / "file.parquet")  # type: ignore[operator]
     df_pl.write_parquet(filepath)
     df = nw.from_native(constructor_eager(data))
-    native_namespace = nw.get_native_namespace(df)
-    result = nw.read_parquet(filepath, native_namespace=native_namespace)
+    backend = nw.get_native_namespace(df)
+    result = nw.read_parquet(filepath, backend=backend)
     assert_equal_data(result, data)
     assert isinstance(result, nw.DataFrame)
 
@@ -132,8 +132,8 @@ def test_read_parquet_v1(
     filepath = str(tmpdir / "file.parquet")  # type: ignore[operator]
     df_pl.write_parquet(filepath)
     df = nw_v1.from_native(constructor_eager(data))
-    native_namespace = nw_v1.get_native_namespace(df)
-    result = nw_v1.read_parquet(filepath, native_namespace=native_namespace)
+    backend = nw_v1.get_native_namespace(df)
+    result = nw_v1.read_parquet(filepath, backend=backend)
     assert_equal_data(result, data)
     assert isinstance(result, nw_v1.DataFrame)
 
@@ -143,7 +143,7 @@ def test_read_parquet_kwargs(tmpdir: pytest.TempdirFactory) -> None:
     df_pl = pl.DataFrame(data)
     filepath = str(tmpdir / "file.parquet")  # type: ignore[operator]
     df_pl.write_parquet(filepath)
-    result = nw.read_parquet(filepath, native_namespace=pd, engine="pyarrow")
+    result = nw.read_parquet(filepath, backend=pd, engine="pyarrow")
     assert_equal_data(result, data)