Skip to content

Commit a27419e

Browse files
lithomas1yehoshuadimarsky
authored andcommitted
CI: Test on Cython 3.0 on numpydev (pandas-dev#46029)
1 parent abc9e2e commit a27419e

File tree

6 files changed

+106
-2
lines changed

6 files changed

+106
-2
lines changed

ci/deps/actions-310-numpydev.yaml

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ dependencies:
1616
- pytz
1717
- pip
1818
- pip:
19-
- cython==0.29.24 # GH#34014
19+
#- cython # TODO: don't install from master after Cython 3.0.0a11 is released
20+
- "git+https://github.com/cython/cython.git@master"
2021
- "--extra-index-url https://pypi.anaconda.org/scipy-wheels-nightly/simple"
2122
- "--pre"
2223
- "numpy"

pandas/_libs/interval.pyx

+18
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,8 @@ cdef class Interval(IntervalMixin):
404404
):
405405
return Interval(self.left + y, self.right + y, closed=self.closed)
406406
elif (
407+
# __radd__ pattern
408+
# TODO(cython3): remove this
407409
isinstance(y, Interval)
408410
and (
409411
isinstance(self, numbers.Number)
@@ -414,6 +416,15 @@ cdef class Interval(IntervalMixin):
414416
return Interval(y.left + self, y.right + self, closed=y.closed)
415417
return NotImplemented
416418

419+
def __radd__(self, other):
420+
if (
421+
isinstance(other, numbers.Number)
422+
or PyDelta_Check(other)
423+
or is_timedelta64_object(other)
424+
):
425+
return Interval(self.left + other, self.right + other, closed=self.closed)
426+
return NotImplemented
427+
417428
def __sub__(self, y):
418429
if (
419430
isinstance(y, numbers.Number)
@@ -427,9 +438,16 @@ cdef class Interval(IntervalMixin):
427438
if isinstance(y, numbers.Number):
428439
return Interval(self.left * y, self.right * y, closed=self.closed)
429440
elif isinstance(y, Interval) and isinstance(self, numbers.Number):
441+
# __radd__ semantics
442+
# TODO(cython3): remove this
430443
return Interval(y.left * self, y.right * self, closed=y.closed)
431444
return NotImplemented
432445

446+
def __rmul__(self, other):
447+
if isinstance(other, numbers.Number):
448+
return Interval(self.left * other, self.right * other, closed=self.closed)
449+
return NotImplemented
450+
433451
def __truediv__(self, y):
434452
if isinstance(y, numbers.Number):
435453
return Interval(self.left / y, self.right / y, closed=self.closed)

pandas/_libs/tslibs/nattype.pyx

+25
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ cdef class _NaT(datetime):
143143

144144
def __add__(self, other):
145145
if self is not c_NaT:
146+
# TODO(cython3): remove this it moved to __radd__
146147
# cython __radd__ semantics
147148
self, other = other, self
148149

@@ -169,6 +170,9 @@ cdef class _NaT(datetime):
169170
# Includes Period, DateOffset going through here
170171
return NotImplemented
171172

173+
def __radd__(self, other):
174+
return self.__add__(other)
175+
172176
def __sub__(self, other):
173177
# Duplicate some logic from _Timestamp.__sub__ to avoid needing
174178
# to subclass; allows us to @final(_Timestamp.__sub__)
@@ -177,6 +181,7 @@ cdef class _NaT(datetime):
177181

178182
if self is not c_NaT:
179183
# cython __rsub__ semantics
184+
# TODO(cython3): remove __rsub__ logic from here
180185
self, other = other, self
181186
is_rsub = True
182187

@@ -200,6 +205,8 @@ cdef class _NaT(datetime):
200205
result.fill("NaT")
201206
return result
202207

208+
# __rsub__ logic here
209+
# TODO(cython3): remove this, move above code out of ``if not is_rsub`` block
203210
# timedelta64 - NaT we have to treat NaT as timedelta64
204211
# for this to be meaningful, and the result is timedelta64
205212
result = np.empty(other.shape, dtype="timedelta64[ns]")
@@ -220,6 +227,24 @@ cdef class _NaT(datetime):
220227
# Includes Period, DateOffset going through here
221228
return NotImplemented
222229

230+
def __rsub__(self, other):
231+
if util.is_array(other):
232+
if other.dtype.kind == "m":
233+
# timedelta64 - NaT we have to treat NaT as timedelta64
234+
# for this to be meaningful, and the result is timedelta64
235+
result = np.empty(other.shape, dtype="timedelta64[ns]")
236+
result.fill("NaT")
237+
return result
238+
239+
elif other.dtype.kind == "M":
240+
# We treat NaT as a datetime, so regardless of whether this is
241+
# NaT - other or other - NaT, the result is timedelta64
242+
result = np.empty(other.shape, dtype="timedelta64[ns]")
243+
result.fill("NaT")
244+
return result
245+
# other cases are same, swap operands is allowed even though we subtract because this is NaT
246+
return self.__sub__(other)
247+
223248
def __pos__(self):
224249
return NaT
225250

pandas/_libs/tslibs/offsets.pyx

+24
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,7 @@ cdef class BaseOffset:
450450
def __add__(self, other):
451451
if not isinstance(self, BaseOffset):
452452
# cython semantics; this is __radd__
453+
# TODO(cython3): remove this, this moved to __radd__
453454
return other.__add__(self)
454455

455456
elif util.is_array(other) and other.dtype == object:
@@ -460,19 +461,26 @@ cdef class BaseOffset:
460461
except ApplyTypeError:
461462
return NotImplemented
462463

464+
def __radd__(self, other):
465+
return self.__add__(other)
466+
463467
def __sub__(self, other):
464468
if PyDateTime_Check(other):
465469
raise TypeError('Cannot subtract datetime from offset.')
466470
elif type(other) == type(self):
467471
return type(self)(self.n - other.n, normalize=self.normalize,
468472
**self.kwds)
469473
elif not isinstance(self, BaseOffset):
474+
# TODO(cython3): remove, this moved to __rsub__
470475
# cython semantics, this is __rsub__
471476
return (-other).__add__(self)
472477
else:
473478
# e.g. PeriodIndex
474479
return NotImplemented
475480

481+
def __rsub__(self, other):
482+
return (-self).__add__(other)
483+
476484
def __call__(self, other):
477485
warnings.warn(
478486
"DateOffset.__call__ is deprecated and will be removed in a future "
@@ -499,10 +507,14 @@ cdef class BaseOffset:
499507
return type(self)(n=other * self.n, normalize=self.normalize,
500508
**self.kwds)
501509
elif not isinstance(self, BaseOffset):
510+
# TODO(cython3): remove this, this moved to __rmul__
502511
# cython semantics, this is __rmul__
503512
return other.__mul__(self)
504513
return NotImplemented
505514

515+
def __rmul__(self, other):
516+
return self.__mul__(other)
517+
506518
def __neg__(self):
507519
# Note: we are deferring directly to __mul__ instead of __rmul__, as
508520
# that allows us to use methods that can go in a `cdef class`
@@ -887,6 +899,7 @@ cdef class Tick(SingleConstructorOffset):
887899

888900
def __mul__(self, other):
889901
if not isinstance(self, Tick):
902+
# TODO(cython3), remove this, this moved to __rmul__
890903
# cython semantics, this is __rmul__
891904
return other.__mul__(self)
892905
if is_float_object(other):
@@ -900,6 +913,9 @@ cdef class Tick(SingleConstructorOffset):
900913
return new_self * other
901914
return BaseOffset.__mul__(self, other)
902915

916+
def __rmul__(self, other):
917+
return self.__mul__(other)
918+
903919
def __truediv__(self, other):
904920
if not isinstance(self, Tick):
905921
# cython semantics mean the args are sometimes swapped
@@ -908,9 +924,14 @@ cdef class Tick(SingleConstructorOffset):
908924
result = self.delta.__truediv__(other)
909925
return _wrap_timedelta_result(result)
910926

927+
def __rtruediv__(self, other):
928+
result = self.delta.__rtruediv__(other)
929+
return _wrap_timedelta_result(result)
930+
911931
def __add__(self, other):
912932
if not isinstance(self, Tick):
913933
# cython semantics; this is __radd__
934+
# TODO(cython3): remove this, this moved to __radd__
914935
return other.__add__(self)
915936

916937
if isinstance(other, Tick):
@@ -928,6 +949,9 @@ cdef class Tick(SingleConstructorOffset):
928949
f"the add operation between {self} and {other} will overflow"
929950
) from err
930951

952+
def __radd__(self, other):
953+
return self.__add__(other)
954+
931955
def _apply(self, other):
932956
# Timestamp can handle tz and nano sec, thus no need to use apply_wraps
933957
if isinstance(other, _Timestamp):

pandas/_libs/tslibs/period.pyx

+10
Original file line numberDiff line numberDiff line change
@@ -1710,6 +1710,7 @@ cdef class _Period(PeriodMixin):
17101710
def __add__(self, other):
17111711
if not is_period_object(self):
17121712
# cython semantics; this is analogous to a call to __radd__
1713+
# TODO(cython3): remove this
17131714
if self is NaT:
17141715
return NaT
17151716
return other.__add__(self)
@@ -1734,9 +1735,13 @@ cdef class _Period(PeriodMixin):
17341735

17351736
return NotImplemented
17361737

1738+
def __radd__(self, other):
1739+
return self.__add__(other)
1740+
17371741
def __sub__(self, other):
17381742
if not is_period_object(self):
17391743
# cython semantics; this is like a call to __rsub__
1744+
# TODO(cython3): remove this
17401745
if self is NaT:
17411746
return NaT
17421747
return NotImplemented
@@ -1760,6 +1765,11 @@ cdef class _Period(PeriodMixin):
17601765

17611766
return NotImplemented
17621767

1768+
def __rsub__(self, other):
1769+
if other is NaT:
1770+
return NaT
1771+
return NotImplemented
1772+
17631773
def asfreq(self, freq, how='E') -> "Period":
17641774
"""
17651775
Convert Period to desired frequency, at the start or end of the interval.

pandas/_libs/tslibs/timestamps.pyx

+27-1
Original file line numberDiff line numberDiff line change
@@ -280,13 +280,19 @@ cdef class _Timestamp(ABCTimestamp):
280280
return other.tzinfo is not None
281281
return other.tzinfo is None
282282

283+
@cython.overflowcheck(True)
283284
def __add__(self, other):
284285
cdef:
285286
int64_t nanos = 0
286287

287288
if is_any_td_scalar(other):
288289
nanos = delta_to_nanoseconds(other)
289-
result = type(self)(self.value + nanos, tz=self.tzinfo)
290+
try:
291+
result = type(self)(self.value + nanos, tz=self.tzinfo)
292+
except OverflowError:
293+
# Use Python ints
294+
# Hit in test_tdi_add_overflow
295+
result = type(self)(int(self.value) + int(nanos), tz=self.tzinfo)
290296
if result is not NaT:
291297
result._set_freq(self._freq) # avoid warning in constructor
292298
return result
@@ -307,9 +313,16 @@ cdef class _Timestamp(ABCTimestamp):
307313

308314
elif not isinstance(self, _Timestamp):
309315
# cython semantics, args have been switched and this is __radd__
316+
# TODO(cython3): remove this it moved to __radd__
310317
return other.__add__(self)
311318
return NotImplemented
312319

320+
def __radd__(self, other):
321+
# Have to duplicate checks to avoid infinite recursion due to NotImplemented
322+
if is_any_td_scalar(other) or is_integer_object(other) or is_array(other):
323+
return self.__add__(other)
324+
return NotImplemented
325+
313326
def __sub__(self, other):
314327

315328
if is_any_td_scalar(other) or is_integer_object(other):
@@ -336,6 +349,7 @@ cdef class _Timestamp(ABCTimestamp):
336349
and (PyDateTime_Check(other) or is_datetime64_object(other))):
337350
# both_timestamps is to determine whether Timedelta(self - other)
338351
# should raise the OOB error, or fall back returning a timedelta.
352+
# TODO(cython3): clean out the bits that moved to __rsub__
339353
both_timestamps = (isinstance(other, _Timestamp) and
340354
isinstance(self, _Timestamp))
341355
if isinstance(self, _Timestamp):
@@ -366,10 +380,22 @@ cdef class _Timestamp(ABCTimestamp):
366380
elif is_datetime64_object(self):
367381
# GH#28286 cython semantics for __rsub__, `other` is actually
368382
# the Timestamp
383+
# TODO(cython3): remove this, this moved to __rsub__
369384
return type(other)(self) - other
370385

371386
return NotImplemented
372387

388+
def __rsub__(self, other):
389+
if PyDateTime_Check(other):
390+
try:
391+
return type(self)(other) - self
392+
except (OverflowError, OutOfBoundsDatetime) as err:
393+
# We get here in stata tests, fall back to stdlib datetime
394+
# method and return stdlib timedelta object
395+
pass
396+
elif is_datetime64_object(other):
397+
return type(self)(other) - self
398+
return NotImplemented
373399
# -----------------------------------------------------------------
374400

375401
cdef int64_t _maybe_convert_value_to_local(self):

0 commit comments

Comments
 (0)