Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 39 additions & 18 deletions pandas-stubs/core/series.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ from pandas._typing import (
np_ndarray_complex,
np_ndarray_dt,
np_ndarray_float,
np_ndarray_str,
np_ndarray_td,
npt,
num,
Expand Down Expand Up @@ -1673,7 +1674,8 @@ class Series(IndexOpsMixin[S1], NDFrame):
def __add__(
self: Series[complex],
other: (
Sequence[_T_COMPLEX]
_T_COMPLEX
| Sequence[_T_COMPLEX]
| np_ndarray_bool
| np_ndarray_anyint
| np_ndarray_float
Expand All @@ -1682,7 +1684,16 @@ class Series(IndexOpsMixin[S1], NDFrame):
),
) -> Series[complex]: ...
@overload
def __add__(self, other: S1 | Series[S1]) -> Self: ...
def __add__(
self: Series[_str],
other: (
np_ndarray_bool | np_ndarray_anyint | np_ndarray_float | np_ndarray_complex
),
) -> Never: ...
@overload
def __add__(
self: Series[_str], other: _str | Sequence[_str] | np_ndarray_str | Series[_str]
) -> Series[_str]: ...
@overload
def add(
self: Series[Never],
Expand Down Expand Up @@ -1808,7 +1819,8 @@ class Series(IndexOpsMixin[S1], NDFrame):
def add(
self: Series[complex],
other: (
Sequence[_T_COMPLEX]
_T_COMPLEX
| Sequence[_T_COMPLEX]
| np_ndarray_bool
| np_ndarray_anyint
| np_ndarray_float
Expand All @@ -1821,13 +1833,13 @@ class Series(IndexOpsMixin[S1], NDFrame):
) -> Series[complex]: ...
@overload
def add(
self,
other: S1 | Series[S1],
self: Series[_str],
other: _str | Sequence[_str] | np_ndarray_str | Series[_str],
level: Level | None = None,
fill_value: float | None = None,
axis: int = 0,
) -> Self: ...
@overload
) -> Series[_str]: ...
@overload # type: ignore[override]
def __radd__(self: Series[Never], other: Scalar | _ListLike) -> Series: ...
@overload
def __radd__(
Expand Down Expand Up @@ -1874,7 +1886,8 @@ class Series(IndexOpsMixin[S1], NDFrame):
def __radd__(
self: Series[complex],
other: (
Sequence[_T_COMPLEX]
_T_COMPLEX
| Sequence[_T_COMPLEX]
| np_ndarray_bool
| np_ndarray_anyint
| np_ndarray_float
Expand All @@ -1886,7 +1899,16 @@ class Series(IndexOpsMixin[S1], NDFrame):
self: Series[_T_COMPLEX], other: np_ndarray_complex
) -> Series[complex]: ...
@overload
def __radd__(self, other: S1 | Series[S1]) -> Self: ...
def __radd__(
self: Series[_str],
other: (
np_ndarray_bool | np_ndarray_anyint | np_ndarray_float | np_ndarray_complex
),
) -> Never: ...
@overload
def __radd__(
self: Series[_str], other: _str | Sequence[_str] | np_ndarray_str | Series[_str]
) -> Series[_str]: ...
@overload
def radd(
self: Series[Never],
Expand Down Expand Up @@ -1980,7 +2002,8 @@ class Series(IndexOpsMixin[S1], NDFrame):
def radd(
self: Series[complex],
other: (
Sequence[_T_COMPLEX]
_T_COMPLEX
| Sequence[_T_COMPLEX]
| np_ndarray_bool
| np_ndarray_anyint
| np_ndarray_float
Expand All @@ -2000,12 +2023,12 @@ class Series(IndexOpsMixin[S1], NDFrame):
) -> Series[complex]: ...
@overload
def radd(
self,
other: S1 | Series[S1],
self: Series[_str],
other: _str | Sequence[_str] | np_ndarray_str | Series[_str],
level: Level | None = None,
fill_value: float | None = None,
axis: int = 0,
) -> Self: ...
) -> Series[_str]: ...
# ignore needed for mypy as we want different results based on the arguments
@overload # type: ignore[override]
def __and__( # pyright: ignore[reportOverlappingOverload]
Expand Down Expand Up @@ -3682,7 +3705,7 @@ class TimestampSeries(_SeriesSubclassBase[Timestamp, np.datetime64]):
@property
def dt(self) -> TimestampProperties: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
def __add__(self, other: TimedeltaSeries | np.timedelta64 | timedelta | BaseOffset) -> TimestampSeries: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
def __radd__(self, other: TimedeltaSeries | np.timedelta64 | timedelta) -> TimestampSeries: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
def __radd__(self, other: TimedeltaSeries | np.timedelta64 | timedelta) -> TimestampSeries: ... # type: ignore[override]
@overload # type: ignore[override]
def __sub__(
self, other: Timestamp | datetime | TimestampSeries
Expand Down Expand Up @@ -3742,7 +3765,7 @@ class TimedeltaSeries(_SeriesSubclassBase[Timedelta, np.timedelta64]):
def __add__( # pyright: ignore[reportIncompatibleMethodOverride]
self, other: timedelta | Timedelta | np.timedelta64
) -> TimedeltaSeries: ...
def __radd__(self, other: datetime | Timestamp | TimestampSeries) -> TimestampSeries: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
def __radd__(self, other: datetime | Timestamp | TimestampSeries) -> TimestampSeries: ... # type: ignore[override]
def __mul__( # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
self, other: num | Sequence[num] | Series[int] | Series[float]
) -> TimedeltaSeries: ...
Expand Down Expand Up @@ -3859,9 +3882,7 @@ class OffsetSeries(_SeriesSubclassBase[BaseOffset, np.object_]):
@overload # type: ignore[override]
def __radd__(self, other: Period) -> PeriodSeries: ...
@overload
def __radd__( # pyright: ignore[reportIncompatibleMethodOverride]
self, other: BaseOffset
) -> OffsetSeries: ...
def __radd__(self, other: BaseOffset) -> OffsetSeries: ...
def cumprod(
self,
axis: AxisIndex = ...,
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ mypy = "1.17.0"
pandas = "2.3.1"
pyarrow = ">=10.0.1"
pytest = ">=7.1.2"
pyright = ">=1.1.403"
pyright = ">=1.1.404"
ty = "^0.0.1a8"
pyrefly = "^0.21.0"
poethepoet = ">=0.16.5"
Expand Down
Empty file.
122 changes: 122 additions & 0 deletions tests/series/arithmetic/str/test_add.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import sys
from typing import Any

import numpy as np
from numpy import typing as npt # noqa: F401
import pandas as pd
from typing_extensions import (
Never,
assert_type,
)

from tests import (
TYPE_CHECKING_INVALID_USAGE,
check,
)

left = pd.Series(["1", "23", "456"]) # left operand


def test_add_py_scalar() -> None:
"""Testpd.Series[str]+ Python native 'scalar's"""
i = 4
r0 = "right"

if TYPE_CHECKING_INVALID_USAGE:
_0 = left + i # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
check(assert_type(left + r0, "pd.Series[str]"), pd.Series, str)

if TYPE_CHECKING_INVALID_USAGE:
_1 = i + left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
check(assert_type(r0 + left, "pd.Series[str]"), pd.Series, str)

if TYPE_CHECKING_INVALID_USAGE:
left.add(i) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue]
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]
check(assert_type(left.radd(r0), "pd.Series[str]"), pd.Series, str)


def test_add_py_sequence() -> None:
"""Testpd.Series[str]+ Python native sequence"""
i = [3, 5, 8]
r0 = ["a", "bc", "def"]
r1 = tuple(r0)

if TYPE_CHECKING_INVALID_USAGE:
_0 = left + i # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
check(assert_type(left + r0, "pd.Series[str]"), pd.Series, str)
check(assert_type(left + r1, "pd.Series[str]"), pd.Series, str)

if TYPE_CHECKING_INVALID_USAGE:
_1 = i + left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
check(assert_type(r0 + left, "pd.Series[str]"), pd.Series, str)
check(assert_type(r1 + left, "pd.Series[str]"), pd.Series, str)

if TYPE_CHECKING_INVALID_USAGE:
left.add(i) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue]
check(assert_type(left.add(r0), "pd.Series[str]"), pd.Series, str)
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]
check(assert_type(left.radd(r0), "pd.Series[str]"), pd.Series, str)
check(assert_type(left.radd(r1), "pd.Series[str]"), pd.Series, str)


def test_add_numpy_array() -> None:
"""Testpd.Series[str]+ numpy array"""
i = np.array([3, 5, 8], np.int64)
r0 = np.array(["a", "bc", "def"], np.str_)

if TYPE_CHECKING_INVALID_USAGE:
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
# checking, where our `__radd__` cannot override. At runtime, they return
# `Series`s.
if TYPE_CHECKING_INVALID_USAGE:
assert_type(i + left, "npt.NDArray[np.int64]")
if sys.version_info >= (3, 11):
# `numpy` typing gives `npt.NDArray[np.int64]` in the static type
# checking, where our `__radd__` cannot override. At runtime, they return
# `Series`s.
check(assert_type(r0 + left, "npt.NDArray[np.str_]"), pd.Series, str)
else:
# Python 3.10 uses NumPy 2.2.6, and it has for r0 ndarray[tuple[int,...], dtype[str_]]
# Python 3.11+ uses NumPy 2.3.2, and it has for r0 ndarray[tuple[Any,...,dtype[str_]]
# https://github.com/pandas-dev/pandas-stubs/pull/1274#discussion_r2291498975
check(assert_type(r0 + left, Any), pd.Series, str)

if TYPE_CHECKING_INVALID_USAGE:
left.add(i) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue]
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]
check(assert_type(left.radd(r0), "pd.Series[str]"), pd.Series, str)


def test_add_pd_series() -> None:
"""Testpd.Series[str]+ pandas series"""
i = pd.Series([3, 5, 8])
r0 = pd.Series(["a", "bc", "def"])

if TYPE_CHECKING_INVALID_USAGE:
_0 = left + i # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
check(assert_type(left + r0, "pd.Series[str]"), pd.Series, str)

if TYPE_CHECKING_INVALID_USAGE:
_1 = i + left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
check(assert_type(r0 + left, "pd.Series[str]"), pd.Series, str)

if TYPE_CHECKING_INVALID_USAGE:
left.add(i) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue]
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]
check(assert_type(left.radd(r0), "pd.Series[str]"), pd.Series, str)
Loading