Skip to content

TYP: Mypy workaround for NoDefault #47045

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
May 25, 2022
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
6 changes: 4 additions & 2 deletions pandas/_libs/lib.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ ndarray_obj_2d = np.ndarray

from enum import Enum

class NoDefault(Enum): ...
class _NoDefault(Enum):
no_default = ...

no_default: NoDefault
no_default = _NoDefault.no_default
NoDefault = Literal[_NoDefault.no_default]

i8max: int
u8max: int
Expand Down
6 changes: 4 additions & 2 deletions pandas/_libs/lib.pyx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from collections import abc
from decimal import Decimal
from enum import Enum
from typing import Literal
import warnings

cimport cython
Expand Down Expand Up @@ -2791,7 +2792,7 @@ cdef _infer_all_nats(dtype, ndarray datetimes, ndarray timedeltas):
return result


class NoDefault(Enum):
class _NoDefault(Enum):
# We make this an Enum
# 1) because it round-trips through pickle correctly (see GH#40397)
# 2) because mypy does not understand singletons
Expand All @@ -2802,7 +2803,8 @@ class NoDefault(Enum):


# Note: no_default is exported to the public API in pandas.api.extensions
no_default = NoDefault.no_default # Sentinel indicating the default value.
no_default = _NoDefault.no_default # Sentinel indicating the default value.
NoDefault = Literal[_NoDefault.no_default]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of defining NoDefault in lib.pyx and lib.pyi it could be defined in _typing.py (would require changing a lot of import statements)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its actually better where it is as we avoid circular deps this way



@cython.boundscheck(False)
Expand Down
14 changes: 2 additions & 12 deletions pandas/_testing/asserters.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,7 @@ def assert_almost_equal(
FutureWarning,
stacklevel=find_stack_level(),
)
# https://github.com/python/mypy/issues/7642
# error: Argument 1 to "_get_tol_from_less_precise" has incompatible
# type "Union[bool, int, NoDefault]"; expected "Union[bool, int]"
rtol = atol = _get_tol_from_less_precise(
check_less_precise # type: ignore[arg-type]
)
rtol = atol = _get_tol_from_less_precise(check_less_precise)

if isinstance(left, Index):
assert_index_equal(
Expand Down Expand Up @@ -345,12 +340,7 @@ def _get_ilevel_values(index, level):
FutureWarning,
stacklevel=find_stack_level(),
)
# https://github.com/python/mypy/issues/7642
# error: Argument 1 to "_get_tol_from_less_precise" has incompatible
# type "Union[bool, int, NoDefault]"; expected "Union[bool, int]"
rtol = atol = _get_tol_from_less_precise(
check_less_precise # type: ignore[arg-type]
)
rtol = atol = _get_tol_from_less_precise(check_less_precise)

# instance validation
_check_isinstance(left, right, Index)
Expand Down
3 changes: 2 additions & 1 deletion pandas/_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,8 @@ def closed(self) -> bool:
XMLParsers = Literal["lxml", "etree"]

# Interval closed type
IntervalClosedType = Literal["left", "right", "both", "neither"]
IntervalLeftRight = Literal["left", "right"]
IntervalClosedType = Union[IntervalLeftRight, Literal["both", "neither"]]

# datetime and NaTType
DatetimeNaTType = Union[datetime, "NaTType"]
Expand Down
14 changes: 7 additions & 7 deletions pandas/core/arrays/interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ def _simple_new(
cls: type[IntervalArrayT],
left,
right,
closed=None,
closed: IntervalClosedType | None = None,
copy: bool = False,
dtype: Dtype | None = None,
verify_integrity: bool = True,
Expand Down Expand Up @@ -416,7 +416,7 @@ def _from_factorized(
def from_breaks(
cls: type[IntervalArrayT],
breaks,
closed="right",
closed: IntervalClosedType | None = "right",
copy: bool = False,
dtype: Dtype | None = None,
) -> IntervalArrayT:
Expand Down Expand Up @@ -492,7 +492,7 @@ def from_arrays(
cls: type[IntervalArrayT],
left,
right,
closed="right",
closed: IntervalClosedType | None = "right",
copy: bool = False,
dtype: Dtype | None = None,
) -> IntervalArrayT:
Expand Down Expand Up @@ -956,10 +956,10 @@ def _concat_same_type(
-------
IntervalArray
"""
closed = {interval.closed for interval in to_concat}
if len(closed) != 1:
closed_set = {interval.closed for interval in to_concat}
if len(closed_set) != 1:
raise ValueError("Intervals must all be closed on the same side.")
closed = closed.pop()
closed = closed_set.pop()

left = np.concatenate([interval.left for interval in to_concat])
right = np.concatenate([interval.right for interval in to_concat])
Expand Down Expand Up @@ -1328,7 +1328,7 @@ def overlaps(self, other):
# ---------------------------------------------------------------------

@property
def closed(self):
def closed(self) -> IntervalClosedType:
"""
Whether the intervals are closed on the left-side, right-side, both or
neither.
Expand Down
2 changes: 1 addition & 1 deletion pandas/core/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,7 @@ def resolve_numeric_only(numeric_only: bool | None | lib.NoDefault) -> bool:
# first default to None
result = False
else:
result = cast(bool, numeric_only)
result = numeric_only
return result


Expand Down
8 changes: 2 additions & 6 deletions pandas/core/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -6279,8 +6279,7 @@ def dropna(
# faster equivalent to 'agg_obj.count(agg_axis) > 0'
mask = notna(agg_obj).any(axis=agg_axis, bool_only=False)
else:
if how is not no_default:
raise ValueError(f"invalid how option: {how}")
raise ValueError(f"invalid how option: {how}")

if np.all(mask):
result = self.copy()
Expand Down Expand Up @@ -8050,9 +8049,6 @@ def groupby(
raise TypeError("You have to supply one of 'by' and 'level'")
axis = self._get_axis_number(axis)

# https://github.com/python/mypy/issues/7642
# error: Argument "squeeze" to "DataFrameGroupBy" has incompatible type
# "Union[bool, NoDefault]"; expected "bool"
return DataFrameGroupBy(
obj=self,
keys=by,
Expand All @@ -8061,7 +8057,7 @@ def groupby(
as_index=as_index,
sort=sort,
group_keys=group_keys,
squeeze=squeeze, # type: ignore[arg-type]
squeeze=squeeze,
observed=observed,
dropna=dropna,
)
Expand Down
5 changes: 2 additions & 3 deletions pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -7898,8 +7898,8 @@ def between_time(
FutureWarning,
stacklevel=find_stack_level(),
)
left = True if isinstance(include_start, lib.NoDefault) else include_start
right = True if isinstance(include_end, lib.NoDefault) else include_end
left = True if include_start is lib.no_default else include_start
right = True if include_end is lib.no_default else include_end

inc_dict: dict[tuple[bool_t, bool_t], IntervalClosedType] = {
(True, True): "both",
Expand Down Expand Up @@ -10689,7 +10689,6 @@ def _stat_function(

if axis is None:
axis = self._stat_axis_number
axis = cast(Axis, axis)
if level is not None:
warnings.warn(
"Using the level keyword in DataFrame and Series aggregations is "
Expand Down
4 changes: 2 additions & 2 deletions pandas/core/groupby/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1580,7 +1580,7 @@ def idxmax(
# DataFrame.idxmax for backwards compatibility
numeric_only_arg = None if axis == 0 else False
else:
numeric_only_arg = cast(bool, numeric_only)
numeric_only_arg = numeric_only

def func(df):
res = df._reduce(
Expand Down Expand Up @@ -1616,7 +1616,7 @@ def idxmin(
# DataFrame.idxmin for backwards compatibility
numeric_only_arg = None if axis == 0 else False
else:
numeric_only_arg = cast(bool, numeric_only)
numeric_only_arg = numeric_only

def func(df):
res = df._reduce(
Expand Down
4 changes: 1 addition & 3 deletions pandas/core/groupby/groupby.py
Original file line number Diff line number Diff line change
Expand Up @@ -1277,9 +1277,7 @@ def _resolve_numeric_only(
else:
numeric_only = False

# error: Incompatible return value type (got "Union[bool, NoDefault]",
# expected "bool")
return numeric_only # type: ignore[return-value]
return numeric_only

def _maybe_warn_numeric_only_depr(
self, how: str, result: DataFrame | Series, numeric_only: bool | lib.NoDefault
Expand Down
7 changes: 4 additions & 3 deletions pandas/core/indexes/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
Dtype,
DtypeObj,
IntervalClosedType,
IntervalLeftRight,
npt,
)
from pandas.util._decorators import (
Expand Down Expand Up @@ -1039,12 +1040,12 @@ def date_range(
DatetimeIndex(['2017-01-02', '2017-01-03', '2017-01-04'],
dtype='datetime64[ns]', freq='D')
"""
if inclusive is not None and not isinstance(closed, lib.NoDefault):
if inclusive is not None and closed is not lib.no_default:
raise ValueError(
"Deprecated argument `closed` cannot be passed"
"if argument `inclusive` is not None"
)
elif not isinstance(closed, lib.NoDefault):
elif closed is not lib.no_default:
warnings.warn(
"Argument `closed` is deprecated in favor of `inclusive`.",
FutureWarning,
Expand Down Expand Up @@ -1087,7 +1088,7 @@ def bdate_range(
name: Hashable = None,
weekmask=None,
holidays=None,
closed: lib.NoDefault = lib.no_default,
closed: IntervalLeftRight | lib.NoDefault | None = lib.no_default,
inclusive: IntervalClosedType | None = None,
**kwargs,
) -> DatetimeIndex:
Expand Down
10 changes: 5 additions & 5 deletions pandas/core/indexes/interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ def __new__(
def from_breaks(
cls,
breaks,
closed: str = "right",
closed: IntervalClosedType | None = "right",
name: Hashable = None,
copy: bool = False,
dtype: Dtype | None = None,
Expand Down Expand Up @@ -282,7 +282,7 @@ def from_arrays(
cls,
left,
right,
closed: str = "right",
closed: IntervalClosedType = "right",
name: Hashable = None,
copy: bool = False,
dtype: Dtype | None = None,
Expand Down Expand Up @@ -957,7 +957,7 @@ def interval_range(
periods=None,
freq=None,
name: Hashable = None,
closed: lib.NoDefault = lib.no_default,
closed: IntervalClosedType | lib.NoDefault = lib.no_default,
inclusive: IntervalClosedType | None = None,
) -> IntervalIndex:
"""
Expand Down Expand Up @@ -1054,12 +1054,12 @@ def interval_range(
IntervalIndex([[1, 2], [2, 3], [3, 4], [4, 5]],
dtype='interval[int64, both]')
"""
if inclusive is not None and not isinstance(closed, lib.NoDefault):
if inclusive is not None and closed is not lib.no_default:
raise ValueError(
"Deprecated argument `closed` cannot be passed "
"if argument `inclusive` is not None"
)
elif not isinstance(closed, lib.NoDefault):
elif closed is not lib.no_default:
warnings.warn(
"Argument `closed` is deprecated in favor of `inclusive`.",
FutureWarning,
Expand Down
3 changes: 2 additions & 1 deletion pandas/core/indexes/multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
Hashable,
Iterable,
List,
Literal,
Sequence,
Tuple,
cast,
Expand Down Expand Up @@ -1397,7 +1398,7 @@ def format(
sparsify = get_option("display.multi_sparse")

if sparsify:
sentinel = ""
sentinel: Literal[""] | bool | lib.NoDefault = ""
# GH3547 use value of sparsify as sentinel if it's "Falsey"
assert isinstance(sparsify, bool) or sparsify is lib.no_default
if sparsify in [False, lib.no_default]:
Expand Down
3 changes: 2 additions & 1 deletion pandas/core/reshape/tile.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
Timestamp,
)
from pandas._libs.lib import infer_dtype
from pandas._typing import IntervalLeftRight

from pandas.core.dtypes.common import (
DT64NS_DTYPE,
Expand Down Expand Up @@ -560,7 +561,7 @@ def _format_labels(
bins, precision: int, right: bool = True, include_lowest: bool = False, dtype=None
):
"""based on the dtype, return our labels"""
closed = "right" if right else "left"
closed: IntervalLeftRight = "right" if right else "left"

formatter: Callable[[Any], Timestamp] | Callable[[Any], Timedelta]

Expand Down
4 changes: 1 addition & 3 deletions pandas/core/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -1952,8 +1952,6 @@ def groupby(
raise TypeError("You have to supply one of 'by' and 'level'")
axis = self._get_axis_number(axis)

# error: Argument "squeeze" to "SeriesGroupBy" has incompatible type
# "Union[bool, NoDefault]"; expected "bool"
return SeriesGroupBy(
obj=self,
keys=by,
Expand All @@ -1962,7 +1960,7 @@ def groupby(
as_index=as_index,
sort=sort,
group_keys=group_keys,
squeeze=squeeze, # type: ignore[arg-type]
squeeze=squeeze,
observed=observed,
dropna=dropna,
)
Expand Down