Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
4 changes: 2 additions & 2 deletions pandas-stubs/core/arrays/datetimelike.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class DatetimeLikeArrayMixin(ExtensionOpsMixin, ExtensionArray):
@property
def shape(self): ...
def reshape(self, *args: Any, **kwargs: Any): ...
def ravel(self, *args: Any, **kwargs: Any): ... # pyrefly: ignore
def ravel(self, *args: Any, **kwargs: Any): ...
def __iter__(self): ...
@property
def asi8(self) -> np.ndarray: ...
Expand All @@ -85,7 +85,7 @@ class DatetimeLikeArrayMixin(ExtensionOpsMixin, ExtensionArray):
def unique(self): ...
def copy(self): ...
def shift(self, periods: int = 1, fill_value=..., axis: int = ...): ...
def repeat(self, repeats, *args: Any, **kwargs: Any): ... # pyrefly: ignore
def repeat(self, repeats, *args: Any, **kwargs: Any): ...
def value_counts(self, dropna: bool = True): ...
def map(self, mapper): ...
def isna(self): ...
Expand Down
2 changes: 1 addition & 1 deletion pandas-stubs/core/groupby/generic.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class NamedAgg(NamedTuple):

class SeriesGroupBy(GroupBy[Series[S2]], Generic[S2, ByT]):
@overload
def aggregate( # pyrefly: ignore
def aggregate(
self,
func: Callable[Concatenate[Series[S2], P], S3],
/,
Expand Down
22 changes: 21 additions & 1 deletion pandas-stubs/core/indexes/base.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,16 @@ class Index(IndexOpsMixin[S1]):
),
) -> Index[Timedelta]: ...
@overload
def __mul__(
self: Index[_str],
other: np_ndarray_float | np_ndarray_complex | np_ndarray_dt | np_ndarray_td,
) -> Never: ...
@overload
def __mul__(
self: Index[_str],
other: int | Sequence[int] | np_ndarray_bool | np_ndarray_anyint | Index[T_INT],
) -> Index[_str]: ...
@overload
def __mul__(self: Index[T_INT], other: bool | Sequence[bool]) -> Index[T_INT]: ...
@overload
def __mul__(self: Index[float], other: int | Sequence[int]) -> Index[float]: ...
Expand Down Expand Up @@ -854,6 +864,16 @@ class Index(IndexOpsMixin[S1]):
),
) -> Index[Timedelta]: ...
@overload
def __rmul__(
self: Index[_str],
other: np_ndarray_float | np_ndarray_complex | np_ndarray_dt | np_ndarray_td,
) -> Never: ...
@overload
def __rmul__(
self: Index[_str],
other: int | Sequence[int] | np_ndarray_bool | np_ndarray_anyint | Index[T_INT],
) -> Index[_str]: ...
@overload
def __rmul__(self: Index[T_INT], other: bool | Sequence[bool]) -> Index[T_INT]: ...
@overload
def __rmul__(self: Index[float], other: int | Sequence[int]) -> Index[float]: ...
Expand Down Expand Up @@ -909,7 +929,7 @@ class Index(IndexOpsMixin[S1]):
@type_check_only
class _IndexSubclassBase(Index[S1], Generic[S1, GenericT_co]):
@overload
def to_numpy( # pyrefly: ignore
def to_numpy(
self,
dtype: None = None,
copy: bool = False,
Expand Down
4 changes: 2 additions & 2 deletions pandas-stubs/core/indexes/multi.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ class MultiIndex(Index):
def dropna(self, how: AnyAll = "any") -> Self: ...
def get_level_values(self, level: str | int) -> Index: ...
def unique(self, level=...): ...
def to_frame( # pyrefly: ignore
def to_frame(
self,
index: bool = True,
name: list[HashableT] = ...,
Expand All @@ -131,7 +131,7 @@ class MultiIndex(Index):
def __getitem__( # pyright: ignore[reportIncompatibleMethodOverride]
self, key: int
) -> tuple: ...
def append(self, other): ... # pyrefly: ignore
def append(self, other): ...
def repeat(self, repeats, axis=...): ...
def drop(self, codes, level: Level | None = None, errors: str = "raise") -> Self: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
def swaplevel(self, i: int = -2, j: int = -1): ...
Expand Down
2 changes: 1 addition & 1 deletion pandas-stubs/core/indexes/range.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class RangeIndex(_IndexSubclassBase[int, np.int64]):
def all(self, *args: Any, **kwargs: Any) -> bool: ...
def any(self, *args: Any, **kwargs: Any) -> bool: ...
@final
def union( # pyrefly: ignore
def union(
self, other: list[HashableT] | Index, sort: bool | None = None
) -> Index | Index[int] | RangeIndex: ...
@overload # type: ignore[override]
Expand Down
68 changes: 66 additions & 2 deletions pandas-stubs/core/series.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -2583,6 +2583,23 @@ class Series(IndexOpsMixin[S1], NDFrame):
),
) -> Series[Timedelta]: ...
@overload
def __mul__(
self: Series[_str],
other: np_ndarray_float | np_ndarray_complex | np_ndarray_dt | np_ndarray_td,
) -> Never: ...
@overload
def __mul__(
self: Series[_str],
other: (
int
| Sequence[int]
| np_ndarray_bool
| np_ndarray_anyint
| Index[T_INT]
| Series[T_INT]
),
) -> Series[_str]: ...
@overload
def __mul__(self: Series[T_INT], other: bool | Sequence[bool]) -> Series[T_INT]: ...
@overload
def __mul__(self: Series[float], other: int | Sequence[int]) -> Series[float]: ...
Expand Down Expand Up @@ -2675,6 +2692,21 @@ class Series(IndexOpsMixin[S1], NDFrame):
axis: AxisIndex | None = 0,
) -> Series[Timedelta]: ...
@overload
def mul(
self: Series[_str],
other: (
int
| Sequence[int]
| np_ndarray_bool
| np_ndarray_anyint
| Index[T_INT]
| Series[T_INT]
),
level: Level | None = None,
fill_value: float | None = None,
axis: int = 0,
) -> Series[_str]: ...
@overload
def mul(
self: Series[T_INT],
other: bool | Sequence[bool],
Expand Down Expand Up @@ -2800,6 +2832,23 @@ class Series(IndexOpsMixin[S1], NDFrame):
),
) -> Series[Timedelta]: ...
@overload
def __rmul__(
self: Series[_str],
other: np_ndarray_float | np_ndarray_complex | np_ndarray_dt | np_ndarray_td,
) -> Never: ...
@overload
def __rmul__(
self: Series[_str],
other: (
int
| Sequence[int]
| np_ndarray_bool
| np_ndarray_anyint
| Index[T_INT]
| Series[T_INT]
),
) -> Series[_str]: ...
@overload
def __rmul__(
self: Series[T_INT], other: bool | Sequence[bool]
) -> Series[T_INT]: ...
Expand Down Expand Up @@ -2894,6 +2943,21 @@ class Series(IndexOpsMixin[S1], NDFrame):
axis: AxisIndex | None = 0,
) -> Series[Timedelta]: ...
@overload
def rmul(
self: Series[_str],
other: (
int
| Sequence[int]
| np_ndarray_bool
| np_ndarray_anyint
| Index[T_INT]
| Series[T_INT]
),
level: Level | None = None,
fill_value: float | None = None,
axis: int = 0,
) -> Series[_str]: ...
@overload
def rmul(
self: Series[T_INT],
other: bool | Sequence[bool],
Expand Down Expand Up @@ -4556,7 +4620,7 @@ class Series(IndexOpsMixin[S1], NDFrame):
**kwargs: Any,
) -> np_1darray: ...
@overload
def to_numpy( # pyrefly: ignore[bad-override]
def to_numpy(
self: Series[Timestamp],
dtype: type[np.datetime64] | None = None,
copy: bool = False,
Expand All @@ -4572,7 +4636,7 @@ class Series(IndexOpsMixin[S1], NDFrame):
**kwargs: Any,
) -> np_1darray[GenericT]: ...
@overload
def to_numpy( # pyrefly: ignore[bad-override]
def to_numpy(
self: Series[Timedelta],
dtype: type[np.timedelta64] | None = None,
copy: bool = False,
Expand Down
4 changes: 2 additions & 2 deletions pandas-stubs/core/window/ewm.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class ExponentialMovingWindowGroupby(
class OnlineExponentialMovingWindow(ExponentialMovingWindow[NDFrameT]):
def reset(self) -> None: ...
def aggregate(self, func, *args: Any, **kwargs: Any): ...
def std(self, bias: bool = False, *args: Any, **kwargs: Any): ... # pyrefly: ignore
def std(self, bias: bool = False, *args: Any, **kwargs: Any): ...
def corr(
self,
other: DataFrame | Series | None = None,
Expand All @@ -66,7 +66,7 @@ class OnlineExponentialMovingWindow(ExponentialMovingWindow[NDFrameT]):
numeric_only: bool = False,
): ...
def var(self, bias: bool = False, numeric_only: bool = False): ...
def mean( # pyrefly: ignore
def mean(
self,
*args: Any,
update: NDFrameT | None = ...,
Expand Down
126 changes: 126 additions & 0 deletions tests/indexes/arithmetic/str/test_mul.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
from datetime import (
datetime,
timedelta,
)
from typing import Any

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

from tests import (
TYPE_CHECKING_INVALID_USAGE,
check,
)


@pytest.fixture
def left() -> "pd.Index[str]":
"""left operand"""
lo = pd.Index(["1", "2", "3"])
return check(assert_type(lo, "pd.Index[str]"), pd.Index, str)


def test_mul_py_scalar(left: "pd.Index[str]") -> None:
"""Test pd.Index[str] * Python native scalars"""
b, i, f, c = True, 1, 1.0, 1j
s, d = datetime(2025, 9, 27), timedelta(seconds=1)

check(assert_type(left * b, "pd.Index[str]"), pd.Index, str)
check(assert_type(left * i, "pd.Index[str]"), pd.Index, str)
if TYPE_CHECKING_INVALID_USAGE:
_03 = left * f # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
_04 = left * c # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
_05 = left * s # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
_06 = left * d # type: ignore[operator] # pyright: ignore[reportOperatorIssue]

check(assert_type(b * left, "pd.Index[str]"), pd.Index, str)
check(assert_type(i * left, "pd.Index[str]"), pd.Index, str)
if TYPE_CHECKING_INVALID_USAGE:
_13 = f * left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
_14 = c * left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
_15 = s * left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
_16 = d * left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]


def test_mul_py_sequence(left: "pd.Index[str]") -> None:
"""Test pd.Index[str] * Python native sequences"""
b, i, f, c = [True, False, True], [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j]
s = [datetime(2025, 9, d) for d in (27, 28, 29)]
d = [timedelta(seconds=s + 1) for s in range(3)]

check(assert_type(left * b, "pd.Index[str]"), pd.Index, str)
check(assert_type(left * i, "pd.Index[str]"), pd.Index, str)
if TYPE_CHECKING_INVALID_USAGE:
_03 = left * f # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
_04 = left * c # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
_05 = left * s # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
_06 = left * d # type: ignore[operator] # pyright: ignore[reportOperatorIssue]

check(assert_type(b * left, "pd.Index[str]"), pd.Index, str)
check(assert_type(i * left, "pd.Index[str]"), pd.Index, str)
if TYPE_CHECKING_INVALID_USAGE:
_13 = f * left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
_14 = c * left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
_15 = s * left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
_16 = d * left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]


def test_mul_numpy_array(left: "pd.Index[str]") -> None:
"""Test pd.Index[str] * numpy arrays"""
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)
s = np.array([np.datetime64(f"2025-09-{d}") for d in (27, 28, 29)], np.datetime64)
d = np.array([np.timedelta64(s + 1, "s") for s in range(3)], np.timedelta64)

check(assert_type(left * b, "pd.Index[str]"), pd.Index, str)
check(assert_type(left * i, "pd.Index[str]"), pd.Index, str)
if TYPE_CHECKING_INVALID_USAGE:
assert_type(left * f, Never)
assert_type(left * c, Never)
assert_type(left * s, Never)
assert_type(left * d, Never)

# `numpy` typing gives the corresponding `ndarray`s in the static type
# checking, where our `__rmul__` cannot override. At runtime, they return
# `Index` with the correct element type.
check(assert_type(b * left, "npt.NDArray[np.bool_]"), pd.Index, str)
check(assert_type(i * left, "npt.NDArray[np.int64]"), pd.Index, str)
if TYPE_CHECKING_INVALID_USAGE:
assert_type(f * left, "npt.NDArray[np.float64]")
assert_type(c * left, "npt.NDArray[np.complex128]")
assert_type(s * left, Any)
assert_type(d * left, "npt.NDArray[np.timedelta64]")


def test_mul_pd_index(left: "pd.Index[str]") -> None:
"""Test pd.Index[str] * pandas Indexes"""
b = pd.Index([True, False, True])
i = pd.Index([2, 3, 5])
f = pd.Index([1.0, 2.0, 3.0])
c = pd.Index([1.1j, 2.2j, 4.1j])
s = pd.Index([datetime(2025, 9, d) for d in (27, 28, 29)])
d = pd.Index([timedelta(seconds=s + 1) for s in range(3)])

check(assert_type(left * b, "pd.Index[str]"), pd.Index, str)
check(assert_type(left * i, "pd.Index[str]"), pd.Index, str)
if TYPE_CHECKING_INVALID_USAGE:
_03 = left * f # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
_04 = left * c # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
_05 = left * s # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
_06 = left * d # type: ignore[operator] # pyright: ignore[reportOperatorIssue]

check(assert_type(b * left, "pd.Index[str]"), pd.Index, str)
check(assert_type(i * left, "pd.Index[str]"), pd.Index, str)
if TYPE_CHECKING_INVALID_USAGE:
_13 = f * left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
_14 = c * left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
_15 = s * left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
_16 = d * left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
Loading
Loading