Skip to content

Commit 5e3179a

Browse files
zangell44MeeseeksDev[bot]
authored and
MeeseeksDev[bot]
committed
Backport PR pandas-dev#25338: Interval dtype fix
1 parent f0cf03d commit 5e3179a

File tree

4 files changed

+29
-12
lines changed

4 files changed

+29
-12
lines changed

doc/source/whatsnew/v0.24.2.rst

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Fixed Regressions
2626

2727
- Fixed regression in :meth:`DataFrame.duplicated()`, where empty dataframe was not returning a boolean dtyped Series. (:issue:`25184`)
2828
- Fixed regression in :meth:`Series.min` and :meth:`Series.max` where ``numeric_only=True`` was ignored when the ``Series`` contained ```Categorical`` data (:issue:`25299`)
29+
- Fixed regression in ``IntervalDtype`` construction where passing an incorrect string with 'Interval' as a prefix could result in a ``RecursionError``. (:issue:`25338`)
2930

3031
.. _whatsnew_0242.enhancements:
3132

pandas/core/dtypes/dtypes.py

+12-7
Original file line numberDiff line numberDiff line change
@@ -932,13 +932,18 @@ def construct_from_string(cls, string):
932932
attempt to construct this type from a string, raise a TypeError
933933
if its not possible
934934
"""
935-
if (isinstance(string, compat.string_types) and
936-
(string.startswith('interval') or
937-
string.startswith('Interval'))):
938-
return cls(string)
935+
if not isinstance(string, compat.string_types):
936+
msg = "a string needs to be passed, got type {typ}"
937+
raise TypeError(msg.format(typ=type(string)))
938+
939+
if (string.lower() == 'interval' or
940+
cls._match.search(string) is not None):
941+
return cls(string)
939942

940-
msg = "a string needs to be passed, got type {typ}"
941-
raise TypeError(msg.format(typ=type(string)))
943+
msg = ('Incorrectly formatted string passed to constructor. '
944+
'Valid formats include Interval or Interval[dtype] '
945+
'where dtype is numeric, datetime, or timedelta')
946+
raise TypeError(msg)
942947

943948
@property
944949
def type(self):
@@ -979,7 +984,7 @@ def is_dtype(cls, dtype):
979984
return True
980985
else:
981986
return False
982-
except ValueError:
987+
except (ValueError, TypeError):
983988
return False
984989
else:
985990
return False

pandas/tests/dtypes/test_dtypes.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -501,10 +501,11 @@ def test_construction_not_supported(self, subtype):
501501
with pytest.raises(TypeError, match=msg):
502502
IntervalDtype(subtype)
503503

504-
def test_construction_errors(self):
504+
@pytest.mark.parametrize('subtype', ['xx', 'IntervalA', 'Interval[foo]'])
505+
def test_construction_errors(self, subtype):
505506
msg = 'could not construct IntervalDtype'
506507
with pytest.raises(TypeError, match=msg):
507-
IntervalDtype('xx')
508+
IntervalDtype(subtype)
508509

509510
def test_construction_from_string(self):
510511
result = IntervalDtype('interval[int64]')
@@ -513,7 +514,7 @@ def test_construction_from_string(self):
513514
assert is_dtype_equal(self.dtype, result)
514515

515516
@pytest.mark.parametrize('string', [
516-
'foo', 'foo[int64]', 0, 3.14, ('a', 'b'), None])
517+
0, 3.14, ('a', 'b'), None])
517518
def test_construction_from_string_errors(self, string):
518519
# these are invalid entirely
519520
msg = 'a string needs to be passed, got type'
@@ -522,10 +523,12 @@ def test_construction_from_string_errors(self, string):
522523
IntervalDtype.construct_from_string(string)
523524

524525
@pytest.mark.parametrize('string', [
525-
'interval[foo]'])
526+
'foo', 'foo[int64]', 'IntervalA'])
526527
def test_construction_from_string_error_subtype(self, string):
527528
# this is an invalid subtype
528-
msg = 'could not construct IntervalDtype'
529+
msg = ("Incorrectly formatted string passed to constructor. "
530+
r"Valid formats include Interval or Interval\[dtype\] "
531+
"where dtype is numeric, datetime, or timedelta")
529532

530533
with pytest.raises(TypeError, match=msg):
531534
IntervalDtype.construct_from_string(string)
@@ -549,6 +552,7 @@ def test_is_dtype(self):
549552
assert not IntervalDtype.is_dtype('U')
550553
assert not IntervalDtype.is_dtype('S')
551554
assert not IntervalDtype.is_dtype('foo')
555+
assert not IntervalDtype.is_dtype('IntervalA')
552556
assert not IntervalDtype.is_dtype(np.object_)
553557
assert not IntervalDtype.is_dtype(np.int64)
554558
assert not IntervalDtype.is_dtype(np.float64)

pandas/tests/series/test_operators.py

+7
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,13 @@ def test_comp_ops_df_compat(self):
563563
with pytest.raises(ValueError, match=msg):
564564
left.to_frame() < right.to_frame()
565565

566+
def test_compare_series_interval_keyword(self):
567+
# GH 25338
568+
s = Series(['IntervalA', 'IntervalB', 'IntervalC'])
569+
result = s == 'IntervalA'
570+
expected = Series([True, False, False])
571+
assert_series_equal(result, expected)
572+
566573

567574
class TestSeriesFlexComparisonOps(object):
568575

0 commit comments

Comments
 (0)