From b8b943ad34ed395f96d38cb163e9a4e70ef25d27 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 18 Mar 2021 11:14:48 -0700 Subject: [PATCH 1/2] TYP: nattype.pyi --- pandas/_libs/tslibs/nattype.pyi | 135 ++++++++++++++++++++++++++ pandas/core/algorithms.py | 2 + pandas/core/indexes/base.py | 3 +- pandas/core/indexes/datetimelike.py | 3 +- pandas/core/internals/construction.py | 5 +- pandas/core/nanops.py | 22 ++--- pandas/core/tools/datetimes.py | 2 +- pandas/io/excel/_odfreader.py | 3 +- pandas/io/formats/format.py | 13 ++- 9 files changed, 163 insertions(+), 25 deletions(-) create mode 100644 pandas/_libs/tslibs/nattype.pyi diff --git a/pandas/_libs/tslibs/nattype.pyi b/pandas/_libs/tslibs/nattype.pyi new file mode 100644 index 0000000000000..c367aa3e5f7bf --- /dev/null +++ b/pandas/_libs/tslibs/nattype.pyi @@ -0,0 +1,135 @@ + +from datetime import datetime + +import numpy as np + +NaT: NaTType +iNaT: int +nat_strings: set[str] + +def is_null_datetimelike(val: object, inat_is_null: bool = ...) -> bool: ... + +class NaTType(datetime): + value: np.int64 + + def asm8(self) -> np.datetime64: ... + def to_datetime64(self) -> np.datetime64: ... + def to_numpy(self, dtype=..., copy: bool = False) -> np.datetime64: ... + + @property + def is_leap_year(self) -> bool: ... + @property + def is_month_start(self) -> bool: ... + @property + def is_quarter_start(self) -> bool: ... + @property + def is_year_start(self) -> bool: ... + @property + def is_month_end(self) -> bool: ... + @property + def is_quarter_end(self) -> bool: ... + @property + def is_year_end(self) -> bool: ... + + @property + def day_of_year(self) -> float: ... + @property + def dayofyear(self) -> float: ... + @property + def days_in_month(self) -> float: ... + @property + def daysinmonth(self) -> float: ... + @property + def day_of_week(self) -> float: ... + @property + def dayofweek(self) -> float: ... + @property + def week(self) -> float: ... + @property + def weekofyear(self) -> float: ... + + def day_name(self) -> float: ... + def month_name(self) -> float: ... + + # error: Return type "float" of "weekday" incompatible with return + # type "int" in supertype "date" + def weekday(self) -> float: ... # type: ignore[override] + + # error: Return type "float" of "isoweekday" incompatible with return + # type "int" in supertype "date" + def isoweekday(self) -> float: ... # type: ignore[override] + + def total_seconds(self) -> float: ... + + # error: Signature of "today" incompatible with supertype "datetime" + def today(self, *args, **kwargs) -> NaTType: ... # type: ignore[override] + # error: Signature of "today" incompatible with supertype "datetime" + def now(self, *args, **kwargs) -> NaTType: ... # type: ignore[override] + + def to_pydatetime(self) -> NaTType: ... + def date(self) -> NaTType: ... + + def round(self) -> NaTType: ... + def floor(self) -> NaTType: ... + def ceil(self) -> NaTType: ... + + def tz_convert(self) -> NaTType: ... + def tz_localize(self) -> NaTType: ... + + def replace(self, *args, **kwargs) -> NaTType: ... + + # error: Return type "float" of "year" incompatible with return + # type "int" in supertype "date" + @property + def year(self) -> float: ... # type: ignore[override] + + @property + def quarter(self) -> float: ... + + # error: Return type "float" of "month" incompatible with return + # type "int" in supertype "date" + @property + def month(self) -> float: ... # type: ignore[override] + + # error: Return type "float" of "day" incompatible with return + # type "int" in supertype "date" + @property + def day(self) -> float: ... # type: ignore[override] + + # error: Return type "float" of "hour" incompatible with return + # type "int" in supertype "date" + @property + def hour(self) -> float: ... # type: ignore[override] + + # error: Return type "float" of "minute" incompatible with return + # type "int" in supertype "date" + @property + def minute(self) -> float: ... # type: ignore[override] + + # error: Return type "float" of "second" incompatible with return + # type "int" in supertype "date" + @property + def second(self) -> float: ... # type: ignore[override] + + @property + def millisecond(self) -> float: ... + + # error: Return type "float" of "microsecond" incompatible with return + # type "int" in supertype "date" + @property + def microsecond(self) -> float: ... # type: ignore[override] + + @property + def nanosecond(self) -> float: ... + + # inject Timedelta properties + @property + def days(self) -> float: ... + @property + def microseconds(self) -> float: ... + @property + def nanoseconds(self) -> float: ... + + # inject Period properties + @property + def qyear(self) -> float: ... diff --git a/pandas/core/algorithms.py b/pandas/core/algorithms.py index 15f54c11be0a0..963777a0ddc3c 100644 --- a/pandas/core/algorithms.py +++ b/pandas/core/algorithms.py @@ -33,6 +33,7 @@ ArrayLike, DtypeObj, FrameOrSeriesUnion, + Scalar, ) from pandas.util._decorators import doc @@ -763,6 +764,7 @@ def factorize( dtype = original.dtype else: values, dtype = _ensure_data(values) + na_value: Scalar if original.dtype.kind in ["m", "M"]: # Note: factorize_array will cast NaT bc it has a __int__ diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 3a468758ab3fd..2ab464571a30f 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -38,6 +38,7 @@ ) from pandas._libs.tslibs import ( IncompatibleFrequency, + NaTType, OutOfBoundsDatetime, Timestamp, tz_compare, @@ -2371,7 +2372,7 @@ def __reduce__(self): # -------------------------------------------------------------------- # Null Handling Methods - _na_value = np.nan + _na_value: Union[float, NaTType] = np.nan """The expected NA value to use with this index.""" @cache_readonly diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 31ad8b7d8a295..e194148f0fc24 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -24,6 +24,7 @@ ) from pandas._libs.tslibs import ( BaseOffset, + NaTType, Resolution, Tick, ) @@ -218,7 +219,7 @@ def take(self, indices, axis=0, allow_fill=True, fill_value=None, **kwargs): _can_hold_na = True - _na_value = NaT + _na_value: NaTType = NaT """The expected NA value to use with this index.""" def _convert_tolerance(self, tolerance, target): diff --git a/pandas/core/internals/construction.py b/pandas/core/internals/construction.py index 4a08e733b770c..9959174373034 100644 --- a/pandas/core/internals/construction.py +++ b/pandas/core/internals/construction.py @@ -368,7 +368,10 @@ def maybe_squeeze_dt64tz(dta: ArrayLike) -> ArrayLike: # TODO(EA2D): kludge not needed with 2D EAs if isinstance(dta, DatetimeArray) and dta.ndim == 2 and dta.tz is not None: assert dta.shape[0] == 1 - dta = dta[0] + # error: Incompatible types in assignment (expression has type + # "Union[DatetimeLikeArrayMixin, Union[Any, NaTType]]", variable has + # type "Union[ExtensionArray, ndarray]") + dta = dta[0] # type: ignore[assignment] return dta diff --git a/pandas/core/nanops.py b/pandas/core/nanops.py index 45f275664b206..5fadf7752b049 100644 --- a/pandas/core/nanops.py +++ b/pandas/core/nanops.py @@ -18,6 +18,7 @@ from pandas._libs import ( NaT, + NaTType, Timedelta, iNaT, lib, @@ -414,11 +415,8 @@ def new_func( if datetimelike: result = _wrap_results(result, orig_values.dtype, fill_value=iNaT) if not skipna: - # error: Argument 3 to "_mask_datetimelike_result" has incompatible type - # "Optional[ndarray]"; expected "ndarray" - result = _mask_datetimelike_result( - result, axis, mask, orig_values # type: ignore[arg-type] - ) + assert mask is not None # checked above + result = _mask_datetimelike_result(result, axis, mask, orig_values) return result @@ -601,7 +599,7 @@ def _mask_datetimelike_result( axis: Optional[int], mask: np.ndarray, orig_values: np.ndarray, -): +) -> Union[np.ndarray, np.datetime64, np.timedelta64, NaTType]: if isinstance(result, np.ndarray): # we need to apply the mask result = result.astype("i8").view(orig_values.dtype) @@ -609,7 +607,7 @@ def _mask_datetimelike_result( result[axis_mask] = iNaT else: if mask.any(): - result = NaT + return NaT return result @@ -1435,19 +1433,19 @@ def _get_counts( def _maybe_null_out( - result: np.ndarray, + result: np.ndarray | float | NaTType, axis: Optional[int], mask: Optional[np.ndarray], shape: Tuple[int, ...], min_count: int = 1, -) -> np.ndarray | float: +) -> np.ndarray | float | NaTType: """ Returns ------- Dtype The product of all elements on a given axis. ( NaNs are treated as 1) """ - if mask is not None and axis is not None and getattr(result, "ndim", False): + if mask is not None and axis is not None and isinstance(result, np.ndarray): null_mask = (mask.shape[axis] - mask.sum(axis) - min_count) < 0 if np.any(null_mask): if is_numeric_dtype(result): @@ -1461,9 +1459,7 @@ def _maybe_null_out( result[null_mask] = None elif result is not NaT: if check_below_min_count(shape, mask, min_count): - # error: Incompatible types in assignment (expression has type - # "float", variable has type "ndarray") - result = np.nan # type: ignore[assignment] + result = np.nan return result diff --git a/pandas/core/tools/datetimes.py b/pandas/core/tools/datetimes.py index 2fd91c07ff4ac..5f33d00530361 100644 --- a/pandas/core/tools/datetimes.py +++ b/pandas/core/tools/datetimes.py @@ -701,7 +701,7 @@ def to_datetime( infer_datetime_format: bool = False, origin="unix", cache: bool = True, -) -> Union[DatetimeIndex, Series, DatetimeScalar, NaTType]: +) -> Optional[Union[DatetimeIndex, Series, DatetimeScalar, NaTType]]: """ Convert argument to datetime. diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index c5aa4a061a05b..0278b22995089 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -202,7 +202,8 @@ def _get_cell_value(self, cell, convert_float: bool) -> Scalar: elif cell_type == "time": result = pd.to_datetime(str(cell)) result = cast(pd.Timestamp, result) - return result.time() + # error: Item "str" of "Union[float, str, NaTType]" has no attribute "time" + return result.time() # type: ignore[union-attr] else: self.close() raise ValueError(f"Unrecognized type {cell_type}") diff --git a/pandas/io/formats/format.py b/pandas/io/formats/format.py index 3514fbc8c6293..ceaaef6e27445 100644 --- a/pandas/io/formats/format.py +++ b/pandas/io/formats/format.py @@ -1723,7 +1723,10 @@ def _format_datetime64_dateonly( if date_format: return x.strftime(date_format) else: - return x._date_repr + # error: Item "NaTType" of "Union[NaTType, Any]" has no attribute "_date_repr" + # The underlying problem here is that mypy doesn't understand that NaT + # is a singleton, so that the check above excludes it here. + return x._date_repr # type: ignore[union-attr] def get_format_datetime64( @@ -1796,17 +1799,13 @@ def get_format_timedelta64( If box, then show the return in quotes """ - values_int = values.view(np.int64) + values_int = np.asarray(values.view(np.int64)) consider_values = values_int != iNaT one_day_nanos = 86400 * 10 ** 9 even_days = ( - # error: Unsupported operand types for % ("ExtensionArray" and "int") - np.logical_and( - consider_values, values_int % one_day_nanos != 0 # type: ignore[operator] - ).sum() - == 0 + np.logical_and(consider_values, values_int % one_day_nanos != 0).sum() == 0 ) if even_days: From 2a074d23d5f1462810eed2b0e694c16662c2fdf8 Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 19 Mar 2021 08:14:30 -0700 Subject: [PATCH 2/2] ignore in get_format_timedelt64 --- pandas/_libs/tslibs/nattype.pyi | 2 +- pandas/io/formats/format.py | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/pandas/_libs/tslibs/nattype.pyi b/pandas/_libs/tslibs/nattype.pyi index c367aa3e5f7bf..0f81dcb4b2df1 100644 --- a/pandas/_libs/tslibs/nattype.pyi +++ b/pandas/_libs/tslibs/nattype.pyi @@ -14,7 +14,7 @@ class NaTType(datetime): def asm8(self) -> np.datetime64: ... def to_datetime64(self) -> np.datetime64: ... - def to_numpy(self, dtype=..., copy: bool = False) -> np.datetime64: ... + def to_numpy(self, dtype=..., copy: bool = ...) -> np.datetime64: ... @property def is_leap_year(self) -> bool: ... diff --git a/pandas/io/formats/format.py b/pandas/io/formats/format.py index ceaaef6e27445..6c13350df2fa3 100644 --- a/pandas/io/formats/format.py +++ b/pandas/io/formats/format.py @@ -1799,14 +1799,20 @@ def get_format_timedelta64( If box, then show the return in quotes """ - values_int = np.asarray(values.view(np.int64)) + values_int = values.view(np.int64) consider_values = values_int != iNaT one_day_nanos = 86400 * 10 ** 9 - even_days = ( - np.logical_and(consider_values, values_int % one_day_nanos != 0).sum() == 0 - ) + # error: Unsupported operand types for % ("ExtensionArray" and "int") + not_midnight = values_int % one_day_nanos != 0 # type: ignore[operator] + # error: Argument 1 to "__call__" of "ufunc" has incompatible type + # "Union[Any, ExtensionArray, ndarray]"; expected + # "Union[Union[int, float, complex, str, bytes, generic], + # Sequence[Union[int, float, complex, str, bytes, generic]], + # Sequence[Sequence[Any]], _SupportsArray]" + both = np.logical_and(consider_values, not_midnight) # type: ignore[arg-type] + even_days = both.sum() == 0 if even_days: format = None