Skip to content

Commit 5f20674

Browse files
authored
CLN: disallow tuple in to_offset (#34703)
1 parent b05e6b1 commit 5f20674

File tree

7 files changed

+27
-59
lines changed

7 files changed

+27
-59
lines changed

doc/source/whatsnew/v1.1.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,7 @@ Datetimelike
820820
- Bug in :meth:`DatetimeIndex.intersection` and :meth:`TimedeltaIndex.intersection` with results not having the correct ``name`` attribute (:issue:`33904`)
821821
- Bug in :meth:`DatetimeArray.__setitem__`, :meth:`TimedeltaArray.__setitem__`, :meth:`PeriodArray.__setitem__` incorrectly allowing values with ``int64`` dtype to be silently cast (:issue:`33717`)
822822
- Bug in subtracting :class:`TimedeltaIndex` from :class:`Period` incorrectly raising ``TypeError`` in some cases where it should succeed and ``IncompatibleFrequency`` in some cases where it should raise ``TypeError`` (:issue:`33883`)
823+
- The ``freq`` keyword in :class:`Period`, :func:`date_range`, :func:`period_range`, :func:`pd.tseries.frequencies.to_offset` no longer allows tuples, pass as string instead (:issue:`34703`)
823824

824825
Timedelta
825826
^^^^^^^^^

pandas/_libs/tslibs/offsets.pyx

+5-38
Original file line numberDiff line numberDiff line change
@@ -3482,36 +3482,6 @@ INVALID_FREQ_ERR_MSG = "Invalid frequency: {0}"
34823482
_offset_map = {}
34833483

34843484

3485-
cdef _base_and_stride(str freqstr):
3486-
"""
3487-
Return base freq and stride info from string representation
3488-
3489-
Returns
3490-
-------
3491-
base : str
3492-
stride : int
3493-
3494-
Examples
3495-
--------
3496-
_base_and_stride('5Min') -> 'Min', 5
3497-
"""
3498-
groups = opattern.match(freqstr)
3499-
3500-
if not groups:
3501-
raise ValueError(f"Could not evaluate {freqstr}")
3502-
3503-
stride = groups.group(1)
3504-
3505-
if len(stride):
3506-
stride = int(stride)
3507-
else:
3508-
stride = 1
3509-
3510-
base = groups.group(2)
3511-
3512-
return base, stride
3513-
3514-
35153485
# TODO: better name?
35163486
def _get_offset(name: str) -> BaseOffset:
35173487
"""
@@ -3574,10 +3544,10 @@ cpdef to_offset(freq):
35743544
>>> to_offset("1D1H")
35753545
<25 * Hours>
35763546
3577-
>>> to_offset(("W", 2))
3547+
>>> to_offset("2W")
35783548
<2 * Weeks: weekday=6>
35793549
3580-
>>> to_offset((2, "B"))
3550+
>>> to_offset("2B")
35813551
<2 * BusinessDays>
35823552
35833553
>>> to_offset(pd.Timedelta(days=1))
@@ -3593,12 +3563,9 @@ cpdef to_offset(freq):
35933563
return freq
35943564

35953565
if isinstance(freq, tuple):
3596-
name = freq[0]
3597-
stride = freq[1]
3598-
if isinstance(stride, str):
3599-
name, stride = stride, name
3600-
name, _ = _base_and_stride(name)
3601-
delta = _get_offset(name) * stride
3566+
raise TypeError(
3567+
f"to_offset does not support tuples {freq}, pass as a string instead"
3568+
)
36023569

36033570
elif isinstance(freq, timedelta):
36043571
return delta_to_tick(freq)

pandas/tests/indexes/datetimes/test_constructors.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -946,11 +946,6 @@ def test_datetimeindex_constructor_misc(self):
946946
assert idx[0] == sdate + 0 * offsets.BDay()
947947
assert idx.freq == "B"
948948

949-
idx = date_range(end=edate, freq=("D", 5), periods=20)
950-
assert len(idx) == 20
951-
assert idx[-1] == edate
952-
assert idx.freq == "5D"
953-
954949
idx1 = date_range(start=sdate, end=edate, freq="W-SUN")
955950
idx2 = date_range(start=sdate, end=edate, freq=offsets.Week(weekday=6))
956951
assert len(idx1) == len(idx2)
@@ -979,6 +974,12 @@ def test_pass_datetimeindex_to_index(self):
979974

980975
tm.assert_numpy_array_equal(idx.values, expected.values)
981976

977+
def test_date_range_tuple_freq_raises(self):
978+
# GH#34703
979+
edate = datetime(2000, 1, 1)
980+
with pytest.raises(TypeError, match="pass as a string instead"):
981+
date_range(end=edate, freq=("D", 5), periods=20)
982+
982983

983984
def test_timestamp_constructor_invalid_fold_raise():
984985
# Test for #25057

pandas/tests/indexes/period/test_constructors.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -463,12 +463,6 @@ def test_constructor(self):
463463
assert (i1 == i2).all()
464464
assert i1.freq == i2.freq
465465

466-
end_intv = Period("2006-12-31", ("w", 1))
467-
i2 = period_range(end=end_intv, periods=10)
468-
assert len(i1) == len(i2)
469-
assert (i1 == i2).all()
470-
assert i1.freq == i2.freq
471-
472466
end_intv = Period("2005-05-01", "B")
473467
i1 = period_range(start=start, end=end_intv)
474468

@@ -490,6 +484,10 @@ def test_constructor(self):
490484
with pytest.raises(IncompatibleFrequency, match=msg):
491485
PeriodIndex(vals)
492486

487+
# tuple freq disallowed GH#34703
488+
with pytest.raises(TypeError, match="pass as a string instead"):
489+
Period("2006-12-31", ("w", 1))
490+
493491
@pytest.mark.parametrize(
494492
"freq", ["M", "Q", "A", "D", "B", "T", "S", "L", "U", "N", "H"]
495493
)

pandas/tests/indexes/period/test_period.py

-6
Original file line numberDiff line numberDiff line change
@@ -172,12 +172,6 @@ def test_period_index_length(self):
172172
assert (i1 == i2).all()
173173
assert i1.freq == i2.freq
174174

175-
end_intv = Period("2006-12-31", ("w", 1))
176-
i2 = period_range(end=end_intv, periods=10)
177-
assert len(i1) == len(i2)
178-
assert (i1 == i2).all()
179-
assert i1.freq == i2.freq
180-
181175
msg = "start and end must have same freq"
182176
with pytest.raises(ValueError, match=msg):
183177
period_range(start=start, end=end_intv)

pandas/tests/scalar/period/test_period.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@ def test_construction(self):
4848
i1 = Period("1982", freq="min")
4949
i2 = Period("1982", freq="MIN")
5050
assert i1 == i2
51-
i2 = Period("1982", freq=("Min", 1))
52-
assert i1 == i2
5351

5452
i1 = Period(year=2005, month=3, day=1, freq="D")
5553
i2 = Period("3/1/2005", freq="D")
@@ -80,6 +78,10 @@ def test_construction(self):
8078
with pytest.raises(ValueError, match=msg):
8179
Period("2007-1-1", freq="X")
8280

81+
# GH#34703 tuple freq disallowed
82+
with pytest.raises(TypeError, match="pass as a string instead"):
83+
Period("1982", freq=("Min", 1))
84+
8385
def test_construction_bday(self):
8486

8587
# Biz day construction, roll forward if non-weekday

pandas/tests/tslibs/test_to_offset.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
[
1111
(to_offset("10us"), offsets.Micro(10)),
1212
(offsets.Hour(), offsets.Hour()),
13-
((5, "T"), offsets.Minute(5)),
1413
("2h30min", offsets.Minute(150)),
1514
("2h 30min", offsets.Minute(150)),
1615
("2h30min15s", offsets.Second(150 * 60 + 15)),
@@ -89,10 +88,16 @@ def test_to_offset_invalid(freqstr):
8988

9089

9190
def test_to_offset_no_evaluate():
92-
with pytest.raises(ValueError, match="Could not evaluate"):
91+
msg = str(("", ""))
92+
with pytest.raises(TypeError, match=msg):
9393
to_offset(("", ""))
9494

9595

96+
def test_to_offset_tuple_unsupported():
97+
with pytest.raises(TypeError, match="pass as a string instead"):
98+
to_offset((5, "T"))
99+
100+
96101
@pytest.mark.parametrize(
97102
"freqstr,expected",
98103
[

0 commit comments

Comments
 (0)