Skip to content
6 changes: 5 additions & 1 deletion pandas-stubs/core/indexes/base.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,6 @@ class Index(IndexOpsMixin[S1], ElementOpsMixin[S1]):
@property
def nlevels(self) -> int: ...
def get_level_values(self, level: int | _str) -> Index: ...
def droplevel(self, level: Level | Sequence[Level] = 0) -> Self: ...
@property
def is_monotonic_increasing(self) -> bool: ...
@property
Expand All @@ -452,6 +451,11 @@ class Index(IndexOpsMixin[S1], ElementOpsMixin[S1]):
def dropna(self, how: AnyAll = "any") -> Self: ...
def unique(self, level: Hashable | None = None) -> Self: ...
def drop_duplicates(self, *, keep: DropKeep = "first") -> Self: ...
# droplevel() always raises ValueError on a plain (single-level) Index
# unless you pass an empty sequence — then it's a no-op.
# Use Sequence[Never] so type checkers allow droplevel([]) but reject
# anything else (droplevel(0), droplevel([0]), droplevel("name"), ...).
Comment thread
cmp0xff marked this conversation as resolved.
Outdated
def droplevel(self, level: Sequence[Never] = ...) -> Self: ...
Comment thread
tinezivic marked this conversation as resolved.
Outdated
def duplicated(self, keep: DropKeep = "first") -> np_1darray_bool: ...
def __and__(self, other: Never) -> Never: ...
def __rand__(self, other: Never) -> Never: ...
Expand Down
2 changes: 1 addition & 1 deletion pandas-stubs/core/indexes/multi.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class MultiIndex(Index):
@property
def is_monotonic_decreasing(self) -> bool: ...
def dropna(self, how: AnyAll = "any") -> Self: ...
def droplevel(self, level: Level | Sequence[Level] = 0) -> MultiIndex | Index: ... # type: ignore[override] # pyrefly: ignore[bad-override]
def droplevel(self, level: Level | Sequence[Level] = 0) -> MultiIndex | Index: ... # type: ignore[override] # return type is wider than Self
Comment thread
tinezivic marked this conversation as resolved.
Outdated
def get_level_values(self, level: str | int) -> Index: ...
@overload # type: ignore[override]
def unique( # pyrefly: ignore[bad-override]
Expand Down
11 changes: 9 additions & 2 deletions tests/indexes/test_indexes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1608,15 +1608,22 @@ def test_index_set_names() -> None:


def test_index_droplevel() -> None:
idx = pd.Index([1, 2])
check(assert_type(idx.droplevel([]), "pd.Index[int]"), pd.Index, np.integer)
Comment thread
tinezivic marked this conversation as resolved.
mi = pd.MultiIndex.from_arrays([[1, 2, 3], [4, 5, 6]], names=["elk", "owl"])
check(assert_type(mi.droplevel([]), pd.MultiIndex | pd.Index), pd.MultiIndex)
check(assert_type(mi.droplevel([0]), pd.MultiIndex | pd.Index), pd.Index)
check(assert_type(mi.droplevel((0,)), pd.MultiIndex | pd.Index), pd.Index)
check(assert_type(mi.droplevel(["elk"]), pd.MultiIndex | pd.Index), pd.Index)
check(assert_type(mi.droplevel(("elk",)), pd.MultiIndex | pd.Index), pd.Index)
check(assert_type(mi.droplevel(0), pd.MultiIndex | pd.Index), pd.Index)
# droplevel([]) on a plain Index is a documented no-op — returns the same Index
idx = pd.Index([1, 2])
check(assert_type(idx.droplevel([]), "pd.Index[int]"), pd.Index)
Comment thread
cmp0xff marked this conversation as resolved.
Outdated
if TYPE_CHECKING_INVALID_USAGE:
# Any non-empty argument raises ValueError at runtime, so type checkers
# should reject these — Sequence[Never] catches them all.
idx.droplevel(0) # type: ignore[arg-type] # pyright: ignore[reportArgumentType]
Comment thread
tinezivic marked this conversation as resolved.
idx.droplevel([0]) # type: ignore[arg-type] # pyright: ignore[reportArgumentType]
idx.droplevel("name") # type: ignore[arg-type] # pyright: ignore[reportArgumentType]


def test_index_setitem() -> None:
Expand Down