Repository: narwhals-dev/narwhals
Pull request #2215 (2025-03-15 13:32:42)

chore: Deprecate `native_namespace` in favour of `backend` in `from_numpy`
## 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..3d057eef 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)
diff --git a/narwhals/stable/v1/__init__.py b/narwhals/stable/v1/__init__.py
index 0a04ee63..8a636ae2 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)


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]