diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index ba5bfc108f16b..bb40cf78ea006 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -183,7 +183,7 @@ def _rebox_native(cls, value: int) -> Union[int, np.datetime64, np.timedelta64]: """ raise AbstractMethodError(cls) - def _unbox_scalar(self, value: DTScalarOrNaT) -> int: + def _unbox_scalar(self, value: DTScalarOrNaT, setitem: bool = False) -> int: """ Unbox the integer value of a scalar `value`. @@ -191,6 +191,8 @@ def _unbox_scalar(self, value: DTScalarOrNaT) -> int: ---------- value : Period, Timestamp, Timedelta, or NaT Depending on subclass. + setitem : bool, default False + Whether to check compatiblity with setitem strictness. Returns ------- @@ -841,6 +843,7 @@ def _validate_listlike( if is_dtype_equal(value.categories.dtype, self.dtype): # TODO: do we need equal dtype or just comparable? value = value._internal_get_values() + value = extract_array(value, extract_numpy=True) if allow_object and is_object_dtype(value.dtype): pass @@ -875,8 +878,7 @@ def _validate_setitem_value(self, value): # TODO: cast_str for consistency? value = self._validate_scalar(value, msg, cast_str=False) - self._check_compatible_with(value, setitem=True) - return self._unbox(value) + return self._unbox(value, setitem=True) def _validate_insert_value(self, value): msg = f"cannot insert {type(self).__name__} with incompatible label" @@ -886,6 +888,8 @@ def _validate_insert_value(self, value): # TODO: if we dont have compat, should we raise or astype(object)? # PeriodIndex does astype(object) return value + # Note: we do not unbox here because the caller needs boxed value + # to check for freq. def _validate_where_value(self, other): msg = f"Where requires matching dtype, not {type(other)}" @@ -893,20 +897,18 @@ def _validate_where_value(self, other): other = self._validate_scalar(other, msg) else: other = self._validate_listlike(other, "where") - self._check_compatible_with(other, setitem=True) - self._check_compatible_with(other, setitem=True) - return self._unbox(other) + return self._unbox(other, setitem=True) - def _unbox(self, other) -> Union[np.int64, np.ndarray]: + def _unbox(self, other, setitem: bool = False) -> Union[np.int64, np.ndarray]: """ Unbox either a scalar with _unbox_scalar or an instance of our own type. """ if lib.is_scalar(other): - other = self._unbox_scalar(other) + other = self._unbox_scalar(other, setitem=setitem) else: # same type as self - self._check_compatible_with(other) + self._check_compatible_with(other, setitem=setitem) other = other.view("i8") return other diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 9f10cc84dcfcc..56e0a9861548f 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -451,11 +451,11 @@ def _generate_range( def _rebox_native(cls, value: int) -> np.datetime64: return np.int64(value).view("M8[ns]") - def _unbox_scalar(self, value): + def _unbox_scalar(self, value, setitem: bool = False): if not isinstance(value, self._scalar_type) and value is not NaT: raise ValueError("'value' should be a Timestamp.") if not isna(value): - self._check_compatible_with(value) + self._check_compatible_with(value, setitem=setitem) return value.value def _scalar_from_string(self, value): diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index eea11bde77030..865b1680c008a 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -257,11 +257,13 @@ def _generate_range(cls, start, end, periods, freq, fields): def _rebox_native(cls, value: int) -> np.int64: return np.int64(value) - def _unbox_scalar(self, value: Union[Period, NaTType]) -> int: + def _unbox_scalar( + self, value: Union[Period, NaTType], setitem: bool = False + ) -> int: if value is NaT: return value.value elif isinstance(value, self._scalar_type): - self._check_compatible_with(value) + self._check_compatible_with(value, setitem=setitem) return value.ordinal else: raise ValueError(f"'value' should be a Period. Got '{value}' instead.") diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index 5e3c0f2b8d876..3eaf428bc64b2 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -283,10 +283,10 @@ def _generate_range(cls, start, end, periods, freq, closed=None): def _rebox_native(cls, value: int) -> np.timedelta64: return np.int64(value).view("m8[ns]") - def _unbox_scalar(self, value): + def _unbox_scalar(self, value, setitem: bool = False): if not isinstance(value, self._scalar_type) and value is not NaT: raise ValueError("'value' should be a Timedelta.") - self._check_compatible_with(value) + self._check_compatible_with(value, setitem=setitem) return value.value def _scalar_from_string(self, value): diff --git a/pandas/tests/arrays/test_datetimelike.py b/pandas/tests/arrays/test_datetimelike.py index d2d3766959fbf..9d316c38082af 100644 --- a/pandas/tests/arrays/test_datetimelike.py +++ b/pandas/tests/arrays/test_datetimelike.py @@ -244,7 +244,7 @@ def test_searchsorted(self): # GH#29884 match numpy convention on whether NaT goes # at the end or the beginning result = arr.searchsorted(pd.NaT) - if _np_version_under1p18 or self.array_cls is PeriodArray: + if np_version_under1p18 or self.array_cls is PeriodArray: # Following numpy convention, NaT goes at the beginning # (unlike NaN which goes at the end) assert result == 0