From c19be3b7acb968bf85e5aa26453178544f2df19a Mon Sep 17 00:00:00 2001 From: Liam Connors Date: Thu, 17 Oct 2024 05:59:23 -0400 Subject: [PATCH 1/8] test: update cuDF tests (#1196) * unxfail test_to_dummies_drop_first * xfail timezone tests for cuDF * fix typo --- .../expr_and_series/convert_time_zone_test.py | 20 +++++++++++++------ .../expr_and_series/replace_time_zone_test.py | 2 ++ tests/series_only/to_dummy_test.py | 6 +----- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/tests/expr_and_series/convert_time_zone_test.py b/tests/expr_and_series/convert_time_zone_test.py index ee4ccaec4..d85637a14 100644 --- a/tests/expr_and_series/convert_time_zone_test.py +++ b/tests/expr_and_series/convert_time_zone_test.py @@ -17,8 +17,13 @@ def test_convert_time_zone( constructor: Constructor, request: pytest.FixtureRequest ) -> None: - if (any(x in str(constructor) for x in ("pyarrow", "modin")) and is_windows()) or ( - "pandas_pyarrow" in str(constructor) and parse_version(pd.__version__) < (2, 1) + if ( + (any(x in str(constructor) for x in ("pyarrow", "modin")) and is_windows()) + or ( + "pandas_pyarrow" in str(constructor) + and parse_version(pd.__version__) < (2, 1) + ) + or ("cudf" in str(constructor)) ): request.applymarker(pytest.mark.xfail) data = { @@ -41,10 +46,12 @@ def test_convert_time_zone_series( constructor_eager: Any, request: pytest.FixtureRequest ) -> None: if ( - any(x in str(constructor_eager) for x in ("pyarrow", "modin")) and is_windows() - ) or ( - "pandas_pyarrow" in str(constructor_eager) - and parse_version(pd.__version__) < (2, 1) + (any(x in str(constructor_eager) for x in ("pyarrow", "modin")) and is_windows()) + or ( + "pandas_pyarrow" in str(constructor_eager) + and parse_version(pd.__version__) < (2, 1) + ) + or ("cudf" in str(constructor_eager)) ): request.applymarker(pytest.mark.xfail) data = { @@ -73,6 +80,7 @@ def test_convert_time_zone_from_none( and parse_version(pd.__version__) < (2, 1) ) or ("pyarrow_table" in str(constructor) and parse_version(pa.__version__) < (12,)) + or ("cudf" in str(constructor)) ): request.applymarker(pytest.mark.xfail) if "polars" in str(constructor) and parse_version(pl.__version__) < (0, 20, 7): diff --git a/tests/expr_and_series/replace_time_zone_test.py b/tests/expr_and_series/replace_time_zone_test.py index 560fcfe84..4954875f0 100644 --- a/tests/expr_and_series/replace_time_zone_test.py +++ b/tests/expr_and_series/replace_time_zone_test.py @@ -20,6 +20,7 @@ def test_replace_time_zone( (any(x in str(constructor) for x in ("pyarrow", "modin")) and is_windows()) or ("pandas_pyarrow" in str(constructor) and parse_version(pd.__version__) < (2,)) or ("pyarrow_table" in str(constructor) and parse_version(pa.__version__) < (12,)) + or ("cudf" in str(constructor)) ): request.applymarker(pytest.mark.xfail) data = { @@ -76,6 +77,7 @@ def test_replace_time_zone_series( "pyarrow_table" in str(constructor_eager) and parse_version(pa.__version__) < (12,) ) + or ("cudf" in str(constructor_eager)) ): request.applymarker(pytest.mark.xfail) data = { diff --git a/tests/series_only/to_dummy_test.py b/tests/series_only/to_dummy_test.py index c3d57b9ad..2cf7f59c7 100644 --- a/tests/series_only/to_dummy_test.py +++ b/tests/series_only/to_dummy_test.py @@ -18,11 +18,7 @@ def test_to_dummies(constructor_eager: Any, sep: str) -> None: @pytest.mark.parametrize("sep", ["_", "-"]) -def test_to_dummies_drop_first( - request: pytest.FixtureRequest, constructor_eager: Any, sep: str -) -> None: - if "cudf" in str(constructor_eager): - request.applymarker(pytest.mark.xfail) +def test_to_dummies_drop_first(constructor_eager: Any, sep: str) -> None: s = nw.from_native(constructor_eager({"a": data}), eager_only=True)["a"].alias("a") result = s.to_dummies(drop_first=True, separator=sep) expected = {f"a{sep}2": [0, 1, 0], f"a{sep}3": [0, 0, 1]} From 2a406cf6a7908513f60df89c192fca1d06690e38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dea=20Mar=C3=ADa=20L=C3=A9on?= Date: Thu, 17 Oct 2024 12:03:33 +0200 Subject: [PATCH 2/8] feat: Adding allow inspecting inner fields of nw.Struct (#1192) --- docs/api-reference/dtypes.md | 3 +- narwhals/__init__.py | 2 + narwhals/_arrow/utils.py | 11 ++++- narwhals/_duckdb/dataframe.py | 11 ++++- narwhals/_ibis/dataframe.py | 10 +++- narwhals/_pandas_like/utils.py | 2 +- narwhals/_polars/utils.py | 7 ++- narwhals/dtypes.py | 84 +++++++++++++++++++++++++++++++++- narwhals/stable/v1/__init__.py | 2 + narwhals/stable/v1/_dtypes.py | 2 + narwhals/stable/v1/dtypes.py | 2 + narwhals/stable/v1/typing.py | 1 + narwhals/typing.py | 1 + tests/dtypes_test.py | 37 +++++++++++++++ tests/frame/schema_test.py | 36 +++++++++++---- utils/check_api_reference.py | 9 +++- 16 files changed, 203 insertions(+), 17 deletions(-) diff --git a/docs/api-reference/dtypes.md b/docs/api-reference/dtypes.md index eb96608a6..77bf1266b 100644 --- a/docs/api-reference/dtypes.md +++ b/docs/api-reference/dtypes.md @@ -6,7 +6,6 @@ members: - Array - List - - Struct - Int64 - Int32 - Int16 @@ -15,12 +14,14 @@ - UInt32 - UInt16 - UInt8 + - Field - Float64 - Float32 - Boolean - Categorical - Enum - String + - Struct - Date - Datetime - Duration diff --git a/narwhals/__init__.py b/narwhals/__init__.py index 124f10c45..3a327aad4 100644 --- a/narwhals/__init__.py +++ b/narwhals/__init__.py @@ -10,6 +10,7 @@ from narwhals.dtypes import Datetime from narwhals.dtypes import Duration from narwhals.dtypes import Enum +from narwhals.dtypes import Field from narwhals.dtypes import Float32 from narwhals.dtypes import Float64 from narwhals.dtypes import Int8 @@ -118,6 +119,7 @@ "String", "Datetime", "Duration", + "Field", "Struct", "Array", "List", diff --git a/narwhals/_arrow/utils.py b/narwhals/_arrow/utils.py index e37cb093f..7f6fa6558 100644 --- a/narwhals/_arrow/utils.py +++ b/narwhals/_arrow/utils.py @@ -56,7 +56,16 @@ def native_to_narwhals_dtype(dtype: Any, dtypes: DTypes) -> DType: if pa.types.is_dictionary(dtype): return dtypes.Categorical() if pa.types.is_struct(dtype): - return dtypes.Struct() + return dtypes.Struct( + [ + dtypes.Field( + dtype.field(i).name, + native_to_narwhals_dtype(dtype.field(i).type, dtypes), + ) + for i in range(dtype.num_fields) + ] + ) + if pa.types.is_list(dtype) or pa.types.is_large_list(dtype): return dtypes.List(native_to_narwhals_dtype(dtype.value_type, dtypes)) if pa.types.is_fixed_size_list(dtype): diff --git a/narwhals/_duckdb/dataframe.py b/narwhals/_duckdb/dataframe.py index 5877ed51e..82ac6d41b 100644 --- a/narwhals/_duckdb/dataframe.py +++ b/narwhals/_duckdb/dataframe.py @@ -52,7 +52,16 @@ def map_duckdb_dtype_to_narwhals_dtype(duckdb_dtype: Any, dtypes: DTypes) -> DTy if duckdb_dtype == "INTERVAL": return dtypes.Duration() if duckdb_dtype.startswith("STRUCT"): - return dtypes.Struct() + matchstruc_ = re.findall(r"(\w+)\s+(\w+)", duckdb_dtype) + return dtypes.Struct( + [ + dtypes.Field( + matchstruc_[i][0], + map_duckdb_dtype_to_narwhals_dtype(matchstruc_[i][1], dtypes), + ) + for i in range(len(matchstruc_)) + ] + ) if match_ := re.match(r"(.*)\[\]$", duckdb_dtype): return dtypes.List(map_duckdb_dtype_to_narwhals_dtype(match_.group(1), dtypes)) if match_ := re.match(r"(\w+)\[(\d+)\]", duckdb_dtype): diff --git a/narwhals/_ibis/dataframe.py b/narwhals/_ibis/dataframe.py index 9d7ebefb0..a9c3a49fa 100644 --- a/narwhals/_ibis/dataframe.py +++ b/narwhals/_ibis/dataframe.py @@ -51,7 +51,15 @@ def map_ibis_dtype_to_narwhals_dtype(ibis_dtype: Any, dtypes: DTypes) -> DType: map_ibis_dtype_to_narwhals_dtype(ibis_dtype.value_type, dtypes) ) if ibis_dtype.is_struct(): - return dtypes.Struct() + return dtypes.Struct( + [ + dtypes.Field( + ibis_dtype_name, + map_ibis_dtype_to_narwhals_dtype(ibis_dtype_field, dtypes), + ) + for ibis_dtype_name, ibis_dtype_field in ibis_dtype.items() + ] + ) return dtypes.Unknown() # pragma: no cover diff --git a/narwhals/_pandas_like/utils.py b/narwhals/_pandas_like/utils.py index 381a78c8d..0773764d9 100644 --- a/narwhals/_pandas_like/utils.py +++ b/narwhals/_pandas_like/utils.py @@ -294,7 +294,7 @@ def native_to_narwhals_dtype(native_column: Any, dtypes: DTypes) -> DType: native_column.dtype.pyarrow_dtype.list_size, ) if dtype.startswith("struct"): - return dtypes.Struct() + return arrow_native_to_narwhals_dtype(native_column.dtype.pyarrow_dtype, dtypes) if dtype == "object": if ( # pragma: no cover TODO(unassigned): why does this show as uncovered? idx := getattr(native_column, "first_valid_index", lambda: None)() diff --git a/narwhals/_polars/utils.py b/narwhals/_polars/utils.py index d44535cc7..fe63f515f 100644 --- a/narwhals/_polars/utils.py +++ b/narwhals/_polars/utils.py @@ -75,7 +75,12 @@ def native_to_narwhals_dtype(dtype: Any, dtypes: DTypes) -> DType: du_time_unit: Literal["us", "ns", "ms"] = getattr(dtype, "time_unit", "us") return dtypes.Duration(time_unit=du_time_unit) if dtype == pl.Struct: - return dtypes.Struct() + return dtypes.Struct( + [ + dtypes.Field(field_name, native_to_narwhals_dtype(field_type, dtypes)) + for field_name, field_type in dtype + ] + ) if dtype == pl.List: return dtypes.List(native_to_narwhals_dtype(dtype.inner, dtypes)) if dtype == pl.Array: diff --git a/narwhals/dtypes.py b/narwhals/dtypes.py index 98d8c6914..73a77af1f 100644 --- a/narwhals/dtypes.py +++ b/narwhals/dtypes.py @@ -1,10 +1,15 @@ from __future__ import annotations +from collections import OrderedDict from datetime import timezone from typing import TYPE_CHECKING -from typing import Literal +from typing import Mapping if TYPE_CHECKING: + from typing import Iterator + from typing import Literal + from typing import Sequence + from typing_extensions import Self @@ -170,7 +175,82 @@ class Categorical(DType): ... class Enum(DType): ... -class Struct(DType): ... +class Field: + """ + Definition of a single field within a `Struct` DataType. + + Arguments: + name: The name of the field within its parent `Struct`. + dtype: The `DataType` of the field's values. + + """ + + name: str + dtype: type[DType] | DType + + def __init__(self, name: str, dtype: type[DType] | DType) -> None: + self.name = name + self.dtype = dtype + + def __eq__(self, other: Field) -> bool: # type: ignore[override] + return (self.name == other.name) & (self.dtype == other.dtype) + + def __hash__(self) -> int: + return hash((self.name, self.dtype)) + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + return f"{class_name}({self.name!r}, {self.dtype})" + + +class Struct(DType): + """ + Struct composite type. + + Arguments: + fields: The fields that make up the struct. Can be either a sequence of Field objects or a mapping of column names to data types. + """ + + fields: list[Field] + + def __init__( + self, fields: Sequence[Field] | Mapping[str, DType | type[DType]] + ) -> None: + if isinstance(fields, Mapping): + self.fields = [Field(name, dtype) for name, dtype in fields.items()] + else: + self.fields = list(fields) + + def __eq__(self, other: DType | type[DType]) -> bool: # type: ignore[override] + # The comparison allows comparing objects to classes, and specific + # inner types to those without (eg: inner=None). if one of the + # arguments is not specific about its inner type we infer it + # as being equal. (See the List type for more info). + if type(other) is type and issubclass(other, self.__class__): + return True + elif isinstance(other, self.__class__): + return self.fields == other.fields + else: + return False + + def __hash__(self) -> int: + return hash((self.__class__, tuple(self.fields))) + + def __iter__(self) -> Iterator[tuple[str, DType | type[DType]]]: + for fld in self.fields: + yield fld.name, fld.dtype + + def __reversed__(self) -> Iterator[tuple[str, DType | type[DType]]]: + for fld in reversed(self.fields): + yield fld.name, fld.dtype + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + return f"{class_name}({dict(self)})" + + def to_schema(self) -> OrderedDict[str, DType | type[DType]]: + """Return Struct dtype as a schema dict.""" + return OrderedDict(self) class List(DType): diff --git a/narwhals/stable/v1/__init__.py b/narwhals/stable/v1/__init__.py index 86ddd1def..93406a145 100644 --- a/narwhals/stable/v1/__init__.py +++ b/narwhals/stable/v1/__init__.py @@ -31,6 +31,7 @@ from narwhals.stable.v1.dtypes import Datetime from narwhals.stable.v1.dtypes import Duration from narwhals.stable.v1.dtypes import Enum +from narwhals.stable.v1.dtypes import Field from narwhals.stable.v1.dtypes import Float32 from narwhals.stable.v1.dtypes import Float64 from narwhals.stable.v1.dtypes import Int8 @@ -2296,6 +2297,7 @@ def from_dict( "String", "Datetime", "Duration", + "Field", "Struct", "Array", "List", diff --git a/narwhals/stable/v1/_dtypes.py b/narwhals/stable/v1/_dtypes.py index 13dd3237d..84c9adc90 100644 --- a/narwhals/stable/v1/_dtypes.py +++ b/narwhals/stable/v1/_dtypes.py @@ -6,6 +6,7 @@ from narwhals.dtypes import DType from narwhals.dtypes import Duration as NwDuration from narwhals.dtypes import Enum +from narwhals.dtypes import Field from narwhals.dtypes import Float32 from narwhals.dtypes import Float64 from narwhals.dtypes import Int8 @@ -77,6 +78,7 @@ def __hash__(self) -> int: "NumericType", "Object", "String", + "Field", "Struct", "UInt8", "UInt16", diff --git a/narwhals/stable/v1/dtypes.py b/narwhals/stable/v1/dtypes.py index f36da9725..21bd1c5ed 100644 --- a/narwhals/stable/v1/dtypes.py +++ b/narwhals/stable/v1/dtypes.py @@ -6,6 +6,7 @@ from narwhals.stable.v1._dtypes import DType from narwhals.stable.v1._dtypes import Duration from narwhals.stable.v1._dtypes import Enum +from narwhals.stable.v1._dtypes import Field from narwhals.stable.v1._dtypes import Float32 from narwhals.stable.v1._dtypes import Float64 from narwhals.stable.v1._dtypes import Int8 @@ -34,6 +35,7 @@ "Enum", "Float32", "Float64", + "Field", "Int8", "Int16", "Int32", diff --git a/narwhals/stable/v1/typing.py b/narwhals/stable/v1/typing.py index e8ab9e1ae..aebe78fc7 100644 --- a/narwhals/stable/v1/typing.py +++ b/narwhals/stable/v1/typing.py @@ -73,6 +73,7 @@ class DTypes: Datetime: type[dtypes.Datetime] Duration: type[dtypes.Duration] Date: type[dtypes.Date] + Field: type[dtypes.Field] Struct: type[dtypes.Struct] List: type[dtypes.List] Array: type[dtypes.Array] diff --git a/narwhals/typing.py b/narwhals/typing.py index 30de0a097..8fcbc697c 100644 --- a/narwhals/typing.py +++ b/narwhals/typing.py @@ -73,6 +73,7 @@ class DTypes: Datetime: type[dtypes.Datetime] Duration: type[dtypes.Duration] Date: type[dtypes.Date] + Field: type[dtypes.Field] Struct: type[dtypes.Struct] List: type[dtypes.List] Array: type[dtypes.Array] diff --git a/tests/dtypes_test.py b/tests/dtypes_test.py index c35507873..b2006f6c1 100644 --- a/tests/dtypes_test.py +++ b/tests/dtypes_test.py @@ -87,6 +87,43 @@ def test_array_valid() -> None: dtype = nw.Array(nw.Int64) +def test_struct_valid() -> None: + dtype = nw.Struct([nw.Field("a", nw.Int64)]) + assert dtype == nw.Struct([nw.Field("a", nw.Int64)]) + assert dtype == nw.Struct + assert dtype != nw.Struct([nw.Field("a", nw.Float32)]) + assert dtype != nw.Duration + assert repr(dtype) == "Struct({'a': })" + + dtype = nw.Struct({"a": nw.Int64, "b": nw.String}) + assert dtype == nw.Struct({"a": nw.Int64, "b": nw.String}) + assert dtype.to_schema() == nw.Struct({"a": nw.Int64, "b": nw.String}).to_schema() + assert dtype == nw.Struct + assert dtype != nw.Struct({"a": nw.Int32, "b": nw.String}) + assert dtype in {nw.Struct({"a": nw.Int64, "b": nw.String})} + + +def test_struct_reverse() -> None: + dtype1 = nw.Struct({"a": nw.Int64, "b": nw.String}) + dtype1_reversed = nw.Struct([nw.Field(*field) for field in reversed(dtype1)]) + dtype2 = nw.Struct({"b": nw.String, "a": nw.Int64}) + assert dtype1_reversed == dtype2 + + +def test_field_repr() -> None: + dtype = nw.Field("a", nw.Int32) + assert repr(dtype) == "Field('a', )" + + +def test_struct_hashes() -> None: + dtypes = ( + nw.Struct, + nw.Struct([nw.Field("a", nw.Int64)]), + nw.Struct([nw.Field("a", nw.Int64), nw.Field("b", nw.List(nw.Int64))]), + ) + assert len({hash(tp) for tp in (dtypes)}) == 3 + + @pytest.mark.skipif( parse_version(pl.__version__) < (1,) or parse_version(pd.__version__) < (2, 2), reason="`shape` is only available after 1.0", diff --git a/tests/frame/schema_test.py b/tests/frame/schema_test.py index cb5ddff19..8e1116997 100644 --- a/tests/frame/schema_test.py +++ b/tests/frame/schema_test.py @@ -213,23 +213,39 @@ def test_nested_dtypes() -> None: schema_overrides={"b": pl.Array(pl.Int64, 2)}, ).to_pandas(use_pyarrow_extension_array=True) nwdf = nw.from_native(df) - - assert nwdf.schema == {"a": nw.List, "b": nw.Array, "c": nw.Struct} + assert nwdf.schema == { + "a": nw.List(nw.Int64), + "b": nw.Array(nw.Int64, 2), + "c": nw.Struct({"a": nw.Int64}), + } df = pl.DataFrame( {"a": [[1, 2]], "b": [[1, 2]], "c": [{"a": 1}]}, schema_overrides={"b": pl.Array(pl.Int64, 2)}, ) nwdf = nw.from_native(df) - assert nwdf.schema == {"a": nw.List, "b": nw.Array(nw.Int64, 2), "c": nw.Struct} + assert nwdf.schema == { + "a": nw.List(nw.Int64), + "b": nw.Array(nw.Int64, 2), + "c": nw.Struct({"a": nw.Int64}), + } + df = pl.DataFrame( - {"a": [[1, 2]], "b": [[1, 2]], "c": [{"a": 1}]}, + {"a": [[1, 2]], "b": [[1, 2]], "c": [{"a": 1, "b": "x", "c": 1.1}]}, schema_overrides={"b": pl.Array(pl.Int64, 2)}, ).to_arrow() nwdf = nw.from_native(df) - assert nwdf.schema == {"a": nw.List, "b": nw.Array(nw.Int64, 2), "c": nw.Struct} + assert nwdf.schema == { + "a": nw.List(nw.Int64), + "b": nw.Array(nw.Int64, 2), + "c": nw.Struct({"a": nw.Int64, "b": nw.String, "c": nw.Float64}), + } df = duckdb.sql("select * from df") nwdf = nw.from_native(df) - assert nwdf.schema == {"a": nw.List, "b": nw.Array(nw.Int64, 2), "c": nw.Struct} + assert nwdf.schema == { + "a": nw.List(nw.Int64), + "b": nw.Array(nw.Int64, 2), + "c": nw.Struct({"a": nw.Int64, "b": nw.String, "c": nw.Float64}), + } def test_nested_dtypes_ibis() -> None: # pragma: no cover @@ -240,7 +256,7 @@ def test_nested_dtypes_ibis() -> None: # pragma: no cover ) tbl = ibis.memtable(df[["a", "c"]]) nwdf = nw.from_native(tbl) - assert nwdf.schema == {"a": nw.List, "c": nw.Struct} + assert nwdf.schema == {"a": nw.List(nw.Int64), "c": nw.Struct({"a": nw.Int64})} @pytest.mark.skipif( @@ -259,4 +275,8 @@ def test_nested_dtypes_dask() -> None: ).to_pandas(use_pyarrow_extension_array=True) ) nwdf = nw.from_native(df) - assert nwdf.schema == {"a": nw.List, "b": nw.Array, "c": nw.Struct} + assert nwdf.schema == { + "a": nw.List(nw.Int64), + "b": nw.Array(nw.Int64, 2), + "c": nw.Struct({"a": nw.Int64}), + } diff --git a/utils/check_api_reference.py b/utils/check_api_reference.py index 69c310439..e3aa0fb91 100644 --- a/utils/check_api_reference.py +++ b/utils/check_api_reference.py @@ -31,7 +31,14 @@ "zip_with", "__iter__", } -BASE_DTYPES = {"NumericType", "DType", "TemporalType", "Literal"} +BASE_DTYPES = { + "NumericType", + "DType", + "TemporalType", + "Literal", + "OrderedDict", + "Mapping", +} files = {remove_suffix(i, ".py") for i in os.listdir("narwhals")} From 58973a0147b1ff6e58e97704ae668be4fa40034a Mon Sep 17 00:00:00 2001 From: raisadz <34237447+raisadz@users.noreply.github.com> Date: Thu, 17 Oct 2024 11:08:18 +0100 Subject: [PATCH 3/8] docs: Update CONTRIBUTING.md with Python 3.12 (#1197) * Update CONTRIBUTING.md with Python 3.12 * change counting * change counting --- CONTRIBUTING.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a30273970..c7d7c44a0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,17 +51,20 @@ Here's how you can set up your local development environment to contribute. #### Option 1: Use UV (recommended) -1. Make sure you have Python3.8+ installed (for example, Python 3.11), create a virtual environment, +1. Make sure you have Python3.12 installed, create a virtual environment, and activate it. If you're new to this, here's one way that we recommend: 1. Install uv: https://github.com/astral-sh/uv?tab=readme-ov-file#getting-started - 2. Install some version of Python greater than Python3.8. For example, to install - Python3.11: + or make sure it is up-to-date with: ``` - uv python install 3.11 + uv self update + ``` + 2. Install Python3.12: + ``` + uv python install 3.12 ``` 3. Create a virtual environment: ``` - uv venv -p 3.11 --seed + uv venv -p 3.12 --seed ``` 4. Activate it. On Linux, this is `. .venv/bin/activate`, on Windows `.\.venv\Scripts\activate`. 2. Install Narwhals: `uv pip install -e .` From 82b1d6e64471a3016aa68f06ee147f4fc502411e Mon Sep 17 00:00:00 2001 From: Luciano <66913960+lucianosrp@users.noreply.github.com> Date: Thu, 17 Oct 2024 12:56:22 +0200 Subject: [PATCH 4/8] feat: add ConstructorEager type (#1091) --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> --- tests/expr_and_series/abs_test.py | 5 ++- tests/expr_and_series/all_horizontal_test.py | 3 +- tests/expr_and_series/any_all_test.py | 5 ++- tests/expr_and_series/arg_true_test.py | 5 ++- tests/expr_and_series/arithmetic_test.py | 7 ++-- .../cat/get_categories_test.py | 7 ++-- tests/expr_and_series/clip_test.py | 5 ++- .../expr_and_series/convert_time_zone_test.py | 11 +++++-- tests/expr_and_series/count_test.py | 5 ++- tests/expr_and_series/cum_sum_test.py | 5 ++- tests/expr_and_series/diff_test.py | 7 ++-- tests/expr_and_series/drop_nulls_test.py | 5 ++- .../dt/datetime_attributes_test.py | 6 ++-- .../dt/datetime_duration_test.py | 4 +-- tests/expr_and_series/dt/to_string_test.py | 7 ++-- tests/expr_and_series/fill_null_test.py | 5 ++- tests/expr_and_series/filter_test.py | 5 ++- tests/expr_and_series/gather_every_test.py | 7 ++-- tests/expr_and_series/head_test.py | 5 ++- tests/expr_and_series/is_between_test.py | 5 ++- tests/expr_and_series/is_duplicated_test.py | 5 ++- .../expr_and_series/is_first_distinct_test.py | 5 ++- tests/expr_and_series/is_in_test.py | 5 ++- .../expr_and_series/is_last_distinct_test.py | 5 ++- tests/expr_and_series/is_null_test.py | 5 ++- tests/expr_and_series/is_unique_test.py | 5 ++- tests/expr_and_series/len_test.py | 5 ++- tests/expr_and_series/max_test.py | 7 ++-- tests/expr_and_series/mean_test.py | 7 ++-- tests/expr_and_series/min_test.py | 7 ++-- tests/expr_and_series/mode_test.py | 5 ++- tests/expr_and_series/n_unique_test.py | 5 ++- tests/expr_and_series/null_count_test.py | 5 ++- tests/expr_and_series/operators_test.py | 9 +++-- tests/expr_and_series/pipe_test.py | 5 ++- tests/expr_and_series/quantile_test.py | 4 +-- .../expr_and_series/replace_time_zone_test.py | 11 +++++-- tests/expr_and_series/round_test.py | 5 ++- tests/expr_and_series/shift_test.py | 5 ++- tests/expr_and_series/sort_test.py | 16 ++++----- tests/expr_and_series/std_test.py | 5 ++- tests/expr_and_series/str/contains_test.py | 7 ++-- tests/expr_and_series/str/head_test.py | 5 ++- tests/expr_and_series/str/len_chars_test.py | 5 ++- tests/expr_and_series/str/replace_test.py | 7 ++-- tests/expr_and_series/str/slice_test.py | 3 +- .../str/starts_with_ends_with_test.py | 7 ++-- tests/expr_and_series/str/strip_chars_test.py | 3 +- tests/expr_and_series/str/tail_test.py | 5 ++- tests/expr_and_series/str/to_datetime_test.py | 12 ++++--- .../str/to_uppercase_to_lowercase_test.py | 7 ++-- tests/expr_and_series/sum_test.py | 7 ++-- tests/expr_and_series/tail_test.py | 5 ++- tests/expr_and_series/unary_test.py | 5 ++- tests/expr_and_series/unique_test.py | 5 ++- tests/expr_and_series/when_test.py | 7 ++-- tests/frame/array_dunder_test.py | 11 ++++--- tests/frame/get_column_test.py | 5 ++- tests/frame/getitem_test.py | 33 +++++++++---------- tests/frame/is_duplicated_test.py | 5 ++- tests/frame/is_empty_test.py | 8 ++++- tests/frame/is_unique_test.py | 5 ++- tests/frame/item_test.py | 11 +++++-- tests/frame/lazy_test.py | 5 ++- tests/frame/len_test.py | 6 ++-- tests/frame/null_count_test.py | 5 ++- tests/frame/row_test.py | 3 +- tests/frame/rows_test.py | 6 +++- tests/frame/schema_test.py | 5 ++- tests/frame/shape_test.py | 5 ++- tests/frame/to_arrow_test.py | 9 +++-- tests/frame/to_dict_test.py | 7 ++-- tests/frame/to_native_test.py | 5 ++- tests/frame/to_numpy_test.py | 7 ++-- tests/frame/to_pandas_test.py | 9 +++-- tests/frame/write_csv_test.py | 6 ++-- tests/frame/write_parquet_test.py | 9 +++-- tests/group_by_test.py | 4 +-- tests/new_series_test.py | 7 ++-- tests/series_only/__iter___test.py | 9 +++-- tests/series_only/array_dunder_test.py | 11 ++++--- tests/series_only/dtype_test.py | 7 ++-- tests/series_only/is_empty_test.py | 5 ++- .../is_ordered_categorical_test.py | 5 ++- tests/series_only/is_sorted_test.py | 7 ++-- tests/series_only/item_test.py | 4 +-- tests/series_only/scatter_test.py | 11 ++++--- tests/series_only/shape_test.py | 5 ++- tests/series_only/slice_test.py | 5 ++- tests/series_only/to_arrow_test.py | 9 +++-- tests/series_only/to_dummy_test.py | 7 ++-- tests/series_only/to_frame_test.py | 5 ++- tests/series_only/to_list_test.py | 7 ++-- tests/series_only/to_native_test.py | 9 +++-- tests/series_only/to_numpy_test.py | 9 +++-- tests/series_only/to_pandas_test.py | 9 +++-- tests/series_only/value_counts_test.py | 3 +- tests/series_only/zip_with_test.py | 7 ++-- tests/translate/to_native_test.py | 3 +- tests/utils.py | 4 ++- 100 files changed, 348 insertions(+), 304 deletions(-) diff --git a/tests/expr_and_series/abs_test.py b/tests/expr_and_series/abs_test.py index 286bcca19..c883d7161 100644 --- a/tests/expr_and_series/abs_test.py +++ b/tests/expr_and_series/abs_test.py @@ -1,7 +1,6 @@ -from typing import Any - import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts @@ -12,7 +11,7 @@ def test_abs(constructor: Constructor) -> None: compare_dicts(result, expected) -def test_abs_series(constructor_eager: Any) -> None: +def test_abs_series(constructor_eager: ConstructorEager) -> None: df = nw.from_native(constructor_eager({"a": [1, 2, 3, -4, 5]}), eager_only=True) result = {"b": df["a"].abs()} expected = {"b": [1, 2, 3, 4, 5]} diff --git a/tests/expr_and_series/all_horizontal_test.py b/tests/expr_and_series/all_horizontal_test.py index 01d53fe63..a5ba44600 100644 --- a/tests/expr_and_series/all_horizontal_test.py +++ b/tests/expr_and_series/all_horizontal_test.py @@ -6,6 +6,7 @@ import narwhals.stable.v1 as nw from narwhals.utils import parse_version from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts @@ -23,7 +24,7 @@ def test_allh(constructor: Constructor, expr1: Any, expr2: Any) -> None: compare_dicts(result, expected) -def test_allh_series(constructor_eager: Any) -> None: +def test_allh_series(constructor_eager: ConstructorEager) -> None: data = { "a": [False, False, True], "b": [False, True, True], diff --git a/tests/expr_and_series/any_all_test.py b/tests/expr_and_series/any_all_test.py index 834a91202..73294c708 100644 --- a/tests/expr_and_series/any_all_test.py +++ b/tests/expr_and_series/any_all_test.py @@ -1,7 +1,6 @@ -from typing import Any - import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts @@ -23,7 +22,7 @@ def test_any_all(constructor: Constructor) -> None: compare_dicts(result, expected) -def test_any_all_series(constructor_eager: Any) -> None: +def test_any_all_series(constructor_eager: ConstructorEager) -> None: df = nw.from_native( constructor_eager( { diff --git a/tests/expr_and_series/arg_true_test.py b/tests/expr_and_series/arg_true_test.py index 7e1262aa8..1f71e2c42 100644 --- a/tests/expr_and_series/arg_true_test.py +++ b/tests/expr_and_series/arg_true_test.py @@ -1,9 +1,8 @@ -from typing import Any - import pytest import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts @@ -16,7 +15,7 @@ def test_arg_true(constructor: Constructor, request: pytest.FixtureRequest) -> N compare_dicts(result, expected) -def test_arg_true_series(constructor_eager: Any) -> None: +def test_arg_true_series(constructor_eager: ConstructorEager) -> None: df = nw.from_native(constructor_eager({"a": [1, None, None, 3]}), eager_only=True) result = df.select(df["a"].is_null().arg_true()) expected = {"a": [1, 2]} diff --git a/tests/expr_and_series/arithmetic_test.py b/tests/expr_and_series/arithmetic_test.py index e431aebbe..eb283667f 100644 --- a/tests/expr_and_series/arithmetic_test.py +++ b/tests/expr_and_series/arithmetic_test.py @@ -13,6 +13,7 @@ import narwhals.stable.v1 as nw from narwhals.utils import parse_version from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts @@ -94,7 +95,7 @@ def test_arithmetic_series( attr: str, rhs: Any, expected: list[Any], - constructor_eager: Any, + constructor_eager: ConstructorEager, request: pytest.FixtureRequest, ) -> None: if attr == "__mod__" and any( @@ -124,7 +125,7 @@ def test_right_arithmetic_series( attr: str, rhs: Any, expected: list[Any], - constructor_eager: Any, + constructor_eager: ConstructorEager, request: pytest.FixtureRequest, ) -> None: if attr == "__rmod__" and any( @@ -139,7 +140,7 @@ def test_right_arithmetic_series( def test_truediv_same_dims( - constructor_eager: Any, request: pytest.FixtureRequest + constructor_eager: ConstructorEager, request: pytest.FixtureRequest ) -> None: if "polars" in str(constructor_eager): # https://github.com/pola-rs/polars/issues/17760 diff --git a/tests/expr_and_series/cat/get_categories_test.py b/tests/expr_and_series/cat/get_categories_test.py index 122f3c83e..11ba3ee58 100644 --- a/tests/expr_and_series/cat/get_categories_test.py +++ b/tests/expr_and_series/cat/get_categories_test.py @@ -1,18 +1,19 @@ from __future__ import annotations -from typing import Any - import pyarrow as pa import pytest import narwhals.stable.v1 as nw from narwhals.utils import parse_version +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = {"a": ["one", "two", "two"]} -def test_get_categories(request: pytest.FixtureRequest, constructor_eager: Any) -> None: +def test_get_categories( + request: pytest.FixtureRequest, constructor_eager: ConstructorEager +) -> None: if "pyarrow_table" in str(constructor_eager) and parse_version( pa.__version__ ) < parse_version("15.0.0"): diff --git a/tests/expr_and_series/clip_test.py b/tests/expr_and_series/clip_test.py index d3f90633c..2406f289f 100644 --- a/tests/expr_and_series/clip_test.py +++ b/tests/expr_and_series/clip_test.py @@ -1,7 +1,6 @@ -from typing import Any - import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts @@ -20,7 +19,7 @@ def test_clip(constructor: Constructor) -> None: compare_dicts(result, expected) -def test_clip_series(constructor_eager: Any) -> None: +def test_clip_series(constructor_eager: ConstructorEager) -> None: df = nw.from_native(constructor_eager({"a": [1, 2, 3, -4, 5]}), eager_only=True) result = { "lower_only": df["a"].clip(lower_bound=3), diff --git a/tests/expr_and_series/convert_time_zone_test.py b/tests/expr_and_series/convert_time_zone_test.py index d85637a14..7914c8b56 100644 --- a/tests/expr_and_series/convert_time_zone_test.py +++ b/tests/expr_and_series/convert_time_zone_test.py @@ -1,6 +1,8 @@ +from __future__ import annotations + from datetime import datetime from datetime import timezone -from typing import Any +from typing import TYPE_CHECKING import pandas as pd import polars as pl @@ -13,6 +15,9 @@ from tests.utils import compare_dicts from tests.utils import is_windows +if TYPE_CHECKING: + from tests.utils import ConstructorEager + def test_convert_time_zone( constructor: Constructor, request: pytest.FixtureRequest @@ -43,7 +48,7 @@ def test_convert_time_zone( def test_convert_time_zone_series( - constructor_eager: Any, request: pytest.FixtureRequest + constructor_eager: ConstructorEager, request: pytest.FixtureRequest ) -> None: if ( (any(x in str(constructor_eager) for x in ("pyarrow", "modin")) and is_windows()) @@ -116,7 +121,7 @@ def test_convert_time_zone_to_none(constructor: Constructor) -> None: df.select(nw.col("a").dt.convert_time_zone(None)) # type: ignore[arg-type] -def test_convert_time_zone_to_none_series(constructor_eager: Any) -> None: +def test_convert_time_zone_to_none_series(constructor_eager: ConstructorEager) -> None: data = { "a": [ datetime(2020, 1, 1, tzinfo=timezone.utc), diff --git a/tests/expr_and_series/count_test.py b/tests/expr_and_series/count_test.py index 580bd202b..ec90e1fc1 100644 --- a/tests/expr_and_series/count_test.py +++ b/tests/expr_and_series/count_test.py @@ -1,7 +1,6 @@ -from typing import Any - import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts @@ -13,7 +12,7 @@ def test_count(constructor: Constructor) -> None: compare_dicts(result, expected) -def test_count_series(constructor_eager: Any) -> None: +def test_count_series(constructor_eager: ConstructorEager) -> None: data = {"a": [1, 3, 2], "b": [4, None, 6], "z": [7.0, None, None]} df = nw.from_native(constructor_eager(data), eager_only=True) result = {"a": [df["a"].count()], "b": [df["b"].count()], "z": [df["z"].count()]} diff --git a/tests/expr_and_series/cum_sum_test.py b/tests/expr_and_series/cum_sum_test.py index 94897a850..a490b890e 100644 --- a/tests/expr_and_series/cum_sum_test.py +++ b/tests/expr_and_series/cum_sum_test.py @@ -1,7 +1,6 @@ -from typing import Any - import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = { @@ -22,7 +21,7 @@ def test_cum_sum_simple(constructor: Constructor) -> None: compare_dicts(result, expected) -def test_cum_sum_simple_series(constructor_eager: Any) -> None: +def test_cum_sum_simple_series(constructor_eager: ConstructorEager) -> None: df = nw.from_native(constructor_eager(data), eager_only=True) expected = { "a": [0, 1, 3, 6, 10], diff --git a/tests/expr_and_series/diff_test.py b/tests/expr_and_series/diff_test.py index 33445f763..ada3147ed 100644 --- a/tests/expr_and_series/diff_test.py +++ b/tests/expr_and_series/diff_test.py @@ -1,11 +1,10 @@ -from typing import Any - import pyarrow as pa import pytest import narwhals.stable.v1 as nw from narwhals.utils import parse_version from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = { @@ -32,7 +31,9 @@ def test_diff(constructor: Constructor, request: pytest.FixtureRequest) -> None: compare_dicts(result, expected) -def test_diff_series(constructor_eager: Any, request: pytest.FixtureRequest) -> None: +def test_diff_series( + constructor_eager: ConstructorEager, request: pytest.FixtureRequest +) -> None: if "pyarrow_table_constructor" in str(constructor_eager) and parse_version( pa.__version__ ) < (13,): diff --git a/tests/expr_and_series/drop_nulls_test.py b/tests/expr_and_series/drop_nulls_test.py index bc06eec3a..4b15416ce 100644 --- a/tests/expr_and_series/drop_nulls_test.py +++ b/tests/expr_and_series/drop_nulls_test.py @@ -1,11 +1,10 @@ from __future__ import annotations -from typing import Any - import pytest import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts @@ -36,7 +35,7 @@ def test_drop_nulls(constructor: Constructor, request: pytest.FixtureRequest) -> compare_dicts(result_d, expected_d) -def test_drop_nulls_series(constructor_eager: Any) -> None: +def test_drop_nulls_series(constructor_eager: ConstructorEager) -> None: data = { "A": [1, 2, None, 4], "B": [5, 6, 7, 8], diff --git a/tests/expr_and_series/dt/datetime_attributes_test.py b/tests/expr_and_series/dt/datetime_attributes_test.py index 5b9519f57..757d226ff 100644 --- a/tests/expr_and_series/dt/datetime_attributes_test.py +++ b/tests/expr_and_series/dt/datetime_attributes_test.py @@ -2,12 +2,12 @@ from datetime import date from datetime import datetime -from typing import Any import pytest import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = { @@ -72,7 +72,7 @@ def test_datetime_attributes( ) def test_datetime_attributes_series( request: pytest.FixtureRequest, - constructor_eager: Any, + constructor_eager: ConstructorEager, attribute: str, expected: list[int], ) -> None: @@ -91,7 +91,7 @@ def test_datetime_attributes_series( def test_datetime_chained_attributes( - request: pytest.FixtureRequest, constructor_eager: Any + request: pytest.FixtureRequest, constructor_eager: ConstructorEager ) -> None: if "pandas" in str(constructor_eager) and "pyarrow" not in str(constructor_eager): request.applymarker(pytest.mark.xfail) diff --git a/tests/expr_and_series/dt/datetime_duration_test.py b/tests/expr_and_series/dt/datetime_duration_test.py index da5ff325b..3e4894b0b 100644 --- a/tests/expr_and_series/dt/datetime_duration_test.py +++ b/tests/expr_and_series/dt/datetime_duration_test.py @@ -1,7 +1,6 @@ from __future__ import annotations from datetime import timedelta -from typing import Any import numpy as np import pandas as pd @@ -12,6 +11,7 @@ import narwhals.stable.v1 as nw from narwhals.utils import parse_version from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = { @@ -74,7 +74,7 @@ def test_duration_attributes( ) def test_duration_attributes_series( request: pytest.FixtureRequest, - constructor_eager: Any, + constructor_eager: ConstructorEager, attribute: str, expected_a: list[int], expected_b: list[int], diff --git a/tests/expr_and_series/dt/to_string_test.py b/tests/expr_and_series/dt/to_string_test.py index 6017c33d2..a6261b78a 100644 --- a/tests/expr_and_series/dt/to_string_test.py +++ b/tests/expr_and_series/dt/to_string_test.py @@ -7,6 +7,7 @@ import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts from tests.utils import is_windows @@ -29,7 +30,7 @@ ], ) @pytest.mark.skipif(is_windows(), reason="pyarrow breaking on windows") -def test_dt_to_string_series(constructor_eager: Any, fmt: str) -> None: +def test_dt_to_string_series(constructor_eager: ConstructorEager, fmt: str) -> None: input_frame = nw.from_native(constructor_eager(data), eager_only=True) input_series = input_frame["a"] @@ -100,7 +101,7 @@ def _clean_string_expr(e: Any) -> Any: ) @pytest.mark.skipif(is_windows(), reason="pyarrow breaking on windows") def test_dt_to_string_iso_local_datetime_series( - constructor_eager: Any, data: datetime, expected: str + constructor_eager: ConstructorEager, data: datetime, expected: str ) -> None: df = constructor_eager({"a": [data]}) result = ( @@ -152,7 +153,7 @@ def test_dt_to_string_iso_local_datetime_expr( ) @pytest.mark.skipif(is_windows(), reason="pyarrow breaking on windows") def test_dt_to_string_iso_local_date_series( - constructor_eager: Any, data: datetime, expected: str + constructor_eager: ConstructorEager, data: datetime, expected: str ) -> None: df = constructor_eager({"a": [data]}) result = nw.from_native(df, eager_only=True)["a"].dt.to_string("%Y-%m-%d").item(0) diff --git a/tests/expr_and_series/fill_null_test.py b/tests/expr_and_series/fill_null_test.py index 6efde5ac0..9fa7afaf9 100644 --- a/tests/expr_and_series/fill_null_test.py +++ b/tests/expr_and_series/fill_null_test.py @@ -1,7 +1,6 @@ -from typing import Any - import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = { @@ -23,7 +22,7 @@ def test_fill_null(constructor: Constructor) -> None: compare_dicts(result, expected) -def test_fill_null_series(constructor_eager: Any) -> None: +def test_fill_null_series(constructor_eager: ConstructorEager) -> None: df = nw.from_native(constructor_eager(data), eager_only=True) expected = { diff --git a/tests/expr_and_series/filter_test.py b/tests/expr_and_series/filter_test.py index 80267d1d0..dff987ecb 100644 --- a/tests/expr_and_series/filter_test.py +++ b/tests/expr_and_series/filter_test.py @@ -1,9 +1,8 @@ -from typing import Any - import pytest import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = { @@ -23,7 +22,7 @@ def test_filter(constructor: Constructor, request: pytest.FixtureRequest) -> Non compare_dicts(result, expected) -def test_filter_series(constructor_eager: Any) -> None: +def test_filter_series(constructor_eager: ConstructorEager) -> None: df = nw.from_native(constructor_eager(data), eager_only=True) result = df.select(df["a"].filter((df["i"] < 2) & (df["c"] == 5))) expected = {"a": [0]} diff --git a/tests/expr_and_series/gather_every_test.py b/tests/expr_and_series/gather_every_test.py index e01294ef9..e6f68be1d 100644 --- a/tests/expr_and_series/gather_every_test.py +++ b/tests/expr_and_series/gather_every_test.py @@ -1,9 +1,8 @@ -from typing import Any - import pytest import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = {"a": list(range(10))} @@ -26,7 +25,9 @@ def test_gather_every_expr( @pytest.mark.parametrize("n", [1, 2, 3]) @pytest.mark.parametrize("offset", [1, 2, 3]) -def test_gather_every_series(constructor_eager: Any, n: int, offset: int) -> None: +def test_gather_every_series( + constructor_eager: ConstructorEager, n: int, offset: int +) -> None: series = nw.from_native(constructor_eager(data), eager_only=True)["a"] result = series.gather_every(n=n, offset=offset) diff --git a/tests/expr_and_series/head_test.py b/tests/expr_and_series/head_test.py index 2a6326921..4c750fabf 100644 --- a/tests/expr_and_series/head_test.py +++ b/tests/expr_and_series/head_test.py @@ -1,11 +1,10 @@ from __future__ import annotations -from typing import Any - import pytest import narwhals as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts @@ -22,7 +21,7 @@ def test_head(constructor: Constructor, n: int, request: pytest.FixtureRequest) @pytest.mark.parametrize("n", [2, -1]) -def test_head_series(constructor_eager: Any, n: int) -> None: +def test_head_series(constructor_eager: ConstructorEager, n: int) -> None: df = nw.from_native(constructor_eager({"a": [1, 2, 3]}), eager_only=True) result = df.select(df["a"].head(n)) expected = {"a": [1, 2]} diff --git a/tests/expr_and_series/is_between_test.py b/tests/expr_and_series/is_between_test.py index 0a9e578ea..0550498b6 100644 --- a/tests/expr_and_series/is_between_test.py +++ b/tests/expr_and_series/is_between_test.py @@ -1,11 +1,10 @@ from __future__ import annotations -from typing import Any - import pytest import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = { @@ -39,7 +38,7 @@ def test_is_between(constructor: Constructor, closed: str, expected: list[bool]) ], ) def test_is_between_series( - constructor_eager: Any, closed: str, expected: list[bool] + constructor_eager: ConstructorEager, closed: str, expected: list[bool] ) -> None: df = nw.from_native(constructor_eager(data), eager_only=True) result = df.with_columns(a=df["a"].is_between(1, 5, closed=closed)) diff --git a/tests/expr_and_series/is_duplicated_test.py b/tests/expr_and_series/is_duplicated_test.py index 7859aed02..d0c5ae3dc 100644 --- a/tests/expr_and_series/is_duplicated_test.py +++ b/tests/expr_and_series/is_duplicated_test.py @@ -1,7 +1,6 @@ -from typing import Any - import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = {"a": [1, 1, 2], "b": [1, 2, 3], "index": [0, 1, 2]} @@ -14,7 +13,7 @@ def test_is_duplicated_expr(constructor: Constructor) -> None: compare_dicts(result, expected) -def test_is_duplicated_series(constructor_eager: Any) -> None: +def test_is_duplicated_series(constructor_eager: ConstructorEager) -> None: series = nw.from_native(constructor_eager(data), eager_only=True)["a"] result = series.is_duplicated() expected = {"a": [True, True, False]} diff --git a/tests/expr_and_series/is_first_distinct_test.py b/tests/expr_and_series/is_first_distinct_test.py index 93ffc5d37..4f22d02f9 100644 --- a/tests/expr_and_series/is_first_distinct_test.py +++ b/tests/expr_and_series/is_first_distinct_test.py @@ -1,7 +1,6 @@ -from typing import Any - import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = { @@ -20,7 +19,7 @@ def test_is_first_distinct_expr(constructor: Constructor) -> None: compare_dicts(result, expected) -def test_is_first_distinct_series(constructor_eager: Any) -> None: +def test_is_first_distinct_series(constructor_eager: ConstructorEager) -> None: series = nw.from_native(constructor_eager(data), eager_only=True)["a"] result = series.is_first_distinct() expected = { diff --git a/tests/expr_and_series/is_in_test.py b/tests/expr_and_series/is_in_test.py index 085b1efbe..29d3cf56b 100644 --- a/tests/expr_and_series/is_in_test.py +++ b/tests/expr_and_series/is_in_test.py @@ -1,9 +1,8 @@ -from typing import Any - import pytest import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = {"a": [1, 4, 2, 5]} @@ -17,7 +16,7 @@ def test_expr_is_in(constructor: Constructor) -> None: compare_dicts(result, expected) -def test_ser_is_in(constructor_eager: Any) -> None: +def test_ser_is_in(constructor_eager: ConstructorEager) -> None: ser = nw.from_native(constructor_eager(data), eager_only=True)["a"] result = {"a": ser.is_in([4, 5])} expected = {"a": [False, True, False, True]} diff --git a/tests/expr_and_series/is_last_distinct_test.py b/tests/expr_and_series/is_last_distinct_test.py index 00db7f735..e63c161b3 100644 --- a/tests/expr_and_series/is_last_distinct_test.py +++ b/tests/expr_and_series/is_last_distinct_test.py @@ -1,7 +1,6 @@ -from typing import Any - import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = { @@ -20,7 +19,7 @@ def test_is_last_distinct_expr(constructor: Constructor) -> None: compare_dicts(result, expected) -def test_is_last_distinct_series(constructor_eager: Any) -> None: +def test_is_last_distinct_series(constructor_eager: ConstructorEager) -> None: series = nw.from_native(constructor_eager(data), eager_only=True)["a"] result = series.is_last_distinct() expected = { diff --git a/tests/expr_and_series/is_null_test.py b/tests/expr_and_series/is_null_test.py index 85ba55dc4..a3d5d2bae 100644 --- a/tests/expr_and_series/is_null_test.py +++ b/tests/expr_and_series/is_null_test.py @@ -1,7 +1,6 @@ -from typing import Any - import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts @@ -14,7 +13,7 @@ def test_null(constructor: Constructor) -> None: compare_dicts(result, expected) -def test_null_series(constructor_eager: Any) -> None: +def test_null_series(constructor_eager: ConstructorEager) -> None: data_na = {"a": [None, 3, 2], "z": [7.0, None, None]} expected = {"a": [True, False, False], "z": [True, False, False]} df = nw.from_native(constructor_eager(data_na), eager_only=True) diff --git a/tests/expr_and_series/is_unique_test.py b/tests/expr_and_series/is_unique_test.py index b10f7a68f..8d46db92d 100644 --- a/tests/expr_and_series/is_unique_test.py +++ b/tests/expr_and_series/is_unique_test.py @@ -1,7 +1,6 @@ -from typing import Any - import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = { @@ -22,7 +21,7 @@ def test_is_unique_expr(constructor: Constructor) -> None: compare_dicts(result, expected) -def test_is_unique_series(constructor_eager: Any) -> None: +def test_is_unique_series(constructor_eager: ConstructorEager) -> None: series = nw.from_native(constructor_eager(data), eager_only=True)["a"] result = series.is_unique() expected = { diff --git a/tests/expr_and_series/len_test.py b/tests/expr_and_series/len_test.py index b1e1674bf..535c7dc92 100644 --- a/tests/expr_and_series/len_test.py +++ b/tests/expr_and_series/len_test.py @@ -1,9 +1,8 @@ -from typing import Any - import pytest import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts @@ -46,7 +45,7 @@ def test_namespace_len(constructor: Constructor) -> None: compare_dicts(df, expected) -def test_len_series(constructor_eager: Any) -> None: +def test_len_series(constructor_eager: ConstructorEager) -> None: data = {"a": [1, 2, 1]} s = nw.from_native(constructor_eager(data), eager_only=True)["a"] diff --git a/tests/expr_and_series/max_test.py b/tests/expr_and_series/max_test.py index 1ea32531e..dcacc7d2e 100644 --- a/tests/expr_and_series/max_test.py +++ b/tests/expr_and_series/max_test.py @@ -1,11 +1,10 @@ from __future__ import annotations -from typing import Any - import pytest import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.0, 8, 9]} @@ -20,7 +19,9 @@ def test_expr_max_expr(constructor: Constructor, expr: nw.Expr) -> None: @pytest.mark.parametrize(("col", "expected"), [("a", 3), ("b", 6), ("z", 9.0)]) -def test_expr_max_series(constructor_eager: Any, col: str, expected: float) -> None: +def test_expr_max_series( + constructor_eager: ConstructorEager, col: str, expected: float +) -> None: series = nw.from_native(constructor_eager(data), eager_only=True)[col] result = series.max() compare_dicts({col: [result]}, {col: [expected]}) diff --git a/tests/expr_and_series/mean_test.py b/tests/expr_and_series/mean_test.py index 50e6fd862..0d381286a 100644 --- a/tests/expr_and_series/mean_test.py +++ b/tests/expr_and_series/mean_test.py @@ -1,11 +1,10 @@ from __future__ import annotations -from typing import Any - import pytest import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = {"a": [1, 3, 2], "b": [4, 4, 7], "z": [7.0, 8, 9]} @@ -20,7 +19,9 @@ def test_expr_mean_expr(constructor: Constructor, expr: nw.Expr) -> None: @pytest.mark.parametrize(("col", "expected"), [("a", 2.0), ("b", 5.0), ("z", 8.0)]) -def test_expr_mean_series(constructor_eager: Any, col: str, expected: float) -> None: +def test_expr_mean_series( + constructor_eager: ConstructorEager, col: str, expected: float +) -> None: series = nw.from_native(constructor_eager(data), eager_only=True)[col] result = series.mean() compare_dicts({col: [result]}, {col: [expected]}) diff --git a/tests/expr_and_series/min_test.py b/tests/expr_and_series/min_test.py index f6e98e416..afd659df1 100644 --- a/tests/expr_and_series/min_test.py +++ b/tests/expr_and_series/min_test.py @@ -1,11 +1,10 @@ from __future__ import annotations -from typing import Any - import pytest import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.0, 8, 9]} @@ -20,7 +19,9 @@ def test_expr_min_expr(constructor: Constructor, expr: nw.Expr) -> None: @pytest.mark.parametrize(("col", "expected"), [("a", 1), ("b", 4), ("z", 7.0)]) -def test_expr_min_series(constructor_eager: Any, col: str, expected: float) -> None: +def test_expr_min_series( + constructor_eager: ConstructorEager, col: str, expected: float +) -> None: series = nw.from_native(constructor_eager(data), eager_only=True)[col] result = series.min() compare_dicts({col: [result]}, {col: [expected]}) diff --git a/tests/expr_and_series/mode_test.py b/tests/expr_and_series/mode_test.py index 8e39405af..820e05ad8 100644 --- a/tests/expr_and_series/mode_test.py +++ b/tests/expr_and_series/mode_test.py @@ -1,11 +1,10 @@ -from typing import Any - import polars as pl import pytest import narwhals.stable.v1 as nw from narwhals.utils import parse_version from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = { @@ -39,7 +38,7 @@ def test_mode_multi_expr( compare_dicts(result, expected) -def test_mode_series(constructor_eager: Any) -> None: +def test_mode_series(constructor_eager: ConstructorEager) -> None: series = nw.from_native(constructor_eager(data), eager_only=True)["a"] result = series.mode().sort() expected = {"a": [1, 2]} diff --git a/tests/expr_and_series/n_unique_test.py b/tests/expr_and_series/n_unique_test.py index 3790bb1f3..c4199eec1 100644 --- a/tests/expr_and_series/n_unique_test.py +++ b/tests/expr_and_series/n_unique_test.py @@ -1,7 +1,6 @@ -from typing import Any - import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = { @@ -17,7 +16,7 @@ def test_n_unique(constructor: Constructor) -> None: compare_dicts(result, expected) -def test_n_unique_series(constructor_eager: Any) -> None: +def test_n_unique_series(constructor_eager: ConstructorEager) -> None: df = nw.from_native(constructor_eager(data), eager_only=True) expected = {"a": [3], "b": [4]} result_series = {"a": [df["a"].n_unique()], "b": [df["b"].n_unique()]} diff --git a/tests/expr_and_series/null_count_test.py b/tests/expr_and_series/null_count_test.py index 6be15ab32..93d467cb3 100644 --- a/tests/expr_and_series/null_count_test.py +++ b/tests/expr_and_series/null_count_test.py @@ -1,7 +1,6 @@ -from typing import Any - import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = { @@ -20,7 +19,7 @@ def test_null_count_expr(constructor: Constructor) -> None: compare_dicts(result, expected) -def test_null_count_series(constructor_eager: Any) -> None: +def test_null_count_series(constructor_eager: ConstructorEager) -> None: data = [1, 2, None] series = nw.from_native(constructor_eager({"a": data}), eager_only=True)["a"] result = series.null_count() diff --git a/tests/expr_and_series/operators_test.py b/tests/expr_and_series/operators_test.py index e3f39465c..b4c3677ef 100644 --- a/tests/expr_and_series/operators_test.py +++ b/tests/expr_and_series/operators_test.py @@ -1,11 +1,10 @@ from __future__ import annotations -from typing import Any - import pytest import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts @@ -78,7 +77,7 @@ def test_logic_operators_expr( ], ) def test_comparand_operators_scalar_series( - constructor_eager: Any, operator: str, expected: list[bool] + constructor_eager: ConstructorEager, operator: str, expected: list[bool] ) -> None: data = {"a": [0, 1, 2]} s = nw.from_native(constructor_eager(data), eager_only=True)["a"] @@ -98,7 +97,7 @@ def test_comparand_operators_scalar_series( ], ) def test_comparand_operators_series( - constructor_eager: Any, operator: str, expected: list[bool] + constructor_eager: ConstructorEager, operator: str, expected: list[bool] ) -> None: data = {"a": [0, 1, 1], "b": [0, 0, 2]} df = nw.from_native(constructor_eager(data), eager_only=True) @@ -115,7 +114,7 @@ def test_comparand_operators_series( ], ) def test_logic_operators_series( - constructor_eager: Any, operator: str, expected: list[bool] + constructor_eager: ConstructorEager, operator: str, expected: list[bool] ) -> None: data = {"a": [True, True, False, False], "b": [True, False, True, False]} df = nw.from_native(constructor_eager(data), eager_only=True) diff --git a/tests/expr_and_series/pipe_test.py b/tests/expr_and_series/pipe_test.py index 2134a931b..84b6006d7 100644 --- a/tests/expr_and_series/pipe_test.py +++ b/tests/expr_and_series/pipe_test.py @@ -1,7 +1,6 @@ -from typing import Any - import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts input_list = {"a": [2, 4, 6, 8]} @@ -15,7 +14,7 @@ def test_pipe_expr(constructor: Constructor) -> None: def test_pipe_series( - constructor_eager: Any, + constructor_eager: ConstructorEager, ) -> None: s = nw.from_native(constructor_eager(input_list), eager_only=True)["a"] result = s.pipe(lambda x: x**2) diff --git a/tests/expr_and_series/quantile_test.py b/tests/expr_and_series/quantile_test.py index aae2b3647..4fd5fa3f4 100644 --- a/tests/expr_and_series/quantile_test.py +++ b/tests/expr_and_series/quantile_test.py @@ -1,13 +1,13 @@ from __future__ import annotations from contextlib import nullcontext as does_not_raise -from typing import Any from typing import Literal import pytest import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts @@ -62,7 +62,7 @@ def test_quantile_expr( ) @pytest.mark.filterwarnings("ignore:the `interpolation=` argument to percentile") def test_quantile_series( - constructor_eager: Any, + constructor_eager: ConstructorEager, interpolation: Literal["nearest", "higher", "lower", "midpoint", "linear"], expected: float, ) -> None: diff --git a/tests/expr_and_series/replace_time_zone_test.py b/tests/expr_and_series/replace_time_zone_test.py index 4954875f0..1c029a478 100644 --- a/tests/expr_and_series/replace_time_zone_test.py +++ b/tests/expr_and_series/replace_time_zone_test.py @@ -1,6 +1,8 @@ +from __future__ import annotations + from datetime import datetime from datetime import timezone -from typing import Any +from typing import TYPE_CHECKING import pandas as pd import pyarrow as pa @@ -12,6 +14,9 @@ from tests.utils import compare_dicts from tests.utils import is_windows +if TYPE_CHECKING: + from tests.utils import ConstructorEager + def test_replace_time_zone( constructor: Constructor, request: pytest.FixtureRequest @@ -65,7 +70,7 @@ def test_replace_time_zone_none( def test_replace_time_zone_series( - constructor_eager: Any, request: pytest.FixtureRequest + constructor_eager: ConstructorEager, request: pytest.FixtureRequest ) -> None: if ( (any(x in str(constructor_eager) for x in ("pyarrow", "modin")) and is_windows()) @@ -97,7 +102,7 @@ def test_replace_time_zone_series( def test_replace_time_zone_none_series( - constructor_eager: Any, request: pytest.FixtureRequest + constructor_eager: ConstructorEager, request: pytest.FixtureRequest ) -> None: if ( (any(x in str(constructor_eager) for x in ("pyarrow", "modin")) and is_windows()) diff --git a/tests/expr_and_series/round_test.py b/tests/expr_and_series/round_test.py index 37d6ce131..613a82afe 100644 --- a/tests/expr_and_series/round_test.py +++ b/tests/expr_and_series/round_test.py @@ -1,11 +1,10 @@ from __future__ import annotations -from typing import Any - import pytest import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts @@ -21,7 +20,7 @@ def test_round(constructor: Constructor, decimals: int) -> None: @pytest.mark.parametrize("decimals", [0, 1, 2]) -def test_round_series(constructor_eager: Any, decimals: int) -> None: +def test_round_series(constructor_eager: ConstructorEager, decimals: int) -> None: data = {"a": [1.12345, 2.56789, 3.901234]} df_raw = constructor_eager(data) df = nw.from_native(df_raw, eager_only=True) diff --git a/tests/expr_and_series/shift_test.py b/tests/expr_and_series/shift_test.py index b165adf12..a665ff768 100644 --- a/tests/expr_and_series/shift_test.py +++ b/tests/expr_and_series/shift_test.py @@ -1,9 +1,8 @@ -from typing import Any - import pyarrow as pa import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = { @@ -26,7 +25,7 @@ def test_shift(constructor: Constructor) -> None: compare_dicts(result, expected) -def test_shift_series(constructor_eager: Any) -> None: +def test_shift_series(constructor_eager: ConstructorEager) -> None: df = nw.from_native(constructor_eager(data), eager_only=True) result = df.with_columns( df["a"].shift(2), diff --git a/tests/expr_and_series/sort_test.py b/tests/expr_and_series/sort_test.py index f06e21f74..2ea8cd145 100644 --- a/tests/expr_and_series/sort_test.py +++ b/tests/expr_and_series/sort_test.py @@ -3,6 +3,8 @@ import pytest import narwhals.stable.v1 as nw +from tests.utils import ConstructorEager +from tests.utils import compare_dicts data = {"a": [0, 0, 2, -1], "b": [1, 3, 2, None]} @@ -17,16 +19,14 @@ ], ) def test_sort_expr( - constructor_eager: Any, descending: Any, nulls_last: Any, expected: Any + constructor_eager: ConstructorEager, descending: Any, nulls_last: Any, expected: Any ) -> None: df = nw.from_native(constructor_eager(data), eager_only=True) - result = nw.to_native( - df.select( - "a", - nw.col("b").sort(descending=descending, nulls_last=nulls_last), - ) + result = df.select( + "a", + nw.col("b").sort(descending=descending, nulls_last=nulls_last), ) - assert result.equals(constructor_eager(expected)) + compare_dicts(result, expected) @pytest.mark.parametrize( @@ -39,7 +39,7 @@ def test_sort_expr( ], ) def test_sort_series( - constructor_eager: Any, descending: Any, nulls_last: Any, expected: Any + constructor_eager: ConstructorEager, descending: Any, nulls_last: Any, expected: Any ) -> None: series = nw.from_native(constructor_eager(data), eager_only=True)["b"] result = series.sort(descending=descending, nulls_last=nulls_last) diff --git a/tests/expr_and_series/std_test.py b/tests/expr_and_series/std_test.py index 400a6e0af..09779c109 100644 --- a/tests/expr_and_series/std_test.py +++ b/tests/expr_and_series/std_test.py @@ -1,7 +1,6 @@ -from typing import Any - import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.0, 8, 9]} @@ -26,7 +25,7 @@ def test_std(constructor: Constructor) -> None: compare_dicts(result, expected) -def test_std_series(constructor_eager: Any) -> None: +def test_std_series(constructor_eager: ConstructorEager) -> None: df = nw.from_native(constructor_eager(data), eager_only=True) result = { "a_ddof_default": [df["a"].std()], diff --git a/tests/expr_and_series/str/contains_test.py b/tests/expr_and_series/str/contains_test.py index 6b9e74b69..139b71eb8 100644 --- a/tests/expr_and_series/str/contains_test.py +++ b/tests/expr_and_series/str/contains_test.py @@ -1,11 +1,10 @@ -from typing import Any - import pandas as pd import polars as pl import pytest import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = {"pets": ["cat", "dog", "rabbit and parrot", "dove"]} @@ -32,7 +31,7 @@ def test_contains_case_insensitive( def test_contains_series_case_insensitive( - constructor_eager: Any, request: pytest.FixtureRequest + constructor_eager: ConstructorEager, request: pytest.FixtureRequest ) -> None: if "cudf" in str(constructor_eager): request.applymarker(pytest.mark.xfail) @@ -58,7 +57,7 @@ def test_contains_case_sensitive(constructor: Constructor) -> None: compare_dicts(result, expected) -def test_contains_series_case_sensitive(constructor_eager: Any) -> None: +def test_contains_series_case_sensitive(constructor_eager: ConstructorEager) -> None: df = nw.from_native(constructor_eager(data), eager_only=True) result = df.with_columns(case_sensitive_match=df["pets"].str.contains("parrot|Dove")) expected = { diff --git a/tests/expr_and_series/str/head_test.py b/tests/expr_and_series/str/head_test.py index a4b3e7296..8da64553e 100644 --- a/tests/expr_and_series/str/head_test.py +++ b/tests/expr_and_series/str/head_test.py @@ -1,7 +1,6 @@ -from typing import Any - import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = {"a": ["foo", "bars"]} @@ -16,7 +15,7 @@ def test_str_head(constructor: Constructor) -> None: compare_dicts(result, expected) -def test_str_head_series(constructor_eager: Any) -> None: +def test_str_head_series(constructor_eager: ConstructorEager) -> None: df = nw.from_native(constructor_eager(data), eager_only=True) expected = { "a": ["foo", "bar"], diff --git a/tests/expr_and_series/str/len_chars_test.py b/tests/expr_and_series/str/len_chars_test.py index ace145552..80a791c61 100644 --- a/tests/expr_and_series/str/len_chars_test.py +++ b/tests/expr_and_series/str/len_chars_test.py @@ -1,7 +1,6 @@ -from typing import Any - import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = {"a": ["foo", "foobar", "Café", "345", "東京"]} @@ -16,7 +15,7 @@ def test_str_len_chars(constructor: Constructor) -> None: compare_dicts(result, expected) -def test_str_len_chars_series(constructor_eager: Any) -> None: +def test_str_len_chars_series(constructor_eager: ConstructorEager) -> None: df = nw.from_native(constructor_eager(data), eager_only=True) expected = { "a": [3, 6, 4, 3, 2], diff --git a/tests/expr_and_series/str/replace_test.py b/tests/expr_and_series/str/replace_test.py index b0cffb1b4..8db24c91e 100644 --- a/tests/expr_and_series/str/replace_test.py +++ b/tests/expr_and_series/str/replace_test.py @@ -1,11 +1,10 @@ from __future__ import annotations -from typing import Any - import pytest import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts replace_data = [ @@ -54,7 +53,7 @@ replace_data, ) def test_str_replace_series( - constructor_eager: Any, + constructor_eager: ConstructorEager, data: dict[str, list[str]], pattern: str, value: str, @@ -75,7 +74,7 @@ def test_str_replace_series( replace_all_data, ) def test_str_replace_all_series( - constructor_eager: Any, + constructor_eager: ConstructorEager, data: dict[str, list[str]], pattern: str, value: str, diff --git a/tests/expr_and_series/str/slice_test.py b/tests/expr_and_series/str/slice_test.py index e7fe0efa1..3b7bb90ce 100644 --- a/tests/expr_and_series/str/slice_test.py +++ b/tests/expr_and_series/str/slice_test.py @@ -6,6 +6,7 @@ import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = {"a": ["fdas", "edfas"]} @@ -28,7 +29,7 @@ def test_str_slice( [(1, 2, {"a": ["da", "df"]}), (-2, None, {"a": ["as", "as"]})], ) def test_str_slice_series( - constructor_eager: Any, offset: int, length: int | None, expected: Any + constructor_eager: ConstructorEager, offset: int, length: int | None, expected: Any ) -> None: df = nw.from_native(constructor_eager(data), eager_only=True) diff --git a/tests/expr_and_series/str/starts_with_ends_with_test.py b/tests/expr_and_series/str/starts_with_ends_with_test.py index e8b0afaa9..3682c4182 100644 --- a/tests/expr_and_series/str/starts_with_ends_with_test.py +++ b/tests/expr_and_series/str/starts_with_ends_with_test.py @@ -1,9 +1,8 @@ from __future__ import annotations -from typing import Any - import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager # Don't move this into typechecking block, for coverage # purposes @@ -21,7 +20,7 @@ def test_ends_with(constructor: Constructor) -> None: compare_dicts(result, expected) -def test_ends_with_series(constructor_eager: Any) -> None: +def test_ends_with_series(constructor_eager: ConstructorEager) -> None: df = nw.from_native(constructor_eager(data), eager_only=True) result = df.select(df["a"].str.ends_with("das")) expected = { @@ -39,7 +38,7 @@ def test_starts_with(constructor: Constructor) -> None: compare_dicts(result, expected) -def test_starts_with_series(constructor_eager: Any) -> None: +def test_starts_with_series(constructor_eager: ConstructorEager) -> None: df = nw.from_native(constructor_eager(data), eager_only=True) result = df.select(df["a"].str.starts_with("fda")) expected = { diff --git a/tests/expr_and_series/str/strip_chars_test.py b/tests/expr_and_series/str/strip_chars_test.py index 3d5b74456..66b9cda0d 100644 --- a/tests/expr_and_series/str/strip_chars_test.py +++ b/tests/expr_and_series/str/strip_chars_test.py @@ -6,6 +6,7 @@ import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = {"a": ["foobar", "bar\n", " baz"]} @@ -34,7 +35,7 @@ def test_str_strip_chars( ], ) def test_str_strip_chars_series( - constructor_eager: Any, characters: str | None, expected: Any + constructor_eager: ConstructorEager, characters: str | None, expected: Any ) -> None: df = nw.from_native(constructor_eager(data), eager_only=True) diff --git a/tests/expr_and_series/str/tail_test.py b/tests/expr_and_series/str/tail_test.py index 92d474262..260ab745c 100644 --- a/tests/expr_and_series/str/tail_test.py +++ b/tests/expr_and_series/str/tail_test.py @@ -1,7 +1,6 @@ -from typing import Any - import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = {"a": ["foo", "bars"]} @@ -15,7 +14,7 @@ def test_str_tail(constructor: Constructor) -> None: compare_dicts(result_frame, expected) -def test_str_tail_series(constructor_eager: Any) -> None: +def test_str_tail_series(constructor_eager: ConstructorEager) -> None: df = nw.from_native(constructor_eager(data), eager_only=True) expected = {"a": ["foo", "ars"]} diff --git a/tests/expr_and_series/str/to_datetime_test.py b/tests/expr_and_series/str/to_datetime_test.py index 8474357e0..62afda474 100644 --- a/tests/expr_and_series/str/to_datetime_test.py +++ b/tests/expr_and_series/str/to_datetime_test.py @@ -1,10 +1,14 @@ -from typing import Any +from __future__ import annotations + +from typing import TYPE_CHECKING import pytest import narwhals.stable.v1 as nw -from tests.utils import Constructor +if TYPE_CHECKING: + from tests.utils import Constructor + from tests.utils import ConstructorEager data = {"a": ["2020-01-01T12:34:56"]} @@ -24,7 +28,7 @@ def test_to_datetime(constructor: Constructor) -> None: assert str(result) == expected -def test_to_datetime_series(constructor_eager: Any) -> None: +def test_to_datetime_series(constructor_eager: ConstructorEager) -> None: if "cudf" in str(constructor_eager): # pragma: no cover expected = "2020-01-01T12:34:56.000000000" else: @@ -60,7 +64,7 @@ def test_to_datetime_infer_fmt( def test_to_datetime_series_infer_fmt( - request: pytest.FixtureRequest, constructor_eager: Any + request: pytest.FixtureRequest, constructor_eager: ConstructorEager ) -> None: if "pyarrow_table" in str(constructor_eager): request.applymarker(pytest.mark.xfail) diff --git a/tests/expr_and_series/str/to_uppercase_to_lowercase_test.py b/tests/expr_and_series/str/to_uppercase_to_lowercase_test.py index 877409138..e5b5832f6 100644 --- a/tests/expr_and_series/str/to_uppercase_to_lowercase_test.py +++ b/tests/expr_and_series/str/to_uppercase_to_lowercase_test.py @@ -1,13 +1,12 @@ from __future__ import annotations -from typing import Any - import pyarrow as pa import pytest import narwhals.stable.v1 as nw from narwhals.utils import parse_version from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts @@ -68,7 +67,7 @@ def test_str_to_uppercase( ], ) def test_str_to_uppercase_series( - constructor_eager: Any, + constructor_eager: ConstructorEager, data: dict[str, list[str]], expected: dict[str, list[str]], request: pytest.FixtureRequest, @@ -134,7 +133,7 @@ def test_str_to_lowercase( ], ) def test_str_to_lowercase_series( - constructor_eager: Any, + constructor_eager: ConstructorEager, data: dict[str, list[str]], expected: dict[str, list[str]], ) -> None: diff --git a/tests/expr_and_series/sum_test.py b/tests/expr_and_series/sum_test.py index 8059a097d..914d902f3 100644 --- a/tests/expr_and_series/sum_test.py +++ b/tests/expr_and_series/sum_test.py @@ -1,11 +1,10 @@ from __future__ import annotations -from typing import Any - import pytest import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.0, 8, 9]} @@ -20,7 +19,9 @@ def test_expr_sum_expr(constructor: Constructor, expr: nw.Expr) -> None: @pytest.mark.parametrize(("col", "expected"), [("a", 6), ("b", 14), ("z", 24.0)]) -def test_expr_sum_series(constructor_eager: Any, col: str, expected: float) -> None: +def test_expr_sum_series( + constructor_eager: ConstructorEager, col: str, expected: float +) -> None: series = nw.from_native(constructor_eager(data), eager_only=True)[col] result = series.sum() compare_dicts({col: [result]}, {col: [expected]}) diff --git a/tests/expr_and_series/tail_test.py b/tests/expr_and_series/tail_test.py index fc3e6159a..73acb6848 100644 --- a/tests/expr_and_series/tail_test.py +++ b/tests/expr_and_series/tail_test.py @@ -1,9 +1,8 @@ -from typing import Any - import pytest import narwhals as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts @@ -20,7 +19,7 @@ def test_head(constructor: Constructor, n: int, request: pytest.FixtureRequest) @pytest.mark.parametrize("n", [2, -1]) -def test_head_series(constructor_eager: Any, n: int) -> None: +def test_head_series(constructor_eager: ConstructorEager, n: int) -> None: df = nw.from_native(constructor_eager({"a": [1, 2, 3]}), eager_only=True) result = df.select(df["a"].tail(n)) expected = {"a": [2, 3]} diff --git a/tests/expr_and_series/unary_test.py b/tests/expr_and_series/unary_test.py index 66afd22af..c1e1d007b 100644 --- a/tests/expr_and_series/unary_test.py +++ b/tests/expr_and_series/unary_test.py @@ -1,7 +1,6 @@ -from typing import Any - import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts @@ -29,7 +28,7 @@ def test_unary(constructor: Constructor) -> None: compare_dicts(result, expected) -def test_unary_series(constructor_eager: Any) -> None: +def test_unary_series(constructor_eager: ConstructorEager) -> None: data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.0, 8, 9]} df = nw.from_native(constructor_eager(data), eager_only=True) result = { diff --git a/tests/expr_and_series/unique_test.py b/tests/expr_and_series/unique_test.py index 5639179ba..5048d3250 100644 --- a/tests/expr_and_series/unique_test.py +++ b/tests/expr_and_series/unique_test.py @@ -1,9 +1,8 @@ -from typing import Any - import pytest import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = {"a": [1, 1, 2]} @@ -18,7 +17,7 @@ def test_unique_expr(constructor: Constructor, request: pytest.FixtureRequest) - compare_dicts(result, expected) -def test_unique_series(constructor_eager: Any) -> None: +def test_unique_series(constructor_eager: ConstructorEager) -> None: series = nw.from_native(constructor_eager(data), eager_only=True)["a"] result = series.unique() expected = {"a": [1, 2]} diff --git a/tests/expr_and_series/when_test.py b/tests/expr_and_series/when_test.py index 6fabaa68b..eb1ac9c41 100644 --- a/tests/expr_and_series/when_test.py +++ b/tests/expr_and_series/when_test.py @@ -1,12 +1,11 @@ from __future__ import annotations -from typing import Any - import numpy as np import pytest import narwhals.stable.v1 as nw from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = { @@ -71,7 +70,7 @@ def test_value_numpy_array( compare_dicts(result, expected) -def test_value_series(constructor_eager: Any) -> None: +def test_value_series(constructor_eager: ConstructorEager) -> None: df = nw.from_native(constructor_eager(data)) s_data = {"s": [3, 4, 5]} s = nw.from_native(constructor_eager(s_data))["s"] @@ -110,7 +109,7 @@ def test_otherwise_numpy_array( compare_dicts(result, expected) -def test_otherwise_series(constructor_eager: Any) -> None: +def test_otherwise_series(constructor_eager: ConstructorEager) -> None: df = nw.from_native(constructor_eager(data)) s_data = {"s": [0, 9, 10]} s = nw.from_native(constructor_eager(s_data))["s"] diff --git a/tests/frame/array_dunder_test.py b/tests/frame/array_dunder_test.py index 8a082bb1f..ad3085f56 100644 --- a/tests/frame/array_dunder_test.py +++ b/tests/frame/array_dunder_test.py @@ -1,5 +1,3 @@ -from typing import Any - import numpy as np import pandas as pd import polars as pl @@ -8,10 +6,13 @@ import narwhals.stable.v1 as nw from narwhals.utils import parse_version +from tests.utils import ConstructorEager from tests.utils import compare_dicts -def test_array_dunder(request: pytest.FixtureRequest, constructor_eager: Any) -> None: +def test_array_dunder( + request: pytest.FixtureRequest, constructor_eager: ConstructorEager +) -> None: if "pyarrow_table" in str(constructor_eager) and parse_version( pa.__version__ ) < parse_version("16.0.0"): # pragma: no cover @@ -23,7 +24,7 @@ def test_array_dunder(request: pytest.FixtureRequest, constructor_eager: Any) -> def test_array_dunder_with_dtype( - request: pytest.FixtureRequest, constructor_eager: Any + request: pytest.FixtureRequest, constructor_eager: ConstructorEager ) -> None: if "pyarrow_table" in str(constructor_eager) and parse_version( pa.__version__ @@ -36,7 +37,7 @@ def test_array_dunder_with_dtype( def test_array_dunder_with_copy( - request: pytest.FixtureRequest, constructor_eager: Any + request: pytest.FixtureRequest, constructor_eager: ConstructorEager ) -> None: if "pyarrow_table" in str(constructor_eager) and parse_version(pa.__version__) < ( 16, diff --git a/tests/frame/get_column_test.py b/tests/frame/get_column_test.py index 58766ac31..ff4ebc506 100644 --- a/tests/frame/get_column_test.py +++ b/tests/frame/get_column_test.py @@ -1,13 +1,12 @@ -from typing import Any - import pandas as pd import pytest import narwhals.stable.v1 as nw +from tests.utils import ConstructorEager from tests.utils import compare_dicts -def test_get_column(constructor_eager: Any) -> None: +def test_get_column(constructor_eager: ConstructorEager) -> None: df = nw.from_native(constructor_eager({"a": [1, 2], "b": [3, 4]}), eager_only=True) result = df.get_column("a") compare_dicts({"a": result}, {"a": [1, 2]}) diff --git a/tests/frame/getitem_test.py b/tests/frame/getitem_test.py index ce96c1b24..a5397c7ef 100644 --- a/tests/frame/getitem_test.py +++ b/tests/frame/getitem_test.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Any - import numpy as np import pandas as pd import polars as pl @@ -9,6 +7,7 @@ import pytest import narwhals.stable.v1 as nw +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = { @@ -17,13 +16,13 @@ } -def test_slice_column(constructor_eager: Any) -> None: +def test_slice_column(constructor_eager: ConstructorEager) -> None: result = nw.from_native(constructor_eager(data))["a"] assert isinstance(result, nw.Series) compare_dicts({"a": result}, {"a": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]}) -def test_slice_rows(constructor_eager: Any) -> None: +def test_slice_rows(constructor_eager: ConstructorEager) -> None: result = nw.from_native(constructor_eager(data))[1:] compare_dicts(result, {"a": [2.0, 3.0, 4.0, 5.0, 6.0], "b": [12, 13, 14, 15, 16]}) @@ -32,7 +31,7 @@ def test_slice_rows(constructor_eager: Any) -> None: def test_slice_rows_with_step( - request: pytest.FixtureRequest, constructor_eager: Any + request: pytest.FixtureRequest, constructor_eager: ConstructorEager ) -> None: if "pyarrow_table" in str(constructor_eager): request.applymarker(pytest.mark.xfail) @@ -53,19 +52,19 @@ def test_slice_lazy_fails() -> None: _ = nw.from_native(pl.LazyFrame(data))[1:] -def test_slice_int(constructor_eager: Any) -> None: +def test_slice_int(constructor_eager: ConstructorEager) -> None: result = nw.from_native(constructor_eager(data), eager_only=True)[1] # type: ignore[call-overload] compare_dicts(result, {"a": [2], "b": [12]}) -def test_slice_fails(constructor_eager: Any) -> None: +def test_slice_fails(constructor_eager: ConstructorEager) -> None: class Foo: ... with pytest.raises(TypeError, match="Expected str or slice, got:"): nw.from_native(constructor_eager(data), eager_only=True)[Foo()] # type: ignore[call-overload] -def test_gather(constructor_eager: Any) -> None: +def test_gather(constructor_eager: ConstructorEager) -> None: df = nw.from_native(constructor_eager(data), eager_only=True) result = df[[0, 3, 1]] expected = { @@ -89,7 +88,7 @@ def test_gather_pandas_index() -> None: compare_dicts(result, expected) -def test_gather_rows_cols(constructor_eager: Any) -> None: +def test_gather_rows_cols(constructor_eager: ConstructorEager) -> None: native_df = constructor_eager(data) df = nw.from_native(native_df, eager_only=True) @@ -102,7 +101,7 @@ def test_gather_rows_cols(constructor_eager: Any) -> None: compare_dicts(result, expected) -def test_slice_both_tuples_of_ints(constructor_eager: Any) -> None: +def test_slice_both_tuples_of_ints(constructor_eager: ConstructorEager) -> None: data = {"a": [1, 2, 3], "b": [4, 5, 6], "c": [7, 8, 9]} df = nw.from_native(constructor_eager(data), eager_only=True) result = df[[0, 1], [0, 2]] @@ -110,7 +109,7 @@ def test_slice_both_tuples_of_ints(constructor_eager: Any) -> None: compare_dicts(result, expected) -def test_slice_int_rows_str_columns(constructor_eager: Any) -> None: +def test_slice_int_rows_str_columns(constructor_eager: ConstructorEager) -> None: data = {"a": [1, 2, 3], "b": [4, 5, 6], "c": [7, 8, 9]} df = nw.from_native(constructor_eager(data), eager_only=True) result = df[[0, 1], ["a", "c"]] @@ -118,7 +117,7 @@ def test_slice_int_rows_str_columns(constructor_eager: Any) -> None: compare_dicts(result, expected) -def test_slice_slice_columns(constructor_eager: Any) -> None: # noqa: PLR0915 +def test_slice_slice_columns(constructor_eager: ConstructorEager) -> None: # noqa: PLR0915 data = {"a": [1, 2, 3], "b": [4, 5, 6], "c": [7, 8, 9], "d": [1, 4, 2]} df = nw.from_native(constructor_eager(data), eager_only=True) result = df[[0, 1], "b":"c"] # type: ignore[misc] @@ -188,14 +187,14 @@ def test_slice_slice_columns(constructor_eager: Any) -> None: # noqa: PLR0915 compare_dicts(result, expected) -def test_slice_invalid(constructor_eager: Any) -> None: +def test_slice_invalid(constructor_eager: ConstructorEager) -> None: data = {"a": [1, 2], "b": [4, 5]} df = nw.from_native(constructor_eager(data), eager_only=True) with pytest.raises(TypeError, match="Hint:"): df[0, 0] -def test_slice_edge_cases(constructor_eager: Any) -> None: +def test_slice_edge_cases(constructor_eager: ConstructorEager) -> None: data = {"a": [1, 2, 3], "b": [4, 5, 6], "c": [7, 8, 9], "d": [1, 4, 2]} df = nw.from_native(constructor_eager(data), eager_only=True) assert df[[], :].shape == (0, 4) @@ -219,7 +218,7 @@ def test_slice_edge_cases(constructor_eager: Any) -> None: ], ) def test_get_item_works_with_tuple_and_list_and_range_row_and_col_indexing( - constructor_eager: Any, + constructor_eager: ConstructorEager, row_idx: list[int] | tuple[int] | range, col_idx: list[int] | tuple[int] | range, ) -> None: @@ -236,7 +235,7 @@ def test_get_item_works_with_tuple_and_list_and_range_row_and_col_indexing( ], ) def test_get_item_works_with_tuple_and_list_and_range_row_indexing_and_slice_col_indexing( - constructor_eager: Any, + constructor_eager: ConstructorEager, row_idx: list[int] | tuple[int] | range, col: slice, ) -> None: @@ -253,7 +252,7 @@ def test_get_item_works_with_tuple_and_list_and_range_row_indexing_and_slice_col ], ) def test_get_item_works_with_tuple_and_list_indexing_and_str( - constructor_eager: Any, + constructor_eager: ConstructorEager, row_idx: list[int] | tuple[int] | range, col: str, ) -> None: diff --git a/tests/frame/is_duplicated_test.py b/tests/frame/is_duplicated_test.py index e1eb3f298..a4dbd97aa 100644 --- a/tests/frame/is_duplicated_test.py +++ b/tests/frame/is_duplicated_test.py @@ -1,12 +1,11 @@ from __future__ import annotations -from typing import Any - import narwhals.stable.v1 as nw +from tests.utils import ConstructorEager from tests.utils import compare_dicts -def test_is_duplicated(constructor_eager: Any) -> None: +def test_is_duplicated(constructor_eager: ConstructorEager) -> None: data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.0, 8, 9]} df_raw = constructor_eager(data) df = nw.from_native(df_raw, eager_only=True) diff --git a/tests/frame/is_empty_test.py b/tests/frame/is_empty_test.py index a772abc8b..7ea6b22ad 100644 --- a/tests/frame/is_empty_test.py +++ b/tests/frame/is_empty_test.py @@ -1,14 +1,20 @@ from __future__ import annotations +from typing import TYPE_CHECKING from typing import Any import pytest import narwhals.stable.v1 as nw +if TYPE_CHECKING: + from tests.utils import ConstructorEager + @pytest.mark.parametrize(("threshold", "expected"), [(0, False), (10, True)]) -def test_is_empty(constructor_eager: Any, threshold: Any, expected: Any) -> None: +def test_is_empty( + constructor_eager: ConstructorEager, threshold: Any, expected: Any +) -> None: data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.0, 8, 9]} df_raw = constructor_eager(data) df = nw.from_native(df_raw, eager_only=True) diff --git a/tests/frame/is_unique_test.py b/tests/frame/is_unique_test.py index 4259c8773..cb9d57ba2 100644 --- a/tests/frame/is_unique_test.py +++ b/tests/frame/is_unique_test.py @@ -1,12 +1,11 @@ from __future__ import annotations -from typing import Any - import narwhals.stable.v1 as nw +from tests.utils import ConstructorEager from tests.utils import compare_dicts -def test_is_unique(constructor_eager: Any) -> None: +def test_is_unique(constructor_eager: ConstructorEager) -> None: data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.0, 8, 9]} df_raw = constructor_eager(data) df = nw.from_native(df_raw, eager_only=True) diff --git a/tests/frame/item_test.py b/tests/frame/item_test.py index 7afbee12d..4453ea611 100644 --- a/tests/frame/item_test.py +++ b/tests/frame/item_test.py @@ -6,6 +6,7 @@ import pytest import narwhals.stable.v1 as nw +from tests.utils import ConstructorEager from tests.utils import compare_dicts @@ -14,7 +15,10 @@ [(0, 2, 7), (1, "z", 8)], ) def test_item( - constructor_eager: Any, row: int | None, column: int | str | None, expected: Any + constructor_eager: ConstructorEager, + row: int | None, + column: int | str | None, + expected: Any, ) -> None: data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.0, 8, 9]} df = nw.from_native(constructor_eager(data), eager_only=True) @@ -43,7 +47,10 @@ def test_item( ], ) def test_item_value_error( - constructor_eager: Any, row: int | None, column: int | str | None, err_msg: str + constructor_eager: ConstructorEager, + row: int | None, + column: int | str | None, + err_msg: str, ) -> None: data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.0, 8, 9]} with pytest.raises(ValueError, match=err_msg): diff --git a/tests/frame/lazy_test.py b/tests/frame/lazy_test.py index 09ca734c2..8f1566e69 100644 --- a/tests/frame/lazy_test.py +++ b/tests/frame/lazy_test.py @@ -1,10 +1,9 @@ -from typing import Any - import narwhals as nw import narwhals.stable.v1 as nw_v1 +from tests.utils import ConstructorEager -def test_lazy(constructor_eager: Any) -> None: +def test_lazy(constructor_eager: ConstructorEager) -> None: df = nw.from_native(constructor_eager({"a": [1, 2, 3]}), eager_only=True) result = df.lazy() assert isinstance(result, nw.LazyFrame) diff --git a/tests/frame/len_test.py b/tests/frame/len_test.py index c06884e03..cd082ef2e 100644 --- a/tests/frame/len_test.py +++ b/tests/frame/len_test.py @@ -1,6 +1,5 @@ -from typing import Any - import narwhals.stable.v1 as nw +from tests.utils import ConstructorEager data = { "a": [1.0, 2.0, None, 4.0], @@ -8,6 +7,7 @@ } -def test_len(constructor_eager: Any) -> None: +def test_len(constructor_eager: ConstructorEager) -> None: result = len(nw.from_native(constructor_eager(data), eager_only=True)) + assert result == 4 diff --git a/tests/frame/null_count_test.py b/tests/frame/null_count_test.py index d3bf7f25c..71ac965f8 100644 --- a/tests/frame/null_count_test.py +++ b/tests/frame/null_count_test.py @@ -1,12 +1,11 @@ from __future__ import annotations -from typing import Any - import narwhals.stable.v1 as nw +from tests.utils import ConstructorEager from tests.utils import compare_dicts -def test_null_count(constructor_eager: Any) -> None: +def test_null_count(constructor_eager: ConstructorEager) -> None: data = {"a": [None, 3, 2], "b": [4, 4, 6], "z": [7.0, None, 9]} df_raw = constructor_eager(data) df = nw.from_native(df_raw, eager_only=True) diff --git a/tests/frame/row_test.py b/tests/frame/row_test.py index 599dcaeaf..d977a81f1 100644 --- a/tests/frame/row_test.py +++ b/tests/frame/row_test.py @@ -3,9 +3,10 @@ import pytest import narwhals.stable.v1 as nw +from tests.utils import ConstructorEager -def test_row_column(request: Any, constructor_eager: Any) -> None: +def test_row_column(request: Any, constructor_eager: ConstructorEager) -> None: if "cudf" in str(constructor_eager): request.applymarker(pytest.mark.xfail) diff --git a/tests/frame/rows_test.py b/tests/frame/rows_test.py index 744f66065..60e18658c 100644 --- a/tests/frame/rows_test.py +++ b/tests/frame/rows_test.py @@ -1,5 +1,6 @@ from __future__ import annotations +from typing import TYPE_CHECKING from typing import Any import pandas as pd @@ -10,6 +11,9 @@ import narwhals.stable.v1 as nw from narwhals.utils import parse_version +if TYPE_CHECKING: + from tests.utils import ConstructorEager + df_pandas = pd.DataFrame({"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.0, 8, 9]}) df_pa = pa.table({"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.0, 8, 9]}) if parse_version(pd.__version__) >= parse_version("1.5.0"): @@ -56,7 +60,7 @@ ) def test_iter_rows( request: Any, - constructor_eager: Any, + constructor_eager: ConstructorEager, named: bool, # noqa: FBT001 expected: list[tuple[Any, ...]] | list[dict[str, Any]], ) -> None: diff --git a/tests/frame/schema_test.py b/tests/frame/schema_test.py index 8e1116997..97c3722a7 100644 --- a/tests/frame/schema_test.py +++ b/tests/frame/schema_test.py @@ -12,6 +12,7 @@ import narwhals.stable.v1 as nw from narwhals.utils import parse_version from tests.utils import Constructor +from tests.utils import ConstructorEager data = { "a": [datetime(2020, 1, 1)], @@ -60,7 +61,9 @@ def test_string_disguised_as_object() -> None: assert result["a"] == nw.String -def test_actual_object(request: pytest.FixtureRequest, constructor_eager: Any) -> None: +def test_actual_object( + request: pytest.FixtureRequest, constructor_eager: ConstructorEager +) -> None: if any(x in str(constructor_eager) for x in ("modin", "pyarrow_table", "cudf")): request.applymarker(pytest.mark.xfail) diff --git a/tests/frame/shape_test.py b/tests/frame/shape_test.py index 2ab3a23bc..6930214f7 100644 --- a/tests/frame/shape_test.py +++ b/tests/frame/shape_test.py @@ -1,9 +1,8 @@ -from typing import Any - import narwhals.stable.v1 as nw +from tests.utils import ConstructorEager -def test_shape(constructor_eager: Any) -> None: +def test_shape(constructor_eager: ConstructorEager) -> None: result = nw.from_native( constructor_eager({"a": [1, 2], "b": [4, 5], "c": [7, 8]}), eager_only=True ).shape diff --git a/tests/frame/to_arrow_test.py b/tests/frame/to_arrow_test.py index f20bdf28c..373f6310b 100644 --- a/tests/frame/to_arrow_test.py +++ b/tests/frame/to_arrow_test.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING import pandas as pd import pyarrow as pa @@ -9,8 +9,13 @@ import narwhals.stable.v1 as nw from narwhals.utils import parse_version +if TYPE_CHECKING: + from tests.utils import ConstructorEager -def test_to_arrow(request: pytest.FixtureRequest, constructor_eager: Any) -> None: + +def test_to_arrow( + request: pytest.FixtureRequest, constructor_eager: ConstructorEager +) -> None: if "pandas" in str(constructor_eager) and parse_version(pd.__version__) < (1, 0, 0): # pyarrow requires pandas>=1.0.0 request.applymarker(pytest.mark.xfail) diff --git a/tests/frame/to_dict_test.py b/tests/frame/to_dict_test.py index 29c3d2270..b76003bd1 100644 --- a/tests/frame/to_dict_test.py +++ b/tests/frame/to_dict_test.py @@ -1,22 +1,21 @@ -from typing import Any - import pytest import narwhals.stable.v1 as nw +from tests.utils import ConstructorEager from tests.utils import compare_dicts @pytest.mark.filterwarnings( "ignore:.*all arguments of to_dict except for the argument:FutureWarning" ) -def test_to_dict(constructor_eager: Any) -> None: +def test_to_dict(constructor_eager: ConstructorEager) -> None: data = {"a": [1, 3, 2], "b": [4, 4, 6], "c": [7.0, 8, 9]} df = nw.from_native(constructor_eager(data), eager_only=True) result = df.to_dict(as_series=False) assert result == data -def test_to_dict_as_series(constructor_eager: Any) -> None: +def test_to_dict_as_series(constructor_eager: ConstructorEager) -> None: data = {"a": [1, 3, 2], "b": [4, 4, 6], "c": [7.0, 8, 9]} df = nw.from_native(constructor_eager(data), eager_only=True) result = df.to_dict(as_series=True) diff --git a/tests/frame/to_native_test.py b/tests/frame/to_native_test.py index d8f4132bf..c6de99a17 100644 --- a/tests/frame/to_native_test.py +++ b/tests/frame/to_native_test.py @@ -1,9 +1,8 @@ -from typing import Any - import narwhals.stable.v1 as nw +from tests.utils import Constructor -def test_to_native(constructor: Any) -> None: +def test_to_native(constructor: Constructor) -> None: data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.1, 8, 9]} df_raw = constructor(data) df = nw.from_native(df_raw) diff --git a/tests/frame/to_numpy_test.py b/tests/frame/to_numpy_test.py index d573f4322..aa3dfc2e4 100644 --- a/tests/frame/to_numpy_test.py +++ b/tests/frame/to_numpy_test.py @@ -1,13 +1,16 @@ from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING import numpy as np import narwhals.stable.v1 as nw +if TYPE_CHECKING: + from tests.utils import ConstructorEager -def test_to_numpy(constructor_eager: Any) -> None: + +def test_to_numpy(constructor_eager: ConstructorEager) -> None: data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.1, 8, 9]} df_raw = constructor_eager(data) result = nw.from_native(df_raw, eager_only=True).to_numpy() diff --git a/tests/frame/to_pandas_test.py b/tests/frame/to_pandas_test.py index 671a5d857..d6370f02e 100644 --- a/tests/frame/to_pandas_test.py +++ b/tests/frame/to_pandas_test.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING import pandas as pd import pytest @@ -8,13 +8,18 @@ import narwhals.stable.v1 as nw from narwhals.utils import parse_version +if TYPE_CHECKING: + from tests.utils import ConstructorEager + @pytest.mark.filterwarnings("ignore:.*Passing a BlockManager.*:DeprecationWarning") @pytest.mark.skipif( parse_version(pd.__version__) < parse_version("2.0.0"), reason="too old for pandas-pyarrow", ) -def test_convert_pandas(constructor_eager: Any, request: pytest.FixtureRequest) -> None: +def test_convert_pandas( + constructor_eager: ConstructorEager, request: pytest.FixtureRequest +) -> None: if "modin" in str(constructor_eager): request.applymarker(pytest.mark.xfail) data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.0, 8, 9]} diff --git a/tests/frame/write_csv_test.py b/tests/frame/write_csv_test.py index ed9303604..84ce84f0d 100644 --- a/tests/frame/write_csv_test.py +++ b/tests/frame/write_csv_test.py @@ -1,16 +1,18 @@ from __future__ import annotations from typing import TYPE_CHECKING -from typing import Any import narwhals.stable.v1 as nw +from tests.utils import ConstructorEager from tests.utils import is_windows if TYPE_CHECKING: import pytest -def test_write_csv(constructor_eager: Any, tmpdir: pytest.TempdirFactory) -> None: +def test_write_csv( + constructor_eager: ConstructorEager, tmpdir: pytest.TempdirFactory +) -> None: data = {"a": [1, 2, 3]} path = tmpdir / "foo.csv" # type: ignore[operator] result = nw.from_native(constructor_eager(data), eager_only=True).write_csv(str(path)) diff --git a/tests/frame/write_parquet_test.py b/tests/frame/write_parquet_test.py index 8efaefb55..c616de198 100644 --- a/tests/frame/write_parquet_test.py +++ b/tests/frame/write_parquet_test.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING import pandas as pd import pytest @@ -8,13 +8,18 @@ import narwhals.stable.v1 as nw from narwhals.utils import parse_version +if TYPE_CHECKING: + from tests.utils import ConstructorEager + data = {"a": [1, 2, 3]} @pytest.mark.skipif( parse_version(pd.__version__) < parse_version("2.0.0"), reason="too old for pyarrow" ) -def test_write_parquet(constructor_eager: Any, tmpdir: pytest.TempdirFactory) -> None: +def test_write_parquet( + constructor_eager: ConstructorEager, tmpdir: pytest.TempdirFactory +) -> None: path = tmpdir / "foo.parquet" # type: ignore[operator] nw.from_native(constructor_eager(data), eager_only=True).write_parquet(str(path)) assert path.exists() diff --git a/tests/group_by_test.py b/tests/group_by_test.py index 90cb48c26..27407a26a 100644 --- a/tests/group_by_test.py +++ b/tests/group_by_test.py @@ -1,7 +1,6 @@ from __future__ import annotations from contextlib import nullcontext -from typing import Any import pandas as pd import polars as pl @@ -11,6 +10,7 @@ import narwhals.stable.v1 as nw from narwhals.utils import parse_version from tests.utils import Constructor +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = {"a": [1, 1, 3], "b": [4, 4, 6], "c": [7.0, 8, 9]} @@ -73,7 +73,7 @@ def test_invalid_group_by() -> None: ) -def test_group_by_iter(constructor_eager: Any) -> None: +def test_group_by_iter(constructor_eager: ConstructorEager) -> None: df = nw.from_native(constructor_eager(data), eager_only=True) expected_keys = [(1,), (3,)] keys = [] diff --git a/tests/new_series_test.py b/tests/new_series_test.py index fad4a7536..37e5d2633 100644 --- a/tests/new_series_test.py +++ b/tests/new_series_test.py @@ -1,14 +1,13 @@ -from typing import Any - import pandas as pd import pytest import narwhals as nw import narwhals.stable.v1 as nw_v1 +from tests.utils import ConstructorEager from tests.utils import compare_dicts -def test_new_series(constructor_eager: Any) -> None: +def test_new_series(constructor_eager: ConstructorEager) -> None: s = nw.from_native(constructor_eager({"a": [1, 2, 3]}), eager_only=True)["a"] result = nw.new_series("b", [4, 1, 2], native_namespace=nw.get_native_namespace(s)) expected = {"b": [4, 1, 2]} @@ -25,7 +24,7 @@ def test_new_series(constructor_eager: Any) -> None: compare_dicts(result.to_frame(), expected) -def test_new_series_v1(constructor_eager: Any) -> None: +def test_new_series_v1(constructor_eager: ConstructorEager) -> None: s = nw_v1.from_native(constructor_eager({"a": [1, 2, 3]}), eager_only=True)["a"] result = nw_v1.new_series( "b", [4, 1, 2], native_namespace=nw_v1.get_native_namespace(s) diff --git a/tests/series_only/__iter___test.py b/tests/series_only/__iter___test.py index a0a5c1189..06753917b 100644 --- a/tests/series_only/__iter___test.py +++ b/tests/series_only/__iter___test.py @@ -1,17 +1,22 @@ from __future__ import annotations from collections.abc import Iterable -from typing import Any +from typing import TYPE_CHECKING import pytest import narwhals.stable.v1 as nw from tests.utils import compare_dicts +if TYPE_CHECKING: + from tests.utils import ConstructorEager + data = [1, 2, 3] -def test_iter(constructor_eager: Any, request: pytest.FixtureRequest) -> None: +def test_iter( + constructor_eager: ConstructorEager, request: pytest.FixtureRequest +) -> None: if "cudf" in str(constructor_eager): request.applymarker(pytest.mark.xfail) s = nw.from_native(constructor_eager({"a": data}), eager_only=True)["a"] diff --git a/tests/series_only/array_dunder_test.py b/tests/series_only/array_dunder_test.py index c09bea9ec..0d95e2db3 100644 --- a/tests/series_only/array_dunder_test.py +++ b/tests/series_only/array_dunder_test.py @@ -1,5 +1,3 @@ -from typing import Any - import numpy as np import pandas as pd import pyarrow as pa @@ -7,10 +5,13 @@ import narwhals.stable.v1 as nw from narwhals.utils import parse_version +from tests.utils import ConstructorEager from tests.utils import compare_dicts -def test_array_dunder(request: pytest.FixtureRequest, constructor_eager: Any) -> None: +def test_array_dunder( + request: pytest.FixtureRequest, constructor_eager: ConstructorEager +) -> None: if "pyarrow_table" in str(constructor_eager) and parse_version( pa.__version__ ) < parse_version("16.0.0"): # pragma: no cover @@ -22,7 +23,7 @@ def test_array_dunder(request: pytest.FixtureRequest, constructor_eager: Any) -> def test_array_dunder_with_dtype( - request: pytest.FixtureRequest, constructor_eager: Any + request: pytest.FixtureRequest, constructor_eager: ConstructorEager ) -> None: if "pyarrow_table" in str(constructor_eager) and parse_version( pa.__version__ @@ -35,7 +36,7 @@ def test_array_dunder_with_dtype( def test_array_dunder_with_copy( - request: pytest.FixtureRequest, constructor_eager: Any + request: pytest.FixtureRequest, constructor_eager: ConstructorEager ) -> None: if "pyarrow_table" in str(constructor_eager) and parse_version( pa.__version__ diff --git a/tests/series_only/dtype_test.py b/tests/series_only/dtype_test.py index 68d10fbca..8200150f0 100644 --- a/tests/series_only/dtype_test.py +++ b/tests/series_only/dtype_test.py @@ -1,13 +1,16 @@ from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING import narwhals.stable.v1 as nw +if TYPE_CHECKING: + from tests.utils import ConstructorEager + data = {"a": [1, 3, 2]} -def test_dtype(constructor_eager: Any) -> None: +def test_dtype(constructor_eager: ConstructorEager) -> None: series = nw.from_native(constructor_eager(data), eager_only=True)["a"] result = series.dtype assert result == nw.Int64 diff --git a/tests/series_only/is_empty_test.py b/tests/series_only/is_empty_test.py index 80b8ab799..390fa7f4f 100644 --- a/tests/series_only/is_empty_test.py +++ b/tests/series_only/is_empty_test.py @@ -1,9 +1,8 @@ -from typing import Any - import narwhals.stable.v1 as nw +from tests.utils import ConstructorEager -def test_is_empty(constructor_eager: Any) -> None: +def test_is_empty(constructor_eager: ConstructorEager) -> None: series = nw.from_native(constructor_eager({"a": [1, 2, 3]}), eager_only=True)["a"] assert not series.is_empty() assert not series[:1].is_empty() diff --git a/tests/series_only/is_ordered_categorical_test.py b/tests/series_only/is_ordered_categorical_test.py index 26358f9a6..10251e362 100644 --- a/tests/series_only/is_ordered_categorical_test.py +++ b/tests/series_only/is_ordered_categorical_test.py @@ -1,5 +1,3 @@ -from typing import Any - import pandas as pd import polars as pl import pyarrow as pa @@ -7,6 +5,7 @@ import narwhals.stable.v1 as nw from narwhals.utils import parse_version +from tests.utils import ConstructorEager def test_is_ordered_categorical() -> None: @@ -39,7 +38,7 @@ def test_is_ordered_categorical_interchange_protocol() -> None: def test_is_definitely_not_ordered_categorical( - constructor_eager: Any, + constructor_eager: ConstructorEager, ) -> None: assert not nw.is_ordered_categorical( nw.from_native(constructor_eager({"a": [1, 2, 3]}), eager_only=True)["a"] diff --git a/tests/series_only/is_sorted_test.py b/tests/series_only/is_sorted_test.py index 3942b5619..23610ee56 100644 --- a/tests/series_only/is_sorted_test.py +++ b/tests/series_only/is_sorted_test.py @@ -1,10 +1,9 @@ from __future__ import annotations -from typing import Any - import pytest import narwhals.stable.v1 as nw +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = [1, 3, 2] @@ -17,7 +16,7 @@ [(data, False, False), (data_sorted, False, True), (data_sorted, True, False)], ) def test_is_sorted( - constructor_eager: Any, + constructor_eager: ConstructorEager, input_data: str, descending: bool, # noqa: FBT001 expected: bool, # noqa: FBT001 @@ -27,7 +26,7 @@ def test_is_sorted( compare_dicts({"a": [result]}, {"a": [expected]}) -def test_is_sorted_invalid(constructor_eager: Any) -> None: +def test_is_sorted_invalid(constructor_eager: ConstructorEager) -> None: series = nw.from_native(constructor_eager({"a": data_sorted}), eager_only=True)["a"] with pytest.raises(TypeError): diff --git a/tests/series_only/item_test.py b/tests/series_only/item_test.py index 869bd7c38..4c199578b 100644 --- a/tests/series_only/item_test.py +++ b/tests/series_only/item_test.py @@ -1,18 +1,18 @@ from __future__ import annotations import re -from typing import Any import pytest import narwhals.stable.v1 as nw +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = [1, 3, 2] @pytest.mark.parametrize(("index", "expected"), [(0, 1), (1, 3)]) -def test_item(constructor_eager: Any, index: int, expected: int) -> None: +def test_item(constructor_eager: ConstructorEager, index: int, expected: int) -> None: series = nw.from_native(constructor_eager({"a": data}), eager_only=True)["a"] result = series.item(index) compare_dicts({"a": [result]}, {"a": [expected]}) diff --git a/tests/series_only/scatter_test.py b/tests/series_only/scatter_test.py index 0677a8dd8..9e4bb08af 100644 --- a/tests/series_only/scatter_test.py +++ b/tests/series_only/scatter_test.py @@ -1,14 +1,15 @@ from __future__ import annotations -from typing import Any - import pytest import narwhals as nw +from tests.utils import ConstructorEager from tests.utils import compare_dicts -def test_scatter(constructor_eager: Any, request: pytest.FixtureRequest) -> None: +def test_scatter( + constructor_eager: ConstructorEager, request: pytest.FixtureRequest +) -> None: if "modin" in str(constructor_eager): # https://github.com/modin-project/modin/issues/7392 request.applymarker(pytest.mark.xfail) @@ -26,7 +27,7 @@ def test_scatter(constructor_eager: Any, request: pytest.FixtureRequest) -> None compare_dicts(result, expected) -def test_scatter_unchanged(constructor_eager: Any) -> None: +def test_scatter_unchanged(constructor_eager: ConstructorEager) -> None: df = nw.from_native( constructor_eager({"a": [1, 2, 3], "b": [142, 124, 132]}), eager_only=True ) @@ -40,7 +41,7 @@ def test_scatter_unchanged(constructor_eager: Any) -> None: compare_dicts(df, expected) -def test_single_series(constructor_eager: Any) -> None: +def test_single_series(constructor_eager: ConstructorEager) -> None: df = nw.from_native( constructor_eager({"a": [1, 2, 3], "b": [142, 124, 132]}), eager_only=True ) diff --git a/tests/series_only/shape_test.py b/tests/series_only/shape_test.py index 4a1c0726d..d3e276bb2 100644 --- a/tests/series_only/shape_test.py +++ b/tests/series_only/shape_test.py @@ -1,9 +1,8 @@ -from typing import Any - import narwhals.stable.v1 as nw +from tests.utils import ConstructorEager -def test_shape(constructor_eager: Any) -> None: +def test_shape(constructor_eager: ConstructorEager) -> None: result = nw.from_native(constructor_eager({"a": [1, 2]}), eager_only=True)["a"].shape expected = (2,) assert result == expected diff --git a/tests/series_only/slice_test.py b/tests/series_only/slice_test.py index 9ae194774..eba24fdbd 100644 --- a/tests/series_only/slice_test.py +++ b/tests/series_only/slice_test.py @@ -1,10 +1,9 @@ -from typing import Any - import narwhals.stable.v1 as nw +from tests.utils import ConstructorEager from tests.utils import compare_dicts -def test_slice(constructor_eager: Any) -> None: +def test_slice(constructor_eager: ConstructorEager) -> None: data = {"a": [1, 2, 3], "b": [4, 5, 6], "c": [7, 8, 9], "d": [1, 4, 2]} df = nw.from_native(constructor_eager(data), eager_only=True) result = {"a": df["a"][[0, 1]]} diff --git a/tests/series_only/to_arrow_test.py b/tests/series_only/to_arrow_test.py index 5181a6786..ae6246e55 100644 --- a/tests/series_only/to_arrow_test.py +++ b/tests/series_only/to_arrow_test.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING import pyarrow as pa import pyarrow.compute as pc @@ -8,8 +8,11 @@ import narwhals.stable.v1 as nw +if TYPE_CHECKING: + from tests.utils import ConstructorEager -def test_to_arrow(constructor_eager: Any) -> None: + +def test_to_arrow(constructor_eager: ConstructorEager) -> None: data = [1, 2, 3] result = nw.from_native(constructor_eager({"a": data}), eager_only=True)[ "a" @@ -20,7 +23,7 @@ def test_to_arrow(constructor_eager: Any) -> None: def test_to_arrow_with_nulls( - constructor_eager: Any, request: pytest.FixtureRequest + constructor_eager: ConstructorEager, request: pytest.FixtureRequest ) -> None: if "pandas_constructor" in str(constructor_eager) or "modin_constructor" in str( constructor_eager diff --git a/tests/series_only/to_dummy_test.py b/tests/series_only/to_dummy_test.py index 2cf7f59c7..938b8d04e 100644 --- a/tests/series_only/to_dummy_test.py +++ b/tests/series_only/to_dummy_test.py @@ -1,15 +1,14 @@ -from typing import Any - import pytest import narwhals.stable.v1 as nw +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = [1, 2, 3] @pytest.mark.parametrize("sep", ["_", "-"]) -def test_to_dummies(constructor_eager: Any, sep: str) -> None: +def test_to_dummies(constructor_eager: ConstructorEager, sep: str) -> None: s = nw.from_native(constructor_eager({"a": data}), eager_only=True)["a"].alias("a") result = s.to_dummies(separator=sep) expected = {f"a{sep}1": [1, 0, 0], f"a{sep}2": [0, 1, 0], f"a{sep}3": [0, 0, 1]} @@ -18,7 +17,7 @@ def test_to_dummies(constructor_eager: Any, sep: str) -> None: @pytest.mark.parametrize("sep", ["_", "-"]) -def test_to_dummies_drop_first(constructor_eager: Any, sep: str) -> None: +def test_to_dummies_drop_first(constructor_eager: ConstructorEager, sep: str) -> None: s = nw.from_native(constructor_eager({"a": data}), eager_only=True)["a"].alias("a") result = s.to_dummies(drop_first=True, separator=sep) expected = {f"a{sep}2": [0, 1, 0], f"a{sep}3": [0, 0, 1]} diff --git a/tests/series_only/to_frame_test.py b/tests/series_only/to_frame_test.py index 890036183..065da1414 100644 --- a/tests/series_only/to_frame_test.py +++ b/tests/series_only/to_frame_test.py @@ -1,12 +1,11 @@ -from typing import Any - import narwhals.stable.v1 as nw +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = [1, 2, 3] -def test_to_frame(constructor_eager: Any) -> None: +def test_to_frame(constructor_eager: ConstructorEager) -> None: df = ( nw.from_native(constructor_eager({"a": data}), eager_only=True)["a"] .alias("") diff --git a/tests/series_only/to_list_test.py b/tests/series_only/to_list_test.py index 11d02d0d2..0f91b9879 100644 --- a/tests/series_only/to_list_test.py +++ b/tests/series_only/to_list_test.py @@ -1,14 +1,15 @@ -from typing import Any - import pytest import narwhals.stable.v1 as nw +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = [1, 2, 3] -def test_to_list(constructor_eager: Any, request: pytest.FixtureRequest) -> None: +def test_to_list( + constructor_eager: ConstructorEager, request: pytest.FixtureRequest +) -> None: if "cudf" in str(constructor_eager): # pragma: no cover request.applymarker(pytest.mark.xfail) s = nw.from_native(constructor_eager({"a": data}), eager_only=True)["a"] diff --git a/tests/series_only/to_native_test.py b/tests/series_only/to_native_test.py index 269348ea3..e6955b4c3 100644 --- a/tests/series_only/to_native_test.py +++ b/tests/series_only/to_native_test.py @@ -1,14 +1,17 @@ from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING import narwhals.stable.v1 as nw +if TYPE_CHECKING: + from tests.utils import ConstructorEager + data = [4, 4, 4, 1, 6, 6, 4, 4, 1, 1] -def test_to_native(constructor_eager: Any) -> None: - orig_series = constructor_eager({"a": data})["a"] +def test_to_native(constructor_eager: ConstructorEager) -> None: + orig_series = constructor_eager({"a": data})["a"] # type: ignore[index] 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/series_only/to_numpy_test.py b/tests/series_only/to_numpy_test.py index 2f1464a57..966a44449 100644 --- a/tests/series_only/to_numpy_test.py +++ b/tests/series_only/to_numpy_test.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING import numpy as np import pytest @@ -8,8 +8,13 @@ import narwhals.stable.v1 as nw +if TYPE_CHECKING: + from tests.utils import ConstructorEager -def test_to_numpy(constructor_eager: Any, request: pytest.FixtureRequest) -> None: + +def test_to_numpy( + constructor_eager: ConstructorEager, request: pytest.FixtureRequest +) -> None: if ( "pandas_constructor" in str(constructor_eager) or "modin_constructor" in str(constructor_eager) diff --git a/tests/series_only/to_pandas_test.py b/tests/series_only/to_pandas_test.py index 30c7906c7..46d7df6da 100644 --- a/tests/series_only/to_pandas_test.py +++ b/tests/series_only/to_pandas_test.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING import pandas as pd import pytest @@ -9,13 +9,18 @@ import narwhals.stable.v1 as nw from narwhals.utils import parse_version +if TYPE_CHECKING: + from tests.utils import ConstructorEager + data = [1, 3, 2] @pytest.mark.skipif( parse_version(pd.__version__) < parse_version("2.0.0"), reason="too old for pyarrow" ) -def test_convert(request: pytest.FixtureRequest, constructor_eager: Any) -> None: +def test_convert( + request: pytest.FixtureRequest, constructor_eager: ConstructorEager +) -> None: if any( cname in str(constructor_eager) for cname in ("pandas_nullable", "pandas_pyarrow", "modin") diff --git a/tests/series_only/value_counts_test.py b/tests/series_only/value_counts_test.py index d19a1440b..342ad7272 100644 --- a/tests/series_only/value_counts_test.py +++ b/tests/series_only/value_counts_test.py @@ -7,6 +7,7 @@ import narwhals.stable.v1 as nw from narwhals.utils import parse_version +from tests.utils import ConstructorEager from tests.utils import compare_dicts data = [4, 4, 4, 1, 6, 6, 4, 4, 1, 1] @@ -16,7 +17,7 @@ @pytest.mark.parametrize("name", [None, "count_name"]) def test_value_counts( request: pytest.FixtureRequest, - constructor_eager: Any, + constructor_eager: ConstructorEager, normalize: Any, name: str | None, ) -> None: diff --git a/tests/series_only/zip_with_test.py b/tests/series_only/zip_with_test.py index 5d1461da3..2de31c060 100644 --- a/tests/series_only/zip_with_test.py +++ b/tests/series_only/zip_with_test.py @@ -1,12 +1,11 @@ from __future__ import annotations -from typing import Any - import narwhals.stable.v1 as nw +from tests.utils import ConstructorEager from tests.utils import compare_dicts -def test_zip_with(constructor_eager: Any) -> None: +def test_zip_with(constructor_eager: ConstructorEager) -> None: series1 = nw.from_native(constructor_eager({"a": [1, 3, 2]}), eager_only=True)["a"] series2 = nw.from_native(constructor_eager({"a": [4, 4, 6]}), eager_only=True)["a"] mask = nw.from_native(constructor_eager({"a": [True, False, True]}), eager_only=True)[ @@ -18,7 +17,7 @@ def test_zip_with(constructor_eager: Any) -> None: compare_dicts({"a": result}, {"a": expected}) -def test_zip_with_length_1(constructor_eager: Any) -> None: +def test_zip_with_length_1(constructor_eager: ConstructorEager) -> None: series1 = nw.from_native(constructor_eager({"a": [1]}), eager_only=True)["a"] series2 = nw.from_native(constructor_eager({"a": [4]}), eager_only=True)["a"] mask = nw.from_native(constructor_eager({"a": [False]}), eager_only=True)["a"] diff --git a/tests/translate/to_native_test.py b/tests/translate/to_native_test.py index 03d7704ec..90ec11ab1 100644 --- a/tests/translate/to_native_test.py +++ b/tests/translate/to_native_test.py @@ -4,6 +4,7 @@ import pytest import narwhals.stable.v1 as nw +from tests.utils import ConstructorEager @pytest.mark.parametrize( @@ -20,7 +21,7 @@ ], ) def test_to_native( - constructor_eager: Any, method: str, strict: Any, context: Any + constructor_eager: ConstructorEager, method: str, strict: Any, context: Any ) -> None: df = nw.from_native(constructor_eager({"a": [1, 2, 3]})) diff --git a/tests/utils.py b/tests/utils.py index 15ce25140..302f26f1d 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -10,6 +10,7 @@ import pandas as pd +from narwhals.typing import IntoDataFrame from narwhals.typing import IntoFrame from narwhals.utils import Implementation @@ -19,6 +20,7 @@ from typing_extensions import TypeAlias # pragma: no cover Constructor: TypeAlias = Callable[[Any], IntoFrame] +ConstructorEager: TypeAlias = Callable[[Any], IntoDataFrame] def zip_strict(left: Sequence[Any], right: Sequence[Any]) -> Iterator[Any]: @@ -52,7 +54,7 @@ def compare_dicts(result: Any, expected: dict[str, Any]) -> None: rhs = rhs.item() # noqa: PLW2901 if isinstance(lhs, float) and not math.isnan(lhs): assert math.isclose(lhs, rhs, rel_tol=0, abs_tol=1e-6), (lhs, rhs) - elif isinstance(lhs, float) and math.isnan(lhs): + elif isinstance(lhs, float) and math.isnan(lhs) and rhs is not None: assert math.isnan(rhs), (lhs, rhs) # pragma: no cover elif pd.isna(lhs): assert pd.isna(rhs), (lhs, rhs) From 9b628ee06f39b7649b071e94c3a663a8c758c46d Mon Sep 17 00:00:00 2001 From: Zhengbo Wang Date: Thu, 17 Oct 2024 18:56:42 +0800 Subject: [PATCH 5/8] feat: Add 'IntoSeries' (#991) --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> --- .github/workflows/extremes.yml | 2 +- narwhals/stable/v1/__init__.py | 21 +++++++++++---------- narwhals/stable/v1/typing.py | 9 +++++++++ narwhals/translate.py | 21 +++++++++++---------- narwhals/typing.py | 9 +++++++++ 5 files changed, 41 insertions(+), 21 deletions(-) diff --git a/.github/workflows/extremes.yml b/.github/workflows/extremes.yml index cf488fd2d..3f02f965f 100644 --- a/.github/workflows/extremes.yml +++ b/.github/workflows/extremes.yml @@ -90,7 +90,7 @@ jobs: nightlies: strategy: matrix: - python-version: ["3.11"] + python-version: ["3.12"] os: [ubuntu-latest] if: github.event.pull_request.head.repo.full_name == github.repository runs-on: ${{ matrix.os }} diff --git a/narwhals/stable/v1/__init__.py b/narwhals/stable/v1/__init__.py index 93406a145..c500cdab1 100644 --- a/narwhals/stable/v1/__init__.py +++ b/narwhals/stable/v1/__init__.py @@ -52,6 +52,7 @@ from narwhals.translate import to_native from narwhals.typing import IntoDataFrameT from narwhals.typing import IntoFrameT +from narwhals.typing import IntoSeriesT from narwhals.utils import is_ordered_categorical as nw_is_ordered_categorical from narwhals.utils import maybe_align_index as nw_maybe_align_index from narwhals.utils import maybe_convert_dtypes as nw_maybe_convert_dtypes @@ -571,26 +572,26 @@ def _stableify( @overload def from_native( - native_dataframe: Any, + native_dataframe: IntoDataFrameT | IntoSeriesT, *, strict: Literal[False], eager_only: None = ..., eager_or_interchange_only: Literal[True], series_only: None = ..., allow_series: Literal[True], -) -> Any: ... +) -> DataFrame[IntoFrameT] | Series: ... @overload def from_native( - native_dataframe: Any, + native_dataframe: IntoDataFrameT | IntoSeriesT, *, strict: Literal[False], eager_only: Literal[True], eager_or_interchange_only: None = ..., series_only: None = ..., allow_series: Literal[True], -) -> Any: ... +) -> DataFrame[IntoDataFrameT] | Series: ... @overload @@ -643,26 +644,26 @@ def from_native( @overload def from_native( - native_dataframe: Any, + native_dataframe: IntoFrameT | IntoSeriesT, *, strict: Literal[False], eager_only: None = ..., eager_or_interchange_only: None = ..., series_only: None = ..., allow_series: Literal[True], -) -> Any: ... +) -> DataFrame[IntoFrameT] | LazyFrame[IntoFrameT] | Series: ... @overload def from_native( - native_dataframe: Any, + native_dataframe: IntoSeriesT, *, strict: Literal[False], eager_only: None = ..., eager_or_interchange_only: None = ..., series_only: Literal[True], allow_series: None = ..., -) -> Any: ... +) -> Series: ... @overload @@ -723,7 +724,7 @@ def from_native( @overload def from_native( - native_dataframe: Any, + native_dataframe: IntoFrameT | IntoSeriesT, *, strict: Literal[True] = ..., eager_only: None = ..., @@ -739,7 +740,7 @@ def from_native( @overload def from_native( - native_dataframe: Any, + native_dataframe: IntoSeriesT | Any, # remain `Any` for downstream compatibility *, strict: Literal[True] = ..., eager_only: None = ..., diff --git a/narwhals/stable/v1/typing.py b/narwhals/stable/v1/typing.py index aebe78fc7..79adf5063 100644 --- a/narwhals/stable/v1/typing.py +++ b/narwhals/stable/v1/typing.py @@ -29,6 +29,9 @@ def columns(self) -> Any: ... def join(self, *args: Any, **kwargs: Any) -> Any: ... + class NativeSeries(Protocol): + def __len__(self) -> int: ... + class DataFrameLike(Protocol): def __dataframe__(self, *args: Any, **kwargs: Any) -> Any: ... @@ -47,11 +50,15 @@ def __dataframe__(self, *args: Any, **kwargs: Any) -> Any: ... Frame: TypeAlias = Union["DataFrame[Any]", "LazyFrame[Any]"] """Narwhals DataFrame or Narwhals LazyFrame""" +IntoSeries: TypeAlias = Union["Series", "NativeSeries"] +"""Anything which can be converted to a Narwhals Series.""" + # TypeVars for some of the above IntoFrameT = TypeVar("IntoFrameT", bound="IntoFrame") IntoDataFrameT = TypeVar("IntoDataFrameT", bound="IntoDataFrame") FrameT = TypeVar("FrameT", bound="Frame") DataFrameT = TypeVar("DataFrameT", bound="DataFrame[Any]") +IntoSeriesT = TypeVar("IntoSeriesT", bound="IntoSeries") class DTypes: @@ -89,4 +96,6 @@ class DTypes: "Frame", "FrameT", "DataFrameT", + "IntoSeries", + "IntoSeriesT", ] diff --git a/narwhals/translate.py b/narwhals/translate.py index 4c23f6d91..0dc0cd467 100644 --- a/narwhals/translate.py +++ b/narwhals/translate.py @@ -37,6 +37,7 @@ from narwhals.typing import DTypes from narwhals.typing import IntoDataFrameT from narwhals.typing import IntoFrameT + from narwhals.typing import IntoSeriesT T = TypeVar("T") @@ -86,26 +87,26 @@ def to_native( @overload def from_native( - native_object: Any, + native_object: IntoDataFrameT | IntoSeriesT, *, strict: Literal[False], eager_only: None = ..., eager_or_interchange_only: Literal[True], series_only: None = ..., allow_series: Literal[True], -) -> Any: ... +) -> DataFrame[IntoDataFrameT]: ... @overload def from_native( - native_object: Any, + native_object: IntoDataFrameT | IntoSeriesT, *, strict: Literal[False], eager_only: Literal[True], eager_or_interchange_only: None = ..., series_only: None = ..., allow_series: Literal[True], -) -> Any: ... +) -> DataFrame[IntoDataFrameT] | Series: ... @overload @@ -158,26 +159,26 @@ def from_native( @overload def from_native( - native_object: Any, + native_object: IntoFrameT | IntoSeriesT, *, strict: Literal[False], eager_only: None = ..., eager_or_interchange_only: None = ..., series_only: None = ..., allow_series: Literal[True], -) -> Any: ... +) -> DataFrame[IntoFrameT] | LazyFrame[IntoFrameT] | Series: ... @overload def from_native( - native_object: Any, + native_object: IntoSeriesT, *, strict: Literal[False], eager_only: None = ..., eager_or_interchange_only: None = ..., series_only: Literal[True], allow_series: None = ..., -) -> Any: ... +) -> Series: ... @overload @@ -238,7 +239,7 @@ def from_native( @overload def from_native( - native_object: Any, + native_object: IntoFrameT | IntoSeriesT, *, strict: Literal[True] = ..., eager_only: None = ..., @@ -254,7 +255,7 @@ def from_native( @overload def from_native( - native_object: Any, + native_object: IntoSeriesT, *, strict: Literal[True] = ..., eager_only: None = ..., diff --git a/narwhals/typing.py b/narwhals/typing.py index 8fcbc697c..044962ac3 100644 --- a/narwhals/typing.py +++ b/narwhals/typing.py @@ -29,6 +29,9 @@ def columns(self) -> Any: ... def join(self, *args: Any, **kwargs: Any) -> Any: ... + class NativeSeries(Protocol): + def __len__(self) -> int: ... + class DataFrameLike(Protocol): def __dataframe__(self, *args: Any, **kwargs: Any) -> Any: ... @@ -47,11 +50,15 @@ def __dataframe__(self, *args: Any, **kwargs: Any) -> Any: ... Frame: TypeAlias = Union["DataFrame[Any]", "LazyFrame[Any]"] """Narwhals DataFrame or Narwhals LazyFrame""" +IntoSeries: TypeAlias = Union["Series", "NativeSeries"] +"""Anything which can be converted to a Narwhals Series.""" + # TypeVars for some of the above IntoFrameT = TypeVar("IntoFrameT", bound="IntoFrame") IntoDataFrameT = TypeVar("IntoDataFrameT", bound="IntoDataFrame") FrameT = TypeVar("FrameT", bound="Frame") DataFrameT = TypeVar("DataFrameT", bound="DataFrame[Any]") +IntoSeriesT = TypeVar("IntoSeriesT", bound="IntoSeries") class DTypes: @@ -89,4 +96,6 @@ class DTypes: "Frame", "FrameT", "DataFrameT", + "IntoSeries", + "IntoSeriesT", ] From 879d3cf63265f6b758e90eca8c5aca425e5c3a41 Mon Sep 17 00:00:00 2001 From: Marco Edward Gorelli Date: Thu, 17 Oct 2024 12:15:12 +0100 Subject: [PATCH 6/8] feat: add from_arrow (which uses the PyCapsule Interface) (#1181) --- docs/api-reference/narwhals.md | 1 + narwhals/__init__.py | 2 + narwhals/functions.py | 101 +++++++++++++++++++++++++++++++++ narwhals/stable/v1/__init__.py | 49 ++++++++++++++++ tests/from_pycapsule_test.py | 45 +++++++++++++++ 5 files changed, 198 insertions(+) create mode 100644 tests/from_pycapsule_test.py diff --git a/docs/api-reference/narwhals.md b/docs/api-reference/narwhals.md index 044b20e0a..b8ec2d793 100644 --- a/docs/api-reference/narwhals.md +++ b/docs/api-reference/narwhals.md @@ -14,6 +14,7 @@ Here are the top-level functions available in Narwhals. - concat_str - from_dict - from_native + - from_arrow - get_level - get_native_namespace - is_ordered_categorical diff --git a/narwhals/__init__.py b/narwhals/__init__.py index 3a327aad4..db8bc842c 100644 --- a/narwhals/__init__.py +++ b/narwhals/__init__.py @@ -45,6 +45,7 @@ from narwhals.expr import sum_horizontal from narwhals.expr import when from narwhals.functions import concat +from narwhals.functions import from_arrow from narwhals.functions import from_dict from narwhals.functions import get_level from narwhals.functions import new_series @@ -69,6 +70,7 @@ "selectors", "concat", "from_dict", + "from_arrow", "get_level", "new_series", "to_native", diff --git a/narwhals/functions.py b/narwhals/functions.py index b84dcb174..395da97ca 100644 --- a/narwhals/functions.py +++ b/narwhals/functions.py @@ -6,6 +6,7 @@ from typing import Any from typing import Iterable from typing import Literal +from typing import Protocol from typing import TypeVar from typing import Union @@ -21,6 +22,7 @@ # The rest of the annotations seem to work fine with this anyway FrameT = TypeVar("FrameT", bound=Union[DataFrame, LazyFrame]) # type: ignore[type-arg] + if TYPE_CHECKING: from types import ModuleType @@ -29,6 +31,11 @@ from narwhals.series import Series from narwhals.typing import DTypes + class ArrowStreamExportable(Protocol): + def __arrow_c_stream__( + self, requested_schema: object | None = None + ) -> object: ... + def concat( items: Iterable[FrameT], @@ -406,6 +413,100 @@ def _from_dict_impl( return from_native(native_frame, eager_only=True) +def from_arrow( + native_frame: ArrowStreamExportable, *, native_namespace: ModuleType +) -> DataFrame[Any]: + """ + Construct a DataFrame from an object which supports the PyCapsule Interface. + + Arguments: + native_frame: Object which implements `__arrow_c_stream__`. + native_namespace: The native library to use for DataFrame creation. + + Examples: + >>> import pandas as pd + >>> import polars as pl + >>> import pyarrow as pa + >>> import narwhals as nw + >>> data = {"a": [1, 2, 3], "b": [4, 5, 6]} + + Let's define a dataframe-agnostic function which creates a PyArrow + Table. + + >>> @nw.narwhalify + ... def func(df): + ... return nw.from_arrow(df, native_namespace=pa) + + Let's see what happens when passing pandas / Polars input: + + >>> func(pd.DataFrame(data)) # doctest: +SKIP + pyarrow.Table + a: int64 + b: int64 + ---- + a: [[1,2,3]] + b: [[4,5,6]] + >>> func(pl.DataFrame(data)) # doctest: +SKIP + pyarrow.Table + a: int64 + b: int64 + ---- + a: [[1,2,3]] + b: [[4,5,6]] + """ + if not hasattr(native_frame, "__arrow_c_stream__"): + msg = f"Given object of type {type(native_frame)} does not support PyCapsule interface" + raise TypeError(msg) + implementation = Implementation.from_native_namespace(native_namespace) + + if implementation is Implementation.POLARS and parse_version( + native_namespace.__version__ + ) >= (1, 3): + native_frame = native_namespace.DataFrame(native_frame) + elif implementation in { + Implementation.PANDAS, + Implementation.MODIN, + Implementation.CUDF, + Implementation.POLARS, + }: + # These don't (yet?) support the PyCapsule Interface for import + # so we go via PyArrow + try: + import pyarrow as pa # ignore-banned-import + except ModuleNotFoundError as exc: # pragma: no cover + msg = f"PyArrow>=14.0.0 is required for `from_arrow` for object of type {native_namespace}" + raise ModuleNotFoundError(msg) from exc + if parse_version(pa.__version__) < (14, 0): # pragma: no cover + msg = f"PyArrow>=14.0.0 is required for `from_arrow` for object of type {native_namespace}" + raise ModuleNotFoundError(msg) from None + + tbl = pa.table(native_frame) + if implementation is Implementation.PANDAS: + native_frame = tbl.to_pandas() + elif implementation is Implementation.MODIN: # pragma: no cover + from modin.pandas.utils import from_arrow + + native_frame = from_arrow(tbl) + elif implementation is Implementation.CUDF: # pragma: no cover + native_frame = native_namespace.DataFrame.from_arrow(tbl) + elif implementation is Implementation.POLARS: # pragma: no cover + native_frame = native_namespace.from_arrow(tbl) + else: # pragma: no cover + msg = "congratulations, you entered unrecheable code - please report a bug" + raise AssertionError(msg) + elif implementation is Implementation.PYARROW: + native_frame = native_namespace.table(native_frame) + else: # pragma: no cover + try: + # implementation is UNKNOWN, Narwhals extension using this feature should + # implement PyCapsule support + native_frame = native_namespace.DataFrame(native_frame) + except AttributeError as e: + msg = "Unknown namespace is expected to implement `DataFrame` class which accepts object which supports PyCapsule Interface." + raise AttributeError(msg) from e + return from_native(native_frame, eager_only=True) + + def _get_sys_info() -> dict[str, str]: """System information diff --git a/narwhals/stable/v1/__init__.py b/narwhals/stable/v1/__init__.py index c500cdab1..75da2a42c 100644 --- a/narwhals/stable/v1/__init__.py +++ b/narwhals/stable/v1/__init__.py @@ -21,6 +21,7 @@ from narwhals.expr import when as nw_when from narwhals.functions import _from_dict_impl from narwhals.functions import _new_series_impl +from narwhals.functions import from_arrow as nw_from_arrow from narwhals.functions import show_versions from narwhals.schema import Schema as NwSchema from narwhals.series import Series as NwSeries @@ -66,6 +67,7 @@ from typing_extensions import Self from narwhals.dtypes import DType + from narwhals.functions import ArrowStreamExportable from narwhals.typing import IntoExpr T = TypeVar("T") @@ -2183,6 +2185,52 @@ def new_series( ) +def from_arrow( + native_frame: ArrowStreamExportable, *, native_namespace: ModuleType +) -> DataFrame[Any]: + """ + Construct a DataFrame from an object which supports the PyCapsule Interface. + + Arguments: + native_frame: Object which implements `__arrow_c_stream__`. + native_namespace: The native library to use for DataFrame creation. + + Examples: + >>> import pandas as pd + >>> import polars as pl + >>> import pyarrow as pa + >>> import narwhals.stable.v1 as nw + >>> data = {"a": [1, 2, 3], "b": [4, 5, 6]} + + Let's define a dataframe-agnostic function which creates a PyArrow + Table. + + >>> @nw.narwhalify + ... def func(df): + ... return nw.from_arrow(df, native_namespace=pa) + + Let's see what happens when passing pandas / Polars input: + + >>> func(pd.DataFrame(data)) # doctest: +SKIP + pyarrow.Table + a: int64 + b: int64 + ---- + a: [[1,2,3]] + b: [[4,5,6]] + >>> func(pl.DataFrame(data)) # doctest: +SKIP + pyarrow.Table + a: int64 + b: int64 + ---- + a: [[1,2,3]] + b: [[4,5,6]] + """ + return _stableify( # type: ignore[no-any-return] + nw_from_arrow(native_frame, native_namespace=native_namespace) + ) + + def from_dict( data: dict[str, Any], schema: dict[str, DType] | Schema | None = None, @@ -2307,5 +2355,6 @@ def from_dict( "show_versions", "Schema", "from_dict", + "from_arrow", "new_series", ] diff --git a/tests/from_pycapsule_test.py b/tests/from_pycapsule_test.py new file mode 100644 index 000000000..7ab8f1fe8 --- /dev/null +++ b/tests/from_pycapsule_test.py @@ -0,0 +1,45 @@ +import sys + +import pandas as pd +import polars as pl +import pyarrow as pa +import pytest + +import narwhals.stable.v1 as nw +from narwhals.utils import parse_version +from tests.utils import compare_dicts + + +@pytest.mark.xfail(parse_version(pa.__version__) < (14,), reason="too old") +def test_from_arrow_to_arrow() -> None: + df = nw.from_native(pl.DataFrame({"ab": [1, 2, 3], "ba": [4, 5, 6]}), eager_only=True) + result = nw.from_arrow(df, native_namespace=pa) + assert isinstance(result.to_native(), pa.Table) + expected = {"ab": [1, 2, 3], "ba": [4, 5, 6]} + compare_dicts(result, expected) + + +@pytest.mark.xfail(parse_version(pa.__version__) < (14,), reason="too old") +def test_from_arrow_to_polars(monkeypatch: pytest.MonkeyPatch) -> None: + tbl = pa.table({"ab": [1, 2, 3], "ba": [4, 5, 6]}) + monkeypatch.delitem(sys.modules, "pandas") + df = nw.from_native(tbl, eager_only=True) + result = nw.from_arrow(df, native_namespace=pl) + assert isinstance(result.to_native(), pl.DataFrame) + expected = {"ab": [1, 2, 3], "ba": [4, 5, 6]} + compare_dicts(result, expected) + assert "pandas" not in sys.modules + + +@pytest.mark.xfail(parse_version(pa.__version__) < (14,), reason="too old") +def test_from_arrow_to_pandas() -> None: + df = nw.from_native(pa.table({"ab": [1, 2, 3], "ba": [4, 5, 6]}), eager_only=True) + result = nw.from_arrow(df, native_namespace=pd) + assert isinstance(result.to_native(), pd.DataFrame) + expected = {"ab": [1, 2, 3], "ba": [4, 5, 6]} + compare_dicts(result, expected) + + +def test_from_arrow_invalid() -> None: + with pytest.raises(TypeError, match="PyCapsule"): + nw.from_arrow({"a": [1]}, native_namespace=pa) # type: ignore[arg-type] From e98048355bb5840690a52fba1afec16a7144f894 Mon Sep 17 00:00:00 2001 From: Marco Edward Gorelli Date: Thu, 17 Oct 2024 12:15:29 +0100 Subject: [PATCH 7/8] fix: preserve dtypes when using with_columns and length-1 pandas df (#1201) * fix: preserve dtypes when using with_columns and length-1 pandas df * pyarrow versions --- narwhals/_pandas_like/series.py | 4 +--- narwhals/_pandas_like/utils.py | 12 ++++++------ tests/frame/with_columns_test.py | 14 ++++++++++++++ 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/narwhals/_pandas_like/series.py b/narwhals/_pandas_like/series.py index 2fe53b22a..9dc9f20f6 100644 --- a/narwhals/_pandas_like/series.py +++ b/narwhals/_pandas_like/series.py @@ -619,9 +619,7 @@ def quantile( def zip_with(self: Self, mask: Any, other: Any) -> PandasLikeSeries: ser = self._native_series - mask = validate_column_comparand( - ser.index, mask, treat_length_one_as_scalar=False - ) + mask = validate_column_comparand(ser.index, mask) other = validate_column_comparand(ser.index, other) res = ser.where(mask, other) return self._from_native_series(res) diff --git a/narwhals/_pandas_like/utils.py b/narwhals/_pandas_like/utils.py index 0773764d9..5267dd07f 100644 --- a/narwhals/_pandas_like/utils.py +++ b/narwhals/_pandas_like/utils.py @@ -32,9 +32,7 @@ } -def validate_column_comparand( - index: Any, other: Any, *, treat_length_one_as_scalar: bool = True -) -> Any: +def validate_column_comparand(index: Any, other: Any) -> Any: """Validate RHS of binary operation. If the comparison isn't supported, return `NotImplemented` so that the @@ -55,9 +53,10 @@ def validate_column_comparand( if isinstance(other, PandasLikeDataFrame): return NotImplemented if isinstance(other, PandasLikeSeries): - if other.len() == 1 and treat_length_one_as_scalar: + if other.len() == 1: # broadcast - return other.item() + s = other._native_series + return s.__class__(s.iloc[0], index=index, dtype=s.dtype) if other._native_series.index is not index: return set_axis( other._native_series, @@ -83,7 +82,8 @@ def validate_dataframe_comparand(index: Any, other: Any) -> Any: if isinstance(other, PandasLikeSeries): if other.len() == 1: # broadcast - return other._native_series.iloc[0] + s = other._native_series + return s.__class__(s.iloc[0], index=index, dtype=s.dtype) if other._native_series.index is not index: return set_axis( other._native_series, diff --git a/tests/frame/with_columns_test.py b/tests/frame/with_columns_test.py index 44bcd39a5..8c949cc53 100644 --- a/tests/frame/with_columns_test.py +++ b/tests/frame/with_columns_test.py @@ -1,7 +1,10 @@ import numpy as np import pandas as pd +import pyarrow as pa +import pytest import narwhals.stable.v1 as nw +from narwhals.utils import parse_version from tests.utils import Constructor from tests.utils import compare_dicts @@ -40,3 +43,14 @@ def test_with_columns_order_single_row(constructor: Constructor) -> None: assert result.collect_schema().names() == ["a", "b", "z", "d"] expected = {"a": [2], "b": [4], "z": [7.0], "d": [0]} compare_dicts(result, expected) + + +def test_with_columns_dtypes_single_row( + constructor: Constructor, request: pytest.FixtureRequest +) -> None: + if "pyarrow_table" in str(constructor) and parse_version(pa.__version__) < (15,): + request.applymarker(pytest.mark.xfail) + data = {"a": ["foo"]} + df = nw.from_native(constructor(data)).with_columns(nw.col("a").cast(nw.Categorical)) + result = df.with_columns(nw.col("a")) + assert result.collect_schema() == {"a": nw.Categorical} From 5ef1803cc2fbeb5e39ddeecb3d45172a5c7244a2 Mon Sep 17 00:00:00 2001 From: Marco Edward Gorelli Date: Thu, 17 Oct 2024 12:30:22 +0100 Subject: [PATCH 8/8] release: Bump version to 1.9.4 (#1203) --- docs/installation.md | 2 +- narwhals/__init__.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index e3cd8f6db..974eaf1f4 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -13,7 +13,7 @@ Then, if you start the Python REPL and see the following: ```python >>> import narwhals >>> narwhals.__version__ -'1.9.3' +'1.9.4' ``` then installation worked correctly! diff --git a/narwhals/__init__.py b/narwhals/__init__.py index db8bc842c..21964da15 100644 --- a/narwhals/__init__.py +++ b/narwhals/__init__.py @@ -63,7 +63,7 @@ from narwhals.utils import maybe_reset_index from narwhals.utils import maybe_set_index -__version__ = "1.9.3" +__version__ = "1.9.4" __all__ = [ "dependencies", diff --git a/pyproject.toml b/pyproject.toml index c4974d8c0..3cbeff8f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "narwhals" -version = "1.9.3" +version = "1.9.4" authors = [ { name="Marco Gorelli", email="33491632+MarcoGorelli@users.noreply.github.com" }, ]