From a5490c0fe739838cf285d3d5f63c5312bf37e9d1 Mon Sep 17 00:00:00 2001 From: FBruzzesi Date: Mon, 17 Nov 2025 10:31:48 +0100 Subject: [PATCH 01/17] pyproject.toml --- .pre-commit-config.yaml | 2 +- pyproject.toml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3cfe445feb..1c7a919355 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -51,7 +51,7 @@ repos: # TODO(unassigned): replace with ruff once https://github.com/astral-sh/ruff/issues/2302 is addressed. name: flake8-typing-imports alias: flake8-typing-imports - entry: flake8 --select TYP --min-python-version=3.9.0 + entry: flake8 --select TYP --min-python-version=3.10.0 # Keep in sync with `darglint` so the same venv is reused. additional_dependencies: [darglint==1.8.1, flake8-typing-imports==1.17.0] - repo: local diff --git a/pyproject.toml b/pyproject.toml index 6126488db6..9d53d52a57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "hatchling.build" name = "narwhals" version = "2.11.0" dependencies = [] -requires-python = ">=3.9" +requires-python = ">=3.10" authors = [ { name = "Marco Gorelli", email = "hello_narwhals@proton.me" }, ] @@ -130,7 +130,7 @@ include = [ [tool.ruff] line-length = 90 fix = true -target-version = "py39" +target-version = "py310" extend-exclude = ["**/this.py"] [tool.ruff.lint] @@ -361,7 +361,7 @@ warn_return_any = false [tool.pyright] pythonPlatform = "All" # NOTE (`pyarrow-stubs` do unsafe `TypeAlias` and `TypeVar` imports) -# pythonVersion = "3.9" +# pythonVersion = "3.10" reportMissingTypeArgument = "error" reportIncompatibleMethodOverride = "error" reportMissingImports = "none" From 54d2a25fa87782d49c32ae68a8a54b1d182ffcc8 Mon Sep 17 00:00:00 2001 From: FBruzzesi Date: Mon, 17 Nov 2025 10:32:41 +0100 Subject: [PATCH 02/17] ruff: narwhals, no zip --- narwhals/_arrow/dataframe.py | 3 +- narwhals/_arrow/series.py | 7 +-- narwhals/_arrow/series_dt.py | 7 ++- narwhals/_arrow/typing.py | 8 +--- narwhals/_arrow/utils.py | 3 +- narwhals/_compliant/any_namespace.py | 2 +- narwhals/_compliant/dataframe.py | 3 +- narwhals/_compliant/expr.py | 4 +- narwhals/_compliant/group_by.py | 4 +- narwhals/_compliant/namespace.py | 3 +- narwhals/_compliant/selectors.py | 3 +- narwhals/_compliant/typing.py | 6 +-- narwhals/_dask/dataframe.py | 3 +- narwhals/_dask/expr.py | 4 +- narwhals/_dask/group_by.py | 6 +-- narwhals/_duckdb/expr.py | 4 +- narwhals/_duration.py | 3 +- narwhals/_expression_parsing.py | 4 +- narwhals/_ibis/dataframe.py | 3 +- narwhals/_ibis/expr.py | 4 +- narwhals/_ibis/expr_dt.py | 4 +- narwhals/_ibis/expr_str.py | 6 ++- narwhals/_ibis/utils.py | 3 +- narwhals/_namespace.py | 3 +- narwhals/_native.py | 10 +++-- narwhals/_pandas_like/dataframe.py | 7 +-- narwhals/_pandas_like/group_by.py | 3 +- narwhals/_pandas_like/namespace.py | 3 +- narwhals/_pandas_like/series.py | 7 +-- narwhals/_pandas_like/typing.py | 3 +- narwhals/_pandas_like/utils.py | 7 +-- narwhals/_polars/dataframe.py | 6 +-- narwhals/_polars/expr.py | 4 +- narwhals/_polars/series.py | 4 +- narwhals/_spark_like/dataframe.py | 3 +- narwhals/_spark_like/expr.py | 7 +-- narwhals/_spark_like/utils.py | 2 +- narwhals/_sql/dataframe.py | 3 +- narwhals/_sql/expr.py | 4 +- narwhals/_translate.py | 4 +- narwhals/_typing.py | 6 +-- narwhals/_typing_compat.py | 2 +- narwhals/_utils.py | 65 ++++++---------------------- narwhals/dataframe.py | 6 +-- narwhals/dependencies.py | 4 +- narwhals/expr.py | 8 ++-- narwhals/expr_name.py | 4 +- narwhals/functions.py | 3 +- narwhals/plugins.py | 7 ++- narwhals/series.py | 13 +----- narwhals/stable/v1/__init__.py | 4 +- narwhals/stable/v1/typing.py | 2 +- narwhals/stable/v2/__init__.py | 4 +- narwhals/stable/v2/typing.py | 2 +- narwhals/testing/asserts/series.py | 5 ++- narwhals/testing/asserts/utils.py | 4 +- narwhals/translate.py | 4 +- narwhals/typing.py | 2 +- 58 files changed, 149 insertions(+), 173 deletions(-) diff --git a/narwhals/_arrow/dataframe.py b/narwhals/_arrow/dataframe.py index 20ff27c529..e61573e787 100644 --- a/narwhals/_arrow/dataframe.py +++ b/narwhals/_arrow/dataframe.py @@ -35,10 +35,11 @@ from io import BytesIO from pathlib import Path from types import ModuleType + from typing import TypeAlias import pandas as pd import polars as pl - from typing_extensions import Self, TypeAlias, TypeIs + from typing_extensions import Self, TypeIs from narwhals._arrow.expr import ArrowExpr from narwhals._arrow.group_by import ArrowGroupBy diff --git a/narwhals/_arrow/series.py b/narwhals/_arrow/series.py index 22e6488ff0..751e45d955 100644 --- a/narwhals/_arrow/series.py +++ b/narwhals/_arrow/series.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, Literal, cast, overload +from typing import TYPE_CHECKING, Any, Literal, cast, overload import pyarrow as pa import pyarrow.compute as pc @@ -36,12 +36,13 @@ from narwhals.exceptions import InvalidOperationError, ShapeError if TYPE_CHECKING: - from collections.abc import Iterable, Iterator, Sequence + from collections.abc import Callable, Iterable, Iterator, Sequence from types import ModuleType + from typing import TypeAlias import pandas as pd import polars as pl - from typing_extensions import Self, TypeAlias, TypeIs + from typing_extensions import Self, TypeIs from narwhals._arrow.dataframe import ArrowDataFrame from narwhals._arrow.namespace import ArrowNamespace diff --git a/narwhals/_arrow/series_dt.py b/narwhals/_arrow/series_dt.py index 6f29767f14..3489698928 100644 --- a/narwhals/_arrow/series_dt.py +++ b/narwhals/_arrow/series_dt.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, ClassVar, cast +from typing import TYPE_CHECKING, Any, ClassVar, cast import pyarrow as pa import pyarrow.compute as pc @@ -22,9 +22,8 @@ from narwhals._duration import Interval if TYPE_CHECKING: - from collections.abc import Mapping - - from typing_extensions import TypeAlias + from collections.abc import Callable, Mapping + from typing import TypeAlias from narwhals._arrow.series import ArrowSeries from narwhals._arrow.typing import ChunkedArrayAny, ScalarAny diff --git a/narwhals/_arrow/typing.py b/narwhals/_arrow/typing.py index 8180663f1f..bd15b16d66 100644 --- a/narwhals/_arrow/typing.py +++ b/narwhals/_arrow/typing.py @@ -7,13 +7,7 @@ ) if TYPE_CHECKING: - import sys - from typing import Generic, Literal - - if sys.version_info >= (3, 10): - from typing import TypeAlias - else: - from typing_extensions import TypeAlias + from typing import Generic, Literal, TypeAlias import pyarrow as pa from pyarrow.__lib_pxi.table import ( diff --git a/narwhals/_arrow/utils.py b/narwhals/_arrow/utils.py index 46b5985e1d..ee93c31776 100644 --- a/narwhals/_arrow/utils.py +++ b/narwhals/_arrow/utils.py @@ -11,8 +11,9 @@ if TYPE_CHECKING: from collections.abc import Iterable, Iterator, Mapping + from typing import TypeAlias - from typing_extensions import TypeAlias, TypeIs + from typing_extensions import TypeIs from narwhals._arrow.series import ArrowSeries from narwhals._arrow.typing import ( diff --git a/narwhals/_compliant/any_namespace.py b/narwhals/_compliant/any_namespace.py index b7e48a273f..a6cf600655 100644 --- a/narwhals/_compliant/any_namespace.py +++ b/narwhals/_compliant/any_namespace.py @@ -7,7 +7,7 @@ from narwhals._utils import CompliantT_co, _StoresCompliant if TYPE_CHECKING: - from typing import Callable + from collections.abc import Callable from narwhals._compliant.typing import Accessor from narwhals.typing import NonNestedLiteral, TimeUnit diff --git a/narwhals/_compliant/dataframe.py b/narwhals/_compliant/dataframe.py index 458b5a3794..72889261b4 100644 --- a/narwhals/_compliant/dataframe.py +++ b/narwhals/_compliant/dataframe.py @@ -44,11 +44,12 @@ from io import BytesIO from pathlib import Path from types import ModuleType + from typing import TypeAlias import pandas as pd import polars as pl import pyarrow as pa - from typing_extensions import Self, TypeAlias + from typing_extensions import Self from narwhals._compliant.group_by import CompliantGroupBy, DataFrameGroupBy from narwhals._compliant.namespace import EagerNamespace diff --git a/narwhals/_compliant/expr.py b/narwhals/_compliant/expr.py index cc0d6cc3f8..db6feedbd1 100644 --- a/narwhals/_compliant/expr.py +++ b/narwhals/_compliant/expr.py @@ -2,7 +2,7 @@ from functools import partial from operator import methodcaller -from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, Protocol +from typing import TYPE_CHECKING, Any, Generic, Literal, Protocol from narwhals._compliant.any_namespace import ( CatNamespace, @@ -37,7 +37,7 @@ from narwhals.exceptions import MultiOutputExpressionError if TYPE_CHECKING: - from collections.abc import Sequence + from collections.abc import Callable, Sequence from typing_extensions import Self, TypeIs diff --git a/narwhals/_compliant/group_by.py b/narwhals/_compliant/group_by.py index cb62edf7ac..10dcae5373 100644 --- a/narwhals/_compliant/group_by.py +++ b/narwhals/_compliant/group_by.py @@ -1,7 +1,7 @@ from __future__ import annotations from itertools import chain -from typing import TYPE_CHECKING, Any, Callable, ClassVar, Protocol, TypeVar +from typing import TYPE_CHECKING, Any, ClassVar, Protocol, TypeVar from narwhals._compliant.typing import ( CompliantDataFrameT, @@ -18,7 +18,7 @@ from narwhals._utils import is_sequence_of, zip_strict if TYPE_CHECKING: - from collections.abc import Iterable, Iterator, Mapping, Sequence + from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence from narwhals._compliant.expr import ImplExpr diff --git a/narwhals/_compliant/namespace.py b/narwhals/_compliant/namespace.py index 6f7062be35..c321fc5611 100644 --- a/narwhals/_compliant/namespace.py +++ b/narwhals/_compliant/namespace.py @@ -24,8 +24,9 @@ if TYPE_CHECKING: from collections.abc import Iterable, Sequence + from typing import TypeAlias - from typing_extensions import TypeAlias, TypeIs + from typing_extensions import TypeIs from narwhals._compliant.selectors import CompliantSelectorNamespace from narwhals._utils import Implementation, Version diff --git a/narwhals/_compliant/selectors.py b/narwhals/_compliant/selectors.py index bb4f612d10..c7c3de2c02 100644 --- a/narwhals/_compliant/selectors.py +++ b/narwhals/_compliant/selectors.py @@ -18,8 +18,9 @@ if TYPE_CHECKING: from collections.abc import Collection, Iterable, Iterator, Sequence from datetime import timezone + from typing import TypeAlias - from typing_extensions import Self, TypeAlias, TypeIs + from typing_extensions import Self, TypeIs from narwhals._compliant.expr import NativeExpr from narwhals._compliant.typing import ( diff --git a/narwhals/_compliant/typing.py b/narwhals/_compliant/typing.py index 91ede3b2f0..4854c0bc07 100644 --- a/narwhals/_compliant/typing.py +++ b/narwhals/_compliant/typing.py @@ -1,10 +1,10 @@ from __future__ import annotations -from collections.abc import Sequence -from typing import TYPE_CHECKING, Any, Callable, Literal, TypedDict, TypeVar +from collections.abc import Callable, Sequence +from typing import TYPE_CHECKING, Any, Literal, TypedDict, TypeVar if TYPE_CHECKING: - from typing_extensions import TypeAlias + from typing import TypeAlias from narwhals._compliant.dataframe import ( CompliantDataFrame, diff --git a/narwhals/_dask/dataframe.py b/narwhals/_dask/dataframe.py index 2f2a9e374b..7ae45c46cd 100644 --- a/narwhals/_dask/dataframe.py +++ b/narwhals/_dask/dataframe.py @@ -26,9 +26,10 @@ from io import BytesIO from pathlib import Path from types import ModuleType + from typing import TypeAlias import dask.dataframe.dask_expr as dx - from typing_extensions import Self, TypeAlias, TypeIs + from typing_extensions import Self, TypeIs from narwhals._compliant.typing import CompliantDataFrameAny from narwhals._dask.expr import DaskExpr diff --git a/narwhals/_dask/expr.py b/narwhals/_dask/expr.py index 8efa65ed91..66970c01c5 100644 --- a/narwhals/_dask/expr.py +++ b/narwhals/_dask/expr.py @@ -1,7 +1,7 @@ from __future__ import annotations import warnings -from typing import TYPE_CHECKING, Any, Callable, cast +from typing import TYPE_CHECKING, Any, cast import pandas as pd @@ -25,7 +25,7 @@ from narwhals.exceptions import InvalidOperationError if TYPE_CHECKING: - from collections.abc import Sequence + from collections.abc import Callable, Sequence import dask.dataframe.dask_expr as dx from typing_extensions import Self diff --git a/narwhals/_dask/group_by.py b/narwhals/_dask/group_by.py index 3eb47a2ec3..214a0e3e10 100644 --- a/narwhals/_dask/group_by.py +++ b/narwhals/_dask/group_by.py @@ -1,7 +1,7 @@ from __future__ import annotations from functools import partial -from typing import TYPE_CHECKING, Any, Callable, ClassVar +from typing import TYPE_CHECKING, Any, ClassVar import dask.dataframe as dd @@ -10,12 +10,12 @@ from narwhals._utils import zip_strict if TYPE_CHECKING: - from collections.abc import Mapping, Sequence + from collections.abc import Callable, Mapping, Sequence + from typing import TypeAlias import pandas as pd from dask.dataframe.api import GroupBy as _DaskGroupBy from pandas.core.groupby import SeriesGroupBy as _PandasSeriesGroupBy - from typing_extensions import TypeAlias from narwhals._compliant.typing import NarwhalsAggregation from narwhals._dask.dataframe import DaskLazyFrame diff --git a/narwhals/_duckdb/expr.py b/narwhals/_duckdb/expr.py index cf00420b61..d189594272 100644 --- a/narwhals/_duckdb/expr.py +++ b/narwhals/_duckdb/expr.py @@ -1,7 +1,7 @@ from __future__ import annotations import operator -from typing import TYPE_CHECKING, Any, Callable, cast +from typing import TYPE_CHECKING, Any, cast from duckdb import CoalesceOperator, StarExpression @@ -24,7 +24,7 @@ from narwhals._utils import Implementation, Version, extend_bool, no_default if TYPE_CHECKING: - from collections.abc import Sequence + from collections.abc import Callable, Sequence from duckdb import Expression from typing_extensions import Self diff --git a/narwhals/_duration.py b/narwhals/_duration.py index 8e678be7ff..40e330a276 100644 --- a/narwhals/_duration.py +++ b/narwhals/_duration.py @@ -8,8 +8,7 @@ if TYPE_CHECKING: from collections.abc import Container, Mapping - - from typing_extensions import TypeAlias + from typing import TypeAlias __all__ = ["IntervalUnit"] diff --git a/narwhals/_expression_parsing.py b/narwhals/_expression_parsing.py index de08397b71..b800243fcc 100644 --- a/narwhals/_expression_parsing.py +++ b/narwhals/_expression_parsing.py @@ -5,7 +5,7 @@ from __future__ import annotations from enum import Enum, auto -from typing import TYPE_CHECKING, Any, Callable, Literal, cast +from typing import TYPE_CHECKING, Any, Literal, cast from narwhals._utils import zip_strict from narwhals.dependencies import is_numpy_array_1d @@ -16,7 +16,7 @@ ) if TYPE_CHECKING: - from collections.abc import Iterator, Sequence + from collections.abc import Callable, Iterator, Sequence from typing_extensions import Never, TypeIs diff --git a/narwhals/_ibis/dataframe.py b/narwhals/_ibis/dataframe.py index f5470700a1..67192d55f6 100644 --- a/narwhals/_ibis/dataframe.py +++ b/narwhals/_ibis/dataframe.py @@ -26,11 +26,12 @@ from collections.abc import Iterable, Iterator, Mapping, Sequence from pathlib import Path from types import ModuleType + from typing import TypeAlias import pandas as pd import pyarrow as pa from ibis.expr.operations import Binary - from typing_extensions import Self, TypeAlias, TypeIs + from typing_extensions import Self, TypeIs from narwhals._compliant.typing import CompliantDataFrameAny from narwhals._ibis.group_by import IbisGroupBy diff --git a/narwhals/_ibis/expr.py b/narwhals/_ibis/expr.py index 3adb9bae4f..958c8808c7 100644 --- a/narwhals/_ibis/expr.py +++ b/narwhals/_ibis/expr.py @@ -1,7 +1,7 @@ from __future__ import annotations import operator -from typing import TYPE_CHECKING, Any, Callable, TypeVar, cast +from typing import TYPE_CHECKING, Any, TypeVar, cast import ibis @@ -30,7 +30,7 @@ ) if TYPE_CHECKING: - from collections.abc import Iterator, Sequence + from collections.abc import Callable, Iterator, Sequence import ibis.expr.types as ir from typing_extensions import Self diff --git a/narwhals/_ibis/expr_dt.py b/narwhals/_ibis/expr_dt.py index 7d98dba0df..95fb6a4329 100644 --- a/narwhals/_ibis/expr_dt.py +++ b/narwhals/_ibis/expr_dt.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable +from typing import TYPE_CHECKING, Any from narwhals._duration import Interval from narwhals._ibis.utils import ( @@ -12,6 +12,8 @@ from narwhals._utils import not_implemented if TYPE_CHECKING: + from collections.abc import Callable + import ibis.expr.types as ir from narwhals._ibis.expr import IbisExpr diff --git a/narwhals/_ibis/expr_str.py b/narwhals/_ibis/expr_str.py index 26a1f0c0ed..7d0a4158ab 100644 --- a/narwhals/_ibis/expr_str.py +++ b/narwhals/_ibis/expr_str.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable +from typing import TYPE_CHECKING, Any from ibis.expr.datatypes import Timestamp @@ -8,8 +8,10 @@ from narwhals._utils import _is_naive_format, not_implemented if TYPE_CHECKING: + from collections.abc import Callable + from typing import TypeAlias + import ibis.expr.types as ir - from typing_extensions import TypeAlias from narwhals._ibis.expr import IbisExpr diff --git a/narwhals/_ibis/utils.py b/narwhals/_ibis/utils.py index 09a8ce2ffb..c4477060eb 100644 --- a/narwhals/_ibis/utils.py +++ b/narwhals/_ibis/utils.py @@ -11,11 +11,12 @@ if TYPE_CHECKING: from collections.abc import Callable, Mapping from datetime import timedelta + from typing import TypeAlias import ibis.expr.types as ir from ibis.common.temporal import TimestampUnit from ibis.expr.datatypes import DataType as IbisDataType - from typing_extensions import TypeAlias, TypeIs + from typing_extensions import TypeIs from narwhals._duration import IntervalUnit from narwhals._ibis.dataframe import IbisLazyFrame diff --git a/narwhals/_namespace.py b/narwhals/_namespace.py index 0d58963825..c24bf82e79 100644 --- a/narwhals/_namespace.py +++ b/narwhals/_namespace.py @@ -37,8 +37,9 @@ from narwhals._utils import Implementation, Version if TYPE_CHECKING: + from typing import TypeAlias + import pandas as pd - from typing_extensions import TypeAlias from narwhals._arrow.namespace import ArrowNamespace from narwhals._dask.namespace import DaskNamespace diff --git a/narwhals/_native.py b/narwhals/_native.py index 594370515e..63df32467c 100644 --- a/narwhals/_native.py +++ b/narwhals/_native.py @@ -105,7 +105,7 @@ class NativeDask(NativeLazyFrame, Protocol): from __future__ import annotations from collections.abc import Callable, Collection, Iterable, Sized -from typing import TYPE_CHECKING, Any, Protocol, TypeVar, Union, cast +from typing import TYPE_CHECKING, Any, Protocol, TypeVar, cast from narwhals.dependencies import ( get_cudf, @@ -122,12 +122,14 @@ class NativeDask(NativeLazyFrame, Protocol): ) if TYPE_CHECKING: + from typing import TypeAlias + import duckdb import pandas as pd import polars as pl import pyarrow as pa from sqlframe.base.dataframe import BaseDataFrame as _BaseDataFrame - from typing_extensions import Self, TypeAlias, TypeIs + from typing_extensions import Self, TypeIs SQLFrameDataFrame = _BaseDataFrame[Any, Any, Any, Any, Any] T = TypeVar("T") @@ -289,8 +291,8 @@ def dropDuplicatesWithinWatermark(self, *arg: Any, **kwargs: Any) -> Any: ... # ... return df.shape """ -IntoLazyFrame: TypeAlias = Union[NativeLazyFrame, NativeIbis] -IntoFrame: TypeAlias = Union[IntoDataFrame, IntoLazyFrame] +IntoLazyFrame: TypeAlias = NativeLazyFrame | NativeIbis +IntoFrame: TypeAlias = IntoDataFrame | IntoLazyFrame """Anything which can be converted to a Narwhals DataFrame or LazyFrame. Use this if your function can accept an object which can be converted to either diff --git a/narwhals/_pandas_like/dataframe.py b/narwhals/_pandas_like/dataframe.py index 3f05405132..4bd6cb5acf 100644 --- a/narwhals/_pandas_like/dataframe.py +++ b/narwhals/_pandas_like/dataframe.py @@ -1,8 +1,8 @@ from __future__ import annotations -from collections.abc import Iterable, Iterator, Mapping, Sequence +from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence from itertools import chain, product -from typing import TYPE_CHECKING, Any, Callable, Literal, cast, overload +from typing import TYPE_CHECKING, Any, Literal, cast, overload import numpy as np @@ -40,10 +40,11 @@ from io import BytesIO from pathlib import Path from types import ModuleType + from typing import TypeAlias import pandas as pd import polars as pl - from typing_extensions import Self, TypeAlias, TypeIs + from typing_extensions import Self, TypeIs from narwhals._compliant.typing import CompliantDataFrameAny, CompliantLazyFrameAny from narwhals._pandas_like.expr import PandasLikeExpr diff --git a/narwhals/_pandas_like/group_by.py b/narwhals/_pandas_like/group_by.py index def77bd15b..7bb28e61ac 100644 --- a/narwhals/_pandas_like/group_by.py +++ b/narwhals/_pandas_like/group_by.py @@ -14,10 +14,11 @@ if TYPE_CHECKING: from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence + from typing import TypeAlias import pandas as pd from pandas.api.typing import DataFrameGroupBy as _NativeGroupBy - from typing_extensions import TypeAlias, Unpack + from typing_extensions import Unpack from narwhals._compliant.typing import NarwhalsAggregation, ScalarKwargs from narwhals._pandas_like.dataframe import PandasLikeDataFrame diff --git a/narwhals/_pandas_like/namespace.py b/narwhals/_pandas_like/namespace.py index 96b7a22290..6f8b54e289 100644 --- a/narwhals/_pandas_like/namespace.py +++ b/narwhals/_pandas_like/namespace.py @@ -21,8 +21,7 @@ if TYPE_CHECKING: from collections.abc import Iterable, Sequence - - from typing_extensions import TypeAlias + from typing import TypeAlias from narwhals._utils import Implementation, Version from narwhals.typing import IntoDType, NonNestedLiteral diff --git a/narwhals/_pandas_like/series.py b/narwhals/_pandas_like/series.py index 7cb441dac0..55b3aca1c0 100644 --- a/narwhals/_pandas_like/series.py +++ b/narwhals/_pandas_like/series.py @@ -2,7 +2,7 @@ import operator import warnings -from typing import TYPE_CHECKING, Any, Callable, Literal, cast +from typing import TYPE_CHECKING, Any, Literal, cast import numpy as np @@ -29,13 +29,14 @@ from narwhals.exceptions import InvalidOperationError if TYPE_CHECKING: - from collections.abc import Hashable, Iterable, Iterator, Sequence + from collections.abc import Callable, Hashable, Iterable, Iterator, Sequence from types import ModuleType + from typing import TypeAlias import pandas as pd import polars as pl import pyarrow as pa - from typing_extensions import Self, TypeAlias, TypeIs + from typing_extensions import Self, TypeIs from narwhals._arrow.typing import ChunkedArrayAny from narwhals._compliant.series import HistData diff --git a/narwhals/_pandas_like/typing.py b/narwhals/_pandas_like/typing.py index 55c644bb5e..139bde96f8 100644 --- a/narwhals/_pandas_like/typing.py +++ b/narwhals/_pandas_like/typing.py @@ -5,10 +5,9 @@ from narwhals._typing_compat import TypeVar if TYPE_CHECKING: - from typing import Any + from typing import Any, TypeAlias import pandas as pd - from typing_extensions import TypeAlias from narwhals._native import ( NativePandasLikeDataFrame, diff --git a/narwhals/_pandas_like/utils.py b/narwhals/_pandas_like/utils.py index 7e224f6062..281a3c19ad 100644 --- a/narwhals/_pandas_like/utils.py +++ b/narwhals/_pandas_like/utils.py @@ -3,7 +3,7 @@ import functools import operator import re -from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar, cast +from typing import TYPE_CHECKING, Any, Literal, TypeVar, cast import pandas as pd @@ -28,12 +28,13 @@ from narwhals.exceptions import ShapeError if TYPE_CHECKING: - from collections.abc import Iterable, Iterator, Mapping + from collections.abc import Callable, Iterable, Iterator, Mapping from types import ModuleType + from typing import TypeAlias from pandas._typing import Dtype as PandasDtype from pandas.core.dtypes.dtypes import BaseMaskedDtype - from typing_extensions import TypeAlias, TypeIs + from typing_extensions import TypeIs from narwhals._duration import IntervalUnit from narwhals._pandas_like.expr import PandasLikeExpr diff --git a/narwhals/_polars/dataframe.py b/narwhals/_polars/dataframe.py index 68b820cd5e..43802a94e0 100644 --- a/narwhals/_polars/dataframe.py +++ b/narwhals/_polars/dataframe.py @@ -32,13 +32,13 @@ from narwhals.exceptions import ColumnNotFoundError if TYPE_CHECKING: - from collections.abc import Iterable + from collections.abc import Callable, Iterable from types import ModuleType - from typing import Callable + from typing import TypeAlias import pandas as pd import pyarrow as pa - from typing_extensions import Self, TypeAlias, TypeIs + from typing_extensions import Self, TypeIs from narwhals._compliant.typing import CompliantDataFrameAny, CompliantLazyFrameAny from narwhals._polars.expr import PolarsExpr diff --git a/narwhals/_polars/expr.py b/narwhals/_polars/expr.py index d4baa6bfa6..02899a24e0 100644 --- a/narwhals/_polars/expr.py +++ b/narwhals/_polars/expr.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, ClassVar, cast +from typing import TYPE_CHECKING, Any, ClassVar, cast import polars as pl @@ -19,7 +19,7 @@ from narwhals._utils import Implementation, no_default, requires if TYPE_CHECKING: - from collections.abc import Sequence + from collections.abc import Callable, Sequence from typing_extensions import Self diff --git a/narwhals/_polars/series.py b/narwhals/_polars/series.py index 4156c3e9ce..2faea2b699 100644 --- a/narwhals/_polars/series.py +++ b/narwhals/_polars/series.py @@ -26,11 +26,11 @@ if TYPE_CHECKING: from collections.abc import Iterable, Iterator, Mapping, Sequence from types import ModuleType - from typing import Literal, TypeVar + from typing import Literal, TypeAlias, TypeVar import pandas as pd import pyarrow as pa - from typing_extensions import Self, TypeAlias, TypeIs + from typing_extensions import Self, TypeIs from narwhals._polars.dataframe import Method, PolarsDataFrame from narwhals._polars.namespace import PolarsNamespace diff --git a/narwhals/_spark_like/dataframe.py b/narwhals/_spark_like/dataframe.py index 04e17f3ac0..8ab2c12d37 100644 --- a/narwhals/_spark_like/dataframe.py +++ b/narwhals/_spark_like/dataframe.py @@ -33,12 +33,13 @@ from io import BytesIO from pathlib import Path from types import ModuleType + from typing import TypeAlias import pyarrow as pa from sqlframe.base.column import Column from sqlframe.base.dataframe import BaseDataFrame from sqlframe.base.window import Window - from typing_extensions import Self, TypeAlias, TypeIs + from typing_extensions import Self, TypeIs from narwhals._compliant.typing import CompliantDataFrameAny from narwhals._spark_like.expr import SparkLikeExpr diff --git a/narwhals/_spark_like/expr.py b/narwhals/_spark_like/expr.py index 7a9b7d0d07..07368b9226 100644 --- a/narwhals/_spark_like/expr.py +++ b/narwhals/_spark_like/expr.py @@ -1,7 +1,7 @@ from __future__ import annotations import operator -from typing import TYPE_CHECKING, Any, Callable, ClassVar, Literal, cast +from typing import TYPE_CHECKING, Any, ClassVar, Literal, cast from narwhals._spark_like.expr_dt import SparkLikeExprDateTimeNamespace from narwhals._spark_like.expr_list import SparkLikeExprListNamespace @@ -25,11 +25,12 @@ ) if TYPE_CHECKING: - from collections.abc import Iterator, Mapping, Sequence + from collections.abc import Callable, Iterator, Mapping, Sequence + from typing import TypeAlias from sqlframe.base.column import Column from sqlframe.base.window import Window, WindowSpec - from typing_extensions import Self, TypeAlias + from typing_extensions import Self from narwhals._compliant import WindowInputs from narwhals._compliant.typing import ( diff --git a/narwhals/_spark_like/utils.py b/narwhals/_spark_like/utils.py index b26e071efd..7765d20a0a 100644 --- a/narwhals/_spark_like/utils.py +++ b/narwhals/_spark_like/utils.py @@ -13,11 +13,11 @@ if TYPE_CHECKING: from collections.abc import Mapping + from typing import TypeAlias import sqlframe.base.types as sqlframe_types from sqlframe.base.column import Column from sqlframe.base.session import _BaseSession as Session - from typing_extensions import TypeAlias from narwhals._compliant.typing import CompliantLazyFrameAny from narwhals._spark_like.dataframe import SparkLikeLazyFrame diff --git a/narwhals/_sql/dataframe.py b/narwhals/_sql/dataframe.py index f4a03dc541..e892965e83 100644 --- a/narwhals/_sql/dataframe.py +++ b/narwhals/_sql/dataframe.py @@ -14,8 +14,9 @@ if TYPE_CHECKING: from collections.abc import Sequence + from typing import TypeAlias - from typing_extensions import Self, TypeAlias + from typing_extensions import Self from narwhals._compliant.window import WindowInputs from narwhals._sql.expr import SQLExpr diff --git a/narwhals/_sql/expr.py b/narwhals/_sql/expr.py index 670f4583ad..ce5d37c5cb 100644 --- a/narwhals/_sql/expr.py +++ b/narwhals/_sql/expr.py @@ -1,7 +1,7 @@ from __future__ import annotations import operator as op -from typing import TYPE_CHECKING, Any, Callable, Literal, Protocol +from typing import TYPE_CHECKING, Any, Literal, Protocol from narwhals._compliant.expr import LazyExpr from narwhals._compliant.typing import ( @@ -20,7 +20,7 @@ from narwhals._utils import Implementation, Version, extend_bool, not_implemented if TYPE_CHECKING: - from collections.abc import Sequence + from collections.abc import Callable, Sequence from typing_extensions import Self diff --git a/narwhals/_translate.py b/narwhals/_translate.py index 7bfd531a2a..c0a03d4141 100644 --- a/narwhals/_translate.py +++ b/narwhals/_translate.py @@ -68,8 +68,10 @@ class OtherConvertible( from narwhals._typing_compat import TypeVar if TYPE_CHECKING: + from typing import TypeAlias + import pyarrow as pa - from typing_extensions import Self, TypeAlias, TypeIs + from typing_extensions import Self, TypeIs class ArrowStreamExportable(Protocol): diff --git a/narwhals/_typing.py b/narwhals/_typing.py index c4307c2680..8361d96303 100644 --- a/narwhals/_typing.py +++ b/narwhals/_typing.py @@ -1,13 +1,13 @@ from __future__ import annotations from types import ModuleType -from typing import TYPE_CHECKING, Literal, Union +from typing import TYPE_CHECKING, Literal from narwhals._typing_compat import TypeVar from narwhals._utils import Implementation, _NoDefault if TYPE_CHECKING: - from typing_extensions import TypeAlias + from typing import TypeAlias # `str` aliases _Polars: TypeAlias = Literal["polars"] @@ -92,7 +92,7 @@ """ BackendT = TypeVar("BackendT", bound=Backend) -IntoBackend: TypeAlias = Union[BackendT, ModuleType] +IntoBackend: TypeAlias = BackendT | ModuleType """Anything that can be converted into a [`narwhals.Implementation`][]. `backend` can be specified in three ways. diff --git a/narwhals/_typing_compat.py b/narwhals/_typing_compat.py index 47cbf3464a..ff1e5b49e1 100644 --- a/narwhals/_typing_compat.py +++ b/narwhals/_typing_compat.py @@ -22,7 +22,7 @@ from typing import TYPE_CHECKING, Any if TYPE_CHECKING: - from typing import Callable + from collections.abc import Callable if sys.version_info >= (3, 13): from typing import TypeVar diff --git a/narwhals/_utils.py b/narwhals/_utils.py index 9f9bcc8a97..783d973d02 100644 --- a/narwhals/_utils.py +++ b/narwhals/_utils.py @@ -2,26 +2,30 @@ import os import re -import sys -from collections.abc import Collection, Container, Iterable, Iterator, Mapping, Sequence +from collections.abc import ( + Callable, + Collection, + Container, + Iterable, + Iterator, + Mapping, + Sequence, +) from datetime import timezone from enum import Enum, auto from functools import cache, lru_cache, partial, wraps from importlib.util import find_spec from inspect import getattr_static, getdoc -from itertools import chain from operator import attrgetter from secrets import token_hex from typing import ( TYPE_CHECKING, Any, - Callable, Final, Generic, Literal, Protocol, TypeVar, - Union, cast, overload, ) @@ -53,18 +57,12 @@ if TYPE_CHECKING: from collections.abc import Set # noqa: PYI025 from types import ModuleType + from typing import Concatenate, TypeAlias import pandas as pd import polars as pl import pyarrow as pa - from typing_extensions import ( - Concatenate, - LiteralString, - ParamSpec, - Self, - TypeAlias, - TypeIs, - ) + from typing_extensions import LiteralString, ParamSpec, Self, TypeIs from narwhals._compliant import CompliantExprT, CompliantSeriesT, NativeSeriesT_co from narwhals._compliant.any_namespace import NamespaceAccessor @@ -134,7 +132,7 @@ UnknownBackendName: TypeAlias = str FrameOrSeriesT = TypeVar( - "FrameOrSeriesT", bound=Union[LazyFrame[Any], DataFrame[Any], Series[Any]] + "FrameOrSeriesT", bound=LazyFrame[Any] | DataFrame[Any] | Series[Any] ) _T1 = TypeVar("_T1") @@ -1023,44 +1021,7 @@ def maybe_reset_index(obj: FrameOrSeriesT) -> FrameOrSeriesT: return obj_any -if TYPE_CHECKING: - zip_strict = partial(zip, strict=True) -else: - import sys - - if sys.version_info >= (3, 10): - zip_strict = partial(zip, strict=True) - else: # pragma: no cover - # https://stackoverflow.com/questions/32954486/zip-iterators-asserting-for-equal-length-in-python/69485272#69485272 - - def zip_strict(*iterables: Iterable[Any]) -> Iterable[tuple[Any, ...]]: - # For trivial cases, use pure zip. - if len(iterables) < 2: - return zip(*iterables) - # Tail for the first iterable - first_stopped = False - - def first_tail() -> Any: - nonlocal first_stopped - first_stopped = True - return - yield - - # Tail for the zip - def zip_tail() -> Any: - if not first_stopped: # pragma: no cover - msg = "zip_strict: first iterable is longer" - raise ValueError(msg) - for _ in chain.from_iterable(rest): # pragma: no cover - msg = "zip_strict: first iterable is shorter" - raise ValueError(msg) - yield - - # Put the pieces together - iterables_it = iter(iterables) - first = chain(next(iterables_it), first_tail()) - rest = list(map(iter, iterables_it)) - return chain(zip(first, *rest), zip_tail()) +zip_strict = partial(zip, strict=True) def _is_range_index(obj: Any, native_namespace: Any) -> TypeIs[pd.RangeIndex]: diff --git a/narwhals/dataframe.py b/narwhals/dataframe.py index 428c037c10..7f6452f9ea 100644 --- a/narwhals/dataframe.py +++ b/narwhals/dataframe.py @@ -6,7 +6,6 @@ from typing import ( TYPE_CHECKING, Any, - Callable, ClassVar, Generic, Literal, @@ -57,15 +56,16 @@ from narwhals.translate import to_native if TYPE_CHECKING: - from collections.abc import Iterable, Iterator, Mapping, Sequence + from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence from io import BytesIO from pathlib import Path from types import ModuleType + from typing import Concatenate, TypeAlias import pandas as pd import polars as pl import pyarrow as pa - from typing_extensions import Concatenate, ParamSpec, Self, TypeAlias + from typing_extensions import ParamSpec, Self from narwhals._compliant import CompliantDataFrame, CompliantLazyFrame from narwhals._compliant.typing import CompliantExprAny diff --git a/narwhals/dependencies.py b/narwhals/dependencies.py index aacc916d43..0a9d7c8dc3 100644 --- a/narwhals/dependencies.py +++ b/narwhals/dependencies.py @@ -9,6 +9,8 @@ from narwhals._exceptions import issue_warning if TYPE_CHECKING: + from typing import TypeGuard + import cudf import dask.dataframe as dd import duckdb @@ -19,7 +21,7 @@ import pyarrow as pa import pyspark.sql as pyspark_sql from pyspark.sql.connect.dataframe import DataFrame as PySparkConnectDataFrame - from typing_extensions import TypeGuard, TypeIs + from typing_extensions import TypeIs from narwhals._spark_like.dataframe import SQLFrameDataFrame from narwhals.dataframe import DataFrame, LazyFrame diff --git a/narwhals/expr.py b/narwhals/expr.py index 11f93cc17a..bdf4c2adfb 100644 --- a/narwhals/expr.py +++ b/narwhals/expr.py @@ -1,8 +1,8 @@ from __future__ import annotations import math -from collections.abc import Iterable, Mapping, Sequence -from typing import TYPE_CHECKING, Any, Callable +from collections.abc import Callable, Iterable, Mapping, Sequence +from typing import TYPE_CHECKING, Any from narwhals._expression_parsing import ( ExprKind, @@ -22,9 +22,9 @@ from narwhals.translate import to_native if TYPE_CHECKING: - from typing import NoReturn, TypeVar + from typing import Concatenate, NoReturn, TypeVar - from typing_extensions import Concatenate, ParamSpec, Self + from typing_extensions import ParamSpec, Self from narwhals._compliant import CompliantExpr, CompliantNamespace from narwhals._typing import NoDefault diff --git a/narwhals/expr_name.py b/narwhals/expr_name.py index 64525aae56..820c9f682b 100644 --- a/narwhals/expr_name.py +++ b/narwhals/expr_name.py @@ -1,10 +1,12 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Callable, Generic, TypeVar +from typing import TYPE_CHECKING, Generic, TypeVar from narwhals._expression_parsing import ExprKind, ExprNode if TYPE_CHECKING: + from collections.abc import Callable + from narwhals.expr import Expr ExprT = TypeVar("ExprT", bound="Expr") diff --git a/narwhals/functions.py b/narwhals/functions.py index 322da7019e..ffca9ba5bb 100644 --- a/narwhals/functions.py +++ b/narwhals/functions.py @@ -29,8 +29,9 @@ if TYPE_CHECKING: from types import ModuleType + from typing import TypeAlias - from typing_extensions import TypeAlias, TypeIs + from typing_extensions import TypeIs from narwhals._native import NativeDataFrame, NativeLazyFrame, NativeSeries from narwhals._translate import IntoArrowTable diff --git a/narwhals/plugins.py b/narwhals/plugins.py index f781303bb0..a3341e1fa5 100644 --- a/narwhals/plugins.py +++ b/narwhals/plugins.py @@ -2,7 +2,7 @@ import sys from functools import cache -from typing import TYPE_CHECKING, Any, Protocol, cast +from typing import TYPE_CHECKING, Any, Protocol from narwhals._compliant import CompliantNamespace from narwhals._typing_compat import TypeVar @@ -10,8 +10,9 @@ if TYPE_CHECKING: from collections.abc import Iterator from importlib.metadata import EntryPoints + from typing import TypeAlias - from typing_extensions import LiteralString, TypeAlias + from typing_extensions import LiteralString from narwhals._compliant.typing import ( CompliantDataFrameAny, @@ -44,8 +45,6 @@ def _discover_entrypoints() -> EntryPoints: from importlib.metadata import entry_points as eps group = "narwhals.plugins" - if sys.version_info < (3, 10): - return cast("EntryPoints", eps().get(group, ())) return eps(group=group) diff --git a/narwhals/series.py b/narwhals/series.py index 2198e41f3b..e28ef8fcf6 100644 --- a/narwhals/series.py +++ b/narwhals/series.py @@ -1,17 +1,8 @@ from __future__ import annotations import math -from collections.abc import Iterable, Iterator, Mapping, Sequence -from typing import ( - TYPE_CHECKING, - Any, - Callable, - ClassVar, - Generic, - Literal, - cast, - overload, -) +from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence +from typing import TYPE_CHECKING, Any, ClassVar, Generic, Literal, cast, overload from narwhals._expression_parsing import ExprKind, ExprNode from narwhals._utils import ( diff --git a/narwhals/stable/v1/__init__.py b/narwhals/stable/v1/__init__.py index 8e5a78672d..61c295a3df 100644 --- a/narwhals/stable/v1/__init__.py +++ b/narwhals/stable/v1/__init__.py @@ -1,7 +1,7 @@ from __future__ import annotations from functools import wraps -from typing import TYPE_CHECKING, Any, Callable, Final, Literal, cast, overload +from typing import TYPE_CHECKING, Any, Final, Literal, cast, overload import narwhals as nw from narwhals import exceptions, functions as nw_f @@ -63,7 +63,7 @@ from narwhals.translate import _from_native_impl, get_native_namespace, to_py_scalar if TYPE_CHECKING: - from collections.abc import Iterable, Mapping, Sequence + from collections.abc import Callable, Iterable, Mapping, Sequence from types import ModuleType from typing_extensions import ParamSpec, Self diff --git a/narwhals/stable/v1/typing.py b/narwhals/stable/v1/typing.py index ab5cd17552..6184bb34c8 100644 --- a/narwhals/stable/v1/typing.py +++ b/narwhals/stable/v1/typing.py @@ -5,7 +5,7 @@ from narwhals._native import IntoSeries, IntoSeriesT if TYPE_CHECKING: - from typing_extensions import TypeAlias + from typing import TypeAlias from narwhals._native import NativeDataFrame, NativeLazyFrame from narwhals.stable.v1 import DataFrame, Expr, LazyFrame, Series diff --git a/narwhals/stable/v2/__init__.py b/narwhals/stable/v2/__init__.py index cbc5ff21d3..65dd1ac045 100644 --- a/narwhals/stable/v2/__init__.py +++ b/narwhals/stable/v2/__init__.py @@ -1,7 +1,7 @@ from __future__ import annotations from functools import wraps -from typing import TYPE_CHECKING, Any, Callable, Final, Literal, cast, overload +from typing import TYPE_CHECKING, Any, Final, Literal, cast, overload import narwhals as nw from narwhals import exceptions, functions as nw_f @@ -59,7 +59,7 @@ from narwhals.typing import IntoDataFrameT, IntoLazyFrameT if TYPE_CHECKING: - from collections.abc import Iterable, Mapping, Sequence + from collections.abc import Callable, Iterable, Mapping, Sequence from typing_extensions import ParamSpec, Self diff --git a/narwhals/stable/v2/typing.py b/narwhals/stable/v2/typing.py index ca27111410..df8435f089 100644 --- a/narwhals/stable/v2/typing.py +++ b/narwhals/stable/v2/typing.py @@ -14,7 +14,7 @@ ) if TYPE_CHECKING: - from typing_extensions import TypeAlias + from typing import TypeAlias from narwhals.stable.v2 import DataFrame, Expr, LazyFrame, Series diff --git a/narwhals/testing/asserts/series.py b/narwhals/testing/asserts/series.py index 1408da0309..140ba1fe64 100644 --- a/narwhals/testing/asserts/series.py +++ b/narwhals/testing/asserts/series.py @@ -1,7 +1,7 @@ from __future__ import annotations from functools import partial -from typing import TYPE_CHECKING, Any, Callable +from typing import TYPE_CHECKING, Any from narwhals._utils import qualified_type_name, zip_strict from narwhals.dependencies import is_narwhals_series @@ -10,7 +10,8 @@ from narwhals.testing.asserts.utils import raise_series_assertion_error if TYPE_CHECKING: - from typing_extensions import TypeAlias + from collections.abc import Callable + from typing import TypeAlias from narwhals.series import Series from narwhals.typing import IntoSeriesT, SeriesT diff --git a/narwhals/testing/asserts/utils.py b/narwhals/testing/asserts/utils.py index 13720c2a35..d393e40656 100644 --- a/narwhals/testing/asserts/utils.py +++ b/narwhals/testing/asserts/utils.py @@ -5,7 +5,9 @@ from narwhals.dependencies import is_narwhals_series if TYPE_CHECKING: - from typing_extensions import Never, TypeAlias + from typing import TypeAlias + + from typing_extensions import Never # NOTE: This alias is created to facilitate autocomplete. Feel free to extend it as # you please when adding a new feature. diff --git a/narwhals/translate.py b/narwhals/translate.py index 1590931f04..be904dd1da 100644 --- a/narwhals/translate.py +++ b/narwhals/translate.py @@ -3,7 +3,7 @@ import datetime as dt from decimal import Decimal from functools import wraps -from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar, overload +from typing import TYPE_CHECKING, Any, Literal, TypeVar, overload from narwhals import plugins from narwhals._constants import EPOCH, MS_PER_SECOND @@ -38,6 +38,8 @@ ) if TYPE_CHECKING: + from collections.abc import Callable + from narwhals.dataframe import DataFrame, LazyFrame from narwhals.series import Series from narwhals.typing import ( diff --git a/narwhals/typing.py b/narwhals/typing.py index 8c89b7745b..3dbffafeb3 100644 --- a/narwhals/typing.py +++ b/narwhals/typing.py @@ -22,12 +22,12 @@ from collections.abc import Sequence from decimal import Decimal from types import ModuleType + from typing import TypeAlias import numpy as np import pandas as pd import polars as pl import pyarrow as pa - from typing_extensions import TypeAlias from narwhals import dtypes from narwhals.dataframe import DataFrame, LazyFrame From 0662940bd6db5a9fd09085e8401c9de9f80a5b9a Mon Sep 17 00:00:00 2001 From: FBruzzesi Date: Mon, 17 Nov 2025 10:33:39 +0100 Subject: [PATCH 03/17] ruff: tests, no zip --- tests/conftest.py | 6 +++--- .../dependencies/is_native_dataframe_series_raise_test.py | 5 ++++- tests/expr_and_series/concat_str_test.py | 7 +++++-- tests/expr_and_series/division_by_zero_test.py | 5 ++++- tests/frame/schema_test.py | 2 +- tests/hypothesis/getitem_test.py | 4 ++-- tests/namespace_test.py | 8 +++++--- tests/preserve_pandas_like_columns_name_attr_test.py | 4 +++- tests/read_scan_test.py | 3 +-- tests/series_only/from_iterable_test.py | 3 +-- tests/test_plugin/test_plugin/dataframe.py | 4 +++- tests/testing/assert_series_equal_test.py | 5 +++-- tests/utils.py | 5 +++-- tests/utils_test.py | 4 ++-- tests/v1_test.py | 4 ++-- 15 files changed, 42 insertions(+), 27 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 3e80bcdff4..0b13c173b5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,7 +5,7 @@ from copy import deepcopy from functools import lru_cache from importlib.util import find_spec -from typing import TYPE_CHECKING, Any, Callable, cast +from typing import TYPE_CHECKING, Any, cast import pytest @@ -14,14 +14,14 @@ from tests.utils import ID_PANDAS_LIKE, PANDAS_VERSION, pyspark_session, sqlframe_session if TYPE_CHECKING: - from collections.abc import Sequence + from collections.abc import Callable, Sequence + from typing import TypeAlias import ibis import pandas as pd import polars as pl import pyarrow as pa from ibis.backends.duckdb import Backend as IbisDuckDBBackend - from typing_extensions import TypeAlias from narwhals._native import NativeDask, NativeDuckDB, NativePySpark, NativeSQLFrame from narwhals._typing import EagerAllowed diff --git a/tests/dependencies/is_native_dataframe_series_raise_test.py b/tests/dependencies/is_native_dataframe_series_raise_test.py index 5e22fc6107..a865553822 100644 --- a/tests/dependencies/is_native_dataframe_series_raise_test.py +++ b/tests/dependencies/is_native_dataframe_series_raise_test.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Callable +from typing import TYPE_CHECKING, Any import pytest @@ -25,6 +25,9 @@ is_pyarrow_table, ) +if TYPE_CHECKING: + from collections.abc import Callable + @pytest.mark.parametrize( "is_native_dataframe", diff --git a/tests/expr_and_series/concat_str_test.py b/tests/expr_and_series/concat_str_test.py index ad8c47e3df..9527c8f426 100644 --- a/tests/expr_and_series/concat_str_test.py +++ b/tests/expr_and_series/concat_str_test.py @@ -1,15 +1,18 @@ from __future__ import annotations -from typing import Callable - import pytest import narwhals as nw from tests.utils import POLARS_VERSION, Constructor, assert_equal_data pytest.importorskip("pyarrow") +from typing import TYPE_CHECKING + import pyarrow as pa +if TYPE_CHECKING: + from collections.abc import Callable + data = {"a": [1, 2, 3], "b": ["dogs", "cats", None], "c": ["play", "swim", "walk"]} diff --git a/tests/expr_and_series/division_by_zero_test.py b/tests/expr_and_series/division_by_zero_test.py index 073d1fd375..b2d2b8a577 100644 --- a/tests/expr_and_series/division_by_zero_test.py +++ b/tests/expr_and_series/division_by_zero_test.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Callable +from typing import TYPE_CHECKING, Any import pytest @@ -8,6 +8,9 @@ from narwhals._utils import zip_strict from tests.utils import POLARS_VERSION, Constructor, ConstructorEager, assert_equal_data +if TYPE_CHECKING: + from collections.abc import Callable + data: dict[str, list[float]] = { "int": [-2, 0, 2], "float": [-2.1, 0.0, 2.1], diff --git a/tests/frame/schema_test.py b/tests/frame/schema_test.py index d216807f0e..d17e70022d 100644 --- a/tests/frame/schema_test.py +++ b/tests/frame/schema_test.py @@ -13,9 +13,9 @@ if TYPE_CHECKING: from collections.abc import Callable, Sequence + from typing import TypeAlias import polars as pl - from typing_extensions import TypeAlias from narwhals.typing import ( DTypeBackend, diff --git a/tests/hypothesis/getitem_test.py b/tests/hypothesis/getitem_test.py index 759a292f97..115154ea61 100644 --- a/tests/hypothesis/getitem_test.py +++ b/tests/hypothesis/getitem_test.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, cast +from typing import TYPE_CHECKING, Any, cast import hypothesis.strategies as st import pytest @@ -11,7 +11,7 @@ from tests.utils import assert_equal_data if TYPE_CHECKING: - from collections.abc import Sequence + from collections.abc import Callable, Sequence from narwhals.typing import IntoDataFrame diff --git a/tests/namespace_test.py b/tests/namespace_test.py index e94a6690c1..9d900b39b9 100644 --- a/tests/namespace_test.py +++ b/tests/namespace_test.py @@ -2,8 +2,8 @@ import re from collections import deque -from collections.abc import Iterable, Sequence -from typing import TYPE_CHECKING, Any, Callable, TypeVar, cast +from collections.abc import Callable, Iterable, Sequence +from typing import TYPE_CHECKING, Any, TypeVar, cast import pytest @@ -12,7 +12,9 @@ from narwhals._utils import Version if TYPE_CHECKING: - from typing_extensions import Never, TypeAlias, assert_type # noqa: F401 + from typing import TypeAlias + + from typing_extensions import assert_type from narwhals._arrow.namespace import ArrowNamespace # noqa: F401 from narwhals._compliant import CompliantNamespace diff --git a/tests/preserve_pandas_like_columns_name_attr_test.py b/tests/preserve_pandas_like_columns_name_attr_test.py index 3127040bee..70e7d06e24 100644 --- a/tests/preserve_pandas_like_columns_name_attr_test.py +++ b/tests/preserve_pandas_like_columns_name_attr_test.py @@ -1,12 +1,14 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING import pytest import narwhals as nw if TYPE_CHECKING: + from collections.abc import Callable + import pandas as pd diff --git a/tests/read_scan_test.py b/tests/read_scan_test.py index 7eb32c74d0..3430beac7a 100644 --- a/tests/read_scan_test.py +++ b/tests/read_scan_test.py @@ -23,8 +23,7 @@ from collections.abc import Mapping from pathlib import Path from types import ModuleType - - from typing_extensions import TypeAlias + from typing import TypeAlias from narwhals._typing import EagerAllowed, _LazyOnly, _SparkLike from narwhals.typing import FileSource diff --git a/tests/series_only/from_iterable_test.py b/tests/series_only/from_iterable_test.py index 4daac24ea8..6ebb4115c0 100644 --- a/tests/series_only/from_iterable_test.py +++ b/tests/series_only/from_iterable_test.py @@ -20,8 +20,7 @@ Sequence, ValuesView, ) - - from typing_extensions import TypeAlias + from typing import TypeAlias from narwhals._typing import EagerAllowed from narwhals.typing import IntoDType diff --git a/tests/test_plugin/test_plugin/dataframe.py b/tests/test_plugin/test_plugin/dataframe.py index 74d539b2ec..d7f1f237c7 100644 --- a/tests/test_plugin/test_plugin/dataframe.py +++ b/tests/test_plugin/test_plugin/dataframe.py @@ -11,7 +11,9 @@ from narwhals.typing import CompliantLazyFrame if TYPE_CHECKING: - from typing_extensions import Self, TypeAlias + from typing import TypeAlias + + from typing_extensions import Self from narwhals import LazyFrame # noqa: F401 diff --git a/tests/testing/assert_series_equal_test.py b/tests/testing/assert_series_equal_test.py index d16e1065ce..d1d83af795 100644 --- a/tests/testing/assert_series_equal_test.py +++ b/tests/testing/assert_series_equal_test.py @@ -2,7 +2,7 @@ import re from contextlib import AbstractContextManager, nullcontext as does_not_raise -from typing import TYPE_CHECKING, Any, Callable +from typing import TYPE_CHECKING, Any import pytest @@ -11,7 +11,8 @@ from tests.utils import PANDAS_VERSION, POLARS_VERSION, PYARROW_VERSION if TYPE_CHECKING: - from typing_extensions import TypeAlias + from collections.abc import Callable + from typing import TypeAlias from narwhals.typing import IntoSchema, IntoSeriesT from tests.conftest import Data diff --git a/tests/utils.py b/tests/utils.py index 693569feb6..339adc27ce 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -4,9 +4,10 @@ import os import sys import warnings +from collections.abc import Callable from datetime import date, datetime from pathlib import Path -from typing import TYPE_CHECKING, Any, Callable, cast +from typing import TYPE_CHECKING, Any, cast import narwhals as nw from narwhals._utils import Implementation, parse_version, zip_strict @@ -15,12 +16,12 @@ if TYPE_CHECKING: from collections.abc import Mapping, Sequence + from typing import TypeAlias import pandas as pd import pytest from pyspark.sql import SparkSession from sqlframe.duckdb import DuckDBSession - from typing_extensions import TypeAlias from narwhals._native import NativeLazyFrame from narwhals.typing import Frame, IntoDataFrame, TimeUnit diff --git a/tests/utils_test.py b/tests/utils_test.py index db97b29400..55c5724102 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -4,7 +4,7 @@ import string from dataclasses import dataclass from itertools import chain -from typing import TYPE_CHECKING, Any, Callable, ClassVar, Protocol, cast +from typing import TYPE_CHECKING, Any, ClassVar, Protocol, cast import hypothesis.strategies as st import pytest @@ -26,7 +26,7 @@ from pandas.testing import assert_frame_equal, assert_index_equal, assert_series_equal if TYPE_CHECKING: - from collections.abc import Iterable, Iterator + from collections.abc import Callable, Iterable, Iterator from types import ModuleType from typing_extensions import Self diff --git a/tests/v1_test.py b/tests/v1_test.py index b578913e20..527ba0ee93 100644 --- a/tests/v1_test.py +++ b/tests/v1_test.py @@ -5,7 +5,7 @@ from collections import deque from contextlib import nullcontext as does_not_raise from datetime import datetime, timedelta -from typing import TYPE_CHECKING, Any, Callable, cast +from typing import TYPE_CHECKING, Any, cast import pytest @@ -43,7 +43,7 @@ ) if TYPE_CHECKING: - from collections.abc import Sequence + from collections.abc import Callable, Sequence from typing_extensions import assert_type From c327000d99edb34c5ab1a4e302fb8082c4024b20 Mon Sep 17 00:00:00 2001 From: FBruzzesi Date: Mon, 17 Nov 2025 10:40:49 +0100 Subject: [PATCH 04/17] rm zip_strict --- narwhals/_arrow/dataframe.py | 9 ++++---- narwhals/_compliant/expr.py | 21 +++++++++---------- narwhals/_compliant/group_by.py | 4 ++-- narwhals/_compliant/selectors.py | 13 ++++++------ narwhals/_dask/dataframe.py | 3 +-- narwhals/_dask/group_by.py | 3 +-- narwhals/_dask/namespace.py | 6 +++--- narwhals/_duckdb/dataframe.py | 17 ++++++++------- narwhals/_duckdb/utils.py | 6 ++++-- narwhals/_expression_parsing.py | 8 +++---- narwhals/_ibis/dataframe.py | 3 +-- narwhals/_ibis/expr.py | 3 +-- narwhals/_pandas_like/dataframe.py | 3 +-- narwhals/_pandas_like/group_by.py | 3 +-- narwhals/_pandas_like/namespace.py | 5 ++--- narwhals/_polars/namespace.py | 6 +++--- narwhals/_spark_like/dataframe.py | 9 ++++---- narwhals/_spark_like/expr.py | 3 +-- narwhals/_sql/group_by.py | 7 +++---- narwhals/_utils.py | 5 +---- narwhals/dataframe.py | 6 +++--- narwhals/schema.py | 6 ++++-- narwhals/testing/asserts/series.py | 8 ++++--- .../expr_and_series/division_by_zero_test.py | 13 +++++++++--- tests/utils.py | 6 +++--- 25 files changed, 89 insertions(+), 87 deletions(-) diff --git a/narwhals/_arrow/dataframe.py b/narwhals/_arrow/dataframe.py index e61573e787..c7fbc22810 100644 --- a/narwhals/_arrow/dataframe.py +++ b/narwhals/_arrow/dataframe.py @@ -25,7 +25,6 @@ parse_columns_to_drop, scale_bytes, supports_arrow_c_stream, - zip_strict, ) from narwhals.dependencies import is_numpy_array_1d from narwhals.exceptions import ShapeError @@ -254,7 +253,7 @@ def rows(self, *, named: bool) -> list[tuple[Any, ...]] | list[dict[str, Any]]: return self.native.to_pylist() def iter_columns(self) -> Iterator[ArrowSeries]: - for name, series in zip_strict(self.columns, self.native.itercolumns()): + for name, series in zip(self.columns, self.native.itercolumns(), strict=True): yield ArrowSeries.from_native(series, context=self, name=name) _iter_columns = iter_columns @@ -268,7 +267,7 @@ def iter_rows( if not named: for i in range(0, num_rows, buffer_size): rows = df[i : i + buffer_size].to_pydict().values() - yield from zip_strict(*rows) + yield from zip(*rows, strict=True) else: for i in range(0, num_rows, buffer_size): yield from df[i : i + buffer_size].to_pylist() @@ -480,7 +479,7 @@ def sort(self, *by: str, descending: bool | Sequence[bool], nulls_last: bool) -> else: sorting = [ (key, "descending" if is_descending else "ascending") - for key, is_descending in zip_strict(by, descending) + for key, is_descending in zip(by, descending, strict=True) ] null_placement = "at_end" if nulls_last else "at_start" @@ -497,7 +496,7 @@ def top_k(self, k: int, *, by: Iterable[str], reverse: bool | Sequence[bool]) -> else: sorting = [ (key, "ascending" if is_ascending else "descending") - for key, is_ascending in zip_strict(by, reverse) + for key, is_ascending in zip(by, reverse, strict=True) ] return self._with_native( self.native.take(pc.select_k_unstable(self.native, k, sorting)), # type: ignore[call-overload] diff --git a/narwhals/_compliant/expr.py b/narwhals/_compliant/expr.py index db6feedbd1..577ded5e6b 100644 --- a/narwhals/_compliant/expr.py +++ b/narwhals/_compliant/expr.py @@ -27,12 +27,7 @@ LazyExprT, NativeExprT, ) -from narwhals._utils import ( - _StoresCompliant, - not_implemented, - qualified_type_name, - zip_strict, -) +from narwhals._utils import _StoresCompliant, not_implemented, qualified_type_name from narwhals.dependencies import is_numpy_array, is_numpy_scalar from narwhals.exceptions import MultiOutputExpressionError @@ -293,13 +288,17 @@ def func(df: EagerDataFrameT) -> list[EagerSeriesT]: if alias_output_names: return [ series.alias(name) - for series, name in zip_strict( - self(df), alias_output_names(self._evaluate_output_names(df)) + for series, name in zip( + self(df), + alias_output_names(self._evaluate_output_names(df)), + strict=True, ) ] return [ series.alias(name) - for series, name in zip_strict(self(df), self._evaluate_output_names(df)) + for series, name in zip( + self(df), self._evaluate_output_names(df), strict=True + ) ] return self.__class__( @@ -373,7 +372,7 @@ def _reuse_series_inner( ] aliases, names = self._evaluate_aliases(df), (s.name for s in out) if any( - alias != name for alias, name in zip_strict(aliases, names) + alias != name for alias, name in zip(aliases, names, strict=True) ): # pragma: no cover msg = ( f"Safety assertion failed, please report a bug to https://github.com/narwhals-dev/narwhals/issues\n" @@ -749,7 +748,7 @@ def func(df: EagerDataFrameT) -> Sequence[EagerSeriesT]: _first_in, _first_out = udf_series_in[0], udf_series_out[0] result: Sequence[EagerSeriesT] - it = zip_strict(udf_series_out, output_names) + it = zip(udf_series_out, output_names, strict=True) if is_numpy_array(_first_out) or is_numpy_scalar(_first_out): from_numpy = partial(_first_in.from_numpy, context=self) result = tuple(from_numpy(arr).alias(out_name) for arr, out_name in it) diff --git a/narwhals/_compliant/group_by.py b/narwhals/_compliant/group_by.py index 10dcae5373..7ebb9687b7 100644 --- a/narwhals/_compliant/group_by.py +++ b/narwhals/_compliant/group_by.py @@ -15,7 +15,7 @@ ImplExprT_contra, NarwhalsAggregation, ) -from narwhals._utils import is_sequence_of, zip_strict +from narwhals._utils import is_sequence_of if TYPE_CHECKING: from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence @@ -109,7 +109,7 @@ def _temporary_name(key: str) -> str: if (metadata := key._metadata) and metadata.expansion_kind.is_multi_output() # otherwise it's single named and we can use Expr.alias else key.alias(_temporary_name(new_names[0])) - for key, new_names in zip_strict(keys, keys_aliases) + for key, new_names in zip(keys, keys_aliases, strict=True) ] return ( compliant_frame.with_columns(*safe_keys), diff --git a/narwhals/_compliant/selectors.py b/narwhals/_compliant/selectors.py index c7c3de2c02..71bb3d6988 100644 --- a/narwhals/_compliant/selectors.py +++ b/narwhals/_compliant/selectors.py @@ -12,7 +12,6 @@ dtype_matches_time_unit_and_time_zone, get_column_names, is_compliant_dataframe, - zip_strict, ) if TYPE_CHECKING: @@ -76,7 +75,7 @@ def _iter_columns_dtypes( self, df: FrameT, / ) -> Iterator[tuple[SeriesOrExprT, DType]]: ... def _iter_columns_names(self, df: FrameT, /) -> Iterator[tuple[SeriesOrExprT, str]]: - yield from zip_strict(self._iter_columns(df), df.columns) + yield from zip(self._iter_columns(df), df.columns, strict=True) def _is_dtype( self: CompliantSelectorNamespace[FrameT, SeriesOrExprT], dtype: type[DType], / @@ -192,7 +191,7 @@ def _iter_columns(self, df: LazyFrameT) -> Iterator[ExprT]: yield from df._iter_columns() def _iter_columns_dtypes(self, df: LazyFrameT, /) -> Iterator[tuple[ExprT, DType]]: - yield from zip_strict(self._iter_columns(df), df.schema.values()) + yield from zip(self._iter_columns(df), df.schema.values(), strict=True) class CompliantSelector( @@ -245,7 +244,7 @@ def series(df: FrameT) -> Sequence[SeriesOrExprT]: lhs_names, rhs_names = _eval_lhs_rhs(df, self, other) return [ x - for x, name in zip_strict(self(df), lhs_names) + for x, name in zip(self(df), lhs_names, strict=True) if name not in rhs_names ] @@ -272,7 +271,7 @@ def series(df: FrameT) -> Sequence[SeriesOrExprT]: return [ *( x - for x, name in zip_strict(self(df), lhs_names) + for x, name in zip(self(df), lhs_names, strict=True) if name not in rhs_names ), *other(df), @@ -299,7 +298,9 @@ def __and__( def series(df: FrameT) -> Sequence[SeriesOrExprT]: lhs_names, rhs_names = _eval_lhs_rhs(df, self, other) return [ - x for x, name in zip_strict(self(df), lhs_names) if name in rhs_names + x + for x, name in zip(self(df), lhs_names, strict=True) + if name in rhs_names ] def names(df: FrameT) -> Sequence[str]: diff --git a/narwhals/_dask/dataframe.py b/narwhals/_dask/dataframe.py index 7ae45c46cd..914bdc07a4 100644 --- a/narwhals/_dask/dataframe.py +++ b/narwhals/_dask/dataframe.py @@ -16,7 +16,6 @@ generate_temporary_column_name, not_implemented, parse_columns_to_drop, - zip_strict, ) from narwhals.exceptions import MultiOutputExpressionError from narwhals.typing import CompliantLazyFrame @@ -320,7 +319,7 @@ def _join_left( ) extra = [ right_key if right_key not in self.columns else f"{right_key}{suffix}" - for left_key, right_key in zip_strict(left_on, right_on) + for left_key, right_key in zip(left_on, right_on, strict=True) if right_key != left_key ] return result_native.drop(columns=extra) diff --git a/narwhals/_dask/group_by.py b/narwhals/_dask/group_by.py index 214a0e3e10..8b6884a219 100644 --- a/narwhals/_dask/group_by.py +++ b/narwhals/_dask/group_by.py @@ -7,7 +7,6 @@ from narwhals._compliant import DepthTrackingGroupBy from narwhals._expression_parsing import evaluate_output_names_and_aliases -from narwhals._utils import zip_strict if TYPE_CHECKING: from collections.abc import Callable, Mapping, Sequence @@ -140,7 +139,7 @@ def agg(self, *exprs: DaskExpr) -> DaskLazyFrame: agg_fn = agg_fn(**last_node.kwargs) if callable(agg_fn) else agg_fn simple_aggregations.update( (alias, (output_name, agg_fn)) - for alias, output_name in zip_strict(aliases, output_names) + for alias, output_name in zip(aliases, output_names, strict=True) ) return DaskLazyFrame( self._grouped.agg(**simple_aggregations).reset_index(), diff --git a/narwhals/_dask/namespace.py b/narwhals/_dask/namespace.py index c4791a7e0d..4af128ff61 100644 --- a/narwhals/_dask/namespace.py +++ b/narwhals/_dask/namespace.py @@ -22,7 +22,7 @@ combine_alias_output_names, combine_evaluate_output_names, ) -from narwhals._utils import Implementation, zip_strict +from narwhals._utils import Implementation if TYPE_CHECKING: from collections.abc import Iterable, Iterator @@ -231,7 +231,7 @@ def func(df: DaskLazyFrame) -> list[dx.Series]: ) else: init_value, *values = [ - s.where(~nm, "") for s, nm in zip_strict(series, null_mask) + s.where(~nm, "") for s, nm in zip(series, null_mask, strict=True) ] separators = ( @@ -240,7 +240,7 @@ def func(df: DaskLazyFrame) -> list[dx.Series]: ) result = reduce( operator.add, - (s + v for s, v in zip_strict(separators, values)), + (s + v for s, v in zip(separators, values, strict=True)), init_value, ) diff --git a/narwhals/_duckdb/dataframe.py b/narwhals/_duckdb/dataframe.py index d9ad44e4e0..0e607aa525 100644 --- a/narwhals/_duckdb/dataframe.py +++ b/narwhals/_duckdb/dataframe.py @@ -27,7 +27,6 @@ generate_temporary_column_name, parse_columns_to_drop, requires, - zip_strict, ) from narwhals.dependencies import get_duckdb from narwhals.exceptions import InvalidOperationError @@ -235,15 +234,17 @@ def schema(self) -> dict[str, DType]: if self._cached_native_schema is None: # Note: prefer `self._cached_native_schema` over `functools.cached_property` # due to Python3.13 failures. - self._cached_native_schema = dict(zip(self.columns, self.native.types)) + self._cached_native_schema = dict( + zip(self.columns, self.native.types, strict=False) + ) deferred_time_zone = DeferredTimeZone(self.native) return { column_name: native_to_narwhals_dtype( duckdb_dtype, self._version, deferred_time_zone ) - for column_name, duckdb_dtype in zip_strict( - self.native.columns, self.native.types + for column_name, duckdb_dtype in zip( + self.native.columns, self.native.types, strict=True ) } @@ -308,7 +309,7 @@ def join( assert right_on is not None # noqa: S101 it = ( col(f'lhs."{left}"') == col(f'rhs."{right}"') - for left, right in zip_strict(left_on, right_on) + for left, right in zip(left_on, right_on, strict=True) ) condition: Expression = reduce(and_, it) rel = self.native.set_alias("lhs").join( @@ -353,7 +354,7 @@ def join_asof( if by_left is not None and by_right is not None: conditions.extend( col(f'lhs."{left}"') == col(f'rhs."{right}"') - for left, right in zip_strict(by_left, by_right) + for left, right in zip(by_left, by_right, strict=True) ) else: by_left = by_right = [] @@ -425,12 +426,12 @@ def sort(self, *by: str, descending: bool | Sequence[bool], nulls_last: bool) -> if nulls_last: it = ( col(name).nulls_last() if not desc else col(name).desc().nulls_last() - for name, desc in zip_strict(by, descending) + for name, desc in zip(by, descending, strict=True) ) else: it = ( col(name).nulls_first() if not desc else col(name).desc().nulls_first() - for name, desc in zip_strict(by, descending) + for name, desc in zip(by, descending, strict=True) ) return self._with_native(self.native.sort(*it)) diff --git a/narwhals/_duckdb/utils.py b/narwhals/_duckdb/utils.py index 37d86a88ba..ccd2361231 100644 --- a/narwhals/_duckdb/utils.py +++ b/narwhals/_duckdb/utils.py @@ -12,7 +12,7 @@ # DuckDB pre 1.3 import duckdb.typing as duckdb_dtypes -from narwhals._utils import Version, extend_bool, isinstance_or_issubclass, zip_strict +from narwhals._utils import Version, extend_bool, isinstance_or_issubclass from narwhals.exceptions import ColumnNotFoundError if TYPE_CHECKING: @@ -327,7 +327,9 @@ def generate_order_by_sql( return "" by_sql = ",".join( f"{parse_into_expression(x)} {DESCENDING_TO_ORDER[_descending]} {NULLS_LAST_TO_NULLS_POS[_nulls_last]}" - for x, _descending, _nulls_last in zip_strict(order_by, descending, nulls_last) + for x, _descending, _nulls_last in zip( + order_by, descending, nulls_last, strict=True + ) ) return f"order by {by_sql}" diff --git a/narwhals/_expression_parsing.py b/narwhals/_expression_parsing.py index b800243fcc..a04f26a1ed 100644 --- a/narwhals/_expression_parsing.py +++ b/narwhals/_expression_parsing.py @@ -7,7 +7,6 @@ from enum import Enum, auto from typing import TYPE_CHECKING, Any, Literal, cast -from narwhals._utils import zip_strict from narwhals.dependencies import is_numpy_array_1d from narwhals.exceptions import ( InvalidIntoExprError, @@ -80,12 +79,13 @@ def evaluate_output_names_and_aliases( else expr._alias_output_names(output_names) ) if exclude and expr._metadata.expansion_kind.is_multi_unnamed(): - output_names, aliases = zip_strict( + output_names, aliases = zip( *[ (x, alias) - for x, alias in zip_strict(output_names, aliases) + for x, alias in zip(output_names, aliases, strict=True) if x not in exclude - ] + ], + strict=True, ) return output_names, aliases diff --git a/narwhals/_ibis/dataframe.py b/narwhals/_ibis/dataframe.py index 67192d55f6..5981ab5845 100644 --- a/narwhals/_ibis/dataframe.py +++ b/narwhals/_ibis/dataframe.py @@ -18,7 +18,6 @@ not_implemented, parse_columns_to_drop, to_pyarrow_table, - zip_strict, ) from narwhals.exceptions import InvalidOperationError @@ -312,7 +311,7 @@ def _convert_predicates( return left_on return [ cast("ir.BooleanColumn", (self.native[left] == other.native[right])) - for left, right in zip_strict(left_on, right_on) + for left, right in zip(left_on, right_on, strict=True) ] def collect_schema(self) -> dict[str, DType]: diff --git a/narwhals/_ibis/expr.py b/narwhals/_ibis/expr.py index 958c8808c7..e93063dbf3 100644 --- a/narwhals/_ibis/expr.py +++ b/narwhals/_ibis/expr.py @@ -26,7 +26,6 @@ extend_bool, no_default, not_implemented, - zip_strict, ) if TYPE_CHECKING: @@ -151,7 +150,7 @@ def _sort( (True, False): desc_nulls_first, (True, True): desc_nulls_last, } - for col, _desc, _nulls_last in zip_strict(cols, descending, nulls_last): + for col, _desc, _nulls_last in zip(cols, descending, nulls_last, strict=True): yield mapping[(_desc, _nulls_last)](col) @classmethod diff --git a/narwhals/_pandas_like/dataframe.py b/narwhals/_pandas_like/dataframe.py index 4bd6cb5acf..d53b31b9ef 100644 --- a/narwhals/_pandas_like/dataframe.py +++ b/narwhals/_pandas_like/dataframe.py @@ -30,7 +30,6 @@ generate_temporary_column_name, parse_columns_to_drop, scale_bytes, - zip_strict, ) from narwhals.dependencies import is_pandas_like_dataframe from narwhals.exceptions import InvalidOperationError, ShapeError @@ -624,7 +623,7 @@ def _join_left( ) extra = [ right_key if right_key not in self.columns else f"{right_key}{suffix}" - for left_key, right_key in zip_strict(left_on, right_on) + for left_key, right_key in zip(left_on, right_on, strict=True) if right_key != left_key ] # NOTE: Keep `inplace=True` to avoid making a redundant copy. diff --git a/narwhals/_pandas_like/group_by.py b/narwhals/_pandas_like/group_by.py index 7bb28e61ac..e5f18bb926 100644 --- a/narwhals/_pandas_like/group_by.py +++ b/narwhals/_pandas_like/group_by.py @@ -9,7 +9,6 @@ from narwhals._compliant import EagerGroupBy from narwhals._exceptions import issue_warning from narwhals._expression_parsing import evaluate_output_names_and_aliases -from narwhals._utils import zip_strict from narwhals.dependencies import is_pandas_like_dataframe if TYPE_CHECKING: @@ -339,7 +338,7 @@ def fn(df: pd.DataFrame) -> pd.Series[Any]: for expr in exprs for keys in expr(compliant) ) - out_group, out_names = zip_strict(*results) if results else ([], []) + out_group, out_names = zip(*results, strict=True) if results else ([], []) return into_series(out_group, index=out_names, context=ns).native return fn diff --git a/narwhals/_pandas_like/namespace.py b/narwhals/_pandas_like/namespace.py index 6f8b54e289..62e133d6b5 100644 --- a/narwhals/_pandas_like/namespace.py +++ b/narwhals/_pandas_like/namespace.py @@ -17,7 +17,6 @@ from narwhals._pandas_like.series import PandasLikeSeries from narwhals._pandas_like.typing import NativeDataFrameT, NativeSeriesT from narwhals._pandas_like.utils import is_non_nullable_boolean -from narwhals._utils import zip_strict if TYPE_CHECKING: from collections.abc import Iterable, Sequence @@ -308,7 +307,7 @@ def func(df: PandasLikeDataFrame) -> list[PandasLikeSeries]: # error: Cannot determine type of "values" [has-type] values: list[PandasLikeSeries] init_value, *values = ( - s.zip_with(~nm, "") for s, nm in zip_strict(series, null_mask) + s.zip_with(~nm, "") for s, nm in zip(series, null_mask, strict=True) ) sep_array = init_value._with_native( init_value.__native_namespace__().Series( @@ -321,7 +320,7 @@ def func(df: PandasLikeDataFrame) -> list[PandasLikeSeries]: separators = (sep_array.zip_with(~nm, "") for nm in null_mask[:-1]) result = reduce( operator.add, - (s + v for s, v in zip_strict(separators, values)), + (s + v for s, v in zip(separators, values, strict=True)), init_value, ) diff --git a/narwhals/_polars/namespace.py b/narwhals/_polars/namespace.py index ac8da364be..a90d50c5b1 100644 --- a/narwhals/_polars/namespace.py +++ b/narwhals/_polars/namespace.py @@ -8,7 +8,7 @@ from narwhals._polars.expr import PolarsExpr from narwhals._polars.series import PolarsSeries from narwhals._polars.utils import extract_args_kwargs, narwhals_to_native_dtype -from narwhals._utils import Implementation, requires, zip_strict +from narwhals._utils import Implementation, requires from narwhals.dependencies import is_numpy_array_2d from narwhals.dtypes import DType @@ -179,7 +179,7 @@ def concat_str( else: init_value, *values = [ pl.when(nm).then(pl.lit("")).otherwise(expr.cast(pl.String())) - for expr, nm in zip_strict(pl_exprs, null_mask) + for expr, nm in zip(pl_exprs, null_mask, strict=True) ] separators = [ pl.when(~nm).then(sep).otherwise(pl.lit("")) for nm in null_mask[:-1] @@ -188,7 +188,7 @@ def concat_str( result = pl.fold( # type: ignore[assignment] acc=init_value, function=operator.add, - exprs=[s + v for s, v in zip_strict(separators, values)], + exprs=[s + v for s, v in zip(separators, values, strict=True)], ) return self._expr(result, version=self._version) diff --git a/narwhals/_spark_like/dataframe.py b/narwhals/_spark_like/dataframe.py index 8ab2c12d37..79fb8c1a64 100644 --- a/narwhals/_spark_like/dataframe.py +++ b/narwhals/_spark_like/dataframe.py @@ -24,7 +24,6 @@ not_implemented, parse_columns_to_drop, to_pyarrow_table, - zip_strict, ) from narwhals.exceptions import InvalidOperationError @@ -339,7 +338,7 @@ def sort(self, *by: str, descending: bool | Sequence[bool], nulls_last: bool) -> for d in descending ) - sort_cols = [sort_f(col) for col, sort_f in zip_strict(by, sort_funcs)] + sort_cols = [sort_f(col) for col, sort_f in zip(by, sort_funcs, strict=True)] return self._with_native(self.native.sort(*sort_cols)) def top_k(self, k: int, *, by: Iterable[str], reverse: bool | Sequence[bool]) -> Self: @@ -348,7 +347,7 @@ def top_k(self, k: int, *, by: Iterable[str], reverse: bool | Sequence[bool]) -> sort_funcs = ( self._F.desc_nulls_last if not d else self._F.asc_nulls_last for d in reverse ) - sort_cols = [sort_f(col) for col, sort_f in zip_strict(by, sort_funcs)] + sort_cols = [sort_f(col) for col, sort_f in zip(by, sort_funcs, strict=True)] return self._with_native(self.native.sort(*sort_cols).limit(k)) def drop_nulls(self, subset: Sequence[str] | None) -> Self: @@ -449,7 +448,9 @@ def join( and_, ( getattr(self.native, left_key) == getattr(other_native, right_key) - for left_key, right_key in zip_strict(left_on_, right_on_remapped) + for left_key, right_key in zip( + left_on_, right_on_remapped, strict=True + ) ), ) if how == "full" diff --git a/narwhals/_spark_like/expr.py b/narwhals/_spark_like/expr.py index 07368b9226..b6945e6906 100644 --- a/narwhals/_spark_like/expr.py +++ b/narwhals/_spark_like/expr.py @@ -21,7 +21,6 @@ extend_bool, no_default, not_implemented, - zip_strict, ) if TYPE_CHECKING: @@ -158,7 +157,7 @@ def _sort( } yield from ( mapping[(_desc, _nulls_last)](col) - for col, _desc, _nulls_last in zip_strict(cols, descending, nulls_last) + for col, _desc, _nulls_last in zip(cols, descending, nulls_last, strict=True) ) def partition_by(self, *cols: Column | str) -> WindowSpec: diff --git a/narwhals/_sql/group_by.py b/narwhals/_sql/group_by.py index 3fc4dbb44b..dc3b3393c5 100644 --- a/narwhals/_sql/group_by.py +++ b/narwhals/_sql/group_by.py @@ -5,7 +5,6 @@ from narwhals._compliant.group_by import CompliantGroupBy, ParseKeysGroupBy from narwhals._compliant.typing import CompliantLazyFrameT, NativeExprT_co from narwhals._sql.typing import SQLExprT_contra -from narwhals._utils import zip_strict if TYPE_CHECKING: from collections.abc import Iterable, Iterator @@ -29,13 +28,13 @@ def _evaluate_expr(self, expr: SQLExprT_contra, /) -> Iterator[NativeExprT_co]: native_exprs = expr(self.compliant) if expr._is_multi_output_unnamed(): exclude = {*self._keys, *self._output_key_names} - for native_expr, name, alias in zip_strict( - native_exprs, output_names, aliases + for native_expr, name, alias in zip( + native_exprs, output_names, aliases, strict=True ): if name not in exclude: yield expr._alias_native(native_expr, alias) else: - for native_expr, alias in zip_strict(native_exprs, aliases): + for native_expr, alias in zip(native_exprs, aliases, strict=True): yield expr._alias_native(native_expr, alias) def _evaluate_exprs( diff --git a/narwhals/_utils.py b/narwhals/_utils.py index 783d973d02..b2fb11d445 100644 --- a/narwhals/_utils.py +++ b/narwhals/_utils.py @@ -13,7 +13,7 @@ ) from datetime import timezone from enum import Enum, auto -from functools import cache, lru_cache, partial, wraps +from functools import cache, lru_cache, wraps from importlib.util import find_spec from inspect import getattr_static, getdoc from operator import attrgetter @@ -1021,9 +1021,6 @@ def maybe_reset_index(obj: FrameOrSeriesT) -> FrameOrSeriesT: return obj_any -zip_strict = partial(zip, strict=True) - - def _is_range_index(obj: Any, native_namespace: Any) -> TypeIs[pd.RangeIndex]: return isinstance(obj, native_namespace.RangeIndex) diff --git a/narwhals/dataframe.py b/narwhals/dataframe.py index 7f6452f9ea..95ae29d6a1 100644 --- a/narwhals/dataframe.py +++ b/narwhals/dataframe.py @@ -42,7 +42,6 @@ predicates_contains_list_of_bool, qualified_type_name, supports_arrow_c_stream, - zip_strict, ) from narwhals.dependencies import is_numpy_array_2d, is_pyarrow_table from narwhals.exceptions import ( @@ -1796,7 +1795,7 @@ def group_by( _keys = [ k if is_expr else col(k) - for k, is_expr in zip_strict(flat_keys, key_is_expr_or_series) + for k, is_expr in zip(flat_keys, key_is_expr_or_series, strict=True) ] expr_flat_keys = self._flatten_and_extract(*_keys) check_expressions_preserve_length( @@ -3021,7 +3020,8 @@ def group_by( raise NotImplementedError(msg) _keys = [ - k if is_expr else col(k) for k, is_expr in zip_strict(flat_keys, key_is_expr) + k if is_expr else col(k) + for k, is_expr in zip(flat_keys, key_is_expr, strict=True) ] expr_flat_keys = self._flatten_and_extract(*_keys) check_expressions_preserve_length( diff --git a/narwhals/schema.py b/narwhals/schema.py index b459be68fa..ddad3fa747 100644 --- a/narwhals/schema.py +++ b/narwhals/schema.py @@ -11,7 +11,7 @@ from functools import partial from typing import TYPE_CHECKING, cast -from narwhals._utils import Implementation, Version, qualified_type_name, zip_strict +from narwhals._utils import Implementation, Version, qualified_type_name from narwhals.dependencies import ( get_cudf, is_cudf_dtype, @@ -305,7 +305,9 @@ def to_pandas( raise ValueError(msg) return { name: to_native_dtype(dtype=dtype, dtype_backend=backend) - for name, dtype, backend in zip_strict(self.keys(), self.values(), backends) + for name, dtype, backend in zip( + self.keys(), self.values(), backends, strict=True + ) } def to_polars(self) -> pl.Schema: diff --git a/narwhals/testing/asserts/series.py b/narwhals/testing/asserts/series.py index 140ba1fe64..5bdcbb2ea7 100644 --- a/narwhals/testing/asserts/series.py +++ b/narwhals/testing/asserts/series.py @@ -3,7 +3,7 @@ from functools import partial from typing import TYPE_CHECKING, Any -from narwhals._utils import qualified_type_name, zip_strict +from narwhals._utils import qualified_type_name from narwhals.dependencies import is_narwhals_series from narwhals.dtypes import Array, Boolean, Categorical, List, String, Struct from narwhals.functions import new_series @@ -243,7 +243,7 @@ def _check_list_like( # `check_order` value at the top level. impl = left_vals.implementation try: - for left_val, right_val in zip_strict(left_vals, right_vals): + for left_val, right_val in zip(left_vals, right_vals, strict=True): check_fn( new_series("", values=left_val, dtype=left_dtype.inner, backend=impl), new_series("", values=right_val, dtype=right_dtype.inner, backend=impl), @@ -265,7 +265,9 @@ def _check_struct( # * dtype differs, regardless of `check_dtypes=False` # * order applies only at top level try: - for left_field, right_field in zip_strict(left_dtype.fields, right_dtype.fields): + for left_field, right_field in zip( + left_dtype.fields, right_dtype.fields, strict=True + ): check_fn( left_vals.struct.field(left_field.name), right_vals.struct.field(right_field.name), diff --git a/tests/expr_and_series/division_by_zero_test.py b/tests/expr_and_series/division_by_zero_test.py index b2d2b8a577..a23e9ed22b 100644 --- a/tests/expr_and_series/division_by_zero_test.py +++ b/tests/expr_and_series/division_by_zero_test.py @@ -5,7 +5,6 @@ import pytest import narwhals as nw -from narwhals._utils import zip_strict from tests.utils import POLARS_VERSION, Constructor, ConstructorEager, assert_equal_data if TYPE_CHECKING: @@ -80,7 +79,11 @@ def test_expr_floordiv_by_zero( @pytest.mark.parametrize( ("numerator", "expected"), list( - zip_strict([*data["int"], *data["float"]], [*expected_truediv, *expected_truediv]) + zip( + [*data["int"], *data["float"]], + [*expected_truediv, *expected_truediv], + strict=True, + ) ), ) def test_series_rtruediv_by_zero( @@ -94,7 +97,11 @@ def test_series_rtruediv_by_zero( @pytest.mark.parametrize( ("numerator", "expected"), list( - zip_strict([*data["int"], *data["float"]], [*expected_truediv, *expected_truediv]) + zip( + [*data["int"], *data["float"]], + [*expected_truediv, *expected_truediv], + strict=True, + ) ), ) def test_expr_rtruediv_by_zero( diff --git a/tests/utils.py b/tests/utils.py index 339adc27ce..651d946e6f 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, Any, cast import narwhals as nw -from narwhals._utils import Implementation, parse_version, zip_strict +from narwhals._utils import Implementation, parse_version from narwhals.dependencies import get_pandas from narwhals.translate import from_native @@ -112,7 +112,7 @@ def assert_equal_data(result: Any, expected: Mapping[str, Any]) -> None: if hasattr(result, "columns"): for idx, (col, key) in enumerate( - zip_strict(result.columns, list(expected.keys())) + zip(result.columns, list(expected.keys()), strict=True) ): assert col == key, f"Expected column name {key} at index {idx}, found {col}" result = {key: _to_comparable_list(result[key]) for key in expected} @@ -122,7 +122,7 @@ def assert_equal_data(result: Any, expected: Mapping[str, Any]) -> None: for key, expected_value in expected.items(): result_value = result[key] - for i, (lhs, rhs) in enumerate(zip_strict(result_value, expected_value)): + for i, (lhs, rhs) in enumerate(zip(result_value, expected_value, strict=True)): if isinstance(lhs, float) and not math.isnan(lhs): are_equivalent_values = rhs is not None and math.isclose( lhs, rhs, rel_tol=0, abs_tol=1e-6 From 31b5dd7145e8ff02f28083e9129f8c1655b8f792 Mon Sep 17 00:00:00 2001 From: FBruzzesi Date: Mon, 17 Nov 2025 10:45:30 +0100 Subject: [PATCH 05/17] zip(..., strict=False) --- narwhals/_arrow/group_by.py | 2 +- narwhals/_arrow/series.py | 2 +- narwhals/_dask/dataframe.py | 4 ++-- narwhals/_dask/expr.py | 6 ++++-- narwhals/_dask/group_by.py | 4 ++-- narwhals/_dask/utils.py | 2 +- narwhals/_duckdb/group_by.py | 2 +- narwhals/_duckdb/utils.py | 2 +- narwhals/_ibis/group_by.py | 2 +- narwhals/_ibis/utils.py | 2 +- narwhals/_pandas_like/dataframe.py | 10 +++++----- narwhals/_pandas_like/expr.py | 2 +- narwhals/_pandas_like/group_by.py | 2 +- narwhals/_pandas_like/series.py | 2 +- narwhals/_spark_like/dataframe.py | 2 +- narwhals/_spark_like/expr.py | 2 +- narwhals/_spark_like/group_by.py | 2 +- narwhals/_spark_like/utils.py | 2 +- narwhals/_utils.py | 2 +- tests/conftest.py | 10 +++++++--- tests/expr_and_series/cast_test.py | 4 ++-- tests/utils.py | 3 ++- 22 files changed, 39 insertions(+), 32 deletions(-) diff --git a/narwhals/_arrow/group_by.py b/narwhals/_arrow/group_by.py index 22785f0ebe..d55bffbcf2 100644 --- a/narwhals/_arrow/group_by.py +++ b/narwhals/_arrow/group_by.py @@ -173,7 +173,7 @@ def agg(self, *exprs: ArrowExpr) -> ArrowDataFrame: new_column_names = [new_column_names[i] for i in index_map] result_simple = result_simple.rename_columns(new_column_names) return self.compliant._with_native(result_simple).rename( - dict(zip(self._keys, self._output_key_names)) + dict(zip(self._keys, self._output_key_names, strict=False)) ) def __iter__(self) -> Iterator[tuple[Any, ArrowDataFrame]]: diff --git a/narwhals/_arrow/series.py b/narwhals/_arrow/series.py index 751e45d955..234ce6826c 100644 --- a/narwhals/_arrow/series.py +++ b/narwhals/_arrow/series.py @@ -203,7 +203,7 @@ def from_numpy(cls, data: Into1DArray, /, *, context: _LimitedContext) -> Self: def _align_full_broadcast(cls, *series: Self) -> Sequence[Self]: lengths = [len(s) for s in series] target_length = max( - length for length, s in zip(lengths, series) if not s._broadcast + length for length, s in zip(lengths, series, strict=False) if not s._broadcast ) fast_path = all(_len == target_length for _len in lengths) if fast_path: diff --git a/narwhals/_dask/dataframe.py b/narwhals/_dask/dataframe.py index 914bdc07a4..36596e8428 100644 --- a/narwhals/_dask/dataframe.py +++ b/narwhals/_dask/dataframe.py @@ -364,7 +364,7 @@ def _join_semi( other_native = self._join_filter_rename( other=other, columns_to_select=list(right_on), - columns_mapping=dict(zip(right_on, left_on)), + columns_mapping=dict(zip(right_on, left_on, strict=True)), ) return self.native.merge( other_native, how="inner", left_on=left_on, right_on=left_on @@ -379,7 +379,7 @@ def _join_anti( other_native = self._join_filter_rename( other=other, columns_to_select=list(right_on), - columns_mapping=dict(zip(right_on, left_on)), + columns_mapping=dict(zip(right_on, left_on, strict=True)), ) df = self.native.merge( other_native, diff --git a/narwhals/_dask/expr.py b/narwhals/_dask/expr.py index 66970c01c5..4775ac0ee3 100644 --- a/narwhals/_dask/expr.py +++ b/narwhals/_dask/expr.py @@ -573,7 +573,9 @@ def func(df: DaskLazyFrame) -> Sequence[dx.Series]: dask_function_name, **dask_kwargs ) result_frame = df._with_native( - res_native.rename(columns=dict(zip(output_names, aliases))) + res_native.rename( + columns=dict(zip(output_names, aliases, strict=False)) + ) ).native return [result_frame[name] for name in aliases] @@ -634,7 +636,7 @@ def replace_strict( msg = "`replace_strict` requires an explicit value for `default` for dask backend." raise ValueError(msg) - mapping = dict(zip(old, new)) + mapping = dict(zip(old, new, strict=True)) old_ = list(old) def func(df: DaskLazyFrame) -> list[dx.Series]: diff --git a/narwhals/_dask/group_by.py b/narwhals/_dask/group_by.py index 8b6884a219..05738aa4a0 100644 --- a/narwhals/_dask/group_by.py +++ b/narwhals/_dask/group_by.py @@ -112,7 +112,7 @@ def agg(self, *exprs: DaskExpr) -> DaskLazyFrame: return ( self.compliant.simple_select(*self._keys) .unique(self._keys, keep="any", order_by=None) - .rename(dict(zip(self._keys, self._output_key_names))) + .rename(dict(zip(self._keys, self._output_key_names, strict=False))) ) self._ensure_all_simple(exprs) @@ -144,4 +144,4 @@ def agg(self, *exprs: DaskExpr) -> DaskLazyFrame: return DaskLazyFrame( self._grouped.agg(**simple_aggregations).reset_index(), version=self.compliant._version, - ).rename(dict(zip(self._keys, self._output_key_names))) + ).rename(dict(zip(self._keys, self._output_key_names, strict=False))) diff --git a/narwhals/_dask/utils.py b/narwhals/_dask/utils.py index 0f1eee32a6..6007b77aa4 100644 --- a/narwhals/_dask/utils.py +++ b/narwhals/_dask/utils.py @@ -31,7 +31,7 @@ def evaluate_exprs(df: DaskLazyFrame, /, *exprs: DaskExpr) -> list[tuple[str, dx if len(aliases) != len(native_series_list): # pragma: no cover msg = f"Internal error: got aliases {aliases}, but only got {len(native_series_list)} results" raise AssertionError(msg) - native_results.extend(zip(aliases, native_series_list)) + native_results.extend(zip(aliases, native_series_list, strict=False)) return native_results diff --git a/narwhals/_duckdb/group_by.py b/narwhals/_duckdb/group_by.py index 09960bb172..7e7ac5d0b3 100644 --- a/narwhals/_duckdb/group_by.py +++ b/narwhals/_duckdb/group_by.py @@ -35,5 +35,5 @@ def agg(self, *exprs: DuckDBExpr) -> DuckDBLazyFrame: ) return self.compliant._with_native(result).rename( - dict(zip(self._keys, self._output_key_names)) + dict(zip(self._keys, self._output_key_names, strict=False)) ) diff --git a/narwhals/_duckdb/utils.py b/narwhals/_duckdb/utils.py index ccd2361231..8ad79081f7 100644 --- a/narwhals/_duckdb/utils.py +++ b/narwhals/_duckdb/utils.py @@ -99,7 +99,7 @@ def evaluate_exprs_and_aliases( if len(output_names) != len(native_series_list): # pragma: no cover msg = f"Internal error: got output names {output_names}, but only got {len(native_series_list)} results" raise AssertionError(msg) - native_results.extend(zip(output_names, native_series_list)) + native_results.extend(zip(output_names, native_series_list, strict=False)) return native_results diff --git a/narwhals/_ibis/group_by.py b/narwhals/_ibis/group_by.py index 44b1d0cb40..9b4d25a68e 100644 --- a/narwhals/_ibis/group_by.py +++ b/narwhals/_ibis/group_by.py @@ -29,4 +29,4 @@ def agg(self, *exprs: IbisExpr) -> IbisLazyFrame: native = self.compliant.native return self.compliant._with_native( native.group_by(self._keys).aggregate(*self._evaluate_exprs(exprs)) - ).rename(dict(zip(self._keys, self._output_key_names))) + ).rename(dict(zip(self._keys, self._output_key_names, strict=False))) diff --git a/narwhals/_ibis/utils.py b/narwhals/_ibis/utils.py index c4477060eb..b9eaaf9fac 100644 --- a/narwhals/_ibis/utils.py +++ b/narwhals/_ibis/utils.py @@ -118,7 +118,7 @@ def evaluate_exprs(df: IbisLazyFrame, /, *exprs: IbisExpr) -> list[tuple[str, ir if len(output_names) != len(native_series_list): # pragma: no cover msg = f"Internal error: got output names {output_names}, but only got {len(native_series_list)} results" raise AssertionError(msg) - native_results.extend(zip(output_names, native_series_list)) + native_results.extend(zip(output_names, native_series_list, strict=False)) return native_results diff --git a/narwhals/_pandas_like/dataframe.py b/narwhals/_pandas_like/dataframe.py index d53b31b9ef..0dc0578d61 100644 --- a/narwhals/_pandas_like/dataframe.py +++ b/narwhals/_pandas_like/dataframe.py @@ -184,7 +184,7 @@ def from_dict( implementation=context._implementation, version=context._version, ) - for ((key, dtype), backend) in zip(schema.items(), backends) + for ((key, dtype), backend) in zip(schema.items(), backends, strict=False) if dtype is not None } native = native.astype(native_schema) @@ -219,7 +219,7 @@ def from_dicts( implementation=context._implementation, version=context._version, ) - for ((key, dtype), backend) in zip(schema.items(), backends) + for ((key, dtype), backend) in zip(schema.items(), backends, strict=False) if dtype is not None } native = native.astype(native_schema) @@ -413,7 +413,7 @@ def iter_rows( else: col_names = self.native.columns for row in self.native.itertuples(index=False): - yield dict(zip(col_names, row)) + yield dict(zip(col_names, row, strict=False)) @property def schema(self) -> dict[str, DType]: @@ -675,7 +675,7 @@ def _join_semi( other_native = self._join_filter_rename( other=other, columns_to_select=list(right_on), - columns_mapping=dict(zip(right_on, left_on)), + columns_mapping=dict(zip(right_on, left_on, strict=True)), ) return self.native.merge( other_native, how="inner", left_on=left_on, right_on=left_on @@ -698,7 +698,7 @@ def _join_anti( other_native = self._join_filter_rename( other=other, columns_to_select=list(right_on), - columns_mapping=dict(zip(right_on, left_on)), + columns_mapping=dict(zip(right_on, left_on, strict=True)), ) result_native = self.native.merge( other_native, diff --git a/narwhals/_pandas_like/expr.py b/narwhals/_pandas_like/expr.py index 91bf159493..de0e14be7a 100644 --- a/narwhals/_pandas_like/expr.py +++ b/narwhals/_pandas_like/expr.py @@ -349,7 +349,7 @@ def func(df: PandasLikeDataFrame) -> Sequence[PandasLikeSeries]: # noqa: C901, pandas_function_name, **pandas_kwargs ) result_frame = df._with_native(res_native).rename( - dict(zip(output_names, aliases)) + dict(zip(output_names, aliases, strict=False)) ) results = [result_frame.get_column(name) for name in aliases] if order_by: diff --git a/narwhals/_pandas_like/group_by.py b/narwhals/_pandas_like/group_by.py index e5f18bb926..4b2ce4759f 100644 --- a/narwhals/_pandas_like/group_by.py +++ b/narwhals/_pandas_like/group_by.py @@ -299,7 +299,7 @@ def _select_results( return ( self.compliant._with_native(df, validate_column_names=False) .simple_select(*self._keys, *new_names) - .rename(dict(zip(self._keys, self._output_key_names))) + .rename(dict(zip(self._keys, self._output_key_names, strict=False))) ) def _getitem_aggs( diff --git a/narwhals/_pandas_like/series.py b/narwhals/_pandas_like/series.py index 55b3aca1c0..b92bd9700f 100644 --- a/narwhals/_pandas_like/series.py +++ b/narwhals/_pandas_like/series.py @@ -205,7 +205,7 @@ def _align_full_broadcast(cls, *series: Self) -> Sequence[Self]: Series = series[0].__native_namespace__().Series lengths = [len(s) for s in series] target_length = max( - length for length, s in zip(lengths, series) if not s._broadcast + length for length, s in zip(lengths, series, strict=False) if not s._broadcast ) idx = series[lengths.index(target_length)].native.index reindexed = [] diff --git a/narwhals/_spark_like/dataframe.py b/narwhals/_spark_like/dataframe.py index 79fb8c1a64..a13907688f 100644 --- a/narwhals/_spark_like/dataframe.py +++ b/narwhals/_spark_like/dataframe.py @@ -418,7 +418,7 @@ def join( ) rename_mapping = { - **dict(zip(right_on_, left_on_)), + **dict(zip(right_on_, left_on_, strict=True)), **{ colname: f"{colname}{suffix}" if colname in left_columns else colname for colname in right_cols_to_rename diff --git a/narwhals/_spark_like/expr.py b/narwhals/_spark_like/expr.py index b6945e6906..3ca1f0a753 100644 --- a/narwhals/_spark_like/expr.py +++ b/narwhals/_spark_like/expr.py @@ -378,7 +378,7 @@ def replace_strict( # pragma: no cover F = self._F - mapping = dict(zip(old, new)) + mapping = dict(zip(old, new, strict=True)) mapping_expr = F.create_map([F.lit(x) for x in chain(*mapping.items())]) def func(df: SparkLikeLazyFrame) -> list[Column]: diff --git a/narwhals/_spark_like/group_by.py b/narwhals/_spark_like/group_by.py index 154ee6e1eb..9a6fb19c75 100644 --- a/narwhals/_spark_like/group_by.py +++ b/narwhals/_spark_like/group_by.py @@ -33,5 +33,5 @@ def agg(self, *exprs: SparkLikeExpr) -> SparkLikeLazyFrame: ) return self.compliant._with_native(result).rename( - dict(zip(self._keys, self._output_key_names)) + dict(zip(self._keys, self._output_key_names, strict=False)) ) diff --git a/narwhals/_spark_like/utils.py b/narwhals/_spark_like/utils.py index 7765d20a0a..76215f4bda 100644 --- a/narwhals/_spark_like/utils.py +++ b/narwhals/_spark_like/utils.py @@ -215,7 +215,7 @@ def evaluate_exprs( if len(output_names) != len(native_series_list): # pragma: no cover msg = f"Internal error: got output names {output_names}, but only got {len(native_series_list)} results" raise AssertionError(msg) - native_results.extend(zip(output_names, native_series_list)) + native_results.extend(zip(output_names, native_series_list, strict=False)) return native_results diff --git a/narwhals/_utils.py b/narwhals/_utils.py index b2fb11d445..90fd25034e 100644 --- a/narwhals/_utils.py +++ b/narwhals/_utils.py @@ -1600,7 +1600,7 @@ def _remap_full_join_keys( right_keys_suffixed = ( f"{key}{suffix}" if key in left_on else key for key in right_on ) - return dict(zip(right_on, right_keys_suffixed)) + return dict(zip(right_on, right_keys_suffixed, strict=False)) def _into_arrow_table(data: IntoArrowTable, context: _LimitedContext, /) -> pa.Table: diff --git a/tests/conftest.py b/tests/conftest.py index 0b13c173b5..35a49d5dd8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -199,7 +199,9 @@ def _constructor(obj: Data) -> NativePySpark: index_col_name = generate_temporary_column_name(n_bytes=8, columns=list(_obj)) _obj[index_col_name] = list(range(len(_obj[next(iter(_obj))]))) result = ( - session.createDataFrame([*zip(*_obj.values())], schema=[*_obj.keys()]) + session.createDataFrame( + [*zip(*_obj.values(), strict=False)], schema=[*_obj.keys()] + ) .repartition(2) .orderBy(index_col_name) .drop(index_col_name) @@ -213,7 +215,9 @@ def sqlframe_pyspark_lazy_constructor(obj: Data) -> NativeSQLFrame: # pragma: n pytest.importorskip("sqlframe") pytest.importorskip("duckdb") session = sqlframe_session() - return session.createDataFrame([*zip(*obj.values())], schema=[*obj.keys()]) + return session.createDataFrame( + [*zip(*obj.values(), strict=False)], schema=[*obj.keys()] + ) @lru_cache(maxsize=1) @@ -311,7 +315,7 @@ def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: elif "constructor_pandas_like" in metafunc.fixturenames: pandas_like_constructors = [] pandas_like_constructors_ids = [] - for fn, name in zip(eager_constructors, eager_constructors_ids): + for fn, name in zip(eager_constructors, eager_constructors_ids, strict=False): if name in ID_PANDAS_LIKE: pandas_like_constructors.append(fn) pandas_like_constructors_ids.append(name) diff --git a/tests/expr_and_series/cast_test.py b/tests/expr_and_series/cast_test.py index cfadaff347..c0a4340aea 100644 --- a/tests/expr_and_series/cast_test.py +++ b/tests/expr_and_series/cast_test.py @@ -112,7 +112,7 @@ def test_cast(constructor: Constructor) -> None: *[nw.col(col_).cast(dtype) for col_, dtype in cast_map.items()] ).collect_schema() - for (key, ltype), rtype in zip(result.items(), cast_map.values()): + for (key, ltype), rtype in zip(result.items(), cast_map.values(), strict=False): if "modin_constructor" in str(constructor) and key in MODIN_XFAIL_COLUMNS: # TODO(unassigned): in modin we end up with `' None: are_equivalent_values = rhs is None elif isinstance(lhs, list) and isinstance(rhs, list): are_equivalent_values = all( - left_side == right_side for left_side, right_side in zip(lhs, rhs) + left_side == right_side + for left_side, right_side in zip(lhs, rhs, strict=False) ) elif is_pd_na(lhs): are_equivalent_values = is_pd_na(rhs) From 3768e2cc2e021e8e41a39c9cdefbc3fb8656c89b Mon Sep 17 00:00:00 2001 From: FBruzzesi Date: Mon, 17 Nov 2025 11:36:17 +0100 Subject: [PATCH 06/17] rm Union[...] --- narwhals/_native.py | 2 +- narwhals/stable/v1/typing.py | 10 +++++----- narwhals/stable/v2/typing.py | 6 +++--- narwhals/typing.py | 6 +++--- tests/namespace_test.py | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/narwhals/_native.py b/narwhals/_native.py index 63df32467c..83960cabe2 100644 --- a/narwhals/_native.py +++ b/narwhals/_native.py @@ -38,7 +38,7 @@ def something_common(self, *args: Any, **kwargs: Any) -> Any: ... **But**, occasionally, there'll be an edge-case which we can spell like: - IntoThing: TypeAlias = Union[, NativeThing] + IntoThing: TypeAlias = " | NativeThing" Tip: Reach for these when there **isn't a need to preserve** the original native type. diff --git a/narwhals/stable/v1/typing.py b/narwhals/stable/v1/typing.py index 6184bb34c8..5e56198e58 100644 --- a/narwhals/stable/v1/typing.py +++ b/narwhals/stable/v1/typing.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Protocol, TypeVar, Union +from typing import TYPE_CHECKING, Any, Protocol, TypeVar from narwhals._native import IntoSeries, IntoSeriesT @@ -14,7 +14,7 @@ class DataFrameLike(Protocol): def __dataframe__(self, *args: Any, **kwargs: Any) -> Any: ... -IntoExpr: TypeAlias = Union["Expr", str, "Series[Any]"] +IntoExpr: TypeAlias = "Expr | str | Series[Any]" """Anything which can be converted to an expression. Use this to mean "either a Narwhals expression, or something @@ -24,7 +24,7 @@ def __dataframe__(self, *args: Any, **kwargs: Any) -> Any: ... `nw.Expr`, e.g. `df.select('a')`. """ -IntoDataFrame: TypeAlias = Union["NativeDataFrame", "DataFrameLike"] +IntoDataFrame: TypeAlias = "NativeDataFrame | DataFrameLike" """Anything which can be converted to a Narwhals DataFrame. Use this if your function accepts a narwhalifiable object but doesn't care about its backend. @@ -38,7 +38,7 @@ def __dataframe__(self, *args: Any, **kwargs: Any) -> Any: ... """ IntoLazyFrame: TypeAlias = "NativeLazyFrame" -IntoFrame: TypeAlias = Union["IntoDataFrame", "IntoLazyFrame"] +IntoFrame: TypeAlias = "IntoDataFrame | IntoLazyFrame" """Anything which can be converted to a Narwhals DataFrame or LazyFrame. Use this if your function can accept an object which can be converted to either @@ -52,7 +52,7 @@ def __dataframe__(self, *args: Any, **kwargs: Any) -> Any: ... ... return df.collect_schema().names() """ -Frame: TypeAlias = Union["DataFrame[Any]", "LazyFrame[Any]"] +Frame: TypeAlias = "DataFrame[Any] | LazyFrame[Any]" """Narwhals DataFrame or Narwhals LazyFrame. Use this if your function can work with either and your function doesn't care diff --git a/narwhals/stable/v2/typing.py b/narwhals/stable/v2/typing.py index df8435f089..148ced6551 100644 --- a/narwhals/stable/v2/typing.py +++ b/narwhals/stable/v2/typing.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, TypeVar, Union +from typing import TYPE_CHECKING, Any, TypeVar from narwhals._native import ( IntoDataFrame, @@ -19,7 +19,7 @@ from narwhals.stable.v2 import DataFrame, Expr, LazyFrame, Series -IntoExpr: TypeAlias = Union["Expr", str, "Series[Any]"] +IntoExpr: TypeAlias = "Expr | str | Series[Any]" """Anything which can be converted to an expression. Use this to mean "either a Narwhals expression, or something @@ -29,7 +29,7 @@ `nw.Expr`, e.g. `df.select('a')`. """ -Frame: TypeAlias = Union["DataFrame[Any]", "LazyFrame[Any]"] +Frame: TypeAlias = "DataFrame[Any] | LazyFrame[Any]" """Narwhals DataFrame or Narwhals LazyFrame. Use this if your function can work with either and your function doesn't care diff --git a/narwhals/typing.py b/narwhals/typing.py index 3dbffafeb3..834f272788 100644 --- a/narwhals/typing.py +++ b/narwhals/typing.py @@ -1,7 +1,7 @@ from __future__ import annotations from collections.abc import Mapping -from typing import TYPE_CHECKING, Any, Literal, Protocol, TypeVar, Union +from typing import TYPE_CHECKING, Any, Literal, Protocol, TypeVar from narwhals._compliant import CompliantDataFrame, CompliantLazyFrame, CompliantSeries from narwhals._native import ( @@ -108,7 +108,7 @@ def Binary(self) -> type[dtypes.Binary]: ... Into1DArray: TypeAlias = "_1DArray | _NumpyScalar" """A 1-dimensional `numpy.ndarray` or scalar that can be converted into one.""" -IntoExpr: TypeAlias = Union["Expr", str, "Series[Any]", _1DArray] +IntoExpr: TypeAlias = "Expr | str | Series[Any] | _1DArray" """Anything which can be converted to an expression. Use this to mean "either a Narwhals expression, or something which can be converted @@ -117,7 +117,7 @@ def Binary(self) -> type[dtypes.Binary]: ... which will be interpreted as a `nw.Expr`, e.g. `df.select('a')`. """ -Frame: TypeAlias = Union["DataFrame[Any]", "LazyFrame[Any]"] +Frame: TypeAlias = "DataFrame[Any] | LazyFrame[Any]" """Narwhals DataFrame or Narwhals LazyFrame. Use this if your function can work with either and your function doesn't care diff --git a/tests/namespace_test.py b/tests/namespace_test.py index 9d900b39b9..e0a6c09a23 100644 --- a/tests/namespace_test.py +++ b/tests/namespace_test.py @@ -14,7 +14,7 @@ if TYPE_CHECKING: from typing import TypeAlias - from typing_extensions import assert_type + from typing_extensions import Never, assert_type # noqa: F401 from narwhals._arrow.namespace import ArrowNamespace # noqa: F401 from narwhals._compliant import CompliantNamespace From 0bd10c7f051fd0cf449a3a3c3ab972043eeaf9b3 Mon Sep 17 00:00:00 2001 From: FBruzzesi Date: Mon, 17 Nov 2025 12:01:20 +0100 Subject: [PATCH 07/17] bump min pandas & modin versions --- narwhals/_pandas_like/utils.py | 34 ++++++----------------- narwhals/_utils.py | 4 +-- tests/expr_and_series/over_test.py | 2 -- tests/expr_and_series/rolling_std_test.py | 1 - tests/expr_and_series/rolling_var_test.py | 1 - tests/frame/with_row_index_test.py | 8 ------ 6 files changed, 11 insertions(+), 39 deletions(-) diff --git a/narwhals/_pandas_like/utils.py b/narwhals/_pandas_like/utils.py index 281a3c19ad..37f9113fcc 100644 --- a/narwhals/_pandas_like/utils.py +++ b/narwhals/_pandas_like/utils.py @@ -376,32 +376,16 @@ def native_to_narwhals_dtype( raise AssertionError(msg) -if Implementation.PANDAS._backend_version() >= (1, 2): - - def is_dtype_numpy_nullable(dtype: Any) -> TypeIs[BaseMaskedDtype]: - """Return `True` if `dtype` is `"numpy_nullable"`.""" - # NOTE: We need a sentinel as the positive case is `BaseMaskedDtype.base = None` - # See https://github.com/narwhals-dev/narwhals/pull/2740#discussion_r2171667055 - sentinel = object() - return ( - isinstance(dtype, pd.api.extensions.ExtensionDtype) - and getattr(dtype, "base", sentinel) is None - ) -else: # pragma: no cover - - def is_dtype_numpy_nullable(dtype: Any) -> TypeIs[BaseMaskedDtype]: - # NOTE: `base` attribute was added between 1.1-1.2 - # Checking by isinstance requires using an import path that is no longer valid - # `1.1`: https://github.com/pandas-dev/pandas/blob/b5958ee1999e9aead1938c0bba2b674378807b3d/pandas/core/arrays/masked.py#L37 - # `1.2`: https://github.com/pandas-dev/pandas/blob/7c48ff4409c622c582c56a5702373f726de08e96/pandas/core/arrays/masked.py#L41 - # `1.5`: https://github.com/pandas-dev/pandas/blob/35b0d1dcadf9d60722c055ee37442dc76a29e64c/pandas/core/dtypes/dtypes.py#L1609 - if isinstance(dtype, pd.api.extensions.ExtensionDtype): - from pandas.core.arrays.masked import ( # type: ignore[attr-defined] - BaseMaskedDtype as OldBaseMaskedDtype, # pyright: ignore[reportAttributeAccessIssue] - ) +def is_dtype_numpy_nullable(dtype: Any) -> TypeIs[BaseMaskedDtype]: + """Return `True` if `dtype` is `"numpy_nullable"`.""" + # NOTE: We need a sentinel as the positive case is `BaseMaskedDtype.base = None` + # See https://github.com/narwhals-dev/narwhals/pull/2740#discussion_r2171667055 + sentinel = object() + return ( + isinstance(dtype, pd.api.extensions.ExtensionDtype) + and getattr(dtype, "base", sentinel) is None + ) - return isinstance(dtype, OldBaseMaskedDtype) - return False def get_dtype_backend(dtype: Any, implementation: Implementation) -> DTypeBackend: diff --git a/narwhals/_utils.py b/narwhals/_utils.py index 90fd25034e..e63a20f666 100644 --- a/narwhals/_utils.py +++ b/narwhals/_utils.py @@ -588,8 +588,8 @@ def _backend_version(self) -> tuple[int, ...]: MIN_VERSIONS: Mapping[Implementation, tuple[int, ...]] = { - Implementation.PANDAS: (1, 1, 3), - Implementation.MODIN: (0, 8, 2), + Implementation.PANDAS: (1, 3, 4), + Implementation.MODIN: (0, 22, 0), Implementation.CUDF: (24, 10), Implementation.PYARROW: (13,), Implementation.PYSPARK: (3, 5), diff --git a/tests/expr_and_series/over_test.py b/tests/expr_and_series/over_test.py index 6c9c6e4a09..839f091dd2 100644 --- a/tests/expr_and_series/over_test.py +++ b/tests/expr_and_series/over_test.py @@ -494,8 +494,6 @@ def test_over_ewm_mean( if any(x in str(constructor_eager) for x in ("pyarrow_table", "modin", "cudf")): # not implemented request.applymarker(pytest.mark.xfail) - if "pandas" in str(constructor_eager) and PANDAS_VERSION < (1, 2): - request.applymarker(pytest.mark.xfail(reason="too old, not implemented")) data = {"a": [0.0, 1.0, 3.0, 5.0, 7.0, 7.5], "b": [1, 1, 1, 2, 2, 2]} diff --git a/tests/expr_and_series/rolling_std_test.py b/tests/expr_and_series/rolling_std_test.py index f027b450d9..79968e549f 100644 --- a/tests/expr_and_series/rolling_std_test.py +++ b/tests/expr_and_series/rolling_std_test.py @@ -312,7 +312,6 @@ def test_rolling_std_expr_lazy_grouped( if ( ("polars" in str(constructor) and POLARS_VERSION < (1, 10)) or ("duckdb" in str(constructor) and DUCKDB_VERSION < (1, 3)) - or ("pandas" in str(constructor) and PANDAS_VERSION < (1, 2)) ): pytest.skip() if any(x in str(constructor) for x in ("dask", "pyarrow_table")): diff --git a/tests/expr_and_series/rolling_var_test.py b/tests/expr_and_series/rolling_var_test.py index b38ba5f077..31c3cd12d7 100644 --- a/tests/expr_and_series/rolling_var_test.py +++ b/tests/expr_and_series/rolling_var_test.py @@ -267,7 +267,6 @@ def test_rolling_var_expr_lazy_grouped( if ( ("polars" in str(constructor) and POLARS_VERSION < (1, 10)) or ("duckdb" in str(constructor) and DUCKDB_VERSION < (1, 3)) - or ("pandas" in str(constructor) and PANDAS_VERSION < (1, 2)) ): pytest.skip() if any(x in str(constructor) for x in ("dask", "pyarrow_table")): diff --git a/tests/frame/with_row_index_test.py b/tests/frame/with_row_index_test.py index 10310ca7f6..a208dffce6 100644 --- a/tests/frame/with_row_index_test.py +++ b/tests/frame/with_row_index_test.py @@ -38,11 +38,6 @@ def test_with_row_index_eager(constructor_eager: ConstructorEager) -> None: def test_with_row_index_lazy( constructor: Constructor, order_by: str | Sequence[str], expected_index: list[int] ) -> None: - if ( - "pandas" in str(constructor) and PANDAS_VERSION < (1, 3) and order_by == "abc" - ): # pragma: no cover - reason = "ValueError: first not supported for non-numeric data." - pytest.skip(reason=reason) if "duckdb" in str(constructor) and DUCKDB_VERSION < (1, 3): pytest.skip() if "polars" in str(constructor) and POLARS_VERSION < (1, 10): @@ -83,9 +78,6 @@ def test_with_row_index_lazy_meaner_examples( # https://github.com/narwhals-dev/narwhals/issues/3289 if "polars" in str(constructor) and POLARS_VERSION < (1, 10): pytest.skip() - if "pandas" in str(constructor) and PANDAS_VERSION < (1, 3): # pragma: no cover - reason = "ValueError: first not supported for non-numeric data." - pytest.skip(reason=reason) if "duckdb" in str(constructor) and DUCKDB_VERSION < (1, 3): pytest.skip() data = {"a": ["A", "B", "A"], "b": [1, 2, 3], "c": [9, 2, 4]} From ebb1ace7a1e29022bde34ccced520e6427b9ec9a Mon Sep 17 00:00:00 2001 From: FBruzzesi Date: Mon, 17 Nov 2025 12:01:59 +0100 Subject: [PATCH 08/17] ruff --- narwhals/_pandas_like/utils.py | 1 - tests/expr_and_series/rolling_std_test.py | 6 ++---- tests/expr_and_series/rolling_var_test.py | 6 ++---- tests/frame/with_row_index_test.py | 1 - 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/narwhals/_pandas_like/utils.py b/narwhals/_pandas_like/utils.py index 37f9113fcc..3d264ca116 100644 --- a/narwhals/_pandas_like/utils.py +++ b/narwhals/_pandas_like/utils.py @@ -387,7 +387,6 @@ def is_dtype_numpy_nullable(dtype: Any) -> TypeIs[BaseMaskedDtype]: ) - def get_dtype_backend(dtype: Any, implementation: Implementation) -> DTypeBackend: """Get dtype backend for pandas type. diff --git a/tests/expr_and_series/rolling_std_test.py b/tests/expr_and_series/rolling_std_test.py index 79968e549f..b27617623f 100644 --- a/tests/expr_and_series/rolling_std_test.py +++ b/tests/expr_and_series/rolling_std_test.py @@ -8,7 +8,6 @@ import narwhals as nw from tests.utils import ( DUCKDB_VERSION, - PANDAS_VERSION, POLARS_VERSION, Constructor, ConstructorEager, @@ -309,9 +308,8 @@ def test_rolling_std_expr_lazy_grouped( center: bool, ddof: int, ) -> None: - if ( - ("polars" in str(constructor) and POLARS_VERSION < (1, 10)) - or ("duckdb" in str(constructor) and DUCKDB_VERSION < (1, 3)) + if ("polars" in str(constructor) and POLARS_VERSION < (1, 10)) or ( + "duckdb" in str(constructor) and DUCKDB_VERSION < (1, 3) ): pytest.skip() if any(x in str(constructor) for x in ("dask", "pyarrow_table")): diff --git a/tests/expr_and_series/rolling_var_test.py b/tests/expr_and_series/rolling_var_test.py index 31c3cd12d7..f80c8764ac 100644 --- a/tests/expr_and_series/rolling_var_test.py +++ b/tests/expr_and_series/rolling_var_test.py @@ -10,7 +10,6 @@ import narwhals as nw from tests.utils import ( DUCKDB_VERSION, - PANDAS_VERSION, POLARS_VERSION, Constructor, ConstructorEager, @@ -264,9 +263,8 @@ def test_rolling_var_expr_lazy_grouped( center: bool, ddof: int, ) -> None: - if ( - ("polars" in str(constructor) and POLARS_VERSION < (1, 10)) - or ("duckdb" in str(constructor) and DUCKDB_VERSION < (1, 3)) + if ("polars" in str(constructor) and POLARS_VERSION < (1, 10)) or ( + "duckdb" in str(constructor) and DUCKDB_VERSION < (1, 3) ): pytest.skip() if any(x in str(constructor) for x in ("dask", "pyarrow_table")): diff --git a/tests/frame/with_row_index_test.py b/tests/frame/with_row_index_test.py index a208dffce6..cfc47b23f7 100644 --- a/tests/frame/with_row_index_test.py +++ b/tests/frame/with_row_index_test.py @@ -7,7 +7,6 @@ import narwhals as nw from tests.utils import ( DUCKDB_VERSION, - PANDAS_VERSION, POLARS_VERSION, Constructor, ConstructorEager, From 575731eddb43a0a0f995d08499349167ef99077c Mon Sep 17 00:00:00 2001 From: FBruzzesi Date: Mon, 17 Nov 2025 12:05:38 +0100 Subject: [PATCH 09/17] github actions --- .github/workflows/downstream_tests.yml | 2 +- .github/workflows/extremes.yml | 42 ++++++++++++++++++++------ .github/workflows/pytest.yml | 6 ++-- .github/workflows/random_ci_pytest.yml | 2 +- utils/generate_random_versions.py | 2 -- 5 files changed, 37 insertions(+), 17 deletions(-) diff --git a/.github/workflows/downstream_tests.yml b/.github/workflows/downstream_tests.yml index b34f0088f9..804b654a26 100644 --- a/.github/workflows/downstream_tests.yml +++ b/.github/workflows/downstream_tests.yml @@ -478,7 +478,7 @@ jobs: validoopsie: strategy: matrix: - python-version: ["3.9", "3.12"] # 3.9 and 3.12 are enough to cover all the tests + python-version: ["3.10", "3.12"] # 3.10 and 3.12 are enough to cover all the tests os: ["ubuntu-latest"] runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/extremes.yml b/.github/workflows/extremes.yml index 0f352ee207..8f30d38c57 100644 --- a/.github/workflows/extremes.yml +++ b/.github/workflows/extremes.yml @@ -13,7 +13,7 @@ jobs: minimum_versions: strategy: matrix: - python-version: ["3.9"] + python-version: ["3.10"] os: [ubuntu-latest] runs-on: ${{ matrix.os }} @@ -29,7 +29,18 @@ jobs: cache-suffix: min-versions-${{ matrix.python-version }} cache-dependency-glob: "pyproject.toml" - name: install-minimum-versions - run: uv pip install pipdeptree tox virtualenv setuptools pandas==1.1.3 polars==0.20.4 numpy==1.19.3 pyarrow==13.0.0 "pyarrow-stubs<17" scipy==1.6.0 scikit-learn==1.1.0 duckdb==1.1 tzdata --system + run: | + uv pip install \ + pipdeptree tox virtualenv setuptools tzdata \ + pandas==1.3.4 \ + polars==0.20.4 \ + numpy==1.21.4 \ + pyarrow==13.0.0 \ + "pyarrow-stubs<17" \ + scipy==1.7.3 \ + scikit-learn==1.1.0 \ + duckdb==1.1 \ + --system - name: install-reqs run: | uv pip install -e . --group tests --system @@ -38,11 +49,11 @@ jobs: - name: Assert dependencies run: | DEPS=$(uv pip freeze) - echo "$DEPS" | grep 'pandas==1.1.3' + echo "$DEPS" | grep 'pandas==1.3.4' echo "$DEPS" | grep 'polars==0.20.4' - echo "$DEPS" | grep 'numpy==1.19.3' + echo "$DEPS" | grep 'numpy==1.21.4' echo "$DEPS" | grep 'pyarrow==13.0.0' - echo "$DEPS" | grep 'scipy==1.6.0' + echo "$DEPS" | grep 'scipy==1.7.3' echo "$DEPS" | grep 'scikit-learn==1.1.0' echo "$DEPS" | grep 'duckdb==1.1' - name: Run pytest @@ -51,7 +62,7 @@ jobs: pretty_old_versions: strategy: matrix: - python-version: ["3.9"] + python-version: ["3.10"] os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: @@ -66,7 +77,18 @@ jobs: cache-suffix: pretty-old-versions-${{ matrix.python-version }} cache-dependency-glob: "pyproject.toml" - name: install-pretty-old-versions - run: uv pip install pipdeptree tox virtualenv setuptools pandas==1.1.5 polars==0.20.4 numpy==1.19.3 pyarrow==14.0.0 "pyarrow-stubs<17" scipy==1.6.0 scikit-learn==1.1.0 duckdb==1.2 tzdata --system + run: | + uv pip install \ + pipdeptree tox virtualenv setuptools tzdata \ + pandas==1.4.0 \ + polars==0.20.4 \ + numpy==1.22.0 \ + pyarrow==14.0.0 \ + "pyarrow-stubs<17" \ + scipy==1.8.0 \ + scikit-learn==1.1.0 \ + duckdb==1.2 \ + --system - name: install-reqs run: uv pip install -e . --group tests --system - name: show-deps @@ -76,11 +98,11 @@ jobs: - name: Assert pretty old versions dependencies run : | DEPS=$(uv pip freeze) - echo "$DEPS" | grep 'pandas==1.1.5' + echo "$DEPS" | grep 'pandas==1.4.0' echo "$DEPS" | grep 'polars==0.20.4' - echo "$DEPS" | grep 'numpy==1.19.3' + echo "$DEPS" | grep 'numpy==1.22.0' echo "$DEPS" | grep 'pyarrow==14.0.0' - echo "$DEPS" | grep 'scipy==1.6.0' + echo "$DEPS" | grep 'scipy==1.8.0' echo "$DEPS" | grep 'scikit-learn==1.1.0' echo "$DEPS" | grep 'duckdb==1.2' - name: Run pytest diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 45fae38ab2..599444df46 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -10,10 +10,10 @@ env: PYTEST_ADDOPTS: "--numprocesses=logical" jobs: - pytest-39: + pytest-310: strategy: matrix: - python-version: ["3.9"] + python-version: ["3.10"] os: [windows-latest, ubuntu-latest] runs-on: ${{ matrix.os }} steps: @@ -25,7 +25,7 @@ jobs: uses: astral-sh/setup-uv@v7 with: enable-cache: "true" - cache-suffix: pytest-39-${{ matrix.python-version }} + cache-suffix: pytest-310-${{ matrix.python-version }} cache-dependency-glob: "pyproject.toml" - name: install-reqs run: uv pip install -e ".[pandas,polars,pyarrow]" --group tests --system diff --git a/.github/workflows/random_ci_pytest.yml b/.github/workflows/random_ci_pytest.yml index e0bff21864..0c3f66f7fd 100644 --- a/.github/workflows/random_ci_pytest.yml +++ b/.github/workflows/random_ci_pytest.yml @@ -11,7 +11,7 @@ jobs: tox: strategy: matrix: - python-version: ["3.9"] + python-version: ["3.10"] os: [ubuntu-latest] runs-on: ${{ matrix.os }} diff --git a/utils/generate_random_versions.py b/utils/generate_random_versions.py index 94e61397f1..9c00fca931 100644 --- a/utils/generate_random_versions.py +++ b/utils/generate_random_versions.py @@ -4,8 +4,6 @@ from pathlib import Path PANDAS_AND_NUMPY_VERSION = [ - ("1.1.5", "1.19.5"), - ("1.2.5", "1.20.3"), ("1.3.5", "1.21.6"), ("1.4.4", "1.22.4"), ("1.5.3", "1.23.5"), From 48a61a12dbec37754180fbca45859161b96fe364 Mon Sep 17 00:00:00 2001 From: FBruzzesi Date: Mon, 17 Nov 2025 15:12:22 +0100 Subject: [PATCH 10/17] old pandas paths --- narwhals/_pandas_like/expr.py | 8 -------- narwhals/_pandas_like/series.py | 7 +------ 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/narwhals/_pandas_like/expr.py b/narwhals/_pandas_like/expr.py index 895409b03e..e8be8bca76 100644 --- a/narwhals/_pandas_like/expr.py +++ b/narwhals/_pandas_like/expr.py @@ -328,14 +328,6 @@ def func(df: PandasLikeDataFrame) -> Sequence[PandasLikeSeries]: # noqa: C901, else: res_native = getattr(rolling, pandas_function_name)() elif function_name.startswith("ewm"): - if self._implementation.is_pandas() and ( - self._implementation._backend_version() - ) < (1, 2): # pragma: no cover - msg = ( - "Exponentially weighted calculation is not available in over " - f"context for pandas versions older than 1.2.0, found {self._implementation._backend_version()}." - ) - raise NotImplementedError(msg) ewm = grouped[list(aliases)].ewm(**pandas_kwargs) assert pandas_function_name is not None # help mypy # noqa: S101 res_native = getattr(ewm, pandas_function_name)() diff --git a/narwhals/_pandas_like/series.py b/narwhals/_pandas_like/series.py index b92bd9700f..e0bebc9d7b 100644 --- a/narwhals/_pandas_like/series.py +++ b/narwhals/_pandas_like/series.py @@ -292,7 +292,6 @@ def scatter(self, indices: int | Sequence[int], values: Any) -> Self: def _scatter_in_place(self, indices: Self, values: Self) -> None: # Scatter, modifying original Series. Use with care! implementation = self._implementation - backend_version = self._backend_version values_native = set_index( values.native, self.native.index[indices.native], @@ -300,11 +299,7 @@ def _scatter_in_place(self, indices: Self, values: Self) -> None: ) if implementation is Implementation.PANDAS and parse_version(np) < (2,): values_native = values_native.copy() # pragma: no cover - min_pd_version = (1, 2) - if implementation is Implementation.PANDAS and backend_version < min_pd_version: - self.native.iloc[indices.native.values] = values_native # noqa: PD011 - else: - self.native.iloc[indices.native] = values_native + self.native.iloc[indices.native] = values_native def cast(self, dtype: IntoDType) -> Self: if self.dtype == dtype and self.native.dtype != "object": From bce15a0f8c8e0342570cb6a24ef2c08821161031 Mon Sep 17 00:00:00 2001 From: FBruzzesi Date: Mon, 30 Mar 2026 22:50:22 +0200 Subject: [PATCH 11/17] simplify pandas-like scatter --- narwhals/_pandas_like/series.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/narwhals/_pandas_like/series.py b/narwhals/_pandas_like/series.py index c4f93e0274..0724ba1bb1 100644 --- a/narwhals/_pandas_like/series.py +++ b/narwhals/_pandas_like/series.py @@ -302,11 +302,8 @@ def scatter( ) series = native_series if in_place else native_series.copy(deep=True) - if impl.is_pandas(): - if in_place and NUMPY_VERSION < (2,): # pragma: no cover - values_native = values_native.copy() - if self._backend_version < (1, 2): - indices_native = indices_native.to_numpy() + if impl.is_pandas() and in_place and NUMPY_VERSION < (2,): # pragma: no cover + values_native = values_native.copy() series.iloc[indices_native] = values_native From 69ec76a4e4c09e11778cf6beedecc59628eb8f9b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 4 Apr 2026 11:42:09 +0000 Subject: [PATCH 12/17] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tpch/typing_.py | 2 +- utils/check_docstrings.py | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/tpch/typing_.py b/tpch/typing_.py index 13fbba2a75..a18dd0cbcb 100644 --- a/tpch/typing_.py +++ b/tpch/typing_.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Any, Literal, Protocol if TYPE_CHECKING: - from typing_extensions import TypeAlias + from typing import TypeAlias import narwhals as nw diff --git a/utils/check_docstrings.py b/utils/check_docstrings.py index b940a1c352..4c3ffe7b99 100644 --- a/utils/check_docstrings.py +++ b/utils/check_docstrings.py @@ -4,7 +4,6 @@ import ast import doctest -import os import subprocess import sys import sysconfig @@ -36,14 +35,7 @@ def find_ruff_bin() -> Path: if scripts_path.is_file(): return scripts_path - if sys.version_info >= (3, 10): - user_scheme = sysconfig.get_preferred_scheme("user") - elif os.name == "nt": - user_scheme = "nt_user" - elif sys.platform == "darwin" and sys._framework: - user_scheme = "osx_framework_user" - else: - user_scheme = "posix_user" + user_scheme = sysconfig.get_preferred_scheme("user") user_path = Path(sysconfig.get_path("scripts", scheme=user_scheme)) / ruff_exe if user_path.is_file(): From a42fe54060741cdb972f90262eb786c4f03866ab Mon Sep 17 00:00:00 2001 From: FBruzzesi Date: Tue, 7 Apr 2026 11:35:17 +0200 Subject: [PATCH 13/17] fix typing, thanks @dangotbanned --- .github/workflows/downstream_tests.yml | 2 +- .github/workflows/pytest.yml | 2 +- narwhals/stable/v1/typing.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/downstream_tests.yml b/.github/workflows/downstream_tests.yml index bcc9516cb5..3616b1b618 100644 --- a/.github/workflows/downstream_tests.yml +++ b/.github/workflows/downstream_tests.yml @@ -482,7 +482,7 @@ jobs: validoopsie: strategy: matrix: - python-version: ["3.10", "3.12"] # 3.10 and 3.12 are enough to cover all the tests + python-version: ["3.10", "3.13"] # 3.10 and 3.12 are enough to cover all the tests os: ["ubuntu-latest"] runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index ea5d86d43e..402981997d 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -37,7 +37,7 @@ jobs: pytest-windows: strategy: matrix: - python-version: ["3.10", "3.12"] + python-version: ["3.10", "3.13"] os: [windows-latest] runs-on: ${{ matrix.os }} steps: diff --git a/narwhals/stable/v1/typing.py b/narwhals/stable/v1/typing.py index 15140a6574..84c100b655 100644 --- a/narwhals/stable/v1/typing.py +++ b/narwhals/stable/v1/typing.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, Any, Protocol -from narwhals._native import IntoSeries +from narwhals._native import IntoSeries, NativeDuckDB from narwhals._typing_compat import TypeVar if TYPE_CHECKING: @@ -25,7 +25,7 @@ def __dataframe__(self, *args: Any, **kwargs: Any) -> Any: ... `nw.Expr`, e.g. `df.select('a')`. """ -IntoDataFrame: TypeAlias = "NativeDataFrame | DataFrameLike" +IntoDataFrame: TypeAlias = "NativeDataFrame | DataFrameLike | NativeDuckDB" """Anything which can be converted to a Narwhals DataFrame. Use this if your function accepts a narwhalifiable object but doesn't care about its backend. From 75c6c526368dbde7d54e21de31dd7830cc36c5db Mon Sep 17 00:00:00 2001 From: FBruzzesi Date: Sun, 12 Apr 2026 20:21:26 +0200 Subject: [PATCH 14/17] change pretty_old_versions pandas to 1.4.1 due to downcasting regression --- .github/workflows/extremes.yml | 2 +- tests/expr_and_series/fill_null_test.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/extremes.yml b/.github/workflows/extremes.yml index 794a41e2a0..72afd5a1ab 100644 --- a/.github/workflows/extremes.yml +++ b/.github/workflows/extremes.yml @@ -78,7 +78,7 @@ jobs: run: | uv pip install \ pipdeptree tox virtualenv setuptools tzdata \ - pandas==1.4.0 \ + pandas==1.4.1 \ polars==0.20.4 \ numpy==1.22.0 \ pyarrow==14.0.0 \ diff --git a/tests/expr_and_series/fill_null_test.py b/tests/expr_and_series/fill_null_test.py index 213b695176..d99991aad5 100644 --- a/tests/expr_and_series/fill_null_test.py +++ b/tests/expr_and_series/fill_null_test.py @@ -54,6 +54,8 @@ def test_fill_null_w_aggregate(constructor: Constructor) -> None: def test_fill_null_pandas_downcast() -> None: + # !NOTE: pandas 1.4.0 does NOT downcast to 'object' dtype. After filling + # null's, the dtype will be inferred to be 'bool' instead. pytest.importorskip("pandas") import pandas as pd From 632e01bff6546135cad869c05be40f12106dda09 Mon Sep 17 00:00:00 2001 From: FBruzzesi Date: Sun, 12 Apr 2026 20:40:16 +0200 Subject: [PATCH 15/17] rollback use of | in union types that require forward references --- narwhals/_native.py | 2 +- narwhals/stable/v1/typing.py | 10 +++++----- narwhals/stable/v2/typing.py | 6 +++--- narwhals/typing.py | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/narwhals/_native.py b/narwhals/_native.py index 6a4d0db7a2..93d5db271d 100644 --- a/narwhals/_native.py +++ b/narwhals/_native.py @@ -38,7 +38,7 @@ def something_common(self, *args: Any, **kwargs: Any) -> Any: ... **But**, occasionally, there'll be an edge-case which we can spell like: - IntoThing: TypeAlias = " | NativeThing" + IntoThing: TypeAlias = Union[, NativeThing] Tip: Reach for these when there **isn't a need to preserve** the original native type. diff --git a/narwhals/stable/v1/typing.py b/narwhals/stable/v1/typing.py index 84c100b655..8ec49dd628 100644 --- a/narwhals/stable/v1/typing.py +++ b/narwhals/stable/v1/typing.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Protocol +from typing import TYPE_CHECKING, Any, Protocol, Union from narwhals._native import IntoSeries, NativeDuckDB from narwhals._typing_compat import TypeVar @@ -15,7 +15,7 @@ class DataFrameLike(Protocol): def __dataframe__(self, *args: Any, **kwargs: Any) -> Any: ... -IntoExpr: TypeAlias = "Expr | str | Series[Any]" +IntoExpr: TypeAlias = Union["Expr", str, "Series[Any]"] """Anything which can be converted to an expression. Use this to mean "either a Narwhals expression, or something @@ -25,7 +25,7 @@ def __dataframe__(self, *args: Any, **kwargs: Any) -> Any: ... `nw.Expr`, e.g. `df.select('a')`. """ -IntoDataFrame: TypeAlias = "NativeDataFrame | DataFrameLike | NativeDuckDB" +IntoDataFrame: TypeAlias = Union["NativeDataFrame", "DataFrameLike", "NativeDuckDB"] """Anything which can be converted to a Narwhals DataFrame. Use this if your function accepts a narwhalifiable object but doesn't care about its backend. @@ -39,7 +39,7 @@ def __dataframe__(self, *args: Any, **kwargs: Any) -> Any: ... """ IntoLazyFrame: TypeAlias = "NativeLazyFrame" -IntoFrame: TypeAlias = "IntoDataFrame | IntoLazyFrame" +IntoFrame: TypeAlias = Union["IntoDataFrame", "IntoLazyFrame"] """Anything which can be converted to a Narwhals DataFrame or LazyFrame. Use this if your function can accept an object which can be converted to either @@ -53,7 +53,7 @@ def __dataframe__(self, *args: Any, **kwargs: Any) -> Any: ... ... return df.collect_schema().names() """ -Frame: TypeAlias = "DataFrame[Any] | LazyFrame[Any]" +Frame: TypeAlias = Union["DataFrame[Any]", "LazyFrame[Any]"] """Narwhals DataFrame or Narwhals LazyFrame. Use this if your function can work with either and your function doesn't care diff --git a/narwhals/stable/v2/typing.py b/narwhals/stable/v2/typing.py index d8080a40b0..1f9e48e4fa 100644 --- a/narwhals/stable/v2/typing.py +++ b/narwhals/stable/v2/typing.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, TypeVar +from typing import TYPE_CHECKING, Any, TypeVar, Union from narwhals._native import ( IntoDataFrame, @@ -19,7 +19,7 @@ from narwhals.stable.v2 import DataFrame, Expr, LazyFrame, Series -IntoExpr: TypeAlias = "Expr | str | Series[Any]" +IntoExpr: TypeAlias = Union["Expr", str, "Series[Any]"] """Anything which can be converted to an expression. Use this to mean "either a Narwhals expression, or something @@ -29,7 +29,7 @@ `nw.Expr`, e.g. `df.select('a')`. """ -Frame: TypeAlias = "DataFrame[Any] | LazyFrame[Any]" +Frame: TypeAlias = Union["DataFrame[Any]", "LazyFrame[Any]"] """Narwhals DataFrame or Narwhals LazyFrame. Use this if your function can work with either and your function doesn't care diff --git a/narwhals/typing.py b/narwhals/typing.py index 8e269641c1..5dd77931f7 100644 --- a/narwhals/typing.py +++ b/narwhals/typing.py @@ -1,7 +1,7 @@ from __future__ import annotations from collections.abc import Mapping -from typing import TYPE_CHECKING, Any, Literal, Protocol, TypeVar +from typing import TYPE_CHECKING, Any, Literal, Protocol, TypeVar, Union from narwhals._compliant import CompliantDataFrame, CompliantLazyFrame, CompliantSeries from narwhals._native import ( @@ -109,7 +109,7 @@ def Binary(self) -> type[dtypes.Binary]: ... Into1DArray: TypeAlias = "_1DArray | _NumpyScalar" """A 1-dimensional `numpy.ndarray` or scalar that can be converted into one.""" -IntoExpr: TypeAlias = "Expr | str | Series[Any] | _1DArray" +IntoExpr: TypeAlias = Union["Expr", str, "Series[Any]", _1DArray] """Anything which can be converted to an expression. Use this to mean "either a Narwhals expression, or something which can be converted @@ -118,7 +118,7 @@ def Binary(self) -> type[dtypes.Binary]: ... which will be interpreted as a `nw.Expr`, e.g. `df.select('a')`. """ -Frame: TypeAlias = "DataFrame[Any] | LazyFrame[Any]" +Frame: TypeAlias = Union["DataFrame[Any]", "LazyFrame[Any]"] """Narwhals DataFrame or Narwhals LazyFrame. Use this if your function can work with either and your function doesn't care From 5cd241ec6891544fc4c0eaa0fa7f6d181aa19e18 Mon Sep 17 00:00:00 2001 From: FBruzzesi Date: Sun, 12 Apr 2026 20:41:44 +0200 Subject: [PATCH 16/17] Forgot to change assertion -- where is my mind? --- .github/workflows/extremes.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/extremes.yml b/.github/workflows/extremes.yml index 72afd5a1ab..1f03355ced 100644 --- a/.github/workflows/extremes.yml +++ b/.github/workflows/extremes.yml @@ -96,7 +96,7 @@ jobs: - name: Assert pretty old versions dependencies run : | DEPS=$(uv pip freeze) - echo "$DEPS" | grep 'pandas==1.4.0' + echo "$DEPS" | grep 'pandas==1.4.1' echo "$DEPS" | grep 'polars==0.20.4' echo "$DEPS" | grep 'numpy==1.22.0' echo "$DEPS" | grep 'pyarrow==14.0.0' From 6b78515e640468615972cfcb59597482787d79fa Mon Sep 17 00:00:00 2001 From: FBruzzesi Date: Tue, 14 Apr 2026 12:22:38 +0200 Subject: [PATCH 17/17] Follow @dangotbanned suggestion for zip(..., strict=False) --- .github/workflows/downstream_tests.yml | 2 +- narwhals/_compliant/dataframe.py | 2 +- narwhals/_dask/dataframe.py | 4 ++-- narwhals/_dask/expr.py | 2 +- narwhals/_duckdb/namespace.py | 8 +++++--- narwhals/_ibis/namespace.py | 8 +++++--- narwhals/_pandas_like/dataframe.py | 2 +- narwhals/_pandas_like/series.py | 1 - narwhals/_polars/dataframe.py | 2 +- narwhals/_spark_like/dataframe.py | 2 +- narwhals/_spark_like/expr.py | 2 +- narwhals/_spark_like/namespace.py | 7 ++++--- tests/conftest.py | 4 +--- 13 files changed, 24 insertions(+), 22 deletions(-) diff --git a/.github/workflows/downstream_tests.yml b/.github/workflows/downstream_tests.yml index 3616b1b618..df62e9246b 100644 --- a/.github/workflows/downstream_tests.yml +++ b/.github/workflows/downstream_tests.yml @@ -482,7 +482,7 @@ jobs: validoopsie: strategy: matrix: - python-version: ["3.10", "3.13"] # 3.10 and 3.12 are enough to cover all the tests + python-version: ["3.10", "3.13"] # these are enough to cover all the tests os: ["ubuntu-latest"] runs-on: ${{ matrix.os }} steps: diff --git a/narwhals/_compliant/dataframe.py b/narwhals/_compliant/dataframe.py index 7ef406fddc..09d78fcf06 100644 --- a/narwhals/_compliant/dataframe.py +++ b/narwhals/_compliant/dataframe.py @@ -422,7 +422,7 @@ def __getitem__( # noqa: C901, PLR0912 compliant = compliant.simple_select( *( col - for col, select in zip(compliant.columns, columns, strict=True) + for col, select in zip(compliant.columns, columns, strict=False) if select ) ) diff --git a/narwhals/_dask/dataframe.py b/narwhals/_dask/dataframe.py index 180880da7d..809e94372a 100644 --- a/narwhals/_dask/dataframe.py +++ b/narwhals/_dask/dataframe.py @@ -379,7 +379,7 @@ def _join_semi( other_native = self._join_filter_rename( other=other, columns_to_select=list(right_on), - columns_mapping=dict(zip(right_on, left_on, strict=True)), + columns_mapping=dict(zip(right_on, left_on, strict=False)), ) return self.native.dropna(subset=left_on, how="any").merge( other_native, how="inner", left_on=left_on, right_on=left_on @@ -394,7 +394,7 @@ def _join_anti( other_native = self._join_filter_rename( other=other, columns_to_select=list(right_on), - columns_mapping=dict(zip(right_on, left_on, strict=True)), + columns_mapping=dict(zip(right_on, left_on, strict=False)), ) df = self.native.merge( other_native.dropna(subset=left_on, how="any"), diff --git a/narwhals/_dask/expr.py b/narwhals/_dask/expr.py index 7914a9e04e..354d3b5621 100644 --- a/narwhals/_dask/expr.py +++ b/narwhals/_dask/expr.py @@ -672,7 +672,7 @@ def replace_strict( msg = "`replace_strict` requires an explicit value for `default` for dask backend." raise ValueError(msg) - mapping = dict(zip(old, new, strict=True)) + mapping = dict(zip(old, new, strict=False)) old_ = list(old) def func(df: DaskLazyFrame) -> list[dx.Series]: diff --git a/narwhals/_duckdb/namespace.py b/narwhals/_duckdb/namespace.py index e726139f29..c246d4aa15 100644 --- a/narwhals/_duckdb/namespace.py +++ b/narwhals/_duckdb/namespace.py @@ -28,7 +28,7 @@ evaluate_output_names_and_aliases, ) from narwhals._sql.namespace import SQLNamespace -from narwhals._utils import Implementation, zip_strict +from narwhals._utils import Implementation if TYPE_CHECKING: from collections.abc import Iterable, Mapping @@ -194,8 +194,10 @@ def func(df: DuckDBLazyFrame) -> list[Expression]: names_to_cols: Mapping[str, Expression] = { alias: native_expr for expr in exprs - for native_expr, _, alias in zip_strict( - expr(df), *evaluate_output_names_and_aliases(expr, df, []) + for native_expr, _, alias in zip( + expr(df), + *evaluate_output_names_and_aliases(expr, df, []), + strict=True, ) } field_args = ", ".join( diff --git a/narwhals/_ibis/namespace.py b/narwhals/_ibis/namespace.py index 013af303b0..9b81c5d602 100644 --- a/narwhals/_ibis/namespace.py +++ b/narwhals/_ibis/namespace.py @@ -19,7 +19,7 @@ from narwhals._ibis.selectors import IbisSelectorNamespace from narwhals._ibis.utils import function, lit, narwhals_to_native_dtype from narwhals._sql.namespace import SQLNamespace -from narwhals._utils import Implementation, zip_strict +from narwhals._utils import Implementation if TYPE_CHECKING: from collections.abc import Iterable, Mapping, Sequence @@ -167,8 +167,10 @@ def func(df: IbisLazyFrame) -> list[ir.Value]: names_to_cols: Mapping[str, ir.Value] = { alias: native_expr for expr in exprs - for native_expr, _, alias in zip_strict( - expr(df), *evaluate_output_names_and_aliases(expr, df, []) + for native_expr, _, alias in zip( + expr(df), + *evaluate_output_names_and_aliases(expr, df, []), + strict=True, ) } return [ibis.struct(names_to_cols)] diff --git a/narwhals/_pandas_like/dataframe.py b/narwhals/_pandas_like/dataframe.py index 72896c8c79..a03d5ed136 100644 --- a/narwhals/_pandas_like/dataframe.py +++ b/narwhals/_pandas_like/dataframe.py @@ -696,7 +696,7 @@ def _join_semi( other_native = self._join_filter_rename( other=other, columns_to_select=list(right_on), - columns_mapping=dict(zip(right_on, left_on, strict=True)), + columns_mapping=dict(zip(right_on, left_on, strict=False)), ) return self.native.dropna(subset=left_on, how="any").merge( other_native, how="inner", left_on=left_on, right_on=left_on diff --git a/narwhals/_pandas_like/series.py b/narwhals/_pandas_like/series.py index 0724ba1bb1..4017638e17 100644 --- a/narwhals/_pandas_like/series.py +++ b/narwhals/_pandas_like/series.py @@ -2,7 +2,6 @@ import operator import warnings -from collections.abc import Callable from typing import TYPE_CHECKING, Any, Literal, overload from narwhals._compliant import EagerSeries, EagerSeriesHist diff --git a/narwhals/_polars/dataframe.py b/narwhals/_polars/dataframe.py index 7012702ea2..3c746c188c 100644 --- a/narwhals/_polars/dataframe.py +++ b/narwhals/_polars/dataframe.py @@ -479,7 +479,7 @@ def __getitem__( # noqa: C901, PLR0912 native = native.select( *( col - for col, select in zip(native.columns, columns, strict=True) + for col, select in zip(native.columns, columns, strict=False) if select ) ) diff --git a/narwhals/_spark_like/dataframe.py b/narwhals/_spark_like/dataframe.py index 21b7024100..b8b407dd8a 100644 --- a/narwhals/_spark_like/dataframe.py +++ b/narwhals/_spark_like/dataframe.py @@ -418,7 +418,7 @@ def join( ) rename_mapping = { - **dict(zip(right_on_, left_on_, strict=True)), + **dict(zip(right_on_, left_on_, strict=False)), **{ colname: f"{colname}{suffix}" if colname in left_columns else colname for colname in right_cols_to_rename diff --git a/narwhals/_spark_like/expr.py b/narwhals/_spark_like/expr.py index 884e387278..95cde685ea 100644 --- a/narwhals/_spark_like/expr.py +++ b/narwhals/_spark_like/expr.py @@ -380,7 +380,7 @@ def replace_strict( F = self._F - mapping = dict(zip(old, new, strict=True)) + mapping = dict(zip(old, new, strict=False)) mapping_expr = F.create_map([F.lit(x) for x in chain(*mapping.items())]) def func(df: SparkLikeLazyFrame) -> list[Column]: diff --git a/narwhals/_spark_like/namespace.py b/narwhals/_spark_like/namespace.py index 201e87eea6..03deefb424 100644 --- a/narwhals/_spark_like/namespace.py +++ b/narwhals/_spark_like/namespace.py @@ -20,7 +20,6 @@ true_divide, ) from narwhals._sql.namespace import SQLNamespace -from narwhals._utils import zip_strict if TYPE_CHECKING: from collections.abc import Iterable, Mapping @@ -250,8 +249,10 @@ def func(df: SparkLikeLazyFrame) -> list[Column]: names_to_cols: Mapping[str, Column] = { alias: native_expr for expr in exprs - for native_expr, _, alias in zip_strict( - expr(df), *evaluate_output_names_and_aliases(expr, df, []) + for native_expr, _, alias in zip( + expr(df), + *evaluate_output_names_and_aliases(expr, df, []), + strict=True, ) } aliased = (col.alias(name) for name, col in names_to_cols.items()) diff --git a/tests/conftest.py b/tests/conftest.py index 35a49d5dd8..5f0ebdce68 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -215,9 +215,7 @@ def sqlframe_pyspark_lazy_constructor(obj: Data) -> NativeSQLFrame: # pragma: n pytest.importorskip("sqlframe") pytest.importorskip("duckdb") session = sqlframe_session() - return session.createDataFrame( - [*zip(*obj.values(), strict=False)], schema=[*obj.keys()] - ) + return session.createDataFrame([*zip(*obj.values(), strict=False)], schema=list(obj)) @lru_cache(maxsize=1)