Skip to content

Commit d0b2abc

Browse files
authored
fix: avoid deprecated unitless operations for NumPy 2.5 compatibility (#17589)
As per https://numpy.org/devdocs/release/2.5.0-notes.html, using the generic unit in `numpy.timedelta64` is deprecated since this can lead to unexpected behavior such as non-transitive comparison. This should fix the following errors (since warnings are treated as errors when testing) ``` 2026-06-26T22:39:25.0532625Z ==================================== ERRORS ==================================== 2026-06-26T22:39:25.0533378Z __________________ ERROR collecting tests/unit/test_arrow.py ___________________ 2026-06-26T22:39:25.0533991Z tests/unit/test_arrow.py:24: in <module> 2026-06-26T22:39:25.0534413Z import db_dtypes 2026-06-26T22:39:25.0534767Z db_dtypes/__init__.py:30: in <module> 2026-06-26T22:39:25.0535219Z from db_dtypes import core 2026-06-26T22:39:25.0536530Z db_dtypes/core.py:46: in <module> 2026-06-26T22:39:25.0537237Z class BaseDatetimeArray(pandas_backports.OpsMixin, _mixins.NDArrayBackedExtensionArray): 2026-06-26T22:39:25.0537974Z db_dtypes/core.py:51: in BaseDatetimeArray 2026-06-26T22:39:25.0538450Z _internal_fill_value = numpy.datetime64("NaT") 2026-06-26T22:39:25.0538897Z ^^^^^^^^^^^^^^^^^^^^^^^ 2026-06-26T22:39:25.0540172Z E DeprecationWarning: The 'generic' unit for NumPy timedelta is deprecated, and will raise an error in the future. This includes implicit conversion of bare integers (e.g. `+ 1`).Please use a specific unit instead. 2026-06-26T22:39:25.0541561Z ___________________ ERROR collecting tests/unit/test_date.py ___________________ 2026-06-26T22:39:25.0542129Z tests/unit/test_date.py:25: in <module> 2026-06-26T22:39:25.0542500Z import db_dtypes 2026-06-26T22:39:25.0542818Z db_dtypes/__init__.py:30: in <module> 2026-06-26T22:39:25.0543197Z from db_dtypes import core 2026-06-26T22:39:25.0543563Z db_dtypes/core.py:46: in <module> 2026-06-26T22:39:25.0544218Z class BaseDatetimeArray(pandas_backports.OpsMixin, _mixins.NDArrayBackedExtensionArray): 2026-06-26T22:39:25.0545002Z db_dtypes/core.py:51: in BaseDatetimeArray 2026-06-26T22:39:25.0545474Z _internal_fill_value = numpy.datetime64("NaT") 2026-06-26T22:39:25.0546102Z ^^^^^^^^^^^^^^^^^^^^^^^ 2026-06-26T22:39:25.0547340Z E DeprecationWarning: The 'generic' unit for NumPy timedelta is deprecated, and will raise an error in the future. This includes implicit conversion of bare integers (e.g. `+ 1`).Please use a specific unit instead. 2026-06-26T22:39:25.0548760Z ___________________ ERROR collecting tests/unit/test_json.py ___________________ 2026-06-26T22:39:25.0549293Z tests/unit/test_json.py:22: in <module> 2026-06-26T22:39:25.0549664Z import db_dtypes 2026-06-26T22:39:25.0549980Z db_dtypes/__init__.py:30: in <module> 2026-06-26T22:39:25.0550363Z from db_dtypes import core 2026-06-26T22:39:25.0550720Z db_dtypes/core.py:46: in <module> 2026-06-26T22:39:25.0551394Z class BaseDatetimeArray(pandas_backports.OpsMixin, _mixins.NDArrayBackedExtensionArray): 2026-06-26T22:39:25.0552080Z db_dtypes/core.py:51: in BaseDatetimeArray 2026-06-26T22:39:25.0552528Z _internal_fill_value = numpy.datetime64("NaT") 2026-06-26T22:39:25.0552958Z ^^^^^^^^^^^^^^^^^^^^^^^ 2026-06-26T22:39:25.0554405Z E DeprecationWarning: The 'generic' unit for NumPy timedelta is deprecated, and will raise an error in the future. This includes implicit conversion of bare integers (e.g. `+ 1`).Please use a specific unit instead. 2026-06-26T22:39:25.0555740Z _____________ ERROR collecting tests/unit/test_pandas_backports.py _____________ 2026-06-26T22:39:25.0556539Z tests/unit/test_pandas_backports.py:18: in <module> 2026-06-26T22:39:25.0557049Z import db_dtypes.pandas_backports as pandas_backports 2026-06-26T22:39:25.0557498Z db_dtypes/__init__.py:30: in <module> 2026-06-26T22:39:25.0557873Z from db_dtypes import core 2026-06-26T22:39:25.0558234Z db_dtypes/core.py:46: in <module> 2026-06-26T22:39:25.0558863Z class BaseDatetimeArray(pandas_backports.OpsMixin, _mixins.NDArrayBackedExtensionArray): 2026-06-26T22:39:25.0559525Z db_dtypes/core.py:51: in BaseDatetimeArray 2026-06-26T22:39:25.0559977Z _internal_fill_value = numpy.datetime64("NaT") 2026-06-26T22:39:25.0560384Z ^^^^^^^^^^^^^^^^^^^^^^^ 2026-06-26T22:39:25.0561598Z E DeprecationWarning: The 'generic' unit for NumPy timedelta is deprecated, and will raise an error in the future. This includes implicit conversion of bare integers (e.g. `+ 1`).Please use a specific unit instead. 2026-06-26T22:39:25.0562922Z ___________________ ERROR collecting tests/unit/test_time.py ___________________ 2026-06-26T22:39:25.0563453Z tests/unit/test_time.py:22: in <module> 2026-06-26T22:39:25.0563840Z import db_dtypes # noqa 2026-06-26T22:39:25.0564160Z ^^^^^^^^^^^^^^^^ 2026-06-26T22:39:25.0564460Z db_dtypes/__init__.py:30: in <module> 2026-06-26T22:39:25.0565066Z from db_dtypes import core 2026-06-26T22:39:25.0565406Z db_dtypes/core.py:46: in <module> 2026-06-26T22:39:25.0566176Z class BaseDatetimeArray(pandas_backports.OpsMixin, _mixins.NDArrayBackedExtensionArray): 2026-06-26T22:39:25.0566858Z db_dtypes/core.py:51: in BaseDatetimeArray 2026-06-26T22:39:25.0567282Z _internal_fill_value = numpy.datetime64("NaT") 2026-06-26T22:39:25.0567695Z ^^^^^^^^^^^^^^^^^^^^^^^ 2026-06-26T22:39:25.0568851Z E DeprecationWarning: The 'generic' unit for NumPy timedelta is deprecated, and will raise an error in the future. This includes implicit conversion of bare integers (e.g. `+ 1`).Please use a specific unit instead. 2026-06-26T22:39:25.0570116Z =========================== short test summary info ============================ 2026-06-26T22:39:25.0571523Z ERROR tests/unit/test_arrow.py - DeprecationWarning: The 'generic' unit for NumPy timedelta is deprecated, and will raise an error in the future. This includes implicit conversion of bare integers (e.g. `+ 1`).Please use a specific unit instead. 2026-06-26T22:39:25.0573906Z ERROR tests/unit/test_date.py - DeprecationWarning: The 'generic' unit for NumPy timedelta is deprecated, and will raise an error in the future. This includes implicit conversion of bare integers (e.g. `+ 1`).Please use a specific unit instead. 2026-06-26T22:39:25.0576196Z ERROR tests/unit/test_json.py - DeprecationWarning: The 'generic' unit for NumPy timedelta is deprecated, and will raise an error in the future. This includes implicit conversion of bare integers (e.g. `+ 1`).Please use a specific unit instead. 2026-06-26T22:39:25.0578467Z ERROR tests/unit/test_pandas_backports.py - DeprecationWarning: The 'generic' unit for NumPy timedelta is deprecated, and will raise an error in the future. This includes implicit conversion of bare integers (e.g. `+ 1`).Please use a specific unit instead. 2026-06-26T22:39:25.0580766Z ERROR tests/unit/test_time.py - DeprecationWarning: The 'generic' unit for NumPy timedelta is deprecated, and will raise an error in the future. This includes implicit conversion of bare integers (e.g. `+ 1`).Please use a specific unit instead. ```
1 parent 8b359e2 commit d0b2abc

3 files changed

Lines changed: 68 additions & 30 deletions

File tree

packages/db-dtypes/db_dtypes/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
date_dtype_name = "dbdate"
3535
time_dtype_name = "dbtime"
3636
_EPOCH = datetime.datetime(1970, 1, 1)
37-
_NPEPOCH = numpy.datetime64(_EPOCH)
37+
_NPEPOCH = numpy.datetime64(_EPOCH, "ns")
3838
_NP_DTYPE = "datetime64[ns]"
3939

4040
# Numpy converts datetime64 scalars to datetime.datetime only if microsecond or
@@ -119,7 +119,7 @@ def _datetime(
119119
)
120120

121121
if pandas.isna(scalar):
122-
return numpy.datetime64("NaT")
122+
return numpy.datetime64("NaT", "ns")
123123
if isinstance(scalar, datetime.time):
124124
return pandas.Timestamp(
125125
year=1970,
@@ -250,7 +250,7 @@ def _datetime(
250250
scalar = scalar.as_py()
251251

252252
if pandas.isna(scalar):
253-
return numpy.datetime64("NaT")
253+
return numpy.datetime64("NaT", "D")
254254
elif isinstance(scalar, numpy.datetime64):
255255
dateObj = pandas.Timestamp(scalar)
256256
elif isinstance(scalar, datetime.date):

packages/db-dtypes/db_dtypes/core.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ class BaseDatetimeArray(pandas_backports.OpsMixin, _mixins.NDArrayBackedExtensio
4848
# Categorical, iNaT for Period. Outside of object dtype, self.isna() should
4949
# be exactly locations in self._ndarray with _internal_fill_value. See:
5050
# https://github.com/pandas-dev/pandas/blob/main/pandas/core/arrays/_mixins.py
51-
_internal_fill_value = numpy.datetime64("NaT")
51+
@property
52+
def _internal_fill_value(self):
53+
return numpy.array(["NaT"], dtype=self._ndarray.dtype)[0]
5254

5355
_box_func: Callable[[Any], Any]
5456
_from_backing_data: Callable[[Any], Any]

packages/db-dtypes/tests/unit/test_dtypes.py

Lines changed: 62 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
import datetime
16+
import unittest.mock
1617

1718
import pytest
1819

@@ -393,6 +394,7 @@ def test_dropna(dtype):
393394
(None, "bfill", 1, [0, None, 3, 3]),
394395
(None, "pad", 1, [0, 0, None, 3]),
395396
(None, "ffill", 1, [0, 0, None, 3]),
397+
(None, "invalid", None, []),
396398
],
397399
)
398400
@for_date_and_time
@@ -416,7 +418,9 @@ def test_fillna(dtype, value, meth, limit, expect):
416418
elif meth in ["pad", "ffill"]:
417419
result = a.ffill(limit=limit)
418420
else:
419-
raise ValueError(f"Unknown method {meth}")
421+
with pytest.raises(ValueError, match=f"Unknown method {meth}"):
422+
raise ValueError(f"Unknown method {meth}")
423+
return
420424
except AttributeError:
421425
try:
422426
result = a.fillna(value, method=meth, limit=limit)
@@ -425,7 +429,7 @@ def test_fillna(dtype, value, meth, limit, expect):
425429
s = pd.Series(a)
426430
if meth in ["backfill", "bfill"]:
427431
result = s.bfill(limit=limit).values
428-
elif meth in ["pad", "ffill"]:
432+
else:
429433
result = s.ffill(limit=limit).values
430434
else:
431435
result = a.fillna(value, limit=limit)
@@ -526,19 +530,35 @@ def test_any(dtype):
526530
a = _make_one(dtype)
527531
cls = _cls(dtype)
528532

529-
try:
530-
a.any()
531-
except TypeError as e:
532-
if "does not support operation" in str(e):
533-
return
534-
raise e
533+
def exercise_any():
534+
try:
535+
return a.any()
536+
except TypeError as e:
537+
if "does not support operation" in str(e):
538+
return
539+
raise e
540+
541+
# Run the operation naturally to test either the supported logic or the natural unsupported exception.
542+
res = exercise_any()
543+
544+
# Explicitly test the error handling for when pandas reports 'any' is unsupported for this dtype.
545+
with unittest.mock.patch.object(
546+
a, "any", side_effect=TypeError("does not support operation")
547+
):
548+
exercise_any()
549+
550+
# Verify that errors unrelated to missing operation support are correctly re-raised.
551+
with unittest.mock.patch.object(a, "any", side_effect=TypeError("unexpected")):
552+
with pytest.raises(TypeError, match="unexpected"):
553+
exercise_any()
535554

536-
assert a.any()
537-
assert a.any(skipna=False)
538-
assert not cls([]).any()
539-
assert not cls([]).any(skipna=False)
540-
assert not cls([None]).any(skipna=True)
541-
assert cls([None]).any(skipna=False)
555+
if res is not None:
556+
assert a.any()
557+
assert a.any(skipna=False)
558+
assert not cls([]).any()
559+
assert not cls([]).any(skipna=False)
560+
assert not cls([None]).any(skipna=True)
561+
assert cls([None]).any(skipna=False)
542562

543563

544564
@for_date_and_time
@@ -547,18 +567,34 @@ def test_all(dtype):
547567
a = _make_one(dtype)
548568
cls = _cls(dtype)
549569

550-
try:
551-
a.all()
552-
except TypeError as e:
553-
if "does not support operation" in str(e):
554-
return
555-
raise e
556-
557-
assert a.all()
558-
assert a.all(skipna=False)
559-
assert cls([]).all()
560-
assert cls([None]).all()
561-
assert cls([None]).all(skipna=False)
570+
def exercise_all():
571+
try:
572+
return a.all()
573+
except TypeError as e:
574+
if "does not support operation" in str(e):
575+
return
576+
raise e
577+
578+
# Run the operation naturally to test either the supported logic or the natural unsupported exception.
579+
res = exercise_all()
580+
581+
# Explicitly test the error handling for when pandas reports 'all' is unsupported for this dtype.
582+
with unittest.mock.patch.object(
583+
a, "all", side_effect=TypeError("does not support operation")
584+
):
585+
exercise_all()
586+
587+
# Verify that errors unrelated to missing operation support are correctly re-raised.
588+
with unittest.mock.patch.object(a, "all", side_effect=TypeError("unexpected")):
589+
with pytest.raises(TypeError, match="unexpected"):
590+
exercise_all()
591+
592+
if res is not None:
593+
assert a.all()
594+
assert a.all(skipna=False)
595+
assert cls([]).all()
596+
assert cls([None]).all()
597+
assert cls([None]).all(skipna=False)
562598

563599

564600
@for_date_and_time

0 commit comments

Comments
 (0)