diff --git a/narwhals/testing/constructors.py b/narwhals/testing/constructors.py index dab2d74fbf..1ea6737d29 100644 --- a/narwhals/testing/constructors.py +++ b/narwhals/testing/constructors.py @@ -49,6 +49,7 @@ def my_backend_lazy_constructor(obj: Data, /, **kwds: Any) -> IntoLazyFrame: if TYPE_CHECKING: from collections.abc import Iterable + from types import ModuleType import ibis import pandas as pd @@ -59,9 +60,16 @@ def my_backend_lazy_constructor(obj: Data, /, **kwds: Any) -> IntoLazyFrame: from sqlframe.duckdb import DuckDBSession from typing_extensions import Concatenate, TypeAlias + from narwhals import DataFrame, LazyFrame from narwhals._native import NativeDask, NativeDuckDB, NativePySpark, NativeSQLFrame from narwhals.testing.typing import Data - from narwhals.typing import IntoDataFrame, IntoFrame, IntoLazyFrame + from narwhals.typing import ( + IntoDataFrame, + IntoDataFrameT, + IntoFrame, + IntoLazyFrame, + IntoLazyFrameT, + ) __all__ = ( @@ -97,6 +105,8 @@ class frame_constructor(Generic[T_co]): # noqa: N801 _registry: ClassVar[dict[str, frame_constructor[IntoFrame]]] = {} + func: Callable[Concatenate[Data, ...], T_co] + def __init__( self, func: Callable[Concatenate[Data, ...], T_co], @@ -159,9 +169,44 @@ def decorator(func: Callable[Concatenate[Data, ...], R]) -> frame_constructor[R] return decorator - def __call__(self, obj: Data, /, **kwds: Any) -> T_co: - """Build a native frame from `obj` by delegating to the wrapped function.""" - return self.func(obj, **kwds) + @overload + def __call__( + self: frame_constructor[IntoDataFrameT], + obj: Data, + /, + namespace: ModuleType, + **kwds: Any, + ) -> DataFrame[IntoDataFrameT]: ... + @overload + def __call__( + self: frame_constructor[IntoLazyFrameT], + obj: Data, + /, + namespace: ModuleType, + **kwds: Any, + ) -> LazyFrame[IntoLazyFrameT]: ... + @overload + def __call__( + self: frame_constructor[IntoFrame], + obj: Data, + /, + namespace: ModuleType, + **kwds: Any, + ) -> DataFrame[Any] | LazyFrame[Any]: ... + + def __call__( + self, obj: Data, /, namespace: ModuleType, **kwds: Any + ) -> DataFrame[Any] | LazyFrame[Any]: + """Build a native frame and wrap it with `namespace.from_native`. + + Arguments: + obj: Column-oriented mapping passed to the wrapped builder. + namespace: A narwhals namespace (e.g. `narwhals`, `narwhals.stable.v1`) + whose `from_native` performs the wrapping. + **kwds: Forwarded to the wrapped builder. + """ + native = self.func(obj, **kwds) + return namespace.from_native(native) # type: ignore[no-any-return] @property def identifier(self) -> str: diff --git a/tests/conftest.py b/tests/conftest.py index f3f5ebf759..ed94521264 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,7 @@ from __future__ import annotations from importlib.util import find_spec -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, cast import pytest @@ -15,16 +15,15 @@ if TYPE_CHECKING: from collections.abc import Sequence - - from typing_extensions import TypeAlias + from types import ModuleType from narwhals._typing import EagerAllowed - from narwhals.testing.typing import DataFrameConstructor, FrameConstructor - from narwhals.typing import NonNestedDType + from narwhals.dataframe import DataFrame, LazyFrame + from narwhals.testing.constructors import frame_constructor + from narwhals.testing.typing import Data, DataFrameConstructor, FrameConstructor + from narwhals.typing import IntoFrame, NonNestedDType from tests.utils import NestedOrEnumDType - Data: TypeAlias = "dict[str, list[Any]]" - # Narwhals-internal pytest options (not part of the public testing plugin) @@ -120,22 +119,62 @@ def nested_dtype(request: pytest.FixtureRequest) -> NestedOrEnumDType: return dtype -# The following fixtures are aliases of those registered in `narwhals/testing/pytest_plugin.py` -# in order to be backward compatible with the old fixture names and avoid having to change -# every single test. -# TODO(FBruzzesi): Rm once all tests start using nw_frame_constructor directly +# The following fixtures are aliases of those registered in `narwhals/testing/pytest_plugin.py`, +# wrapped so that calling them without an explicit `namespace` defaults to the main +# `narwhals` namespace. Tests can still pass `nw_v1` / `nw_v2` explicitly to opt in +# to a stable namespace; the legacy pattern `nw.from_native(constructor(data))` keeps +# working because `nw.from_native` is idempotent on narwhals objects. +# TODO(FBruzzesi): Drop these aliases once every test calls `nw_frame` / `nw_dataframe` +# directly with an explicit namespace. + + +class _PatchedFrameConstructor: + """Proxy over a `frame_constructor` defaulting `namespace` to `narwhals`. + + Delegates attribute access, `str()`, and `repr()` to the wrapped instance + so that test helpers (e.g. `constructor.is_nullable`, `"pandas" in str(constructor)`) + keep working unchanged. + """ + + __slots__ = ("_inner",) + + def __init__(self, inner: frame_constructor[IntoFrame]) -> None: + self._inner = inner + + def __call__( + self, obj: Data, /, namespace: ModuleType = nw, **kwds: Any + ) -> DataFrame[Any] | LazyFrame[Any]: + return self._inner(obj, namespace=namespace, **kwds) + + def __getattr__(self, name: str) -> Any: + return getattr(self._inner, name) + + def __str__(self) -> str: + return str(self._inner) + + def __repr__(self) -> str: + return repr(self._inner) + + +class _PatchedDataFrameConstructor(_PatchedFrameConstructor): + def __call__( + self, obj: Data, /, namespace: ModuleType = nw, **kwds: Any + ) -> DataFrame[Any]: + return cast("DataFrame[Any]", self._inner(obj, namespace=namespace, **kwds)) + + @pytest.fixture -def constructor(nw_frame: FrameConstructor) -> FrameConstructor: - return nw_frame +def constructor(nw_frame: FrameConstructor) -> _PatchedFrameConstructor: + return _PatchedFrameConstructor(nw_frame) @pytest.fixture -def constructor_eager(nw_dataframe: DataFrameConstructor) -> FrameConstructor: - return nw_dataframe +def constructor_eager(nw_dataframe: DataFrameConstructor) -> _PatchedDataFrameConstructor: + return _PatchedDataFrameConstructor(nw_dataframe) @pytest.fixture def constructor_pandas_like( nw_pandas_like_frame: DataFrameConstructor, -) -> FrameConstructor: - return nw_pandas_like_frame +) -> _PatchedDataFrameConstructor: + return _PatchedDataFrameConstructor(nw_pandas_like_frame) diff --git a/tests/dependencies/is_narwhals_dataframe_test.py b/tests/dependencies/is_narwhals_dataframe_test.py index aeedf15981..0897e64cc8 100644 --- a/tests/dependencies/is_narwhals_dataframe_test.py +++ b/tests/dependencies/is_narwhals_dataframe_test.py @@ -2,7 +2,6 @@ from typing import TYPE_CHECKING -import narwhals as nw from narwhals.stable.v1.dependencies import is_narwhals_dataframe if TYPE_CHECKING: @@ -12,5 +11,5 @@ def test_is_narwhals_dataframe(constructor_eager: ConstructorEager) -> None: df = constructor_eager({"col1": [1, 2], "col2": [3, 4]}) - assert is_narwhals_dataframe(nw.from_native(df)) - assert not is_narwhals_dataframe(df) + assert is_narwhals_dataframe(df) + assert not is_narwhals_dataframe(df.to_native()) diff --git a/tests/dependencies/is_narwhals_lazyframe_test.py b/tests/dependencies/is_narwhals_lazyframe_test.py index 0e4c6e1bd9..113fd4a511 100644 --- a/tests/dependencies/is_narwhals_lazyframe_test.py +++ b/tests/dependencies/is_narwhals_lazyframe_test.py @@ -2,7 +2,6 @@ from typing import TYPE_CHECKING -import narwhals as nw from narwhals.stable.v1.dependencies import is_narwhals_lazyframe from tests.utils import Constructor @@ -13,5 +12,5 @@ def test_is_narwhals_lazyframe(constructor: Constructor) -> None: lf = constructor({"a": [1, 2, 3]}) - assert is_narwhals_lazyframe(nw.from_native(lf).lazy()) - assert not is_narwhals_lazyframe(lf) + assert is_narwhals_lazyframe(lf.lazy()) + assert not is_narwhals_lazyframe(lf.to_native()) diff --git a/tests/expr_and_series/arithmetic_test.py b/tests/expr_and_series/arithmetic_test.py index ec6c2ff8bc..9755435871 100644 --- a/tests/expr_and_series/arithmetic_test.py +++ b/tests/expr_and_series/arithmetic_test.py @@ -45,7 +45,7 @@ def test_arithmetic_expr( request.applymarker(pytest.mark.xfail) data = {"a": [1.0, 2.0, 3.0]} - df = nw.from_native(constructor(data)) + df = constructor(data, nw) result = df.select(getattr(nw.col("a"), attr)(rhs)) assert_equal_data(result, {"a": expected}) @@ -76,7 +76,7 @@ def test_right_arithmetic_expr( ): request.applymarker(pytest.mark.xfail) data = {"a": [1, 2, 3]} - df = nw.from_native(constructor(data)) + df = constructor(data) result = df.select(getattr(nw.col("a"), attr)(rhs)) assert_equal_data(result, {"literal": expected}) @@ -107,7 +107,7 @@ def test_arithmetic_series( request.applymarker(pytest.mark.xfail) data = {"a": [1, 2, 3]} - df = nw.from_native(nw_dataframe(data), eager_only=True) + df = nw_dataframe(data, nw) result = df.select(getattr(df["a"], attr)(rhs)) assert_equal_data(result, {"a": expected}) @@ -137,7 +137,7 @@ def test_right_arithmetic_series( request.applymarker(pytest.mark.xfail) data = {"a": [1, 2, 3]} - df = nw.from_native(nw_dataframe(data), eager_only=True) + df = nw_dataframe(data, nw) result_series = getattr(df["a"], attr)(rhs) assert result_series.name == "a" assert_equal_data({"a": result_series}, {"a": expected}) @@ -149,8 +149,8 @@ def test_truediv_same_dims( if "polars" in str(nw_dataframe): # https://github.com/pola-rs/polars/issues/17760 request.applymarker(pytest.mark.xfail) - s_left = nw.from_native(nw_dataframe({"a": [1, 2, 3]}), eager_only=True)["a"] - s_right = nw.from_native(nw_dataframe({"a": [2, 2, 1]}), eager_only=True)["a"] + s_left = nw_dataframe({"a": [1, 2, 3]}, nw)["a"] + s_right = nw_dataframe({"a": [2, 2, 1]}, nw)["a"] result = s_left / s_right assert_equal_data({"a": result}, {"a": [0.5, 1.0, 3.0]}) result = s_left.__rtruediv__(s_right) @@ -166,9 +166,7 @@ def test_floordiv(nw_dataframe: ConstructorEager, *, left: int, right: int) -> N pytest.skip() assume(right != 0) expected = {"a": [left // right]} - result = nw.from_native(nw_dataframe({"a": [left]}), eager_only=True).select( - nw.col("a") // right - ) + result = nw_dataframe({"a": [left]}, nw).select(nw.col("a") // right) assert_equal_data(result, expected) @@ -182,9 +180,7 @@ def test_mod(nw_dataframe: ConstructorEager, *, left: int, right: int) -> None: pytest.skip() assume(right != 0) expected = {"a": [left % right]} - result = nw.from_native(nw_dataframe({"a": [left]}), eager_only=True).select( - nw.col("a") % right - ) + result = nw_dataframe({"a": [left]}, nw).select(nw.col("a") % right) assert_equal_data(result, expected) @@ -218,7 +214,7 @@ def test_arithmetic_expr_left_literal( request.applymarker(pytest.mark.xfail) data = {"a": [1.0, 2.0, 4.0]} - df = nw.from_native(constructor(data)) + df = constructor(data, nw) result = df.select(getattr(lhs, attr)(nw.col("a"))) assert_equal_data(result, {"literal": expected}) @@ -249,7 +245,7 @@ def test_arithmetic_series_left_literal( request.applymarker(pytest.mark.xfail) data = {"a": [1.0, 2.0, 4.0]} - df = nw.from_native(nw_dataframe(data)) + df = nw_dataframe(data, nw) result = df.select(getattr(lhs, attr)(nw.col("a"))) assert_equal_data(result, {"literal": expected}) @@ -258,7 +254,7 @@ def test_std_broadcating(constructor: Constructor) -> None: if "duckdb" in str(constructor) and DUCKDB_VERSION < (1, 3): # `std(ddof=2)` fails for duckdb here pytest.skip() - df = nw.from_native(constructor({"a": [1, 2, 3]})) + df = constructor({"a": [1, 2, 3]}, nw) result = df.with_columns(b=nw.col("a").std()).sort("a") expected = {"a": [1, 2, 3], "b": [1.0, 1.0, 1.0]} assert_equal_data(result, expected) diff --git a/tests/expr_and_series/dt/datetime_attributes_test.py b/tests/expr_and_series/dt/datetime_attributes_test.py index c7bf55e7c0..830666cac2 100644 --- a/tests/expr_and_series/dt/datetime_attributes_test.py +++ b/tests/expr_and_series/dt/datetime_attributes_test.py @@ -123,10 +123,10 @@ def test_to_date(request: pytest.FixtureRequest, constructor: Constructor) -> No request.applymarker(pytest.mark.xfail) dates = {"a": [datetime(2001, 1, 1), None, datetime(2001, 1, 3)]} if "dask" in str(constructor): - df_dask = cast("dd.DataFrame", constructor(dates)) + df_dask = cast("dd.DataFrame", constructor(dates).to_native()) df_dask = cast("dd.DataFrame", df_dask.astype({"a": "timestamp[ns][pyarrow]"})) df = nw.from_native(df_dask) else: - df = nw.from_native(constructor(dates)) + df = constructor(dates) result = df.select(nw.col("a").dt.date()) assert result.collect_schema() == {"a": nw.Date} diff --git a/tests/expr_and_series/dt/datetime_duration_test.py b/tests/expr_and_series/dt/datetime_duration_test.py index b84ecfa66e..ac7d132bfc 100644 --- a/tests/expr_and_series/dt/datetime_duration_test.py +++ b/tests/expr_and_series/dt/datetime_duration_test.py @@ -74,7 +74,7 @@ def test_duration_attributes_nano( import numpy as np data = {"c": np.array([None, 20], dtype="timedelta64[ns]")} - df = nw.from_native(constructor(data)) + df = constructor(data, nw) result_c = df.select(getattr(nw.col("c").dt, attribute)().fill_null(0)) assert_equal_data(result_c, {"c": expected_c}) diff --git a/tests/expr_and_series/over_test.py b/tests/expr_and_series/over_test.py index 7fbb8f0f9f..e3fe101c3d 100644 --- a/tests/expr_and_series/over_test.py +++ b/tests/expr_and_series/over_test.py @@ -472,7 +472,7 @@ def test_over_quantile(constructor: Constructor, request: pytest.FixtureRequest) data = {"a": [1, 2, 3, 4, 5, 6], "b": ["x", "x", "x", "y", "y", "y"]} quantile_expr = nw.col("a").quantile(quantile=0.5, interpolation="linear") - native_frame = constructor(data) + native_frame = constructor(data).to_native() if "dask" in str(constructor): native_frame = native_frame.repartition(npartitions=1) # type: ignore[union-attr] diff --git a/tests/expr_and_series/struct_/field_test.py b/tests/expr_and_series/struct_/field_test.py index a351c31500..dd8dd26790 100644 --- a/tests/expr_and_series/struct_/field_test.py +++ b/tests/expr_and_series/struct_/field_test.py @@ -1,84 +1,53 @@ from __future__ import annotations -from typing import cast - import pytest import narwhals as nw -from tests.utils import PANDAS_VERSION, Constructor, ConstructorEager, assert_equal_data +from tests.utils import ( + DUCKDB_VERSION, + PANDAS_VERSION, + Constructor, + ConstructorEager, + assert_equal_data, +) def test_get_field_expr(request: pytest.FixtureRequest, constructor: Constructor) -> None: pytest.importorskip("pyarrow") - import pyarrow as pa - if any(backend in str(constructor) for backend in ("dask", "modin")): + if any(backend in str(constructor) for backend in ("dask",)): request.applymarker(pytest.mark.xfail) - if "pandas" in str(constructor) and PANDAS_VERSION < (2, 2, 0): + if ("pandas" in str(constructor) and PANDAS_VERSION < (2, 2, 0)) or ( + "duckdb" in str(constructor) and DUCKDB_VERSION < (1, 3, 0) + ): pytest.skip() - data = {"user": [{"id": "0", "name": "john"}, {"id": "1", "name": "jane"}]} - - df_native = constructor(data) - - if "pandas" in str(constructor): - import pandas as pd - df_native = cast("pd.DataFrame", df_native).assign( - user=pd.Series( - data["user"], - dtype=pd.ArrowDtype( - pa.struct([("id", pa.string()), ("name", pa.string())]) - ), - ) - ) - - df = nw.from_native(df_native) + data = {"id": ["0", "1"], "name": ["john", "jane"]} + expected = data.copy() + df = constructor(data, nw).select(user=nw.struct("id", "name")) result = nw.from_native(df).select( nw.col("user").struct.field("id"), nw.col("user").struct.field("name") ) - expected = {"id": ["0", "1"], "name": ["john", "jane"]} assert_equal_data(result, expected) result = nw.from_native(df).select(nw.col("user").struct.field("id").name.keep()) expected = {"user": ["0", "1"]} assert_equal_data(result, expected) -def test_get_field_series( - request: pytest.FixtureRequest, constructor_eager: ConstructorEager -) -> None: +def test_get_field_series(constructor_eager: ConstructorEager) -> None: pytest.importorskip("pyarrow") - import pyarrow as pa - if any(backend in str(constructor_eager) for backend in ("modin",)): - request.applymarker(pytest.mark.xfail) if "pandas" in str(constructor_eager) and PANDAS_VERSION < (2, 2, 0): pytest.skip() - data = {"user": [{"id": "0", "name": "john"}, {"id": "1", "name": "jane"}]} - expected = {"id": ["0", "1"], "name": ["john", "jane"]} - - _expected = expected.copy() - df_native = constructor_eager(data) - - if "pandas" in str(constructor_eager): - import pandas as pd - - df_native = cast("pd.DataFrame", df_native).assign( - user=pd.Series( - data["user"], - dtype=pd.ArrowDtype( - pa.struct([("id", pa.string()), ("name", pa.string())]) - ), - ) - ) - - df = nw.from_native(df_native, eager_only=True) + data = {"id": ["0", "1"], "name": ["john", "jane"]} + expected = data.copy() + df = constructor_eager(data, nw).select(user=nw.struct("id", "name")) result = nw.from_native(df).select( df["user"].struct.field("id"), df["user"].struct.field("name") ) - expected = {"id": ["0", "1"], "name": ["john", "jane"]} - assert_equal_data(result, _expected) + assert_equal_data(result, expected) def test_pandas_object_series() -> None: diff --git a/tests/frame/join_test.py b/tests/frame/join_test.py index 7ca4754b2d..ecfe5c53f3 100644 --- a/tests/frame/join_test.py +++ b/tests/frame/join_test.py @@ -16,19 +16,7 @@ ) if TYPE_CHECKING: - from narwhals.typing import IntoDataFrame, IntoLazyFrameT, JoinStrategy - - -def from_native_lazy( - native: IntoLazyFrameT | IntoDataFrame, -) -> nw.LazyFrame[IntoLazyFrameT] | nw.LazyFrame[Any]: - """Every join test [needs to use `.lazy()` for typing]*. - - *Unless both left/right frames are of the same concrete type. - - [needs to use `.lazy()` for typing]: https://github.com/narwhals-dev/narwhals/pull/2944#discussion_r2286264815 - """ - return nw.from_native(native).lazy() + from narwhals.typing import JoinStrategy @pytest.mark.parametrize( @@ -107,8 +95,8 @@ def test_full_join( right_on: None | str | list[str], constructor: Constructor, ) -> None: - df_left = from_native_lazy(constructor(df1)) - df_right = from_native_lazy(constructor(df2)) + df_left = constructor(df1).lazy() + df_right = constructor(df2).lazy() result = df_left.join( df_right, on=on, left_on=left_on, right_on=right_on, how="full" ).sort("id", nulls_last=True) @@ -123,8 +111,8 @@ def test_full_join_duplicate( df1 = {"foo": [1, 2, 3], "val1": [1, 2, 3]} df2 = {"foo": [1, 2, 3], "foo_right": [1, 2, 3]} - df_left = from_native_lazy(constructor(df1)) - df_right = from_native_lazy(constructor(df2)) + df_left = constructor(df1).lazy() + df_right = constructor(df2).lazy() exceptions: list[type[Exception]] = [nw.exceptions.NarwhalsError] if "pyspark" in str(constructor) and "sqlframe" not in str(constructor): @@ -146,7 +134,7 @@ def test_inner_join_two_keys(constructor: Constructor) -> None: "zor ro": [7.0, 8.0, 9.0], "idx": [0, 1, 2], } - df = from_native_lazy(constructor(data)) + df = constructor(data).lazy() df_right = df result = df.join( df_right, @@ -175,7 +163,7 @@ def test_inner_join_single_key(constructor: Constructor) -> None: "zor ro": [7.0, 8.0, 9.0], "idx": [0, 1, 2], } - df = from_native_lazy(constructor(data)) + df = constructor(data).lazy() df_right = df result = df.join( df_right, left_on="antananarivo", right_on="antananarivo", how="inner" @@ -199,7 +187,7 @@ def test_cross_join(constructor: Constructor) -> None: if "duckdb" in str(constructor) and DUCKDB_VERSION < (1, 1, 4): pytest.skip() data = {"antananarivo": [1, 3, 2]} - df = from_native_lazy(constructor(data)) + df = constructor(data).lazy() result = df.join(df, how="cross").sort("antananarivo", "antananarivo_right") expected = { "antananarivo": [1, 1, 1, 2, 2, 2, 3, 3, 3], @@ -219,7 +207,7 @@ def test_suffix( constructor: Constructor, how: Literal["inner", "left"], suffix: str ) -> None: data = {"antananarivo": [1, 3, 2], "bob": [4, 4, 6], "zor ro": [7.0, 8.0, 9.0]} - df = from_native_lazy(constructor(data)) + df = constructor(data).lazy() df_right = df result = df.join( df_right, @@ -237,7 +225,7 @@ def test_cross_join_suffix(constructor: Constructor, suffix: str) -> None: if "duckdb" in str(constructor) and DUCKDB_VERSION < (1, 1, 4): pytest.skip() data = {"antananarivo": [1, 3, 2]} - df = from_native_lazy(constructor(data)) + df = constructor(data).lazy() result = df.join(df, how="cross", suffix=suffix).sort( "antananarivo", f"antananarivo{suffix}" ) @@ -287,7 +275,7 @@ def test_anti_join( expected: dict[str, list[Any]], ) -> None: data = {"antananarivo": [1, 3, 2], "bob": [4, 4, 6], "zor ro": [7.0, 8.0, 9.0]} - df = from_native_lazy(constructor(data)) + df = constructor(data).lazy() other = df.filter(filter_expr) result = df.join(other, how="anti", left_on=join_key, right_on=join_key) assert_equal_data(result, expected) @@ -325,7 +313,7 @@ def test_semi_join( expected: dict[str, list[Any]], ) -> None: data = {"antananarivo": [1, 3, 2], "bob": [4, 4, 6], "zor ro": [7.0, 8.0, 9.0]} - df = from_native_lazy(constructor(data)) + df = constructor(data).lazy() other = df.filter(filter_expr) result = df.join(other, how="semi", left_on=join_key, right_on=join_key).sort( "antananarivo" @@ -336,7 +324,7 @@ def test_semi_join( @pytest.mark.parametrize("how", ["right"]) def test_join_not_implemented(constructor: Constructor, how: str) -> None: data = {"antananarivo": [1, 3, 2], "bob": [4, 4, 6], "zor ro": [7.0, 8.0, 9.0]} - df = from_native_lazy(constructor(data)) + df = constructor(data).lazy() with pytest.raises( NotImplementedError, @@ -363,8 +351,8 @@ def test_left_join(constructor: Constructor) -> None: "co": [4.0, 5.0, 7.0], "idx": [0.0, 1.0, 2.0], } - df_left = from_native_lazy(constructor(data_left)) - df_right = from_native_lazy(constructor(data_right)) + df_left = constructor(data_left).lazy() + df_right = constructor(data_right).lazy() result = df_left.join(df_right, left_on="bob", right_on="co", how="left") result = result.sort("idx") result = result.drop("idx_right") @@ -389,8 +377,8 @@ def test_left_join(constructor: Constructor) -> None: def test_left_join_multiple_column(constructor: Constructor) -> None: data_left = {"antananarivo": [1, 2, 3], "bob": [4, 5, 6], "idx": [0, 1, 2]} data_right = {"antananarivo": [1, 2, 3], "c": [4, 5, 6], "idx": [0, 1, 2]} - df_left = from_native_lazy(constructor(data_left)) - df_right = from_native_lazy(constructor(data_right)) + df_left = constructor(data_left).lazy() + df_right = constructor(data_right).lazy() result = df_left.join( df_right, left_on=["antananarivo", "bob"], @@ -416,8 +404,8 @@ def test_left_join_overlapping_column(constructor: Constructor) -> None: "d": [1.0, 4.0, 2.0], "idx": [0.0, 1.0, 2.0], } - df_left = from_native_lazy(constructor(data_left)) - df_right = from_native_lazy(constructor(data_right)) + df_left = constructor(data_left).lazy() + df_right = constructor(data_right).lazy() result = df_left.join(df_right, left_on="bob", right_on="c", how="left").sort("idx") result = result.drop("idx_right") expected: dict[str, list[Any]] = { @@ -446,7 +434,7 @@ def test_left_join_overlapping_column(constructor: Constructor) -> None: @pytest.mark.parametrize("how", ["inner", "left", "semi", "anti"]) def test_join_keys_exceptions(constructor: Constructor, how: JoinStrategy) -> None: data = {"antananarivo": [1, 3, 2], "bob": [4, 4, 6], "zor ro": [7.0, 8.0, 9.0]} - df = from_native_lazy(constructor(data)) + df = constructor(data).lazy() with pytest.raises( ValueError, @@ -515,8 +503,8 @@ def test_joinasof_numeric( data_left = {"antananarivo": [1, 5, 10], "val": ["a", "b", "c"]} data_right = {"antananarivo": [1, 2, 3, 6, 7], "val": [1, 2, 3, 6, 7]} - left_lf = from_native_lazy(constructor(data_left)).sort("antananarivo") - right_lf = from_native_lazy(constructor(data_right)).sort("antananarivo") + left_lf = constructor(data_left).lazy().sort("antananarivo") + right_lf = constructor(data_right).lazy().sort("antananarivo") result: nw.DataFrame[Any] | nw.LazyFrame[Any] result_on: nw.DataFrame[Any] | nw.LazyFrame[Any] @@ -592,7 +580,7 @@ def test_joinasof_time( request.applymarker(pytest.mark.xfail) if PANDAS_VERSION < (2, 1) and ("pandas_pyarrow" in str(constructor)): request.applymarker(pytest.mark.xfail) - df = from_native_lazy( + df = ( constructor( { "datetime": [ @@ -603,8 +591,10 @@ def test_joinasof_time( "population": [82.19, 82.66, 83.12], } ) - ).sort("datetime") - df_right = from_native_lazy( + .lazy() + .sort("datetime") + ) + df_right = ( constructor( { "datetime": [ @@ -617,7 +607,9 @@ def test_joinasof_time( "gdp": [4164, 4411, 4566, 4696, 4827], } ) - ).sort("datetime") + .lazy() + .sort("datetime") + ) result = df.join_asof( df_right, left_on="datetime", right_on="datetime", strategy=strategy ) @@ -633,7 +625,7 @@ def test_joinasof_by(constructor: Constructor, request: pytest.FixtureRequest) - ("pandas_pyarrow" in str(constructor)) or ("pandas_nullable" in str(constructor)) ): request.applymarker(pytest.mark.xfail) - df = from_native_lazy( + df = ( constructor( { "antananarivo": [1, 5, 7, 10], @@ -641,12 +633,16 @@ def test_joinasof_by(constructor: Constructor, request: pytest.FixtureRequest) - "c": [9, 2, 1, 1], } ) - ).sort("antananarivo") - df_right = from_native_lazy( + .lazy() + .sort("antananarivo") + ) + df_right = ( constructor( {"antananarivo": [1, 4, 5, 8], "bob": ["D", "D", "A", "F"], "d": [1, 3, 4, 1]} ) - ).sort("antananarivo") + .lazy() + .sort("antananarivo") + ) result = df.join_asof(df_right, on="antananarivo", by_left="bob", by_right="bob") result_by = df.join_asof(df_right, on="antananarivo", by="bob") expected = { @@ -668,12 +664,16 @@ def test_joinasof_suffix( ("pandas_pyarrow" in str(constructor)) or ("pandas_nullable" in str(constructor)) ): request.applymarker(pytest.mark.xfail) - df = from_native_lazy( + df = ( constructor({"antananarivo": [1, 5, 10], "val": ["a", "b", "c"]}) - ).sort("antananarivo") - df_right = from_native_lazy( + .lazy() + .sort("antananarivo") + ) + df_right = ( constructor({"antananarivo": [1, 2, 3, 6, 7], "val": [1, 2, 3, 6, 7]}) - ).sort("antananarivo") + .lazy() + .sort("antananarivo") + ) result = df.join_asof( df_right, left_on="antananarivo", right_on="antananarivo", suffix="_y" ) @@ -686,7 +686,7 @@ def test_joinasof_not_implemented( constructor: Constructor, strategy: Literal["backward", "forward"] ) -> None: data = {"antananarivo": [1, 3, 2], "bob": [4, 4, 6], "zor ro": [7.0, 8.0, 9.0]} - df = from_native_lazy(constructor(data)) + df = constructor(data).lazy() with pytest.raises( NotImplementedError, @@ -699,7 +699,7 @@ def test_joinasof_not_implemented( def test_joinasof_keys_exceptions(constructor: Constructor) -> None: data = {"antananarivo": [1, 3, 2], "bob": [4, 4, 6], "zor ro": [7.0, 8.0, 9.0]} - df = from_native_lazy(constructor(data)) + df = constructor(data).lazy() with pytest.raises( ValueError, @@ -765,7 +765,7 @@ def test_joinasof_by_exceptions( message: str, ) -> None: data = {ON: [1, 3, 2], BY: [4, 4, 6], "zor ro": [7.0, 8.0, 9.0]} - frame = from_native_lazy(constructor(data)) + frame = constructor(data).lazy() if constructor.is_lazy: with pytest.raises(ValueError, match=message): @@ -791,7 +791,7 @@ def test_join_duplicate_column_names( ): request.applymarker(pytest.mark.xfail) data = {"a": [1, 2, 3, 4, 5], "b": [6, 6, 6, 6, 6]} - lf = from_native_lazy(constructor(data)) + lf = constructor(data).lazy() if any( x in str(constructor) for x in ("pandas", "pandas[pyarrow]", "pandas[nullable]", "dask") @@ -891,8 +891,8 @@ def test_join_on_null_values( data_left = {**keys, "x": [1, 2, 3, 4]} data_right = {**keys, "y": [1.2, 3.4, 5.6, 7.8]} - df_left = from_native_lazy(constructor(data_left)) - df_right = from_native_lazy(constructor(data_right)) + df_left = constructor(data_left).lazy() + df_right = constructor(data_right).lazy() on = None if how == "cross" else list(keys) sort_by = ["a", "x", "y"] if how in {"cross", "full"} else ["a", "x"] @@ -918,8 +918,8 @@ def test_full_join_with_overlapping_non_key_columns_and_nulls( "right_only": [100, 200, 300], } - df_left = from_native_lazy(constructor(data_left)) - df_right = from_native_lazy(constructor(data_right)) + df_left = constructor(data_left).lazy() + df_right = constructor(data_right).lazy() result = df_left.join(df_right, on="id", how="full", suffix="_r").sort( "id", nulls_last=True diff --git a/tests/frame/sample_test.py b/tests/frame/sample_test.py index b86ddaee1d..8db480e02c 100644 --- a/tests/frame/sample_test.py +++ b/tests/frame/sample_test.py @@ -19,9 +19,7 @@ def test_sample_n(constructor_eager: ConstructorEager) -> None: def test_sample_fraction(constructor_eager: ConstructorEager) -> None: - df = nw.from_native( - constructor_eager({"a": [1, 2, 3, 4], "b": ["x", "y", "x", "y"]}), eager_only=True - ) + df = constructor_eager({"a": [1, 2, 3, 4], "b": ["x", "y", "x", "y"]}) result_expr = df.sample(fraction=0.5).shape expected_expr = (2, 2) @@ -30,11 +28,11 @@ def test_sample_fraction(constructor_eager: ConstructorEager) -> None: def test_sample_with_seed(constructor_eager: ConstructorEager) -> None: size, n = 100, 10 - df = nw.from_native(constructor_eager({"a": range(size)}), eager_only=True) + df = constructor_eager({"a": range(size)}) r1 = nw.to_native(df.sample(n=n, seed=123)) r2 = nw.to_native(df.sample(n=n, seed=123)) r3 = nw.to_native(df.sample(n=n, seed=42)) - assert r1.equals(r2) # type: ignore[attr-defined] - assert not r1.equals(r3) # type: ignore[attr-defined] + assert r1.equals(r2) + assert not r1.equals(r3) diff --git a/tests/frame/schema_test.py b/tests/frame/schema_test.py index 93779cd129..0ab511cf56 100644 --- a/tests/frame/schema_test.py +++ b/tests/frame/schema_test.py @@ -9,7 +9,7 @@ import narwhals as nw from narwhals.exceptions import PerformanceWarning -from tests.utils import PANDAS_VERSION, POLARS_VERSION, ConstructorPandasLike +from tests.utils import PANDAS_VERSION, POLARS_VERSION if TYPE_CHECKING: from collections.abc import Callable, Sequence @@ -23,7 +23,7 @@ IntoPandasSchema, IntoPolarsSchema, ) - from tests.utils import Constructor, ConstructorEager + from tests.utils import Constructor, ConstructorEager, ConstructorPandasLike TimeUnit: TypeAlias = Literal["ns", "us"] @@ -578,7 +578,7 @@ def origin_pandas_like( "d": [5.3, 4.99], "e": [datetime(2006, 1, 1), datetime(2001, 9, 3)], } - return constructor_pandas_like(data).dtypes.to_dict() + return constructor_pandas_like(data).to_native().dtypes.to_dict() @pytest.fixture diff --git a/tests/frame/to_native_test.py b/tests/frame/to_native_test.py index 0ef0ae885a..cdb03e2675 100644 --- a/tests/frame/to_native_test.py +++ b/tests/frame/to_native_test.py @@ -10,7 +10,7 @@ def test_to_native(constructor: Constructor) -> None: data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.1, 8.0, 9.0]} - df_raw = constructor(data) + df_raw = constructor(data).to_native() df = nw.from_native(df_raw) assert isinstance(df.to_native(), df_raw.__class__) diff --git a/tests/frame/to_pandas_test.py b/tests/frame/to_pandas_test.py index b74c9a98b1..bcdcc10fc3 100644 --- a/tests/frame/to_pandas_test.py +++ b/tests/frame/to_pandas_test.py @@ -7,7 +7,6 @@ pytest.importorskip("pandas") import pandas as pd -import narwhals as nw from tests.utils import PANDAS_VERSION if TYPE_CHECKING: @@ -19,11 +18,10 @@ def test_convert_pandas(constructor_eager: ConstructorEager) -> None: pytest.importorskip("pyarrow") data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.0, 8.0, 9.0]} - df_raw = constructor_eager(data) - result = nw.from_native(df_raw, eager_only=True).to_pandas() + result = constructor_eager(data).to_pandas() if str(constructor_eager).startswith("pandas"): - expected = cast("pd.DataFrame", constructor_eager(data)) + expected = cast("pd.DataFrame", constructor_eager(data).to_native()) elif "modin_pyarrow" in str(constructor_eager): expected = pd.DataFrame(data).convert_dtypes(dtype_backend="pyarrow") else: diff --git a/tests/hypothesis/getitem_test.py b/tests/hypothesis/getitem_test.py index d9fc517f60..c18f860872 100644 --- a/tests/hypothesis/getitem_test.py +++ b/tests/hypothesis/getitem_test.py @@ -158,7 +158,7 @@ def test_getitem( # rows/columns sides. return - df_other = nw.from_native(pandas_or_pyarrow_constructor(TEST_DATA)) + df_other = pandas_or_pyarrow_constructor(TEST_DATA, nw) result_other = df_other[cast("Any", selector)] if isinstance(result_polars, nw.Series): diff --git a/tests/ibis_test.py b/tests/ibis_test.py index 10dce38413..a9a9dc413b 100644 --- a/tests/ibis_test.py +++ b/tests/ibis_test.py @@ -10,5 +10,5 @@ def test_from_native() -> None: ibis_constructor = get_backend_constructor("ibis") if not ibis_constructor.is_available: pytest.skip() - df = nw.from_native(ibis_constructor({"a": [1, 2, 3], "b": [4, 5, 6]})) + df = ibis_constructor({"a": [1, 2, 3], "b": [4, 5, 6]}, nw) assert df.columns == ["a", "b"] diff --git a/tests/namespace_test.py b/tests/namespace_test.py index e94a6690c1..e3a9af77ed 100644 --- a/tests/namespace_test.py +++ b/tests/namespace_test.py @@ -72,7 +72,7 @@ def test_namespace_from_backend_name(backend: BackendName) -> None: def test_namespace_from_native_object(constructor: Constructor) -> None: data = {"a": [1, 2, 3], "b": [4, 5, 6]} - frame = constructor(data) + frame = constructor(data, nw).to_native() namespace = Namespace.from_native_object(frame) nw_frame = nw.from_native(frame) assert namespace.implementation == nw_frame.implementation diff --git a/tests/preserve_pandas_like_columns_name_attr_test.py b/tests/preserve_pandas_like_columns_name_attr_test.py index 3127040bee..546b388f67 100644 --- a/tests/preserve_pandas_like_columns_name_attr_test.py +++ b/tests/preserve_pandas_like_columns_name_attr_test.py @@ -1,17 +1,17 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING import pytest import narwhals as nw if TYPE_CHECKING: - import pandas as pd + from tests.utils import Constructor def test_ops_preserve_column_index_name( - constructor: Callable[..., pd.DataFrame], request: pytest.FixtureRequest + constructor: Constructor, request: pytest.FixtureRequest ) -> None: if not any(x in str(constructor) for x in ("pandas", "modin", "cudf", "dask")): pytest.skip( @@ -22,7 +22,7 @@ def test_ops_preserve_column_index_name( request.applymarker(pytest.mark.xfail) data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.0, 8.0, 9.0]} - df_native = constructor(data) + df_native = constructor(data).to_native() df_native.columns.name = "foo" df = nw.from_native(df_native) diff --git a/tests/series_only/hist_test.py b/tests/series_only/hist_test.py index 9bf4f26c62..7db42c31bc 100644 --- a/tests/series_only/hist_test.py +++ b/tests/series_only/hist_test.py @@ -11,13 +11,13 @@ import narwhals as nw from narwhals.exceptions import ComputeError -from narwhals.testing.constructors import get_backend_constructor -from tests.utils import POLARS_VERSION, ConstructorEager, assert_equal_data +from tests.utils import POLARS_VERSION, assert_equal_data if TYPE_CHECKING: from collections.abc import Sequence - from narwhals.testing.constructors import EagerName + from narwhals.testing.typing import DataFrameConstructor + rnd = Random(0) # noqa: S311 @@ -49,14 +49,6 @@ param_name = pytest.mark.parametrize("name", ["pandas", "polars[eager]", "pyarrow"]) -def maybe_name_to_constructor(name: EagerName) -> ConstructorEager: - constructor = get_backend_constructor(name) - if constructor.is_available: - return constructor - - pytest.skip() - - SHIFT_BINS_BY = 10 """shift bins property""" @@ -75,18 +67,14 @@ def maybe_name_to_constructor(name: EagerName) -> ConstructorEager: ], ids=str, ) -@param_name def test_hist_bin( - name: EagerName, + nw_dataframe: DataFrameConstructor, bins: list[float], expected: Sequence[float], *, include_breakpoint: bool, ) -> None: - constructor_eager = maybe_name_to_constructor(name) - df = nw.from_native(constructor_eager(data)).with_columns( - float=nw.col("int").cast(nw.Float64) - ) + df = nw_dataframe(data, nw).with_columns(float=nw.col("int").cast(nw.Float64)) expected_full = {"count": expected} if include_breakpoint: expected_full = {"breakpoint": bins[1:], **expected_full} @@ -111,10 +99,8 @@ def test_hist_bin( assert_equal_data(result, expected_full) # missing/nan results - df = nw.from_native( - constructor_eager( - {"has_nan": [float("nan"), *data["int"]], "has_null": [None, *data["int"]]} - ) + df = nw_dataframe( + {"has_nan": [float("nan"), *data["int"]], "has_null": [None, *data["int"]]}, nw ) expected_full = {"count": expected} if include_breakpoint: @@ -126,14 +112,13 @@ def test_hist_bin( @pytest.mark.parametrize("params", counts_and_expected) @param_include_breakpoint -@param_name def test_hist_count( - name: EagerName, *, params: dict[str, Any], include_breakpoint: bool + nw_dataframe: DataFrameConstructor, + *, + params: dict[str, Any], + include_breakpoint: bool, ) -> None: - constructor_eager = maybe_name_to_constructor(name) - df = nw.from_native(constructor_eager(data)).with_columns( - float=nw.col("int").cast(nw.Float64) - ) + df = nw_dataframe(data, nw).with_columns(float=nw.col("int").cast(nw.Float64)) bin_count = params["bin_count"] expected_bins = params["expected_bins"] @@ -153,10 +138,8 @@ def test_hist_count( assert result["count"].sum() == df[col].count() # missing/nan results - df = nw.from_native( - constructor_eager( - {"has_nan": [float("nan"), *data["int"]], "has_null": [None, *data["int"]]} - ) + df = nw_dataframe( + {"has_nan": [float("nan"), *data["int"]], "has_null": [None, *data["int"]]}, nw ) for col in df.columns: @@ -171,11 +154,9 @@ def test_hist_count( ) -@param_name -def test_hist_count_no_spread(name: EagerName) -> None: - constructor_eager = maybe_name_to_constructor(name) +def test_hist_count_no_spread(nw_dataframe: DataFrameConstructor) -> None: data = {"all_zero": [0, 0, 0], "all_non_zero": [5, 5, 5]} - df = nw.from_native(constructor_eager(data)) + df = nw_dataframe(data, nw) result = df["all_zero"].hist(bin_count=4, include_breakpoint=True) expected = {"breakpoint": [-0.25, 0.0, 0.25, 0.5], "count": [0, 3, 0, 0]} @@ -203,12 +184,12 @@ def test_hist_bin_and_bin_count() -> None: @param_include_breakpoint -@param_name -def test_hist_no_data(name: EagerName, *, include_breakpoint: bool) -> None: - constructor_eager = maybe_name_to_constructor(name) - s = nw.from_native(constructor_eager({"values": []})).select( - nw.col("values").cast(nw.Float64) - )["values"] +def test_hist_no_data( + nw_dataframe: DataFrameConstructor, *, include_breakpoint: bool +) -> None: + s = nw_dataframe({"values": []}, nw).select(nw.col("values").cast(nw.Float64))[ + "values" + ] for bin_count in [1, 10]: result = s.hist(bin_count=bin_count, include_breakpoint=include_breakpoint) assert len(result) == bin_count @@ -225,10 +206,8 @@ def test_hist_no_data(name: EagerName, *, include_breakpoint: bool) -> None: assert result["count"].sum() == 0 -@param_name -def test_hist_small_bins(name: EagerName) -> None: - constructor_eager = maybe_name_to_constructor(name) - s = nw.from_native(constructor_eager({"values": [1, 2, 3]})) +def test_hist_small_bins(nw_dataframe: DataFrameConstructor) -> None: + s = nw_dataframe({"values": [1, 2, 3]}, nw) result = s["values"].hist(bins=None, bin_count=None) assert len(result) == 10 @@ -236,11 +215,11 @@ def test_hist_small_bins(name: EagerName) -> None: s["values"].hist(bins=[1, 3], bin_count=4) -def test_hist_non_monotonic(constructor_eager: ConstructorEager) -> None: - if "cudf" in str(constructor_eager): +def test_hist_non_monotonic(nw_dataframe: DataFrameConstructor) -> None: + if "cudf" in str(nw_dataframe): # TODO(unassigned): too many spurious failures, report and revisit return - df = nw.from_native(constructor_eager({"int": [0, 1, 2, 3, 4, 5, 6]})) + df = nw_dataframe({"int": [0, 1, 2, 3, 4, 5, 6]}, nw) with pytest.raises(ComputeError, match="monotonic"): df["int"].hist(bins=[5, 0, 2]) @@ -275,22 +254,17 @@ def test_hist_non_monotonic(constructor_eager: ConstructorEager) -> None: POLARS_VERSION < (1, 27), reason="polars cannot be used for compatibility checks since narwhals aims to mimic polars>=1.27 behavior", ) -@param_name @pytest.mark.filterwarnings("ignore:invalid value encountered in cast:RuntimeWarning") @pytest.mark.slow def test_hist_bin_hypotheis( - name: EagerName, data: list[float], bin_deltas: list[float] + nw_dataframe: DataFrameConstructor, data: list[float], bin_deltas: list[float] ) -> None: - constructor_eager = maybe_name_to_constructor(name) pytest.importorskip("polars") import polars as pl - df = nw.from_native(constructor_eager({"values": data})).select( - nw.col("values").cast(nw.Float64) - ) - df_bins_native = constructor_eager({"bins": bin_deltas}) + df = nw_dataframe({"values": data}, nw).select(nw.col("values").cast(nw.Float64)) bins = ( - nw.from_native(df_bins_native, eager_only=True) + nw_dataframe({"bins": bin_deltas}, nw) .get_column("bins") .cast(nw.Float64) .cum_sum() @@ -317,18 +291,17 @@ def test_hist_bin_hypotheis( reason="polars cannot be used for compatibility checks since narwhals aims to mimic polars>=1.27 behavior", ) @pytest.mark.filterwarnings("ignore:invalid value encountered in cast:RuntimeWarning") -@param_name @pytest.mark.slow def test_hist_count_hypothesis( - name: EagerName, data: list[float], bin_count: int, request: pytest.FixtureRequest + nw_dataframe: DataFrameConstructor, + data: list[float], + bin_count: int, + request: pytest.FixtureRequest, ) -> None: pytest.importorskip("polars") import polars as pl - constructor_eager = maybe_name_to_constructor(name) - df = nw.from_native(constructor_eager({"values": data})).select( - nw.col("values").cast(nw.Float64) - ) + df = nw_dataframe({"values": data}, nw).select(nw.col("values").cast(nw.Float64)) try: result = df["values"].hist(bin_count=bin_count, include_breakpoint=True) @@ -349,9 +322,7 @@ def test_hist_count_hypothesis( if expected[ "count" - ].sum() != expected_data.is_not_nan().sum() and "polars" not in str( - constructor_eager - ): + ].sum() != expected_data.is_not_nan().sum() and "polars" not in str(nw_dataframe): request.applymarker(pytest.mark.xfail) assert_equal_data(result, expected.to_dict(as_series=False)) diff --git a/tests/series_only/to_native_test.py b/tests/series_only/to_native_test.py index 350d81764d..c2a7ad5ecb 100644 --- a/tests/series_only/to_native_test.py +++ b/tests/series_only/to_native_test.py @@ -11,7 +11,7 @@ def test_to_native(constructor_eager: ConstructorEager) -> None: - orig_series = constructor_eager({"a": data})["a"] # type: ignore[index] + orig_series = constructor_eager({"a": data})["a"].to_native() nw_series = nw.from_native(constructor_eager({"a": data}), eager_only=True)["a"] result = nw_series.to_native() assert isinstance(result, orig_series.__class__) diff --git a/tests/testing/assert_frame_equal_test.py b/tests/testing/assert_frame_equal_test.py index 9b0f813b2f..5d2f187267 100644 --- a/tests/testing/assert_frame_equal_test.py +++ b/tests/testing/assert_frame_equal_test.py @@ -12,8 +12,8 @@ from tests.utils import PANDAS_VERSION if TYPE_CHECKING: + from narwhals.testing.typing import Data from narwhals.typing import IntoSchema - from tests.conftest import Data from tests.utils import Constructor, ConstructorEager @@ -24,12 +24,12 @@ def _assertion_error(detail: str) -> pytest.RaisesExc: def test_check_narwhals_objects(constructor: Constructor) -> None: """Test that a type error is raised if the input is not a Narwhals object.""" - frame = constructor({"a": [1, 2, 3]}) + frame = constructor({"a": [1, 2, 3]}).to_native() msg = re.escape( "Expected `narwhals.DataFrame` or `narwhals.LazyFrame` instance, found" ) with pytest.raises(TypeError, match=msg): - assert_frame_equal(frame, frame) # type: ignore[arg-type] + assert_frame_equal(frame, frame) def test_implementation_mismatch() -> None: diff --git a/tests/testing/assert_series_equal_test.py b/tests/testing/assert_series_equal_test.py index c4826c695e..064cc546dd 100644 --- a/tests/testing/assert_series_equal_test.py +++ b/tests/testing/assert_series_equal_test.py @@ -13,8 +13,8 @@ if TYPE_CHECKING: from typing_extensions import TypeAlias + from narwhals.testing.typing import Data from narwhals.typing import IntoSchema, IntoSeriesT - from tests.conftest import Data from tests.utils import ConstructorEager SetupFn: TypeAlias = Callable[[nw.Series[Any]], tuple[nw.Series[Any], nw.Series[Any]]] @@ -406,7 +406,7 @@ def test_categorical_as_str( "left": ["beluga", "dolphin", "narwhal", "orca"], "right": ["unicorn", "orca", "narwhal", "orca"], } - frame = nw.from_native(constructor_eager(data), eager_only=True) + frame = constructor_eager(data, namespace=nw) left = frame["left"].cast(nw.Categorical())[2:] right = frame["right"].cast(nw.Categorical())[2:] diff --git a/tests/testing/conftest.py b/tests/testing/conftest.py index a41d4fdce4..0ff5e9935f 100644 --- a/tests/testing/conftest.py +++ b/tests/testing/conftest.py @@ -8,8 +8,8 @@ import narwhals as nw if TYPE_CHECKING: + from narwhals.testing.typing import Data from narwhals.typing import IntoSchema - from tests.conftest import Data @pytest.fixture(scope="module") diff --git a/tests/testing/constructors_test.py b/tests/testing/constructors_test.py index af22cee8a1..3520d492da 100644 --- a/tests/testing/constructors_test.py +++ b/tests/testing/constructors_test.py @@ -25,7 +25,7 @@ def test_eager_returns_eager_frame() -> None: if not c.is_available: pytest.skip() - df = nw.from_native(c({"x": [1, 2, 3]})) + df = c({"x": [1, 2, 3]}, nw) assert isinstance(df, nw.DataFrame) @@ -34,7 +34,7 @@ def test_lazy_returns_lazy_frame() -> None: if not c.is_available: pytest.skip() - lf = nw.from_native(c({"x": [1, 2, 3]})) + lf = c({"x": [1, 2, 3]}, nw) assert isinstance(lf, nw.LazyFrame) diff --git a/tests/testing/plugin_test.py b/tests/testing/plugin_test.py index 9f7b6e45a8..2c49818047 100644 --- a/tests/testing/plugin_test.py +++ b/tests/testing/plugin_test.py @@ -18,7 +18,7 @@ def test_constructor_eager_fixture_runs_for_each_backend( from narwhals.testing.typing import DataFrameConstructor def test_shape(nw_dataframe: DataFrameConstructor) -> None: - df = nw.from_native(nw_dataframe({"x": [1, 2, 3]}), eager_only=True) + df = nw_dataframe({"x": [1, 2, 3]}, namespace=nw) assert df.shape == (3, 1) """) result = pytester.runpytest_subprocess( @@ -45,7 +45,7 @@ def test_constructor_fixture_includes_lazy_backends(pytester: pytest.Pytester) - from narwhals.testing.typing import FrameConstructor def test_columns(nw_frame: FrameConstructor) -> None: - df = nw.from_native(nw_frame({"x": [1, 2, 3]})) + df = nw_frame({"x": [1, 2, 3]}, namespace=nw) assert df.collect_schema().names() == ["x"] """) result = pytester.runpytest_subprocess( diff --git a/tests/translate/from_native_test.py b/tests/translate/from_native_test.py index a0d94b99fb..5d7121fa1e 100644 --- a/tests/translate/from_native_test.py +++ b/tests/translate/from_native_test.py @@ -294,7 +294,7 @@ def test_eager_only_lazy_dask(eager_only: Any, context: Any) -> None: def test_series_only_sqlframe() -> None: # pragma: no cover pytest.importorskip("sqlframe") - df = get_backend_constructor("sqlframe")(data) + df = get_backend_constructor("sqlframe")(data, nw).to_native() with pytest.raises(TypeError, match="Cannot only use `series_only`"): nw.from_native(df, series_only=True) # type: ignore[call-overload] @@ -315,7 +315,7 @@ def test_series_only_sqlframe() -> None: # pragma: no cover ) def test_eager_only_sqlframe(eager_only: Any, context: Any) -> None: # pragma: no cover pytest.importorskip("sqlframe") - df = get_backend_constructor("sqlframe")(data) + df = get_backend_constructor("sqlframe")(data, nw).to_native() with context: res = nw.from_native(df, eager_only=eager_only) @@ -528,7 +528,7 @@ def test_eager_only_pass_through_main(constructor: Constructor) -> None: if not any(s in str(constructor) for s in ("pyspark", "dask", "ibis", "duckdb")): pytest.skip(reason="Non lazy or polars") - df = constructor(data) + df = constructor(data).to_native() r1 = nw.from_native(df, eager_only=False, pass_through=False) r2 = nw.from_native(df, eager_only=False, pass_through=True) diff --git a/tests/utils.py b/tests/utils.py index 3281951896..5b53e3a49b 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -6,20 +6,22 @@ import warnings from datetime import date, datetime from pathlib import Path -from typing import TYPE_CHECKING, Any, Callable +from typing import TYPE_CHECKING, Any import pytest import narwhals as nw from narwhals._utils import Implementation, parse_version, zip_strict from narwhals.dependencies import get_pandas -from narwhals.testing.typing import ( - # TODO(FBruzzesi): Remove these aliases once all the test suite migrates to *FrameConstructor's - DataFrameConstructor as ConstructorEager, - FrameConstructor as Constructor, -) from narwhals.translate import from_native +# TODO(FBruzzesi): Replace these aliases once all the test suite migrates to *FrameConstructor's +from tests.conftest import ( + _PatchedDataFrameConstructor as ConstructorEager, + _PatchedDataFrameConstructor as ConstructorPandasLike, + _PatchedFrameConstructor as Constructor, +) + if TYPE_CHECKING: from collections.abc import Mapping, Sequence @@ -31,7 +33,7 @@ # TODO(FBruzzesi): Remove these aliases once all the test suite migrates to *FrameConstructor's # NOTE: Explicitly exported otherwise mypy will raise an [attr-defined] error for each file # importing them from `tests.utils` rather than `narwhals.testing.typing` directly. -__all__ = ("Constructor", "ConstructorEager") +__all__ = ("Constructor", "ConstructorEager", "ConstructorPandasLike") def get_module_version_as_tuple(module_name: str) -> tuple[int, ...]: @@ -51,8 +53,6 @@ def get_module_version_as_tuple(module_name: str) -> tuple[int, ...]: PYSPARK_VERSION: tuple[int, ...] = get_module_version_as_tuple("pyspark") CUDF_VERSION: tuple[int, ...] = get_module_version_as_tuple("cudf") -ConstructorPandasLike: TypeAlias = Callable[[Any], "pd.DataFrame"] - NestedOrEnumDType: TypeAlias = "nw.List | nw.Array | nw.Struct | nw.Enum" """`DType`s which **cannot** be used as bare types.""" diff --git a/tests/v1_test.py b/tests/v1_test.py index 9882c4ed15..8ddb64a118 100644 --- a/tests/v1_test.py +++ b/tests/v1_test.py @@ -318,11 +318,11 @@ def test_cast_to_enum_v1( ): request.applymarker(pytest.mark.xfail) - df_native = constructor({"a": ["a", "b"]}) + df = constructor({"a": ["a", "b"]}, nw_v1) msg = re.escape("Converting to Enum is not supported in narwhals.stable.v1") with pytest.raises(NotImplementedError, match=msg): - nw_v1.from_native(df_native).select(nw_v1.col("a").cast(nw_v1.Enum)) # type: ignore[arg-type] + df.select(nw_v1.col("a").cast(nw_v1.Enum)) # type: ignore[arg-type] def test_v1_ordered_categorical_pandas() -> None: @@ -459,7 +459,7 @@ def test_with_row_index(constructor: Constructor) -> None: pytest.skip() data = {"abc": ["foo", "bars"], "xyz": [100, 200], "const": [42, 42]} - frame = nw_v1.from_native(constructor(data)) + frame = constructor(data, nw_v1) msg = "Cannot pass `order_by`" context = ( @@ -469,7 +469,7 @@ def test_with_row_index(constructor: Constructor) -> None: ) with context: - result = frame.with_row_index() + result = frame.with_row_index() # type: ignore[call-arg] expected = {"index": [0, 1], **data} assert_equal_data(result, expected) @@ -887,7 +887,7 @@ def test_is_frame() -> None: def test_with_version(constructor: Constructor) -> None: - lf = nw_v1.from_native(constructor({"a": [1, 2]})).lazy() + lf = constructor({"a": [1, 2]}, nw_v1).lazy() assert isinstance(lf, nw_v1.LazyFrame) assert lf._compliant_frame._with_version(Version.MAIN)._version is Version.MAIN @@ -896,7 +896,7 @@ def test_with_version(constructor: Constructor) -> None: @pytest.mark.parametrize("offset", [1, 2]) def test_gather_every(constructor_eager: ConstructorEager, n: int, offset: int) -> None: data = {"a": list(range(10))} - df_v1 = nw_v1.from_native(constructor_eager(data)) + df_v1 = constructor_eager(data, nw_v1) result = df_v1.gather_every(n=n, offset=offset) expected = {"a": data["a"][offset::n]} assert_equal_data(result, expected) @@ -1156,7 +1156,7 @@ def test_series_from_iterable( def test_mode_single_expr(constructor_eager: ConstructorEager) -> None: data = {"a": [1, 1, 2, 2, 3], "b": [1, 2, 3, 3, 4]} - df = nw_v1.from_native(constructor_eager(data)) + df = constructor_eager(data, nw_v1) result = df.select(nw_v1.col("a").mode()).sort("a") expected = {"a": [1, 2]} assert_equal_data(result, expected) @@ -1164,7 +1164,7 @@ def test_mode_single_expr(constructor_eager: ConstructorEager) -> None: def test_mode_series(constructor_eager: ConstructorEager) -> None: data = {"a": [1, 1, 2, 2, 3], "b": [1, 2, 3, 3, 4]} - series = nw_v1.from_native(constructor_eager(data), eager_only=True)["a"] + series = constructor_eager(data, nw_v1)["a"] result = series.mode().sort() expected = {"a": [1, 2]} assert_equal_data({"a": result}, expected) @@ -1173,7 +1173,7 @@ def test_mode_series(constructor_eager: ConstructorEager) -> None: def test_mode_different_lengths(constructor_eager: ConstructorEager) -> None: if "polars" in str(constructor_eager) and POLARS_VERSION < (1, 10): pytest.skip() - df = nw_v1.from_native(constructor_eager({"a": [1, 1, 2], "b": [4, 5, 6]})) + df = constructor_eager({"a": [1, 1, 2], "b": [4, 5, 6]}, nw_v1) with pytest.raises(ShapeError): df.select(nw_v1.col("a", "b").mode()) @@ -1196,7 +1196,7 @@ def test_any_value_expr(constructor: Constructor, request: pytest.FixtureRequest "b": [1, 2, 3, 4, 5, 6], "c": [None, None, 1, None, 2, None], } - df = nw_v1.from_native(constructor(data)) + df = constructor(data, nw_v1) with pytest.warns(NarwhalsUnstableWarning): df.select(nw_v1.col("a", "b").any_value()) @@ -1204,7 +1204,7 @@ def test_any_value_expr(constructor: Constructor, request: pytest.FixtureRequest def test_any_value_series(constructor_eager: ConstructorEager) -> None: data = {"a": [1, 1, 1, 2, 2, 3]} - df = nw_v1.from_native(constructor_eager(data)) + df = constructor_eager(data, nw_v1) with pytest.warns(NarwhalsUnstableWarning): df["a"].any_value() diff --git a/tests/v2_test.py b/tests/v2_test.py index 7a1903425c..d33ae97edb 100644 --- a/tests/v2_test.py +++ b/tests/v2_test.py @@ -347,7 +347,7 @@ def fun2(self, df: Any) -> Any: # pragma: no cover def test_with_version(constructor: Constructor) -> None: - lf = nw_v2.from_native(constructor({"a": [1, 2]})).lazy() + lf = constructor({"a": [1, 2]}, nw_v2).lazy() assert isinstance(lf, nw_v2.LazyFrame) assert lf._compliant_frame._with_version(Version.MAIN)._version is Version.MAIN @@ -503,7 +503,7 @@ def test_series_from_iterable( def test_mode_single_expr(constructor_eager: ConstructorEager) -> None: data = {"a": [1, 1, 2, 2, 3], "b": [1, 2, 3, 3, 4]} - df = nw_v2.from_native(constructor_eager(data)) + df = constructor_eager(data, nw_v2) result = df.select(nw_v2.col("a").mode()).sort("a") expected = {"a": [1, 2]} assert_equal_data(result, expected) @@ -511,7 +511,7 @@ def test_mode_single_expr(constructor_eager: ConstructorEager) -> None: def test_mode_series(constructor_eager: ConstructorEager) -> None: data = {"a": [1, 1, 2, 2, 3], "b": [1, 2, 3, 3, 4]} - series = nw_v2.from_native(constructor_eager(data), eager_only=True)["a"] + series = constructor_eager(data, nw_v2)["a"] result = series.mode().sort() expected = {"a": [1, 2]} assert_equal_data({"a": result}, expected) @@ -520,7 +520,7 @@ def test_mode_series(constructor_eager: ConstructorEager) -> None: def test_mode_different_lengths(constructor_eager: ConstructorEager) -> None: if "polars" in str(constructor_eager) and POLARS_VERSION < (1, 10): pytest.skip() - df = nw_v2.from_native(constructor_eager({"a": [1, 1, 2], "b": [4, 5, 6]})) + df = constructor_eager({"a": [1, 1, 2], "b": [4, 5, 6]}, nw_v2) with pytest.raises(ShapeError): df.select(nw_v2.col("a", "b").mode()) @@ -535,7 +535,7 @@ def test_any_value_expr(constructor: Constructor, request: pytest.FixtureRequest "b": [1, 2, 3, 4, 5, 6], "c": [None, None, 1, None, 2, None], } - df = nw_v2.from_native(constructor(data)) + df = constructor(data, nw_v2) with pytest.warns(NarwhalsUnstableWarning): df.select(nw_v2.col("a", "b").any_value()) @@ -543,7 +543,7 @@ def test_any_value_expr(constructor: Constructor, request: pytest.FixtureRequest def test_any_value_series(constructor_eager: ConstructorEager) -> None: data = {"a": [1, 1, 1, 2, 2, 3]} - df = nw_v2.from_native(constructor_eager(data)) + df = constructor_eager(data, nw_v2) with pytest.warns(NarwhalsUnstableWarning): df["a"].any_value()