From b6cdcf12ca65abcc5809497bb0b1cfabd624b88a Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Fri, 22 Aug 2025 09:13:14 +0200 Subject: [PATCH 01/11] fix(series): arithmetics for Series[Any] --- pandas-stubs/core/series.pyi | 18 +- tests/series/arithmetic/str/test_add.py | 8 +- tests/series/arithmetic/test_add.py | 157 +++++++++-------- tests/series/arithmetic/test_mul.py | 149 ++++++++-------- tests/series/arithmetic/test_sub.py | 93 ++++++---- tests/series/arithmetic/test_truediv.py | 219 ++++++++++++------------ tests/series/test_series.py | 43 ++--- 7 files changed, 356 insertions(+), 331 deletions(-) diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index f5bea7bf..938360a4 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -2705,13 +2705,9 @@ class Series(IndexOpsMixin[S1], NDFrame): axis: int = 0, ) -> TimedeltaSeries: ... @overload - def __rsub__( # type: ignore[misc] + def __rsub__( # type: ignore[overload-overlap] self: Series[Never], - other: datetime | np.datetime64 | np_ndarray_dt | TimestampSeries, - ) -> TimedeltaSeries: ... - @overload - def __rsub__( - self: Series[Never], other: complex | _ListLike | Series + other: complex | datetime | np.datetime64 | _ListLike | Series, ) -> Series: ... @overload def __rsub__(self, other: Series[Never]) -> Series: ... @@ -2779,17 +2775,13 @@ class Series(IndexOpsMixin[S1], NDFrame): ), ) -> Series[complex]: ... @overload - def rsub( - self: Series[Never], - other: datetime | np.datetime64 | np_ndarray_dt | TimestampSeries, - level: Level | None = None, - fill_value: float | None = None, - axis: int = 0, + def __rsub__( + self: Series[Timestamp], other: datetime | np.datetime64 | np_ndarray_dt ) -> TimedeltaSeries: ... @overload def rsub( self: Series[Never], - other: complex | _ListLike | Series, + other: complex | datetime | np.datetime64 | _ListLike | Series, level: Level | None = None, fill_value: float | None = None, axis: int = 0, diff --git a/tests/series/arithmetic/str/test_add.py b/tests/series/arithmetic/str/test_add.py index 2eaffd59..fbcc7536 100644 --- a/tests/series/arithmetic/str/test_add.py +++ b/tests/series/arithmetic/str/test_add.py @@ -18,7 +18,7 @@ def test_add_py_scalar() -> None: - """Testpd.Series[str]+ Python native 'scalar's""" + """Test pd.Series[str] + Python native 'scalar's""" i = 4 r0 = "right" @@ -40,7 +40,7 @@ def test_add_py_scalar() -> None: def test_add_py_sequence() -> None: - """Testpd.Series[str]+ Python native sequence""" + """Test pd.Series[str] + Python native sequence""" i = [3, 5, 8] r0 = ["a", "bc", "def"] r1 = tuple(r0) @@ -67,7 +67,7 @@ def test_add_py_sequence() -> None: def test_add_numpy_array() -> None: - """Testpd.Series[str]+ numpy array""" + """Test pd.Series[str] + numpy array""" i = np.array([3, 5, 8], np.int64) r0 = np.array(["a", "bc", "def"], np.str_) @@ -101,7 +101,7 @@ def test_add_numpy_array() -> None: def test_add_pd_series() -> None: - """Testpd.Series[str]+ pandas series""" + """Test pd.Series[str] + pandas series""" i = pd.Series([3, 5, 8]) r0 = pd.Series(["a", "bc", "def"]) diff --git a/tests/series/arithmetic/test_add.py b/tests/series/arithmetic/test_add.py index e4d68c94..c663cc47 100644 --- a/tests/series/arithmetic/test_add.py +++ b/tests/series/arithmetic/test_add.py @@ -5,122 +5,127 @@ from tests import check -left = pd.DataFrame({"a": [1, 2, 3]})["a"] # left operand +left_i = pd.DataFrame({"a": [1, 2, 3]})["a"] # left operand -def test_add_py_scalar() -> None: - """Test pd.Series[Any] + Python native scalars""" +def test_add_i_py_scalar() -> None: + """Test pd.Series[Any] (int) + Python native scalars""" b, i, f, c = True, 1, 1.0, 1j - check(assert_type(left + b, pd.Series), pd.Series) - check(assert_type(left + i, pd.Series), pd.Series) - check(assert_type(left + f, pd.Series), pd.Series) - check(assert_type(left + c, pd.Series), pd.Series) + check(assert_type(left_i + b, pd.Series), pd.Series) + check(assert_type(left_i + i, pd.Series), pd.Series) + check(assert_type(left_i + f, pd.Series), pd.Series) + check(assert_type(left_i + c, pd.Series), pd.Series) - check(assert_type(b + left, pd.Series), pd.Series) - check(assert_type(i + left, pd.Series), pd.Series) - check(assert_type(f + left, pd.Series), pd.Series) - check(assert_type(c + left, pd.Series), pd.Series) + check(assert_type(b + left_i, pd.Series), pd.Series) + check(assert_type(i + left_i, pd.Series), pd.Series) + check(assert_type(f + left_i, pd.Series), pd.Series) + check(assert_type(c + left_i, pd.Series), pd.Series) - check(assert_type(left.add(b), pd.Series), pd.Series) - check(assert_type(left.add(i), pd.Series), pd.Series) - check(assert_type(left.add(f), pd.Series), pd.Series) - check(assert_type(left.add(c), pd.Series), pd.Series) + check(assert_type(left_i.add(b), pd.Series), pd.Series) + check(assert_type(left_i.add(i), pd.Series), pd.Series) + check(assert_type(left_i.add(f), pd.Series), pd.Series) + check(assert_type(left_i.add(c), pd.Series), pd.Series) - check(assert_type(left.radd(b), pd.Series), pd.Series) - check(assert_type(left.radd(i), pd.Series), pd.Series) - check(assert_type(left.radd(f), pd.Series), pd.Series) - check(assert_type(left.radd(c), pd.Series), pd.Series) + check(assert_type(left_i.radd(b), pd.Series), pd.Series) + check(assert_type(left_i.radd(i), pd.Series), pd.Series) + check(assert_type(left_i.radd(f), pd.Series), pd.Series) + check(assert_type(left_i.radd(c), pd.Series), pd.Series) -def test_add_py_sequence() -> None: - """Test pd.Series[Any] + Python native sequence""" +def test_add_i_py_sequence() -> None: + """Test pd.Series[Any] (int) + Python native sequence""" b, i, f, c = [True, False, True], [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] - check(assert_type(left + b, pd.Series), pd.Series) - check(assert_type(left + i, pd.Series), pd.Series) - check(assert_type(left + f, pd.Series), pd.Series) - check(assert_type(left + c, pd.Series), pd.Series) + check(assert_type(left_i + b, pd.Series), pd.Series) + check(assert_type(left_i + i, pd.Series), pd.Series) + check(assert_type(left_i + f, pd.Series), pd.Series) + check(assert_type(left_i + c, pd.Series), pd.Series) - check(assert_type(b + left, pd.Series), pd.Series) - check(assert_type(i + left, pd.Series), pd.Series) - check(assert_type(f + left, pd.Series), pd.Series) - check(assert_type(c + left, pd.Series), pd.Series) + check(assert_type(b + left_i, pd.Series), pd.Series) + check(assert_type(i + left_i, pd.Series), pd.Series) + check(assert_type(f + left_i, pd.Series), pd.Series) + check(assert_type(c + left_i, pd.Series), pd.Series) - check(assert_type(left.add(b), pd.Series), pd.Series) - check(assert_type(left.add(i), pd.Series), pd.Series) - check(assert_type(left.add(f), pd.Series), pd.Series) - check(assert_type(left.add(c), pd.Series), pd.Series) + check(assert_type(left_i.add(b), pd.Series), pd.Series) + check(assert_type(left_i.add(i), pd.Series), pd.Series) + check(assert_type(left_i.add(f), pd.Series), pd.Series) + check(assert_type(left_i.add(c), pd.Series), pd.Series) - check(assert_type(left.radd(b), pd.Series), pd.Series) - check(assert_type(left.radd(i), pd.Series), pd.Series) - check(assert_type(left.radd(f), pd.Series), pd.Series) - check(assert_type(left.radd(c), pd.Series), pd.Series) + check(assert_type(left_i.radd(b), pd.Series), pd.Series) + check(assert_type(left_i.radd(i), pd.Series), pd.Series) + check(assert_type(left_i.radd(f), pd.Series), pd.Series) + check(assert_type(left_i.radd(c), pd.Series), pd.Series) -def test_add_numpy_array() -> None: - """Test pd.Series[Any] + numpy array""" +def test_add_i_numpy_array() -> None: + """Test pd.Series[Any] (int) + numpy array""" b = np.array([True, False, True], np.bool_) i = np.array([2, 3, 5], np.int64) f = np.array([1.0, 2.0, 3.0], np.float64) c = np.array([1.1j, 2.2j, 4.1j], np.complex128) - check(assert_type(left + b, pd.Series), pd.Series) - check(assert_type(left + i, pd.Series), pd.Series) - check(assert_type(left + f, pd.Series), pd.Series) - check(assert_type(left + c, pd.Series), pd.Series) + check(assert_type(left_i + b, pd.Series), pd.Series) + check(assert_type(left_i + i, pd.Series), pd.Series) + check(assert_type(left_i + f, pd.Series), pd.Series) + check(assert_type(left_i + c, pd.Series), pd.Series) # `numpy` typing gives the corresponding `ndarray`s in the static type # checking, where our `__radd__` cannot override. At runtime, they return # `Series`s. # `mypy` thinks the return types are `Any`, which is a bug. check( - assert_type(b + left, "npt.NDArray[np.bool_]"), pd.Series # type: ignore[assert-type] + assert_type(b + left_i, "npt.NDArray[np.bool_]"), pd.Series # type: ignore[assert-type] ) check( - assert_type(i + left, "npt.NDArray[np.int64]"), pd.Series # type: ignore[assert-type] + assert_type(i + left_i, "npt.NDArray[np.int64]"), pd.Series # type: ignore[assert-type] ) check( - assert_type(f + left, "npt.NDArray[np.float64]"), pd.Series # type: ignore[assert-type] + assert_type(f + left_i, "npt.NDArray[np.float64]"), pd.Series # type: ignore[assert-type] ) check( - assert_type(c + left, "npt.NDArray[np.complex128]"), pd.Series # type: ignore[assert-type] + assert_type(c + left_i, "npt.NDArray[np.complex128]"), pd.Series # type: ignore[assert-type] ) - check(assert_type(left.add(b), pd.Series), pd.Series) - check(assert_type(left.add(i), pd.Series), pd.Series) - check(assert_type(left.add(f), pd.Series), pd.Series) - check(assert_type(left.add(c), pd.Series), pd.Series) + check(assert_type(left_i.add(b), pd.Series), pd.Series) + check(assert_type(left_i.add(i), pd.Series), pd.Series) + check(assert_type(left_i.add(f), pd.Series), pd.Series) + check(assert_type(left_i.add(c), pd.Series), pd.Series) - check(assert_type(left.radd(b), pd.Series), pd.Series) - check(assert_type(left.radd(i), pd.Series), pd.Series) - check(assert_type(left.radd(f), pd.Series), pd.Series) - check(assert_type(left.radd(c), pd.Series), pd.Series) + check(assert_type(left_i.radd(b), pd.Series), pd.Series) + check(assert_type(left_i.radd(i), pd.Series), pd.Series) + check(assert_type(left_i.radd(f), pd.Series), pd.Series) + check(assert_type(left_i.radd(c), pd.Series), pd.Series) -def test_add_pd_series() -> None: - """Test pd.Series[Any] + pandas series""" +def test_add_i_pd_series() -> None: + """Test pd.Series[Any] (int) + pandas series""" + a = pd.DataFrame({"a": [1, 2, 3]})["a"] b = pd.Series([True, False, True]) i = pd.Series([2, 3, 5]) f = pd.Series([1.0, 2.0, 3.0]) c = pd.Series([1.1j, 2.2j, 4.1j]) - check(assert_type(left + b, pd.Series), pd.Series) - check(assert_type(left + i, pd.Series), pd.Series) - check(assert_type(left + f, pd.Series), pd.Series) - check(assert_type(left + c, pd.Series), pd.Series) - - check(assert_type(b + left, pd.Series), pd.Series) - check(assert_type(i + left, pd.Series), pd.Series) - check(assert_type(f + left, pd.Series), pd.Series) - check(assert_type(c + left, pd.Series), pd.Series) - - check(assert_type(left.add(b), pd.Series), pd.Series) - check(assert_type(left.add(i), pd.Series), pd.Series) - check(assert_type(left.add(f), pd.Series), pd.Series) - check(assert_type(left.add(c), pd.Series), pd.Series) - - check(assert_type(left.radd(b), pd.Series), pd.Series) - check(assert_type(left.radd(i), pd.Series), pd.Series) - check(assert_type(left.radd(f), pd.Series), pd.Series) - check(assert_type(left.radd(c), pd.Series), pd.Series) + check(assert_type(left_i + a, pd.Series), pd.Series) + check(assert_type(left_i + b, pd.Series), pd.Series) + check(assert_type(left_i + i, pd.Series), pd.Series) + check(assert_type(left_i + f, pd.Series), pd.Series) + check(assert_type(left_i + c, pd.Series), pd.Series) + + check(assert_type(a + left_i, pd.Series), pd.Series) + check(assert_type(b + left_i, pd.Series), pd.Series) + check(assert_type(i + left_i, pd.Series), pd.Series) + check(assert_type(f + left_i, pd.Series), pd.Series) + check(assert_type(c + left_i, pd.Series), pd.Series) + + check(assert_type(left_i.add(a), pd.Series), pd.Series) + check(assert_type(left_i.add(b), pd.Series), pd.Series) + check(assert_type(left_i.add(i), pd.Series), pd.Series) + check(assert_type(left_i.add(f), pd.Series), pd.Series) + check(assert_type(left_i.add(c), pd.Series), pd.Series) + + check(assert_type(left_i.radd(a), pd.Series), pd.Series) + check(assert_type(left_i.radd(b), pd.Series), pd.Series) + check(assert_type(left_i.radd(i), pd.Series), pd.Series) + check(assert_type(left_i.radd(f), pd.Series), pd.Series) + check(assert_type(left_i.radd(c), pd.Series), pd.Series) diff --git a/tests/series/arithmetic/test_mul.py b/tests/series/arithmetic/test_mul.py index ec7ed5f4..335a6014 100644 --- a/tests/series/arithmetic/test_mul.py +++ b/tests/series/arithmetic/test_mul.py @@ -5,122 +5,127 @@ from tests import check -left = pd.DataFrame({"a": [1, 2, 3]})["a"] # left operand +left_i = pd.DataFrame({"a": [1, 2, 3]})["a"] # left operand def test_mul_py_scalar() -> None: - """Test pd.Series[Any] * Python native scalars""" + """Test pd.Series[Any] (int) * Python native scalars""" b, i, f, c = True, 1, 1.0, 1j - check(assert_type(left * b, pd.Series), pd.Series) - check(assert_type(left * i, pd.Series), pd.Series) - check(assert_type(left * f, pd.Series), pd.Series) - check(assert_type(left * c, pd.Series), pd.Series) + check(assert_type(left_i * b, pd.Series), pd.Series) + check(assert_type(left_i * i, pd.Series), pd.Series) + check(assert_type(left_i * f, pd.Series), pd.Series) + check(assert_type(left_i * c, pd.Series), pd.Series) - check(assert_type(b * left, pd.Series), pd.Series) - check(assert_type(i * left, pd.Series), pd.Series) - check(assert_type(f * left, pd.Series), pd.Series) - check(assert_type(c * left, pd.Series), pd.Series) + check(assert_type(b * left_i, pd.Series), pd.Series) + check(assert_type(i * left_i, pd.Series), pd.Series) + check(assert_type(f * left_i, pd.Series), pd.Series) + check(assert_type(c * left_i, pd.Series), pd.Series) - check(assert_type(left.mul(b), pd.Series), pd.Series) - check(assert_type(left.mul(i), pd.Series), pd.Series) - check(assert_type(left.mul(f), pd.Series), pd.Series) - check(assert_type(left.mul(c), pd.Series), pd.Series) + check(assert_type(left_i.mul(b), pd.Series), pd.Series) + check(assert_type(left_i.mul(i), pd.Series), pd.Series) + check(assert_type(left_i.mul(f), pd.Series), pd.Series) + check(assert_type(left_i.mul(c), pd.Series), pd.Series) - check(assert_type(left.rmul(b), pd.Series), pd.Series) - check(assert_type(left.rmul(i), pd.Series), pd.Series) - check(assert_type(left.rmul(f), pd.Series), pd.Series) - check(assert_type(left.rmul(c), pd.Series), pd.Series) + check(assert_type(left_i.rmul(b), pd.Series), pd.Series) + check(assert_type(left_i.rmul(i), pd.Series), pd.Series) + check(assert_type(left_i.rmul(f), pd.Series), pd.Series) + check(assert_type(left_i.rmul(c), pd.Series), pd.Series) def test_mul_py_sequence() -> None: - """Test pd.Series[Any] * Python native sequence""" + """Test pd.Series[Any] (int) * Python native sequence""" b, i, f, c = [True, False, True], [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] - check(assert_type(left * b, pd.Series), pd.Series) - check(assert_type(left * i, pd.Series), pd.Series) - check(assert_type(left * f, pd.Series), pd.Series) - check(assert_type(left * c, pd.Series), pd.Series) + check(assert_type(left_i * b, pd.Series), pd.Series) + check(assert_type(left_i * i, pd.Series), pd.Series) + check(assert_type(left_i * f, pd.Series), pd.Series) + check(assert_type(left_i * c, pd.Series), pd.Series) - check(assert_type(b * left, pd.Series), pd.Series) - check(assert_type(i * left, pd.Series), pd.Series) - check(assert_type(f * left, pd.Series), pd.Series) - check(assert_type(c * left, pd.Series), pd.Series) + check(assert_type(b * left_i, pd.Series), pd.Series) + check(assert_type(i * left_i, pd.Series), pd.Series) + check(assert_type(f * left_i, pd.Series), pd.Series) + check(assert_type(c * left_i, pd.Series), pd.Series) - check(assert_type(left.mul(b), pd.Series), pd.Series) - check(assert_type(left.mul(i), pd.Series), pd.Series) - check(assert_type(left.mul(f), pd.Series), pd.Series) - check(assert_type(left.mul(c), pd.Series), pd.Series) + check(assert_type(left_i.mul(b), pd.Series), pd.Series) + check(assert_type(left_i.mul(i), pd.Series), pd.Series) + check(assert_type(left_i.mul(f), pd.Series), pd.Series) + check(assert_type(left_i.mul(c), pd.Series), pd.Series) - check(assert_type(left.rmul(b), pd.Series), pd.Series) - check(assert_type(left.rmul(i), pd.Series), pd.Series) - check(assert_type(left.rmul(f), pd.Series), pd.Series) - check(assert_type(left.rmul(c), pd.Series), pd.Series) + check(assert_type(left_i.rmul(b), pd.Series), pd.Series) + check(assert_type(left_i.rmul(i), pd.Series), pd.Series) + check(assert_type(left_i.rmul(f), pd.Series), pd.Series) + check(assert_type(left_i.rmul(c), pd.Series), pd.Series) def test_mul_numpy_array() -> None: - """Test pd.Series[Any] * numpy array""" + """Test pd.Series[Any] (int) * numpy array""" b = np.array([True, False, True], np.bool_) i = np.array([2, 3, 5], np.int64) f = np.array([1.0, 2.0, 3.0], np.float64) c = np.array([1.1j, 2.2j, 4.1j], np.complex128) - check(assert_type(left * b, pd.Series), pd.Series) - check(assert_type(left * i, pd.Series), pd.Series) - check(assert_type(left * f, pd.Series), pd.Series) - check(assert_type(left * c, pd.Series), pd.Series) + check(assert_type(left_i * b, pd.Series), pd.Series) + check(assert_type(left_i * i, pd.Series), pd.Series) + check(assert_type(left_i * f, pd.Series), pd.Series) + check(assert_type(left_i * c, pd.Series), pd.Series) # `numpy` typing gives the corresponding `ndarray`s in the static type # checking, where our `__rmul__` cannot override. At runtime, they return # `Series`s. # `mypy` thinks the return types are `Any`, which is a bug. check( - assert_type(b * left, "npt.NDArray[np.bool_]"), pd.Series # type: ignore[assert-type] + assert_type(b * left_i, "npt.NDArray[np.bool_]"), pd.Series # type: ignore[assert-type] ) check( - assert_type(i * left, "npt.NDArray[np.int64]"), pd.Series # type: ignore[assert-type] + assert_type(i * left_i, "npt.NDArray[np.int64]"), pd.Series # type: ignore[assert-type] ) check( - assert_type(f * left, "npt.NDArray[np.float64]"), pd.Series # type: ignore[assert-type] + assert_type(f * left_i, "npt.NDArray[np.float64]"), pd.Series # type: ignore[assert-type] ) check( - assert_type(c * left, "npt.NDArray[np.complex128]"), pd.Series # type: ignore[assert-type] + assert_type(c * left_i, "npt.NDArray[np.complex128]"), pd.Series # type: ignore[assert-type] ) - check(assert_type(left.mul(b), pd.Series), pd.Series) - check(assert_type(left.mul(i), pd.Series), pd.Series) - check(assert_type(left.mul(f), pd.Series), pd.Series) - check(assert_type(left.mul(c), pd.Series), pd.Series) + check(assert_type(left_i.mul(b), pd.Series), pd.Series) + check(assert_type(left_i.mul(i), pd.Series), pd.Series) + check(assert_type(left_i.mul(f), pd.Series), pd.Series) + check(assert_type(left_i.mul(c), pd.Series), pd.Series) - check(assert_type(left.rmul(b), pd.Series), pd.Series) - check(assert_type(left.rmul(i), pd.Series), pd.Series) - check(assert_type(left.rmul(f), pd.Series), pd.Series) - check(assert_type(left.rmul(c), pd.Series), pd.Series) + check(assert_type(left_i.rmul(b), pd.Series), pd.Series) + check(assert_type(left_i.rmul(i), pd.Series), pd.Series) + check(assert_type(left_i.rmul(f), pd.Series), pd.Series) + check(assert_type(left_i.rmul(c), pd.Series), pd.Series) def test_mul_pd_series() -> None: - """Test pd.Series[Any] * pandas series""" + """Test pd.Series[Any] (int) * pandas series""" + a = pd.DataFrame({"a": [1, 2, 3]})["a"] b = pd.Series([True, False, True]) i = pd.Series([2, 3, 5]) f = pd.Series([1.0, 2.0, 3.0]) c = pd.Series([1.1j, 2.2j, 4.1j]) - check(assert_type(left * b, pd.Series), pd.Series) - check(assert_type(left * i, pd.Series), pd.Series) - check(assert_type(left * f, pd.Series), pd.Series) - check(assert_type(left * c, pd.Series), pd.Series) - - check(assert_type(b * left, pd.Series), pd.Series) - check(assert_type(i * left, pd.Series), pd.Series) - check(assert_type(f * left, pd.Series), pd.Series) - check(assert_type(c * left, pd.Series), pd.Series) - - check(assert_type(left.mul(b), pd.Series), pd.Series) - check(assert_type(left.mul(i), pd.Series), pd.Series) - check(assert_type(left.mul(f), pd.Series), pd.Series) - check(assert_type(left.mul(c), pd.Series), pd.Series) - - check(assert_type(left.rmul(b), pd.Series), pd.Series) - check(assert_type(left.rmul(i), pd.Series), pd.Series) - check(assert_type(left.rmul(f), pd.Series), pd.Series) - check(assert_type(left.rmul(c), pd.Series), pd.Series) + check(assert_type(left_i * a, pd.Series), pd.Series) + check(assert_type(left_i * b, pd.Series), pd.Series) + check(assert_type(left_i * i, pd.Series), pd.Series) + check(assert_type(left_i * f, pd.Series), pd.Series) + check(assert_type(left_i * c, pd.Series), pd.Series) + + check(assert_type(a * left_i, pd.Series), pd.Series) + check(assert_type(b * left_i, pd.Series), pd.Series) + check(assert_type(i * left_i, pd.Series), pd.Series) + check(assert_type(f * left_i, pd.Series), pd.Series) + check(assert_type(c * left_i, pd.Series), pd.Series) + + check(assert_type(left_i.mul(a), pd.Series), pd.Series) + check(assert_type(left_i.mul(b), pd.Series), pd.Series) + check(assert_type(left_i.mul(i), pd.Series), pd.Series) + check(assert_type(left_i.mul(f), pd.Series), pd.Series) + check(assert_type(left_i.mul(c), pd.Series), pd.Series) + + check(assert_type(left_i.rmul(a), pd.Series), pd.Series) + check(assert_type(left_i.rmul(b), pd.Series), pd.Series) + check(assert_type(left_i.rmul(i), pd.Series), pd.Series) + check(assert_type(left_i.rmul(f), pd.Series), pd.Series) + check(assert_type(left_i.rmul(c), pd.Series), pd.Series) diff --git a/tests/series/arithmetic/test_sub.py b/tests/series/arithmetic/test_sub.py index 636aa242..f6c489e4 100644 --- a/tests/series/arithmetic/test_sub.py +++ b/tests/series/arithmetic/test_sub.py @@ -12,16 +12,24 @@ import pandas as pd from typing_extensions import assert_type -from tests import check +from tests import ( + TYPE_CHECKING_INVALID_USAGE, + check, +) if TYPE_CHECKING: from pandas.core.series import TimedeltaSeries # noqa: F401 -left_i = pd.DataFrame({"a": [1, 2, 3]})["a"] # left operand +anchor = datetime(2025, 8, 18) + +# left operands +left_i = pd.DataFrame({"a": [1, 2, 3]})["a"] +left_ts = pd.DataFrame({"a": [anchor + timedelta(hours=h + 1) for h in range(3)]})["a"] +left_td = pd.DataFrame({"a": [timedelta(hours=h, minutes=1) for h in range(3)]})["a"] -def test_sub_py_scalar() -> None: - """Test pd.Series[Any] - Python native scalars""" +def test_sub_i_py_scalar() -> None: + """Test pd.Series[Any] (int) - Python native scalars""" b, i, f, c = True, 1, 1.0, 1j check(assert_type(left_i - b, pd.Series), pd.Series) @@ -45,8 +53,8 @@ def test_sub_py_scalar() -> None: check(assert_type(left_i.rsub(c), pd.Series), pd.Series) -def test_sub_py_sequence() -> None: - """Test pd.Series[Any] - Python native sequence""" +def test_sub_i_py_sequence() -> None: + """Test pd.Series[Any] (int) - Python native sequence""" b, i, f, c = [True, False, True], [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] check(assert_type(left_i - b, pd.Series), pd.Series) @@ -70,8 +78,8 @@ def test_sub_py_sequence() -> None: check(assert_type(left_i.rsub(c), pd.Series), pd.Series) -def test_sub_numpy_array() -> None: - """Test pd.Series[Any] - numpy array""" +def test_sub_i_numpy_array() -> None: + """Test pd.Series[Any] (int) - numpy array""" b = np.array([True, False, True], np.bool_) i = np.array([2, 3, 5], np.int64) f = np.array([1.0, 2.0, 3.0], np.float64) @@ -108,85 +116,112 @@ def test_sub_numpy_array() -> None: check(assert_type(left_i.rsub(c), pd.Series), pd.Series) -def test_sub_pd_series() -> None: - """Test pd.Series[Any] - pandas series""" +def test_sub_i_pd_series() -> None: + """Test pd.Series[Any] (int) - pandas series""" + a = pd.DataFrame({"a": [1, 2, 3]})["a"] b = pd.Series([True, False, True]) i = pd.Series([2, 3, 5]) f = pd.Series([1.0, 2.0, 3.0]) c = pd.Series([1.1j, 2.2j, 4.1j]) + check(assert_type(left_i - a, pd.Series), pd.Series) check(assert_type(left_i - b, pd.Series), pd.Series) check(assert_type(left_i - i, pd.Series), pd.Series) check(assert_type(left_i - f, pd.Series), pd.Series) check(assert_type(left_i - c, pd.Series), pd.Series) + check(assert_type(a - left_i, pd.Series), pd.Series) check(assert_type(b - left_i, pd.Series), pd.Series) check(assert_type(i - left_i, pd.Series), pd.Series) check(assert_type(f - left_i, pd.Series), pd.Series) check(assert_type(c - left_i, pd.Series), pd.Series) + check(assert_type(left_i.sub(a), pd.Series), pd.Series) check(assert_type(left_i.sub(b), pd.Series), pd.Series) check(assert_type(left_i.sub(i), pd.Series), pd.Series) check(assert_type(left_i.sub(f), pd.Series), pd.Series) check(assert_type(left_i.sub(c), pd.Series), pd.Series) + check(assert_type(left_i.rsub(a), pd.Series), pd.Series) check(assert_type(left_i.rsub(b), pd.Series), pd.Series) check(assert_type(left_i.rsub(i), pd.Series), pd.Series) check(assert_type(left_i.rsub(f), pd.Series), pd.Series) check(assert_type(left_i.rsub(c), pd.Series), pd.Series) -anchor = datetime(2025, 8, 18) -left_ts = pd.DataFrame({"a": [anchor + timedelta(hours=h + 1) for h in range(3)]})["a"] - - -def test_sub_py_datetime() -> None: - """Test pd.Series[Any] - Python native datetime""" +def test_sub_ts_py_datetime() -> None: + """Test pd.Series[Any] (Timestamp | Timedelta) - Python native datetime""" s = anchor check(assert_type(left_ts - s, "TimedeltaSeries"), pd.Series, pd.Timedelta) - check(assert_type(s - left_ts, "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(s - left_ts, pd.Series), pd.Series, pd.Timedelta) + check(assert_type(s - left_td, pd.Series), pd.Series, pd.Timestamp) check(assert_type(left_ts.sub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) - check(assert_type(left_ts.rsub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(left_ts.rsub(s), pd.Series), pd.Series, pd.Timedelta) + check(assert_type(left_td.rsub(s), pd.Series), pd.Series, pd.Timestamp) -def test_sub_numpy_datetime() -> None: - """Test pd.Series[Any] - numpy datetime(s)""" +def test_sub_ts_numpy_datetime() -> None: + """Test pd.Series[Any] (Timestamp | Timedelta) - numpy datetime(s)""" s = np.datetime64(anchor) a = np.array([s + np.timedelta64(m, "m") for m in range(3)], np.datetime64) check(assert_type(left_ts - s, "TimedeltaSeries"), pd.Series, pd.Timedelta) + if TYPE_CHECKING_INVALID_USAGE: + _0 = left_td - s check(assert_type(left_ts - a, "TimedeltaSeries"), pd.Series, pd.Timedelta) + if TYPE_CHECKING_INVALID_USAGE: + _1 = left_td - a - check(assert_type(s - left_ts, "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(s - left_ts, pd.Series), pd.Series, pd.Timedelta) + check(assert_type(s - left_td, pd.Series), pd.Series, pd.Timestamp) # `numpy` typing gives the corresponding `ndarray`s in the static type # checking, where our `__rsub__` cannot override. At runtime, they return # `Series`s. check(assert_type(a - left_ts, "npt.NDArray[np.datetime64]"), pd.Series, pd.Timedelta) # type: ignore[assert-type] + check(assert_type(a - left_td, "npt.NDArray[np.datetime64]"), pd.Series, pd.Timestamp) # type: ignore[assert-type] check(assert_type(left_ts.sub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) + if TYPE_CHECKING_INVALID_USAGE: + left_td.sub(s) check(assert_type(left_ts.sub(a), "TimedeltaSeries"), pd.Series, pd.Timedelta) + if TYPE_CHECKING_INVALID_USAGE: + left_td.sub(a) - check(assert_type(left_ts.rsub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) - check(assert_type(left_ts.rsub(a), "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(left_ts.rsub(s), pd.Series), pd.Series, pd.Timedelta) + check(assert_type(left_td.rsub(s), pd.Series), pd.Series, pd.Timestamp) + check(assert_type(left_ts.rsub(a), pd.Series), pd.Series, pd.Timedelta) + check(assert_type(left_td.rsub(a), pd.Series), pd.Series, pd.Timestamp) -def test_sub_pd_datetime() -> None: - """Test pd.Series[Any] - Pandas datetime(s)""" +def test_sub_ts_pd_datetime() -> None: + """Test pd.Series[Any] (Timestamp | Timedelta) - Pandas datetime(s)""" s = pd.Timestamp(anchor) a = pd.Series([s + pd.Timedelta(minutes=m) for m in range(3)]) check(assert_type(left_ts - s, "TimedeltaSeries"), pd.Series, pd.Timedelta) + if TYPE_CHECKING_INVALID_USAGE: + _0 = left_td - s check(assert_type(left_ts - a, "TimedeltaSeries"), pd.Series, pd.Timedelta) + if TYPE_CHECKING_INVALID_USAGE: + _1 = left_ts - a - check(assert_type(s - left_ts, "TimedeltaSeries"), pd.Series, pd.Timedelta) - check(assert_type(a - left_ts, "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(s - left_ts, pd.Series), pd.Series, pd.Timedelta) + check(assert_type(s - left_td, pd.Series), pd.Series, pd.Timestamp) + check(assert_type(a - left_ts, pd.Series), pd.Series, pd.Timedelta) + check(assert_type(a - left_td, pd.Series), pd.Series, pd.Timestamp) check(assert_type(left_ts.sub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) + if TYPE_CHECKING_INVALID_USAGE: + left_td.sub(s) check(assert_type(left_ts.sub(a), "TimedeltaSeries"), pd.Series, pd.Timedelta) + if TYPE_CHECKING_INVALID_USAGE: + left_td.sub(a) - check(assert_type(left_ts.rsub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) - check(assert_type(left_ts.rsub(a), "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(left_ts.rsub(s), pd.Series), pd.Series, pd.Timedelta) + check(assert_type(left_td.rsub(s), pd.Series), pd.Series, pd.Timestamp) + check(assert_type(left_ts.rsub(a), pd.Series), pd.Series, pd.Timedelta) + check(assert_type(left_td.rsub(a), pd.Series), pd.Series, pd.Timestamp) diff --git a/tests/series/arithmetic/test_truediv.py b/tests/series/arithmetic/test_truediv.py index 155a0c0e..69d408b3 100644 --- a/tests/series/arithmetic/test_truediv.py +++ b/tests/series/arithmetic/test_truediv.py @@ -10,165 +10,172 @@ check, ) -left = pd.DataFrame({"a": [1, 2, 3]})["a"] # left operand +left_i = pd.DataFrame({"a": [1, 2, 3]})["a"] # left operand def test_truediv_py_scalar() -> None: - """Test pd.Series[Any] / Python native scalars""" + """Test pd.Series[Any] (int) / Python native scalars""" b, i, f, c = True, 1, 1.0, 1j - check(assert_type(left / b, pd.Series), pd.Series) - check(assert_type(left / i, pd.Series), pd.Series) - check(assert_type(left / f, pd.Series), pd.Series) - check(assert_type(left / c, pd.Series), pd.Series) + check(assert_type(left_i / b, pd.Series), pd.Series) + check(assert_type(left_i / i, pd.Series), pd.Series) + check(assert_type(left_i / f, pd.Series), pd.Series) + check(assert_type(left_i / c, pd.Series), pd.Series) - check(assert_type(b / left, pd.Series), pd.Series) - check(assert_type(i / left, pd.Series), pd.Series) - check(assert_type(f / left, pd.Series), pd.Series) - check(assert_type(c / left, pd.Series), pd.Series) + check(assert_type(b / left_i, pd.Series), pd.Series) + check(assert_type(i / left_i, pd.Series), pd.Series) + check(assert_type(f / left_i, pd.Series), pd.Series) + check(assert_type(c / left_i, pd.Series), pd.Series) - check(assert_type(left.truediv(b), pd.Series), pd.Series) - check(assert_type(left.truediv(i), pd.Series), pd.Series) - check(assert_type(left.truediv(f), pd.Series), pd.Series) - check(assert_type(left.truediv(c), pd.Series), pd.Series) + check(assert_type(left_i.truediv(b), pd.Series), pd.Series) + check(assert_type(left_i.truediv(i), pd.Series), pd.Series) + check(assert_type(left_i.truediv(f), pd.Series), pd.Series) + check(assert_type(left_i.truediv(c), pd.Series), pd.Series) - check(assert_type(left.div(b), pd.Series), pd.Series) - check(assert_type(left.div(i), pd.Series), pd.Series) - check(assert_type(left.div(f), pd.Series), pd.Series) - check(assert_type(left.div(c), pd.Series), pd.Series) + check(assert_type(left_i.div(b), pd.Series), pd.Series) + check(assert_type(left_i.div(i), pd.Series), pd.Series) + check(assert_type(left_i.div(f), pd.Series), pd.Series) + check(assert_type(left_i.div(c), pd.Series), pd.Series) - check(assert_type(left.rtruediv(b), pd.Series), pd.Series) - check(assert_type(left.rtruediv(i), pd.Series), pd.Series) - check(assert_type(left.rtruediv(f), pd.Series), pd.Series) - check(assert_type(left.rtruediv(c), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(b), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(i), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(f), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(c), pd.Series), pd.Series) - check(assert_type(left.rdiv(b), pd.Series), pd.Series) - check(assert_type(left.rdiv(i), pd.Series), pd.Series) - check(assert_type(left.rdiv(f), pd.Series), pd.Series) - check(assert_type(left.rdiv(c), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(b), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(i), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(f), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(c), pd.Series), pd.Series) def test_truediv_py_sequence() -> None: - """Test pd.Series[Any] / Python native sequence""" + """Test pd.Series[Any] (int) / Python native sequence""" b, i, f, c = [True, False, True], [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] - check(assert_type(left / b, pd.Series), pd.Series) - check(assert_type(left / i, pd.Series), pd.Series) - check(assert_type(left / f, pd.Series), pd.Series) - check(assert_type(left / c, pd.Series), pd.Series) + check(assert_type(left_i / b, pd.Series), pd.Series) + check(assert_type(left_i / i, pd.Series), pd.Series) + check(assert_type(left_i / f, pd.Series), pd.Series) + check(assert_type(left_i / c, pd.Series), pd.Series) - check(assert_type(b / left, pd.Series), pd.Series) - check(assert_type(i / left, pd.Series), pd.Series) - check(assert_type(f / left, pd.Series), pd.Series) - check(assert_type(c / left, pd.Series), pd.Series) + check(assert_type(b / left_i, pd.Series), pd.Series) + check(assert_type(i / left_i, pd.Series), pd.Series) + check(assert_type(f / left_i, pd.Series), pd.Series) + check(assert_type(c / left_i, pd.Series), pd.Series) - check(assert_type(left.truediv(b), pd.Series), pd.Series) - check(assert_type(left.truediv(i), pd.Series), pd.Series) - check(assert_type(left.truediv(f), pd.Series), pd.Series) - check(assert_type(left.truediv(c), pd.Series), pd.Series) + check(assert_type(left_i.truediv(b), pd.Series), pd.Series) + check(assert_type(left_i.truediv(i), pd.Series), pd.Series) + check(assert_type(left_i.truediv(f), pd.Series), pd.Series) + check(assert_type(left_i.truediv(c), pd.Series), pd.Series) - check(assert_type(left.div(b), pd.Series), pd.Series) - check(assert_type(left.div(i), pd.Series), pd.Series) - check(assert_type(left.div(f), pd.Series), pd.Series) - check(assert_type(left.div(c), pd.Series), pd.Series) + check(assert_type(left_i.div(b), pd.Series), pd.Series) + check(assert_type(left_i.div(i), pd.Series), pd.Series) + check(assert_type(left_i.div(f), pd.Series), pd.Series) + check(assert_type(left_i.div(c), pd.Series), pd.Series) - check(assert_type(left.rtruediv(b), pd.Series), pd.Series) - check(assert_type(left.rtruediv(i), pd.Series), pd.Series) - check(assert_type(left.rtruediv(f), pd.Series), pd.Series) - check(assert_type(left.rtruediv(c), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(b), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(i), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(f), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(c), pd.Series), pd.Series) - check(assert_type(left.rdiv(b), pd.Series), pd.Series) - check(assert_type(left.rdiv(i), pd.Series), pd.Series) - check(assert_type(left.rdiv(f), pd.Series), pd.Series) - check(assert_type(left.rdiv(c), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(b), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(i), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(f), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(c), pd.Series), pd.Series) def test_truediv_numpy_array() -> None: - """Test pd.Series[Any] / numpy array""" + """Test pd.Series[Any] (int) / numpy array""" b = np.array([True, False, True], np.bool_) i = np.array([2, 3, 5], np.int64) f = np.array([1.0, 2.0, 3.0], np.float64) c = np.array([1.1j, 2.2j, 4.1j], np.complex64) - check(assert_type(left / b, pd.Series), pd.Series) - check(assert_type(left / i, pd.Series), pd.Series) - check(assert_type(left / f, pd.Series), pd.Series) - check(assert_type(left / c, pd.Series), pd.Series) + check(assert_type(left_i / b, pd.Series), pd.Series) + check(assert_type(left_i / i, pd.Series), pd.Series) + check(assert_type(left_i / f, pd.Series), pd.Series) + check(assert_type(left_i / c, pd.Series), pd.Series) # `numpy` typing gives the corresponding `ndarray`s in the static type # checking, where our `__rtruediv__` cannot override. At runtime, they return # `Series`s. # `mypy` thinks the return types are `Any`, which is a bug. check( - assert_type(b / left, "npt.NDArray[np.float64]"), pd.Series # type: ignore[assert-type] + assert_type(b / left_i, "npt.NDArray[np.float64]"), pd.Series # type: ignore[assert-type] ) check( - assert_type(i / left, "npt.NDArray[np.float64]"), pd.Series # type: ignore[assert-type] + assert_type(i / left_i, "npt.NDArray[np.float64]"), pd.Series # type: ignore[assert-type] ) check( - assert_type(f / left, "npt.NDArray[np.float64]"), pd.Series # type: ignore[assert-type] + assert_type(f / left_i, "npt.NDArray[np.float64]"), pd.Series # type: ignore[assert-type] ) check( - assert_type(c / left, "npt.NDArray[np.complex128]"), pd.Series # type: ignore[assert-type] + assert_type(c / left_i, "npt.NDArray[np.complex128]"), pd.Series # type: ignore[assert-type] ) - check(assert_type(left.truediv(b), pd.Series), pd.Series) - check(assert_type(left.truediv(i), pd.Series), pd.Series) - check(assert_type(left.truediv(f), pd.Series), pd.Series) - check(assert_type(left.truediv(c), pd.Series), pd.Series) + check(assert_type(left_i.truediv(b), pd.Series), pd.Series) + check(assert_type(left_i.truediv(i), pd.Series), pd.Series) + check(assert_type(left_i.truediv(f), pd.Series), pd.Series) + check(assert_type(left_i.truediv(c), pd.Series), pd.Series) - check(assert_type(left.div(b), pd.Series), pd.Series) - check(assert_type(left.div(i), pd.Series), pd.Series) - check(assert_type(left.div(f), pd.Series), pd.Series) - check(assert_type(left.div(c), pd.Series), pd.Series) + check(assert_type(left_i.div(b), pd.Series), pd.Series) + check(assert_type(left_i.div(i), pd.Series), pd.Series) + check(assert_type(left_i.div(f), pd.Series), pd.Series) + check(assert_type(left_i.div(c), pd.Series), pd.Series) - check(assert_type(left.rtruediv(b), pd.Series), pd.Series) - check(assert_type(left.rtruediv(i), pd.Series), pd.Series) - check(assert_type(left.rtruediv(f), pd.Series), pd.Series) - check(assert_type(left.rtruediv(c), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(b), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(i), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(f), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(c), pd.Series), pd.Series) - check(assert_type(left.rdiv(b), pd.Series), pd.Series) - check(assert_type(left.rdiv(i), pd.Series), pd.Series) - check(assert_type(left.rdiv(f), pd.Series), pd.Series) - check(assert_type(left.rdiv(c), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(b), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(i), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(f), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(c), pd.Series), pd.Series) def test_truediv_pd_series() -> None: - """Test pd.Series[Any] / pandas series""" + """Test pd.Series[Any] (int) / pandas series""" + a = pd.DataFrame({"a": [1, 2, 3]})["a"] b = pd.Series([True, False, True]) i = pd.Series([2, 3, 5]) f = pd.Series([1.0, 2.0, 3.0]) c = pd.Series([1.1j, 2.2j, 4.1j]) - check(assert_type(left / b, pd.Series), pd.Series) - check(assert_type(left / i, pd.Series), pd.Series) - check(assert_type(left / f, pd.Series), pd.Series) - check(assert_type(left / c, pd.Series), pd.Series) - - check(assert_type(b / left, pd.Series), pd.Series) - check(assert_type(i / left, pd.Series), pd.Series) - check(assert_type(f / left, pd.Series), pd.Series) - check(assert_type(c / left, pd.Series), pd.Series) - - check(assert_type(left.truediv(b), pd.Series), pd.Series) - check(assert_type(left.truediv(i), pd.Series), pd.Series) - check(assert_type(left.truediv(f), pd.Series), pd.Series) - check(assert_type(left.truediv(c), pd.Series), pd.Series) - - check(assert_type(left.div(b), pd.Series), pd.Series) - check(assert_type(left.div(i), pd.Series), pd.Series) - check(assert_type(left.div(f), pd.Series), pd.Series) - check(assert_type(left.div(c), pd.Series), pd.Series) - - check(assert_type(left.rtruediv(b), pd.Series), pd.Series) - check(assert_type(left.rtruediv(i), pd.Series), pd.Series) - check(assert_type(left.rtruediv(f), pd.Series), pd.Series) - check(assert_type(left.rtruediv(c), pd.Series), pd.Series) - - check(assert_type(left.rdiv(b), pd.Series), pd.Series) - check(assert_type(left.rdiv(i), pd.Series), pd.Series) - check(assert_type(left.rdiv(f), pd.Series), pd.Series) - check(assert_type(left.rdiv(c), pd.Series), pd.Series) + check(assert_type(left_i / a, pd.Series), pd.Series) + check(assert_type(left_i / b, pd.Series), pd.Series) + check(assert_type(left_i / i, pd.Series), pd.Series) + check(assert_type(left_i / f, pd.Series), pd.Series) + check(assert_type(left_i / c, pd.Series), pd.Series) + + check(assert_type(a / left_i, pd.Series), pd.Series) + check(assert_type(b / left_i, pd.Series), pd.Series) + check(assert_type(i / left_i, pd.Series), pd.Series) + check(assert_type(f / left_i, pd.Series), pd.Series) + check(assert_type(c / left_i, pd.Series), pd.Series) + + check(assert_type(left_i.truediv(a), pd.Series), pd.Series) + check(assert_type(left_i.truediv(b), pd.Series), pd.Series) + check(assert_type(left_i.truediv(i), pd.Series), pd.Series) + check(assert_type(left_i.truediv(f), pd.Series), pd.Series) + check(assert_type(left_i.truediv(c), pd.Series), pd.Series) + + check(assert_type(left_i.div(a), pd.Series), pd.Series) + check(assert_type(left_i.div(b), pd.Series), pd.Series) + check(assert_type(left_i.div(i), pd.Series), pd.Series) + check(assert_type(left_i.div(f), pd.Series), pd.Series) + check(assert_type(left_i.div(c), pd.Series), pd.Series) + + check(assert_type(left_i.rtruediv(a), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(b), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(i), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(f), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(c), pd.Series), pd.Series) + + check(assert_type(left_i.rdiv(a), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(b), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(i), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(f), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(c), pd.Series), pd.Series) def test_truediv_paths(tmp_path: Path) -> None: diff --git a/tests/series/test_series.py b/tests/series/test_series.py index af90cde4..12326fd1 100644 --- a/tests/series/test_series.py +++ b/tests/series/test_series.py @@ -844,17 +844,20 @@ def test_types_element_wise_arithmetic() -> None: def test_types_scalar_arithmetic() -> None: s = pd.Series([0, 1, -10]) - check(assert_type(s + 1, "pd.Series[int]"), pd.Series, np.integer) check(assert_type(s.add(1, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) - res_sub: pd.Series = s - 1 - res_sub2: pd.Series = s.sub(1, fill_value=0) + check(assert_type(s.sub(1, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) - res_mul: pd.Series = s * 2 - res_mul2: pd.Series = s.mul(2, fill_value=0) + check(assert_type(s.mul(1, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) - res_div: pd.Series = s / 2 - res_div2: pd.Series = s.div(2, fill_value=0) + check( + assert_type(s.truediv(2, fill_value=0), "pd.Series[float]"), + pd.Series, + np.floating, + ) + check( + assert_type(s.div(2, fill_value=0), "pd.Series[float]"), pd.Series, np.floating + ) res_floordiv: pd.Series = s // 2 res_floordiv2: pd.Series = s.floordiv(2, fill_value=0) @@ -868,14 +871,6 @@ def test_types_scalar_arithmetic() -> None: res_pow3: pd.Series = s.pow(0.5) -def test_types_complex_arithmetic() -> None: - """Test adding complex number to pd.Series[float] GH 103.""" - c = 1 + 1j - s = pd.Series([1.0, 2.0, 3.0]) - x = s + c - y = s - c - - def test_types_groupby() -> None: s = pd.Series([4, 2, 1, 8], index=["a", "b", "a", "b"]) s.groupby(["a", "b", "a", "b"]) @@ -1594,16 +1589,8 @@ def test_series_loc_setitem() -> None: def test_series_min_max_sub_axis() -> None: df = pd.DataFrame({"x": [1, 2, 3, 4, 5], "y": [5, 4, 3, 2, 1]}) - s1 = df.min(axis=1) - s2 = df.max(axis=1) - sa = s1 + s2 - ss = s1 - s2 - sm = s1 * s2 - sd = s1 / s2 - check(assert_type(sa, pd.Series), pd.Series) - check(assert_type(ss, pd.Series), pd.Series) - check(assert_type(sm, pd.Series), pd.Series) - check(assert_type(sd, pd.Series), pd.Series) + check(assert_type(df.min(axis=1), pd.Series), pd.Series) + check(assert_type(df.max(axis=1), pd.Series), pd.Series) def test_series_index_isin() -> None: @@ -1654,12 +1641,6 @@ def test_reset_index() -> None: assert assert_type(s.reset_index(inplace=True, drop=True), None) is None -def test_series_add_str() -> None: - s = pd.Series(["abc", "def"]) - check(assert_type(s + "x", "pd.Series[str]"), pd.Series, str) - check(assert_type("x" + s, "pd.Series[str]"), pd.Series, str) - - def test_series_dtype() -> None: s = pd.Series(["abc", "def"], dtype=str) check(assert_type(s, "pd.Series[str]"), pd.Series, str) From 1fb597b0c273f06882eb97e36e66dc41486801cb Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Sat, 23 Aug 2025 10:41:51 +0200 Subject: [PATCH 02/11] fix(comment): https://github.com/pandas-dev/pandas-stubs/pull/1343#discussion_r2294233799 --- tests/series/arithmetic/test_sub.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/series/arithmetic/test_sub.py b/tests/series/arithmetic/test_sub.py index f6c489e4..0e76a012 100644 --- a/tests/series/arithmetic/test_sub.py +++ b/tests/series/arithmetic/test_sub.py @@ -207,7 +207,7 @@ def test_sub_ts_pd_datetime() -> None: _0 = left_td - s check(assert_type(left_ts - a, "TimedeltaSeries"), pd.Series, pd.Timedelta) if TYPE_CHECKING_INVALID_USAGE: - _1 = left_ts - a + _1 = left_td - a check(assert_type(s - left_ts, pd.Series), pd.Series, pd.Timedelta) check(assert_type(s - left_td, pd.Series), pd.Series, pd.Timestamp) From 961d692b6dac8b20856237d6a1cbfa7837d91f72 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Sun, 24 Aug 2025 11:57:20 +0200 Subject: [PATCH 03/11] chore(typing): update mypy and ty --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 046abe57..37236a87 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,12 +35,12 @@ types-pytz = ">= 2022.1.1" numpy = ">= 1.23.5" [tool.poetry.group.dev.dependencies] -mypy = "1.17.0" +mypy = "1.17.1" pandas = "2.3.1" pyarrow = ">=10.0.1" pytest = ">=7.1.2" pyright = ">=1.1.404" -ty = "^0.0.1a8" +ty = "^0.0.1a9" pyrefly = "^0.21.0" poethepoet = ">=0.16.5" loguru = ">=0.6.0" From e0b5b5903e9c694059d8a288cdcaa45b8b8379f1 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Sun, 24 Aug 2025 22:26:57 +0200 Subject: [PATCH 04/11] fix(comment): without str https://github.com/pandas-dev/pandas-stubs/pull/1343#issuecomment-3218217255 --- .pre-commit-config.yaml | 2 +- pandas-stubs/core/series.pyi | 45 ++++++---- tests/series/arithmetic/test_add.py | 4 +- tests/series/arithmetic/test_sub.py | 125 ++++++++++++++++------------ 4 files changed, 104 insertions(+), 72 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 02e86620..c04587ea 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ repos: hooks: - id: isort - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.3 + rev: v0.12.10 hooks: - id: ruff-check args: [ diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 938360a4..a4f9bfd3 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -186,7 +186,6 @@ from pandas._typing import ( np_ndarray_anyint, np_ndarray_bool, np_ndarray_complex, - np_ndarray_dt, np_ndarray_float, np_ndarray_str, np_ndarray_td, @@ -264,6 +263,16 @@ class _LocIndexerSeries(_LocIndexer, Generic[S1]): _ListLike: TypeAlias = ( ArrayLike | dict[_str, np.ndarray] | Sequence[S1] | IndexOpsMixin[S1] ) +_NumListLike: TypeAlias = ( + ExtensionArray + | np_ndarray_bool + | np_ndarray_anyint + | np_ndarray_float + | np_ndarray_complex + | dict[_str, np.ndarray] + | Sequence[complex] + | IndexOpsMixin[complex] +) class Series(IndexOpsMixin[S1], NDFrame): # Define __index__ because mypy thinks Series follows protocol `SupportsIndex` https://github.com/pandas-dev/pandas-stubs/pull/1332#discussion_r2285648790 @@ -2475,12 +2484,11 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __rxor__(self, other: int | np_ndarray_anyint | Series[int]) -> Series[int]: ... @overload - def __sub__( - self: Series[Never], - other: datetime | np.datetime64 | np_ndarray_dt | TimestampSeries, - ) -> TimedeltaSeries: ... + def __sub__(self: Series[Never], other: TimestampSeries) -> Never: ... @overload - def __sub__(self: Series[Never], other: complex | _ListLike | Series) -> Series: ... + def __sub__( + self: Series[Never], other: complex | _NumListLike | Series + ) -> Series: ... @overload def __sub__(self, other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] @overload @@ -2571,15 +2579,15 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def sub( self: Series[Never], - other: datetime | np.datetime64 | np_ndarray_dt | TimestampSeries, + other: TimestampSeries, level: Level | None = None, fill_value: float | None = None, axis: int = 0, - ) -> TimedeltaSeries: ... + ) -> Never: ... @overload def sub( self: Series[Never], - other: complex | _ListLike | Series, + other: complex | _NumListLike | Series, level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2705,9 +2713,10 @@ class Series(IndexOpsMixin[S1], NDFrame): axis: int = 0, ) -> TimedeltaSeries: ... @overload - def __rsub__( # type: ignore[overload-overlap] - self: Series[Never], - other: complex | datetime | np.datetime64 | _ListLike | Series, + def __rsub__(self: Series[Never], other: TimestampSeries) -> Never: ... # type: ignore[misc] + @overload + def __rsub__( + self: Series[Never], other: complex | _NumListLike | Series ) -> Series: ... @overload def __rsub__(self, other: Series[Never]) -> Series: ... @@ -2775,13 +2784,17 @@ class Series(IndexOpsMixin[S1], NDFrame): ), ) -> Series[complex]: ... @overload - def __rsub__( - self: Series[Timestamp], other: datetime | np.datetime64 | np_ndarray_dt - ) -> TimedeltaSeries: ... + def rsub( + self: Series[Never], + other: TimestampSeries, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Never: ... @overload def rsub( self: Series[Never], - other: complex | datetime | np.datetime64 | _ListLike | Series, + other: complex | _NumListLike | Series, level: Level | None = None, fill_value: float | None = None, axis: int = 0, diff --git a/tests/series/arithmetic/test_add.py b/tests/series/arithmetic/test_add.py index c663cc47..d2d9630a 100644 --- a/tests/series/arithmetic/test_add.py +++ b/tests/series/arithmetic/test_add.py @@ -5,7 +5,9 @@ from tests import check -left_i = pd.DataFrame({"a": [1, 2, 3]})["a"] # left operand +# left operands +left_i = pd.DataFrame({"a": [1, 2, 3]})["a"] +left_str = pd.DataFrame({"a": ["1", "2", "3_"]})["a"] def test_add_i_py_scalar() -> None: diff --git a/tests/series/arithmetic/test_sub.py b/tests/series/arithmetic/test_sub.py index 0e76a012..a6df939f 100644 --- a/tests/series/arithmetic/test_sub.py +++ b/tests/series/arithmetic/test_sub.py @@ -2,24 +2,21 @@ datetime, timedelta, ) -from typing import ( - TYPE_CHECKING, - NoReturn, -) +from typing import NoReturn import numpy as np from numpy import typing as npt # noqa: F401 import pandas as pd -from typing_extensions import assert_type +from typing_extensions import ( + Never, + assert_type, +) from tests import ( TYPE_CHECKING_INVALID_USAGE, check, ) -if TYPE_CHECKING: - from pandas.core.series import TimedeltaSeries # noqa: F401 - anchor = datetime(2025, 8, 18) # left operands @@ -152,16 +149,28 @@ def test_sub_i_pd_series() -> None: def test_sub_ts_py_datetime() -> None: """Test pd.Series[Any] (Timestamp | Timedelta) - Python native datetime""" s = anchor + a = [s + timedelta(minutes=m) for m in range(3)] - check(assert_type(left_ts - s, "TimedeltaSeries"), pd.Series, pd.Timedelta) + if TYPE_CHECKING_INVALID_USAGE: + _0 = left_ts - s # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + _1 = left_ts - a # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + _2 = left_td - s # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + _3 = left_td - a # type: ignore[operator] # pyright: ignore[reportOperatorIssue] - check(assert_type(s - left_ts, pd.Series), pd.Series, pd.Timedelta) - check(assert_type(s - left_td, pd.Series), pd.Series, pd.Timestamp) + _4 = s - left_ts # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + _5 = a - left_ts # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + _6 = s - left_td # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + _7 = a - left_td # type: ignore[operator] # pyright: ignore[reportOperatorIssue] - check(assert_type(left_ts.sub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) + left_ts.sub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_ts.sub(a) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_td.sub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_td.sub(a) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] - check(assert_type(left_ts.rsub(s), pd.Series), pd.Series, pd.Timedelta) - check(assert_type(left_td.rsub(s), pd.Series), pd.Series, pd.Timestamp) + left_ts.rsub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_ts.rsub(a) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_td.rsub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_td.rsub(a) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] def test_sub_ts_numpy_datetime() -> None: @@ -169,32 +178,28 @@ def test_sub_ts_numpy_datetime() -> None: s = np.datetime64(anchor) a = np.array([s + np.timedelta64(m, "m") for m in range(3)], np.datetime64) - check(assert_type(left_ts - s, "TimedeltaSeries"), pd.Series, pd.Timedelta) - if TYPE_CHECKING_INVALID_USAGE: - _0 = left_td - s - check(assert_type(left_ts - a, "TimedeltaSeries"), pd.Series, pd.Timedelta) if TYPE_CHECKING_INVALID_USAGE: - _1 = left_td - a + # `numpy` typing gives the corresponding `ndarray`s in the static type + # checking, where our `__rsub__` cannot override. + _0 = left_ts - s # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + # _1 = left_ts - a + _2 = left_td - s # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + # _3 = left_td - a - check(assert_type(s - left_ts, pd.Series), pd.Series, pd.Timedelta) - check(assert_type(s - left_td, pd.Series), pd.Series, pd.Timestamp) - # `numpy` typing gives the corresponding `ndarray`s in the static type - # checking, where our `__rsub__` cannot override. At runtime, they return - # `Series`s. - check(assert_type(a - left_ts, "npt.NDArray[np.datetime64]"), pd.Series, pd.Timedelta) # type: ignore[assert-type] - check(assert_type(a - left_td, "npt.NDArray[np.datetime64]"), pd.Series, pd.Timestamp) # type: ignore[assert-type] + _4 = s - left_ts # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + # _5 = a - left_ts + _6 = s - left_td # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + # _7 = a - left_td - check(assert_type(left_ts.sub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) - if TYPE_CHECKING_INVALID_USAGE: - left_td.sub(s) - check(assert_type(left_ts.sub(a), "TimedeltaSeries"), pd.Series, pd.Timedelta) - if TYPE_CHECKING_INVALID_USAGE: - left_td.sub(a) + left_ts.sub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_ts.sub(a) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_td.sub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_td.sub(a) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] - check(assert_type(left_ts.rsub(s), pd.Series), pd.Series, pd.Timedelta) - check(assert_type(left_td.rsub(s), pd.Series), pd.Series, pd.Timestamp) - check(assert_type(left_ts.rsub(a), pd.Series), pd.Series, pd.Timedelta) - check(assert_type(left_td.rsub(a), pd.Series), pd.Series, pd.Timestamp) + left_ts.rsub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_ts.rsub(a) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_td.rsub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_td.rsub(a) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] def test_sub_ts_pd_datetime() -> None: @@ -202,26 +207,38 @@ def test_sub_ts_pd_datetime() -> None: s = pd.Timestamp(anchor) a = pd.Series([s + pd.Timedelta(minutes=m) for m in range(3)]) - check(assert_type(left_ts - s, "TimedeltaSeries"), pd.Series, pd.Timedelta) - if TYPE_CHECKING_INVALID_USAGE: - _0 = left_td - s - check(assert_type(left_ts - a, "TimedeltaSeries"), pd.Series, pd.Timedelta) if TYPE_CHECKING_INVALID_USAGE: - _1 = left_td - a + _0 = left_ts - s # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + assert_type(left_ts - a, Never) + _2 = left_td - s # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + assert_type(left_td - a, Never) - check(assert_type(s - left_ts, pd.Series), pd.Series, pd.Timedelta) - check(assert_type(s - left_td, pd.Series), pd.Series, pd.Timestamp) - check(assert_type(a - left_ts, pd.Series), pd.Series, pd.Timedelta) - check(assert_type(a - left_td, pd.Series), pd.Series, pd.Timestamp) + _4 = s - left_ts # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + assert_type(a - left_ts, Never) + _6 = s - left_td # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + assert_type(a - left_td, Never) + + left_ts.sub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + assert_type(left_ts.sub(a), Never) + + +def test_sub_ts_pd_datetime_1() -> None: + """Test pd.Series[Any] (Timestamp | Timedelta) - Pandas datetime(s)""" + s = pd.Timestamp(anchor) + a = pd.Series([s + pd.Timedelta(minutes=m) for m in range(3)]) - check(assert_type(left_ts.sub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) - if TYPE_CHECKING_INVALID_USAGE: - left_td.sub(s) - check(assert_type(left_ts.sub(a), "TimedeltaSeries"), pd.Series, pd.Timedelta) if TYPE_CHECKING_INVALID_USAGE: - left_td.sub(a) + # When I merge this one to the previous one, mypy does not allow me to pass + left_ts.rsub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + assert_type(left_ts.rsub(a), Never) - check(assert_type(left_ts.rsub(s), pd.Series), pd.Series, pd.Timedelta) - check(assert_type(left_td.rsub(s), pd.Series), pd.Series, pd.Timestamp) - check(assert_type(left_ts.rsub(a), pd.Series), pd.Series, pd.Timedelta) - check(assert_type(left_td.rsub(a), pd.Series), pd.Series, pd.Timestamp) + +def test_sub_ts_pd_datetime_2() -> None: + """Test pd.Series[Any] (Timestamp | Timedelta) - Pandas datetime(s)""" + s = pd.Timestamp(anchor) + a = pd.Series([s + pd.Timedelta(minutes=m) for m in range(3)]) + + if TYPE_CHECKING_INVALID_USAGE: + # When I merge this one to the previous one, mypy does not allow me to pass + left_td.rsub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + assert_type(left_td.rsub(a), Never) From 5c23f687b091b04e57725d858bb8ac3704b18bdc Mon Sep 17 00:00:00 2001 From: Irv Lustig Date: Mon, 25 Aug 2025 10:47:00 -0400 Subject: [PATCH 05/11] handle str ops --- pandas-stubs/core/series.pyi | 21 +++++++++++++-------- tests/series/arithmetic/test_sub.py | 10 ++++++++++ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index a4f9bfd3..118fb149 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -260,7 +260,8 @@ class _LocIndexerSeries(_LocIndexer, Generic[S1]): value: S1 | ArrayLike | Series[S1] | None, ) -> None: ... -_ListLike: TypeAlias = ( +_ListLike: TypeAlias = ArrayLike | dict[_str, np.ndarray] | SequenceNotStr[S1] +_ListLikeS1: TypeAlias = ( ArrayLike | dict[_str, np.ndarray] | Sequence[S1] | IndexOpsMixin[S1] ) _NumListLike: TypeAlias = ( @@ -428,7 +429,9 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __new__( cls, - data: S1 | _ListLike[S1] | dict[HashableT1, S1] | KeysView[S1] | ValuesView[S1], + data: ( + S1 | _ListLikeS1[S1] | dict[HashableT1, S1] | KeysView[S1] | ValuesView[S1] + ), index: AxesData | None = ..., dtype: Dtype = ..., name: Hashable = ..., @@ -2060,7 +2063,9 @@ class Series(IndexOpsMixin[S1], NDFrame): self, other: S1 | _ListLike | Series[S1] | datetime | timedelta | date ) -> Series[_bool]: ... @overload - def __mul__(self: Series[Never], other: complex | _ListLike | Series) -> Series: ... + def __mul__( + self: Series[Never], other: complex | _NumListLike | Series + ) -> Series: ... @overload def __mul__(self, other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] @overload @@ -2255,7 +2260,7 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> TimedeltaSeries: ... @overload def __rmul__( - self: Series[Never], other: complex | _ListLike | Series + self: Series[Never], other: complex | _NumListLike | Series ) -> Series: ... @overload def __rmul__(self, other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] @@ -2892,8 +2897,8 @@ class Series(IndexOpsMixin[S1], NDFrame): axis: int = 0, ) -> Series[complex]: ... @overload - def __truediv__( - self: Series[Never], other: complex | _ListLike | Series + def __truediv__( # type:ignore[overload-overlap] + self: Series[Never], other: complex | _NumListLike | Series ) -> Series: ... @overload def __truediv__(self, other: Series[Never]) -> Series: ... @@ -3088,8 +3093,8 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series: ... div = truediv @overload - def __rtruediv__( - self: Series[Never], other: complex | _ListLike | Series + def __rtruediv__( # type:ignore[overload-overlap] + self: Series[Never], other: complex | _NumListLike | Series ) -> Series: ... @overload def __rtruediv__(self, other: Series[Never]) -> Series: ... diff --git a/tests/series/arithmetic/test_sub.py b/tests/series/arithmetic/test_sub.py index a6df939f..0d1a1833 100644 --- a/tests/series/arithmetic/test_sub.py +++ b/tests/series/arithmetic/test_sub.py @@ -242,3 +242,13 @@ def test_sub_ts_pd_datetime_2() -> None: # When I merge this one to the previous one, mypy does not allow me to pass left_td.rsub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] assert_type(left_td.rsub(a), Never) + + +def test_str_sub() -> None: + if TYPE_CHECKING_INVALID_USAGE: + left_i - "abc" # type: ignore[operator] # pyright:ignore[reportOperatorIssue] + "abc" - left_i # type: ignore[operator] # pyright:ignore[reportOperatorIssue] + left_i * "abc" # type: ignore[operator] # pyright:ignore[reportOperatorIssue] + "abc" * left_i # type: ignore[operator] # pyright:ignore[reportOperatorIssue] + left_i / "abc" # type: ignore[operator] # pyright:ignore[reportOperatorIssue] + "abc" / left_i # type: ignore[operator] # pyright:ignore[reportOperatorIssue] From 8d8691ed7a9bc3539736f15492dee55fd36390cc Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Mon, 25 Aug 2025 22:27:07 +0200 Subject: [PATCH 06/11] feat(series): implement the proposal --- pandas-stubs/core/series.pyi | 30 +++++++++++++-- tests/series/arithmetic/str/test_add.py | 6 ++- tests/series/arithmetic/test_add.py | 26 ++++++++++++- tests/series/arithmetic/test_mul.py | 16 +++++++- tests/series/arithmetic/test_sub.py | 50 +++++++++++-------------- tests/series/arithmetic/test_truediv.py | 16 ++++++++ tests/test_frame.py | 3 +- 7 files changed, 109 insertions(+), 38 deletions(-) diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 118fb149..c78c04f7 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -1631,7 +1631,9 @@ class Series(IndexOpsMixin[S1], NDFrame): # just failed to generate these so I couldn't match # them up. @overload - def __add__(self: Series[Never], other: Scalar | _ListLike | Series) -> Series: ... + def __add__(self: Series[Never], other: _str) -> Never: ... + @overload + def __add__(self: Series[Never], other: complex | _ListLike | Series) -> Series: ... @overload def __add__(self, other: Series[Never]) -> Series: ... @overload @@ -1709,7 +1711,15 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def add( self: Series[Never], - other: Scalar | _ListLike | Series, + other: _str, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Never: ... + @overload + def add( + self: Series[Never], + other: complex | _ListLike | Series, level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -1852,7 +1862,11 @@ class Series(IndexOpsMixin[S1], NDFrame): axis: int = 0, ) -> Series[_str]: ... @overload # type: ignore[override] - def __radd__(self: Series[Never], other: Scalar | _ListLike) -> Series: ... + def __radd__(self: Series[Never], other: _str) -> Never: ... + @overload + def __radd__( + self: Series[Never], other: complex | _ListLike | Series + ) -> Series: ... @overload def __radd__( self: Series[bool], @@ -1924,7 +1938,15 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def radd( self: Series[Never], - other: Scalar | _ListLike | Series, + other: _str, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Never: ... + @overload + def radd( + self: Series[Never], + other: complex | _ListLike | Series, level: Level | None = None, fill_value: float | None = None, axis: int = 0, diff --git a/tests/series/arithmetic/str/test_add.py b/tests/series/arithmetic/str/test_add.py index fbcc7536..88ede230 100644 --- a/tests/series/arithmetic/str/test_add.py +++ b/tests/series/arithmetic/str/test_add.py @@ -4,8 +4,9 @@ import numpy as np from numpy import typing as npt # noqa: F401 import pandas as pd +import pytest from typing_extensions import ( - Never, + assert_never, assert_type, ) @@ -72,7 +73,8 @@ def test_add_numpy_array() -> None: r0 = np.array(["a", "bc", "def"], np.str_) if TYPE_CHECKING_INVALID_USAGE: - assert_type(left + i, Never) + with pytest.raises(AssertionError): + assert_never(left + i) check(assert_type(left + r0, "pd.Series[str]"), pd.Series, str) # `numpy` typing gives `npt.NDArray[np.int64]` in the static type diff --git a/tests/series/arithmetic/test_add.py b/tests/series/arithmetic/test_add.py index d2d9630a..bda598e3 100644 --- a/tests/series/arithmetic/test_add.py +++ b/tests/series/arithmetic/test_add.py @@ -1,9 +1,16 @@ import numpy as np from numpy import typing as npt # noqa: F401 import pandas as pd -from typing_extensions import assert_type +import pytest +from typing_extensions import ( + assert_never, + assert_type, +) -from tests import check +from tests import ( + TYPE_CHECKING_INVALID_USAGE, + check, +) # left operands left_i = pd.DataFrame({"a": [1, 2, 3]})["a"] @@ -131,3 +138,18 @@ def test_add_i_pd_series() -> None: check(assert_type(left_i.radd(i), pd.Series), pd.Series) check(assert_type(left_i.radd(f), pd.Series), pd.Series) check(assert_type(left_i.radd(c), pd.Series), pd.Series) + + +def test_add_str_py_str() -> None: + """Test pd.Series[Any] (int) + Python str""" + s = "abc" + + if TYPE_CHECKING_INVALID_USAGE: + with pytest.raises(AssertionError): + assert_never(left_i + s) + with pytest.raises(AssertionError): + assert_never(s + left_i) + with pytest.raises(AssertionError): + assert_never(left_i.add(s)) + with pytest.raises(AssertionError): + assert_never(left_i.radd(s)) diff --git a/tests/series/arithmetic/test_mul.py b/tests/series/arithmetic/test_mul.py index 335a6014..e98f14c8 100644 --- a/tests/series/arithmetic/test_mul.py +++ b/tests/series/arithmetic/test_mul.py @@ -3,7 +3,10 @@ import pandas as pd from typing_extensions import assert_type -from tests import check +from tests import ( + TYPE_CHECKING_INVALID_USAGE, + check, +) left_i = pd.DataFrame({"a": [1, 2, 3]})["a"] # left operand @@ -129,3 +132,14 @@ def test_mul_pd_series() -> None: check(assert_type(left_i.rmul(i), pd.Series), pd.Series) check(assert_type(left_i.rmul(f), pd.Series), pd.Series) check(assert_type(left_i.rmul(c), pd.Series), pd.Series) + + +def test_mul_str_py_str() -> None: + """Test pd.Series[Any] (int) * Python str""" + s = "abc" + + if TYPE_CHECKING_INVALID_USAGE: + left_i * s # type: ignore[operator] # pyright:ignore[reportOperatorIssue] + s * left_i # type: ignore[operator] # pyright:ignore[reportOperatorIssue] + left_i.mul(s) # type: ignore[type-var] # pyright: ignore[reportArgumentType,reportCallIssue] + left_i.rmul(s) # type: ignore[type-var] # pyright: ignore[reportArgumentType,reportCallIssue] diff --git a/tests/series/arithmetic/test_sub.py b/tests/series/arithmetic/test_sub.py index 0d1a1833..01d2b246 100644 --- a/tests/series/arithmetic/test_sub.py +++ b/tests/series/arithmetic/test_sub.py @@ -7,9 +7,11 @@ import numpy as np from numpy import typing as npt # noqa: F401 import pandas as pd +import pytest from typing_extensions import ( - Never, + assert_never, assert_type, + Never ) from tests import ( @@ -179,8 +181,6 @@ def test_sub_ts_numpy_datetime() -> None: a = np.array([s + np.timedelta64(m, "m") for m in range(3)], np.datetime64) if TYPE_CHECKING_INVALID_USAGE: - # `numpy` typing gives the corresponding `ndarray`s in the static type - # checking, where our `__rsub__` cannot override. _0 = left_ts - s # type: ignore[operator] # pyright: ignore[reportOperatorIssue] # _1 = left_ts - a _2 = left_td - s # type: ignore[operator] # pyright: ignore[reportOperatorIssue] @@ -210,45 +210,39 @@ def test_sub_ts_pd_datetime() -> None: if TYPE_CHECKING_INVALID_USAGE: _0 = left_ts - s # type: ignore[operator] # pyright: ignore[reportOperatorIssue] assert_type(left_ts - a, Never) + _2 = left_td - s # type: ignore[operator] # pyright: ignore[reportOperatorIssue] assert_type(left_td - a, Never) _4 = s - left_ts # type: ignore[operator] # pyright: ignore[reportOperatorIssue] assert_type(a - left_ts, Never) + _6 = s - left_td # type: ignore[operator] # pyright: ignore[reportOperatorIssue] assert_type(a - left_td, Never) left_ts.sub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] - assert_type(left_ts.sub(a), Never) + with pytest.raises(AssertionError): + assert_never(left_ts.sub(a)) + left_td.sub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + with pytest.raises(AssertionError): + assert_never(left_td.sub(a)) -def test_sub_ts_pd_datetime_1() -> None: - """Test pd.Series[Any] (Timestamp | Timedelta) - Pandas datetime(s)""" - s = pd.Timestamp(anchor) - a = pd.Series([s + pd.Timedelta(minutes=m) for m in range(3)]) - - if TYPE_CHECKING_INVALID_USAGE: - # When I merge this one to the previous one, mypy does not allow me to pass left_ts.rsub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] - assert_type(left_ts.rsub(a), Never) + with pytest.raises(AssertionError): + assert_never(left_ts.rsub(a)) - -def test_sub_ts_pd_datetime_2() -> None: - """Test pd.Series[Any] (Timestamp | Timedelta) - Pandas datetime(s)""" - s = pd.Timestamp(anchor) - a = pd.Series([s + pd.Timedelta(minutes=m) for m in range(3)]) - - if TYPE_CHECKING_INVALID_USAGE: - # When I merge this one to the previous one, mypy does not allow me to pass left_td.rsub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] - assert_type(left_td.rsub(a), Never) + with pytest.raises(AssertionError): + assert_never(left_td.rsub(a)) + +def test_sub_str_py_str() -> None: + """Test pd.Series[Any] (int) - Python str""" + s = "abc" -def test_str_sub() -> None: if TYPE_CHECKING_INVALID_USAGE: - left_i - "abc" # type: ignore[operator] # pyright:ignore[reportOperatorIssue] - "abc" - left_i # type: ignore[operator] # pyright:ignore[reportOperatorIssue] - left_i * "abc" # type: ignore[operator] # pyright:ignore[reportOperatorIssue] - "abc" * left_i # type: ignore[operator] # pyright:ignore[reportOperatorIssue] - left_i / "abc" # type: ignore[operator] # pyright:ignore[reportOperatorIssue] - "abc" / left_i # type: ignore[operator] # pyright:ignore[reportOperatorIssue] + _0 = left_i - s # type: ignore[operator] # pyright:ignore[reportOperatorIssue] + _1 = s - left_i # type: ignore[operator] # pyright:ignore[reportOperatorIssue] + left_i.sub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_i.rsub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] diff --git a/tests/series/arithmetic/test_truediv.py b/tests/series/arithmetic/test_truediv.py index 69d408b3..26c0bb8a 100644 --- a/tests/series/arithmetic/test_truediv.py +++ b/tests/series/arithmetic/test_truediv.py @@ -7,6 +7,7 @@ from tests import ( PD_LTE_23, + TYPE_CHECKING_INVALID_USAGE, check, ) @@ -211,3 +212,18 @@ def test_truediv_path(tmp_path: Path) -> None: check(assert_type(fnames.rtruediv(tmp_path), pd.Series), pd.Series, Path) check(assert_type(fnames.rdiv(tmp_path), pd.Series), pd.Series, Path) + + +def test_truediv_str_py_str() -> None: + """Test pd.Series[Any] (int) / Python str""" + s = "abc" + + if TYPE_CHECKING_INVALID_USAGE: + _0 = left_i / s # type: ignore[operator] # pyright:ignore[reportOperatorIssue] + _1 = s / left_i # type: ignore[operator] # pyright:ignore[reportOperatorIssue] + + left_i.truediv(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_i.div(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + + left_i.rtruediv(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_i.rdiv(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] diff --git a/tests/test_frame.py b/tests/test_frame.py index cf1b5801..ebee1759 100644 --- a/tests/test_frame.py +++ b/tests/test_frame.py @@ -74,6 +74,7 @@ if TYPE_CHECKING: from pandas.core.frame import _PandasNamedTuple + from pandas.core.series import TimestampSeries else: _PandasNamedTuple: TypeAlias = tuple @@ -4440,7 +4441,7 @@ def test_frame_setitem_na() -> None: df.loc[ind, :] = pd.NA df.iloc[[0, 2], :] = pd.NA - df["x"] = df["y"] + pd.Timedelta(days=3) + df["x"] = cast("TimestampSeries", df["y"]) + pd.Timedelta(days=3) df.loc[ind, :] = pd.NaT df.iloc[[0, 2], :] = pd.NaT From b4235d087770e214835d915ada7742d75bc1f3fa Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Tue, 26 Aug 2025 16:48:46 +0200 Subject: [PATCH 07/11] fix(comment): reduce with pytest.raises(AssertionError) --- tests/series/arithmetic/str/test_add.py | 6 ++---- tests/series/arithmetic/test_add.py | 7 +++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/tests/series/arithmetic/str/test_add.py b/tests/series/arithmetic/str/test_add.py index 88ede230..fbcc7536 100644 --- a/tests/series/arithmetic/str/test_add.py +++ b/tests/series/arithmetic/str/test_add.py @@ -4,9 +4,8 @@ import numpy as np from numpy import typing as npt # noqa: F401 import pandas as pd -import pytest from typing_extensions import ( - assert_never, + Never, assert_type, ) @@ -73,8 +72,7 @@ def test_add_numpy_array() -> None: r0 = np.array(["a", "bc", "def"], np.str_) if TYPE_CHECKING_INVALID_USAGE: - with pytest.raises(AssertionError): - assert_never(left + i) + assert_type(left + i, Never) check(assert_type(left + r0, "pd.Series[str]"), pd.Series, str) # `numpy` typing gives `npt.NDArray[np.int64]` in the static type diff --git a/tests/series/arithmetic/test_add.py b/tests/series/arithmetic/test_add.py index bda598e3..e0ee6232 100644 --- a/tests/series/arithmetic/test_add.py +++ b/tests/series/arithmetic/test_add.py @@ -3,6 +3,7 @@ import pandas as pd import pytest from typing_extensions import ( + Never, assert_never, assert_type, ) @@ -145,10 +146,8 @@ def test_add_str_py_str() -> None: s = "abc" if TYPE_CHECKING_INVALID_USAGE: - with pytest.raises(AssertionError): - assert_never(left_i + s) - with pytest.raises(AssertionError): - assert_never(s + left_i) + assert_type(left_i + s, Never) + assert_type(s + left_i, Never) with pytest.raises(AssertionError): assert_never(left_i.add(s)) with pytest.raises(AssertionError): From 6a4692f83bca93d5d74a0bc30c1ec5fecfdd8295 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Wed, 27 Aug 2025 00:01:18 +0200 Subject: [PATCH 08/11] doc(comment): https://github.com/pandas-dev/pandas-stubs/pull/1343#pullrequestreview-3153139479 https://github.com/pandas-dev/pandas-stubs/pull/1343#discussion_r2299227000 --- docs/philosophy.md | 37 +++++++++++++++++++++++++++++++++++++ tests/test_frame.py | 2 ++ 2 files changed, 39 insertions(+) diff --git a/docs/philosophy.md b/docs/philosophy.md index 853c516e..31b8eac6 100644 --- a/docs/philosophy.md +++ b/docs/philosophy.md @@ -61,6 +61,43 @@ The type `TimestampSeries` is the result of creating a series from `pd.to_dateti the type `TimedeltaSeries` is the result of subtracting two `TimestampSeries` as well as the result of `pd.to_timedelta()`. +### Generic Series have restricted arithmetic + +Consider the following Series from a DataFrame: + +```python +import pandas as pd +from typing_extensions import reveal_type +from typing import TYPE_CHECKING, cast + +if TYPE_CHECKING: + from pandas.core.series import TimestampSeries # noqa: F401 + + +frame = pd.DataFrame({"timestamp": [pd.Timestamp(2025, 8, 26)], "tag": ["one"], "value": [1.0]}) +values = frame["value"] +reveal_type(values) # type checker: Series[Any], runtime: Series +new_values = values + 2 + +timestamps = frame["timestamp"] +reveal_type(timestamps) # type checker: Series[Any], runtime: Series +reveal_type(timestamps - pd.Timestamp(2025, 7, 12)) # type checker: Unknown and error, runtime: Series +reveal_type(cast("TimestampSeries", timestamps) - pd.Timestamp(2025, 7, 12)) # type checker: TimedeltaSeries, runtime: Series + +tags = frame["tag"] +reveal_type("suffix" + tags) # type checker: Never, runtime: Series +``` + +Since they are taken from a DataFrame, all three of them, `values`, `timestamps` +and `tags`, are recognized by type checkers as `Series[Any]`. The code snippet +runs fine at runtime. In the stub for type checking, however, we restrict +generic Series to perform arithmetic operations only with numeric types, and +give `Series[Any]` for the results. For `Timedelta`, `Timestamp`, `str`, etc., +arithmetic is restricted to `Series[Any]` and the result is either undefined, +showing `Unknown` and errors, or `Never`. Users are encouraged to cast such +generic Series to ones with concrete types, so that type checkers can provide +meaningful results. + ### Interval is Generic A pandas `Interval` can be a time interval, an interval of integers, or an interval of diff --git a/tests/test_frame.py b/tests/test_frame.py index 4705ac32..31ce7e00 100644 --- a/tests/test_frame.py +++ b/tests/test_frame.py @@ -4441,6 +4441,8 @@ def test_frame_setitem_na() -> None: df.loc[ind, :] = pd.NA df.iloc[[0, 2], :] = pd.NA + # reveal_type(df["y"]) gives Series[Any], so we have to cast to tell the + # type checker what kind of type it is when adding to a Timedelta df["x"] = cast("TimestampSeries", df["y"]) + pd.Timedelta(days=3) df.loc[ind, :] = pd.NaT df.iloc[[0, 2], :] = pd.NaT From c4b218e54993c04f7559594f58b83f32e4706f27 Mon Sep 17 00:00:00 2001 From: Irv Lustig Date: Tue, 26 Aug 2025 17:34:10 -0400 Subject: [PATCH 09/11] fix for nightly numpy --- pandas-stubs/core/series.pyi | 8 ++++++++ tests/series/arithmetic/str/test_add.py | 8 ++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index c78c04f7..52f6a110 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -1952,6 +1952,14 @@ class Series(IndexOpsMixin[S1], NDFrame): axis: int = 0, ) -> Series: ... @overload + def radd( + self: Series[S1], + other: Series[Never], + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series: ... + @overload def radd( self: Series[bool], other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], diff --git a/tests/series/arithmetic/str/test_add.py b/tests/series/arithmetic/str/test_add.py index fbcc7536..a58fe663 100644 --- a/tests/series/arithmetic/str/test_add.py +++ b/tests/series/arithmetic/str/test_add.py @@ -35,7 +35,7 @@ def test_add_py_scalar() -> None: check(assert_type(left.add(r0), "pd.Series[str]"), pd.Series, str) if TYPE_CHECKING_INVALID_USAGE: - left.radd(i) # type: ignore[call-overload] # pyright: ignore[reportArgumentType] + left.radd(i) # type: ignore[call-overload] # pyright: ignore[reportArgumentType, reportCallIssue] check(assert_type(left.radd(r0), "pd.Series[str]"), pd.Series, str) @@ -61,7 +61,7 @@ def test_add_py_sequence() -> None: check(assert_type(left.add(r1), "pd.Series[str]"), pd.Series, str) if TYPE_CHECKING_INVALID_USAGE: - left.radd(i) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] + left.radd(i) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] check(assert_type(left.radd(r0), "pd.Series[str]"), pd.Series, str) check(assert_type(left.radd(r1), "pd.Series[str]"), pd.Series, str) @@ -96,7 +96,7 @@ def test_add_numpy_array() -> None: check(assert_type(left.add(r0), "pd.Series[str]"), pd.Series, str) if TYPE_CHECKING_INVALID_USAGE: - left.radd(i) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] + left.radd(i) # type: ignore[arg-type] # pyright: ignore[reportArgumentType, reportCallIssue] check(assert_type(left.radd(r0), "pd.Series[str]"), pd.Series, str) @@ -118,5 +118,5 @@ def test_add_pd_series() -> None: check(assert_type(left.add(r0), "pd.Series[str]"), pd.Series, str) if TYPE_CHECKING_INVALID_USAGE: - left.radd(i) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] + left.radd(i) # type: ignore[arg-type] # pyright: ignore[reportArgumentType, reportCallIssue] check(assert_type(left.radd(r0), "pd.Series[str]"), pd.Series, str) From 3e9c06e8d8419c67b287e6aecc0ccb9d09981727 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Wed, 27 Aug 2025 20:19:54 +0200 Subject: [PATCH 10/11] fix: Never --- tests/series/arithmetic/test_add.py | 12 ++++++------ tests/series/arithmetic/test_sub.py | 23 +++++++++++++---------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/tests/series/arithmetic/test_add.py b/tests/series/arithmetic/test_add.py index e0ee6232..15165300 100644 --- a/tests/series/arithmetic/test_add.py +++ b/tests/series/arithmetic/test_add.py @@ -1,10 +1,8 @@ import numpy as np from numpy import typing as npt # noqa: F401 import pandas as pd -import pytest from typing_extensions import ( Never, - assert_never, assert_type, ) @@ -148,7 +146,9 @@ def test_add_str_py_str() -> None: if TYPE_CHECKING_INVALID_USAGE: assert_type(left_i + s, Never) assert_type(s + left_i, Never) - with pytest.raises(AssertionError): - assert_never(left_i.add(s)) - with pytest.raises(AssertionError): - assert_never(left_i.radd(s)) + + def _type_checking_enabler_0() -> None: # pyright: ignore[reportUnusedFunction] + assert_type(left_i.add(s), Never) + + def _type_checking_enabler_1() -> None: # pyright: ignore[reportUnusedFunction] + assert_type(left_i.radd(s), Never) diff --git a/tests/series/arithmetic/test_sub.py b/tests/series/arithmetic/test_sub.py index 2be1e853..fba533f8 100644 --- a/tests/series/arithmetic/test_sub.py +++ b/tests/series/arithmetic/test_sub.py @@ -7,10 +7,8 @@ import numpy as np from numpy import typing as npt # noqa: F401 import pandas as pd -import pytest from typing_extensions import ( Never, - assert_never, assert_type, ) @@ -181,6 +179,7 @@ def test_sub_ts_numpy_datetime() -> None: a = np.array([s + np.timedelta64(m, "m") for m in range(3)], np.datetime64) if TYPE_CHECKING_INVALID_USAGE: + # We would like to have _1, _3, _5 and _7 below as invalid, but numpy.ndarray.__rsub__ overrides our efforts _0 = left_ts - s # type: ignore[operator] # pyright: ignore[reportOperatorIssue] # _1 = left_ts - a _2 = left_td - s # type: ignore[operator] # pyright: ignore[reportOperatorIssue] @@ -221,20 +220,24 @@ def test_sub_ts_pd_datetime() -> None: assert_type(a - left_td, Never) left_ts.sub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] - with pytest.raises(AssertionError): - assert_never(left_ts.sub(a)) + + def _type_checking_enabler_0() -> None: # pyright: ignore[reportUnusedFunction] + assert_type(left_ts.sub(a), Never) left_td.sub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] - with pytest.raises(AssertionError): - assert_never(left_td.sub(a)) + + def _type_checking_enabler_1() -> None: # pyright: ignore[reportUnusedFunction] + assert_type(left_td.sub(a), Never) left_ts.rsub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] - with pytest.raises(AssertionError): - assert_never(left_ts.rsub(a)) + + def _type_checking_enabler_2() -> None: # pyright: ignore[reportUnusedFunction] + assert_type(left_ts.rsub(a), Never) left_td.rsub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] - with pytest.raises(AssertionError): - assert_never(left_td.rsub(a)) + + def _type_checking_enabler_3() -> None: # pyright: ignore[reportUnusedFunction] + assert_type(left_td.rsub(a), Never) def test_sub_str_py_str() -> None: From d141a6933ade07c975b61a78879d1a15a640b9c6 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Wed, 27 Aug 2025 22:57:39 +0200 Subject: [PATCH 11/11] chore: typo --- tests/series/arithmetic/test_add.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/series/arithmetic/test_add.py b/tests/series/arithmetic/test_add.py index 15165300..a739ddae 100644 --- a/tests/series/arithmetic/test_add.py +++ b/tests/series/arithmetic/test_add.py @@ -139,7 +139,7 @@ def test_add_i_pd_series() -> None: check(assert_type(left_i.radd(c), pd.Series), pd.Series) -def test_add_str_py_str() -> None: +def test_add_i_py_str() -> None: """Test pd.Series[Any] (int) + Python str""" s = "abc"