Repository: narwhals-dev/narwhals
Pull request #2063 (2025-02-21 16:40:26)

enh: Deprecate `native_namespace` in favour of `backend` in `read_csv`
<!--
# Thanks for contributing a pull request! 
## Please make sure you see our contribution guidelines: https://github.com/narwhals-dev/narwhals/blob/main/CONTRIBUTING.md
-->

## What type of PR is this? (check all applicable)

- [ ] 💾 Refactor
- [x] ✨ Feature
- [ ] 🐛 Bug Fix
- [ ] 🔧 Optimization
- [ ] 📝 Documentation
- [ ] ✅ Test
- [ ] 🐳 Other

## Related issues

- Related issue #1888
- Closes #\<issue number\>

## Checklist

- [x] Code follows style guide (ruff)
- [ ] Tests added
- [ ] Documented the changes

## If you have comments or can explain your changes, please do so below



Patch:
diff --git a/narwhals/_dask/expr.py b/narwhals/_dask/expr.py
index c45acfb2..a0af9e64 100644
--- a/narwhals/_dask/expr.py
+++ b/narwhals/_dask/expr.py
@@ -48,7 +48,9 @@ class DaskExpr(CompliantExpr["dx.Series"]):
         alias_output_names: Callable[[Sequence[str]], Sequence[str]] | None,
         backend_version: tuple[int, ...],
         version: Version,
-        kwargs: dict[str, Any],
+        # Kwargs with metadata which we may need in group-by agg
+        # (e.g. `ddof` for `std` and `var`).
+        call_kwargs: dict[str, Any] | None = None,
     ) -> None:
         self._call = call
         self._depth = depth
@@ -57,7 +59,7 @@ class DaskExpr(CompliantExpr["dx.Series"]):
         self._alias_output_names = alias_output_names
         self._backend_version = backend_version
         self._version = version
-        self._kwargs = kwargs
+        self._call_kwargs = call_kwargs or {}
 
     def __call__(self: Self, df: DaskLazyFrame) -> Sequence[dx.Series]:
         return self._call(df)
@@ -82,7 +84,7 @@ class DaskExpr(CompliantExpr["dx.Series"]):
             alias_output_names=self._alias_output_names,
             backend_version=self._backend_version,
             version=self._version,
-            kwargs=self._kwargs,
+            call_kwargs=self._call_kwargs,
         )
 
     @classmethod
@@ -110,7 +112,6 @@ class DaskExpr(CompliantExpr["dx.Series"]):
             alias_output_names=None,
             backend_version=backend_version,
             version=version,
-            kwargs={},
         )
 
     @classmethod
@@ -133,7 +134,6 @@ class DaskExpr(CompliantExpr["dx.Series"]):
             alias_output_names=None,
             backend_version=backend_version,
             version=version,
-            kwargs={},
         )
 
     def _from_call(
@@ -141,6 +141,7 @@ class DaskExpr(CompliantExpr["dx.Series"]):
         # First argument to `call` should be `dx.Series`
         call: Callable[..., dx.Series],
         expr_name: str,
+        call_kwargs: dict[str, Any] | None = None,
         **expressifiable_args: Self | Any,
     ) -> Self:
         def func(df: DaskLazyFrame) -> list[dx.Series]:
@@ -163,7 +164,7 @@ class DaskExpr(CompliantExpr["dx.Series"]):
             alias_output_names=self._alias_output_names,
             backend_version=self._backend_version,
             version=self._version,
-            kwargs={**self._kwargs, **expressifiable_args},
+            call_kwargs=call_kwargs,
         )
 
     def alias(self: Self, name: str) -> Self:
@@ -181,7 +182,7 @@ class DaskExpr(CompliantExpr["dx.Series"]):
             alias_output_names=alias_output_names,
             backend_version=self._backend_version,
             version=self._version,
-            kwargs={**self._kwargs, "name": name},
+            call_kwargs=self._call_kwargs,
         )
 
     def __add__(self: Self, other: Any) -> Self:
@@ -310,12 +311,16 @@ class DaskExpr(CompliantExpr["dx.Series"]):
 
     def std(self: Self, ddof: int) -> Self:
         return self._from_call(
-            lambda _input, ddof: _input.std(ddof=ddof).to_series(), "std", ddof=ddof
+            lambda _input: _input.std(ddof=ddof).to_series(),
+            "std",
+            call_kwargs={"ddof": ddof},
         )
 
     def var(self: Self, ddof: int) -> Self:
         return self._from_call(
-            lambda _input, ddof: _input.var(ddof=ddof).to_series(), "var", ddof=ddof
+            lambda _input: _input.var(ddof=ddof).to_series(),
+            "var",
+            call_kwargs={"ddof": ddof},
         )
 
     def skew(self: Self) -> Self:
@@ -563,7 +568,7 @@ class DaskExpr(CompliantExpr["dx.Series"]):
             alias_output_names=self._alias_output_names,
             backend_version=self._backend_version,
             version=self._version,
-            kwargs={**self._kwargs, "keys": keys},
+            call_kwargs=self._call_kwargs,
         )
 
     def cast(self: Self, dtype: DType | type[DType]) -> Self:
diff --git a/narwhals/_dask/expr_name.py b/narwhals/_dask/expr_name.py
index da1c3a73..40d6d5e6 100644
--- a/narwhals/_dask/expr_name.py
+++ b/narwhals/_dask/expr_name.py
@@ -64,5 +64,5 @@ class DaskExprNameNamespace:
             alias_output_names=alias_output_names,
             backend_version=self._compliant_expr._backend_version,
             version=self._compliant_expr._version,
-            kwargs=self._compliant_expr._kwargs,
+            call_kwargs=self._compliant_expr._call_kwargs,
         )
diff --git a/narwhals/_dask/group_by.py b/narwhals/_dask/group_by.py
index f9cbb2d5..39f79145 100644
--- a/narwhals/_dask/group_by.py
+++ b/narwhals/_dask/group_by.py
@@ -47,11 +47,11 @@ def n_unique() -> dd.Aggregation:
     return dd.Aggregation(name="nunique", chunk=chunk, agg=agg)
 
 
-def var(ddof: int = 1) -> _AggFn:
+def var(ddof: int) -> _AggFn:
     return partial(_DaskGroupBy.var, ddof=ddof)
 
 
-def std(ddof: int = 1) -> _AggFn:
+def std(ddof: int) -> _AggFn:
     return partial(_DaskGroupBy.std, ddof=ddof)
 
 
@@ -145,14 +145,12 @@ def agg_dask(
 
             # e.g. agg(nw.mean('a')) # noqa: ERA001
             function_name = re.sub(r"(\w+->)", "", expr._function_name)
-            kwargs: dict[str, Any] = (
-                {"ddof": expr._kwargs["ddof"]} if function_name in {"std", "var"} else {}  # type: ignore[attr-defined]
-            )
-
             agg_function = POLARS_TO_DASK_AGGREGATIONS.get(function_name, function_name)
             # deal with n_unique case in a "lazy" mode to not depend on dask globally
             agg_function = (
-                agg_function(**kwargs) if callable(agg_function) else agg_function
+                agg_function(**expr._call_kwargs)  # type: ignore[attr-defined]
+                if callable(agg_function)
+                else agg_function
             )
 
             simple_aggregations.update(
diff --git a/narwhals/_dask/namespace.py b/narwhals/_dask/namespace.py
index a8c3e417..1ef2d7e8 100644
--- a/narwhals/_dask/namespace.py
+++ b/narwhals/_dask/namespace.py
@@ -61,7 +61,6 @@ class DaskNamespace(CompliantNamespace["dx.Series"]):
             alias_output_names=None,
             backend_version=self._backend_version,
             version=self._version,
-            kwargs={},
         )
 
     def col(self: Self, *column_names: str) -> DaskExpr:
@@ -93,7 +92,6 @@ class DaskNamespace(CompliantNamespace["dx.Series"]):
             alias_output_names=None,
             backend_version=self._backend_version,
             version=self._version,
-            kwargs={},
         )
 
     def len(self: Self) -> DaskExpr:
@@ -116,7 +114,6 @@ class DaskNamespace(CompliantNamespace["dx.Series"]):
             alias_output_names=None,
             backend_version=self._backend_version,
             version=self._version,
-            kwargs={},
         )
 
     def all_horizontal(self: Self, *exprs: DaskExpr) -> DaskExpr:
@@ -134,7 +131,6 @@ class DaskNamespace(CompliantNamespace["dx.Series"]):
             alias_output_names=combine_alias_output_names(*exprs),
             backend_version=self._backend_version,
             version=self._version,
-            kwargs={"exprs": exprs},
         )
 
     def any_horizontal(self: Self, *exprs: DaskExpr) -> DaskExpr:
@@ -152,7 +148,6 @@ class DaskNamespace(CompliantNamespace["dx.Series"]):
             alias_output_names=combine_alias_output_names(*exprs),
             backend_version=self._backend_version,
             version=self._version,
-            kwargs={"exprs": exprs},
         )
 
     def sum_horizontal(self: Self, *exprs: DaskExpr) -> DaskExpr:
@@ -170,7 +165,6 @@ class DaskNamespace(CompliantNamespace["dx.Series"]):
             alias_output_names=combine_alias_output_names(*exprs),
             backend_version=self._backend_version,
             version=self._version,
-            kwargs={"exprs": exprs},
         )
 
     def concat(
@@ -253,7 +247,6 @@ class DaskNamespace(CompliantNamespace["dx.Series"]):
             alias_output_names=combine_alias_output_names(*exprs),
             backend_version=self._backend_version,
             version=self._version,
-            kwargs={"exprs": exprs},
         )
 
     def min_horizontal(self: Self, *exprs: DaskExpr) -> DaskExpr:
@@ -272,7 +265,6 @@ class DaskNamespace(CompliantNamespace["dx.Series"]):
             alias_output_names=combine_alias_output_names(*exprs),
             backend_version=self._backend_version,
             version=self._version,
-            kwargs={"exprs": exprs},
         )
 
     def max_horizontal(self: Self, *exprs: DaskExpr) -> DaskExpr:
@@ -291,7 +283,6 @@ class DaskNamespace(CompliantNamespace["dx.Series"]):
             alias_output_names=combine_alias_output_names(*exprs),
             backend_version=self._backend_version,
             version=self._version,
-            kwargs={"exprs": exprs},
         )
 
     def when(self: Self, predicate: DaskExpr) -> DaskWhen:
@@ -342,11 +333,6 @@ class DaskNamespace(CompliantNamespace["dx.Series"]):
             alias_output_names=getattr(exprs[0], "_alias_output_names", None),
             backend_version=self._backend_version,
             version=self._version,
-            kwargs={
-                "exprs": exprs,
-                "separator": separator,
-                "ignore_nulls": ignore_nulls,
-            },
         )
 
 
@@ -401,7 +387,6 @@ class DaskWhen:
             alias_output_names=getattr(value, "_alias_output_names", None),
             backend_version=self._backend_version,
             version=self._version,
-            kwargs={"value": value},
         )
 
 
@@ -416,7 +401,7 @@ class DaskThen(DaskExpr):
         alias_output_names: Callable[[Sequence[str]], Sequence[str]] | None,
         backend_version: tuple[int, ...],
         version: Version,
-        kwargs: dict[str, Any],
+        call_kwargs: dict[str, Any] | None = None,
     ) -> None:
         self._backend_version = backend_version
         self._version = version
@@ -425,7 +410,7 @@ class DaskThen(DaskExpr):
         self._function_name = function_name
         self._evaluate_output_names = evaluate_output_names  # pyright: ignore[reportAttributeAccessIssue]
         self._alias_output_names = alias_output_names
-        self._kwargs = kwargs
+        self._call_kwargs = call_kwargs or {}
 
     def otherwise(self: Self, value: DaskExpr | Any) -> DaskExpr:
         # type ignore because we are setting the `_call` attribute to a
diff --git a/narwhals/_dask/selectors.py b/narwhals/_dask/selectors.py
index 123da121..a0e6f49b 100644
--- a/narwhals/_dask/selectors.py
+++ b/narwhals/_dask/selectors.py
@@ -147,7 +147,6 @@ class DaskSelector(DaskExpr):
             alias_output_names=self._alias_output_names,
             backend_version=self._backend_version,
             version=self._version,
-            kwargs={},
         )
 
     def __sub__(self: Self, other: DaskSelector | Any) -> DaskSelector | Any:
@@ -226,5 +225,4 @@ def selector(
         alias_output_names=None,
         backend_version=context._backend_version,
         version=context._version,
-        kwargs={},
     )
diff --git a/narwhals/_pandas_like/expr.py b/narwhals/_pandas_like/expr.py
index b725a71c..f9694c07 100644
--- a/narwhals/_pandas_like/expr.py
+++ b/narwhals/_pandas_like/expr.py
@@ -58,7 +58,7 @@ class PandasLikeExpr(CompliantExpr[PandasLikeSeries]):
         implementation: Implementation,
         backend_version: tuple[int, ...],
         version: Version,
-        kwargs: dict[str, Any],
+        kwargs: dict[str, Any] | None = None,
     ) -> None:
         self._call = call
         self._depth = depth
@@ -68,7 +68,7 @@ class PandasLikeExpr(CompliantExpr[PandasLikeSeries]):
         self._implementation = implementation
         self._backend_version = backend_version
         self._version = version
-        self._kwargs = kwargs
+        self._kwargs = kwargs or {}
 
     def __call__(self: Self, df: PandasLikeDataFrame) -> Sequence[PandasLikeSeries]:
         return self._call(df)
@@ -145,7 +145,6 @@ class PandasLikeExpr(CompliantExpr[PandasLikeSeries]):
             implementation=implementation,
             backend_version=backend_version,
             version=version,
-            kwargs={},
         )
 
     @classmethod
@@ -176,7 +175,6 @@ class PandasLikeExpr(CompliantExpr[PandasLikeSeries]):
             implementation=implementation,
             backend_version=backend_version,
             version=version,
-            kwargs={},
         )
 
     def cast(self: Self, dtype: DType | type[DType]) -> Self:
diff --git a/narwhals/_pandas_like/namespace.py b/narwhals/_pandas_like/namespace.py
index a0c8a3ac..d930630e 100644
--- a/narwhals/_pandas_like/namespace.py
+++ b/narwhals/_pandas_like/namespace.py
@@ -93,7 +93,6 @@ class PandasLikeNamespace(CompliantNamespace[PandasLikeSeries]):
             implementation=self._implementation,
             backend_version=self._backend_version,
             version=self._version,
-            kwargs={},
         )
 
     def _create_compliant_series(self: Self, value: Any) -> PandasLikeSeries:
@@ -139,7 +138,6 @@ class PandasLikeNamespace(CompliantNamespace[PandasLikeSeries]):
             implementation=self._implementation,
             backend_version=self._backend_version,
             version=self._version,
-            kwargs={},
         )
 
     def lit(self: Self, value: Any, dtype: DType | None) -> PandasLikeExpr:
@@ -165,7 +163,6 @@ class PandasLikeNamespace(CompliantNamespace[PandasLikeSeries]):
             implementation=self._implementation,
             backend_version=self._backend_version,
             version=self._version,
-            kwargs={},
         )
 
     def len(self: Self) -> PandasLikeExpr:
@@ -187,7 +184,6 @@ class PandasLikeNamespace(CompliantNamespace[PandasLikeSeries]):
             implementation=self._implementation,
             backend_version=self._backend_version,
             version=self._version,
-            kwargs={},
         )
 
     # --- horizontal ---
diff --git a/narwhals/_pandas_like/selectors.py b/narwhals/_pandas_like/selectors.py
index 4b7a2ef2..9e4c93ec 100644
--- a/narwhals/_pandas_like/selectors.py
+++ b/narwhals/_pandas_like/selectors.py
@@ -218,5 +218,4 @@ def selector(
         implementation=context._implementation,
         backend_version=context._backend_version,
         version=context._version,
-        kwargs={},
     )
diff --git a/narwhals/functions.py b/narwhals/functions.py
index 8d5719c3..22634f98 100644
--- a/narwhals/functions.py
+++ b/narwhals/functions.py
@@ -320,12 +320,12 @@ def from_dict(
         backend: specifies which eager backend instantiate to. Only
             necessary if inputs are not Narwhals Series.
 
-                `backend` can be specified in various ways:
+            `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`.
+            - 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.26.0):
@@ -743,26 +743,37 @@ def get_level(
 def read_csv(
     source: str,
     *,
-    native_namespace: ModuleType,
+    backend: ModuleType | Implementation | str | None = None,
+    native_namespace: ModuleType | None = None,
     **kwargs: Any,
 ) -> DataFrame[Any]:
     """Read a CSV file into a DataFrame.
 
     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.27.2):
+                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 CSV reader.
             For example, you could use
-            `nw.read_csv('file.csv', native_namespace=pd, engine='pyarrow')`.
+            `nw.read_csv('file.csv', backend='pandas', engine='pyarrow')`.
 
     Returns:
         DataFrame.
 
     Examples:
-        >>> import pandas as pd
         >>> import narwhals as nw
-        >>>
-        >>> nw.read_csv("file.csv", native_namespace=pd)  # doctest:+SKIP
+        >>> nw.read_csv("file.csv", backend="pandas")  # doctest:+SKIP
         ┌──────────────────┐
         |Narwhals DataFrame|
         |------------------|
@@ -771,21 +782,28 @@ def read_csv(
         |     1  2   5     |
         └──────────────────┘
     """
-    return _read_csv_impl(source, native_namespace=native_namespace, **kwargs)
+    backend = validate_native_namespace_and_backend(
+        backend, native_namespace, emit_deprecation_warning=True
+    )
+    if backend is None:  # pragma: no cover
+        msg = "`backend` must be specified in `read_csv`."
+        raise ValueError(msg)
+    return _read_csv_impl(source, backend=backend, **kwargs)
 
 
 def _read_csv_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)
-    if implementation in (
+    eager_backend = Implementation.from_backend(backend)
+    native_namespace = eager_backend.to_native_namespace()
+    if eager_backend in (
         Implementation.POLARS,
         Implementation.PANDAS,
         Implementation.MODIN,
         Implementation.CUDF,
     ):
         native_frame = native_namespace.read_csv(source, **kwargs)
-    elif implementation is Implementation.PYARROW:
+    elif eager_backend is Implementation.PYARROW:
         from pyarrow import csv  # ignore-banned-import
 
         native_frame = csv.read_csv(source, **kwargs)
diff --git a/narwhals/stable/v1/__init__.py b/narwhals/stable/v1/__init__.py
index 5a64fb1a..5291e125 100644
--- a/narwhals/stable/v1/__init__.py
+++ b/narwhals/stable/v1/__init__.py
@@ -2191,12 +2191,12 @@ def from_dict(
         backend: specifies which eager backend instantiate to. Only
             necessary if inputs are not Narwhals Series.
 
-                `backend` can be specified in various ways:
+            `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`.
+            - 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.26.0):
@@ -2241,22 +2241,43 @@ def from_numpy(
 
 
 def read_csv(
-    source: str, *, native_namespace: ModuleType, **kwargs: Any
+    source: str,
+    *,
+    backend: ModuleType | Implementation | str | None = None,
+    native_namespace: ModuleType | None = None,
+    **kwargs: Any,
 ) -> DataFrame[Any]:
     """Read a CSV file into a DataFrame.
 
     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.27.2):
+                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 CSV reader.
             For example, you could use
-            `nw.read_csv('file.csv', native_namespace=pd, engine='pyarrow')`.
+            `nw.read_csv('file.csv', backend='pandas', engine='pyarrow')`.
 
     Returns:
         DataFrame.
     """
+    backend = validate_native_namespace_and_backend(
+        backend, native_namespace, emit_deprecation_warning=True
+    )
+    if backend is None:  # pragma: no cover
+        raise AssertionError
     return _stableify(  # type: ignore[no-any-return]
-        _read_csv_impl(source, native_namespace=native_namespace, **kwargs)
+        _read_csv_impl(source, backend=backend, **kwargs)
     )
 
 


Test patch:
diff --git a/tests/read_scan_test.py b/tests/read_scan_test.py
index 46c6d732..1f72250b 100644
--- a/tests/read_scan_test.py
+++ b/tests/read_scan_test.py
@@ -9,37 +9,50 @@ import pytest
 
 import narwhals as nw
 import narwhals.stable.v1 as nw_v1
+from narwhals.utils import Implementation
 from tests.utils import PANDAS_VERSION
 from tests.utils import Constructor
 from tests.utils import ConstructorEager
 from tests.utils import assert_equal_data
 
 data: Mapping[str, Any] = {"a": [1, 2, 3], "b": [4.5, 6.7, 8.9], "z": ["x", "y", "w"]}
-
-
+TEST_EAGER_BACKENDS = [
+    Implementation.POLARS,
+    Implementation.PANDAS,
+    Implementation.PYARROW,
+    "polars",
+    "pandas",
+    "pyarrow",
+]
+
+
+@pytest.mark.parametrize(
+    "backend",
+    TEST_EAGER_BACKENDS,
+)
 def test_read_csv(
     tmpdir: pytest.TempdirFactory,
-    constructor_eager: ConstructorEager,
+    backend: Implementation | str,
 ) -> None:
     df_pl = pl.DataFrame(data)
     filepath = str(tmpdir / "file.csv")  # type: ignore[operator]
     df_pl.write_csv(filepath)
-    df = nw.from_native(constructor_eager(data))
-    native_namespace = nw.get_native_namespace(df)
-    result = nw.read_csv(filepath, native_namespace=native_namespace)
+    result = nw.read_csv(filepath, backend=backend)
     assert_equal_data(result, data)
     assert isinstance(result, nw.DataFrame)
 
 
+@pytest.mark.parametrize(
+    "backend",
+    TEST_EAGER_BACKENDS,
+)
 def test_read_csv_v1(
-    tmpdir: pytest.TempdirFactory, constructor_eager: ConstructorEager
+    tmpdir: pytest.TempdirFactory, backend: Implementation | str
 ) -> None:
     df_pl = pl.DataFrame(data)
     filepath = str(tmpdir / "file.csv")  # type: ignore[operator]
     df_pl.write_csv(filepath)
-    df = nw_v1.from_native(constructor_eager(data))
-    native_namespace = nw_v1.get_native_namespace(df)
-    result = nw_v1.read_csv(filepath, native_namespace=native_namespace)
+    result = nw_v1.read_csv(filepath, backend=backend)
     assert_equal_data(result, data)
     assert isinstance(result, nw_v1.DataFrame)
 
@@ -49,7 +62,7 @@ def test_read_csv_kwargs(tmpdir: pytest.TempdirFactory) -> None:
     df_pl = pl.DataFrame(data)
     filepath = str(tmpdir / "file.csv")  # type: ignore[operator]
     df_pl.write_csv(filepath)
-    result = nw.read_csv(filepath, native_namespace=pd, engine="pyarrow")
+    result = nw.read_csv(filepath, backend=pd, engine="pyarrow")
     assert_equal_data(result, data)