From 06efb8aa25cc3122780c09f62acc89a052fda8f5 Mon Sep 17 00:00:00 2001 From: Simon Hawkins Date: Tue, 28 Jan 2020 11:00:32 +0000 Subject: [PATCH] CLN: more consistent error message for construct_from_string wrong type --- pandas/core/arrays/numpy_.py | 8 +++--- pandas/core/arrays/sparse/dtype.py | 4 +++ pandas/core/dtypes/base.py | 5 ++-- pandas/core/dtypes/dtypes.py | 40 ++++++++++++++++------------ pandas/tests/dtypes/test_dtypes.py | 9 ++++--- pandas/tests/extension/base/dtype.py | 7 +++++ 6 files changed, 47 insertions(+), 26 deletions(-) diff --git a/pandas/core/arrays/numpy_.py b/pandas/core/arrays/numpy_.py index 075096f6cfb54..3e5edd9c2def3 100644 --- a/pandas/core/arrays/numpy_.py +++ b/pandas/core/arrays/numpy_.py @@ -75,9 +75,11 @@ def construct_from_string(cls, string): try: return cls(np.dtype(string)) except TypeError as err: - raise TypeError( - f"Cannot construct a 'PandasDtype' from '{string}'" - ) from err + if not isinstance(string, str): + msg = f"'construct_from_string' expects a string, got {type(string)}" + else: + msg = f"Cannot construct a 'PandasDtype' from '{string}'" + raise TypeError(msg) from err @classmethod def construct_array_type(cls): diff --git a/pandas/core/arrays/sparse/dtype.py b/pandas/core/arrays/sparse/dtype.py index 6f15681cab87e..9cdc0d56d0061 100644 --- a/pandas/core/arrays/sparse/dtype.py +++ b/pandas/core/arrays/sparse/dtype.py @@ -206,6 +206,10 @@ def construct_from_string(cls, string): ------- SparseDtype """ + if not isinstance(string, str): + raise TypeError( + f"'construct_from_string' expects a string, got {type(string)}" + ) msg = f"Cannot construct a 'SparseDtype' from '{string}'" if string.startswith("Sparse"): try: diff --git a/pandas/core/dtypes/base.py b/pandas/core/dtypes/base.py index 1b4e7062b38e5..eddf46ee362d6 100644 --- a/pandas/core/dtypes/base.py +++ b/pandas/core/dtypes/base.py @@ -235,8 +235,9 @@ def construct_from_string(cls, string: str): ... " "'{string}'") """ if not isinstance(string, str): - raise TypeError(f"Expects a string, got {type(string).__name__}") - + raise TypeError( + f"'construct_from_string' expects a string, got {type(string)}" + ) # error: Non-overlapping equality check (left operand type: "str", right # operand type: "Callable[[ExtensionDtype], str]") [comparison-overlap] assert isinstance(cls.name, str), (cls, type(cls.name)) diff --git a/pandas/core/dtypes/dtypes.py b/pandas/core/dtypes/dtypes.py index 93522abc3a48f..d00b46700981c 100644 --- a/pandas/core/dtypes/dtypes.py +++ b/pandas/core/dtypes/dtypes.py @@ -352,7 +352,9 @@ def construct_from_string(cls, string: str_type) -> "CategoricalDtype": If a CategoricalDtype cannot be constructed from the input. """ if not isinstance(string, str): - raise TypeError(f"Expects a string, got {type(string)}") + raise TypeError( + f"'construct_from_string' expects a string, got {type(string)}" + ) if string != cls.name: raise TypeError(f"Cannot construct a 'CategoricalDtype' from '{string}'") @@ -728,22 +730,24 @@ def construct_from_string(cls, string: str_type): >>> DatetimeTZDtype.construct_from_string('datetime64[ns, UTC]') datetime64[ns, UTC] """ - if isinstance(string, str): - msg = f"Cannot construct a 'DatetimeTZDtype' from '{string}'" - match = cls._match.match(string) - if match: - d = match.groupdict() - try: - return cls(unit=d["unit"], tz=d["tz"]) - except (KeyError, TypeError, ValueError) as err: - # KeyError if maybe_get_tz tries and fails to get a - # pytz timezone (actually pytz.UnknownTimeZoneError). - # TypeError if we pass a nonsense tz; - # ValueError if we pass a unit other than "ns" - raise TypeError(msg) from err - raise TypeError(msg) + if not isinstance(string, str): + raise TypeError( + f"'construct_from_string' expects a string, got {type(string)}" + ) - raise TypeError("Cannot construct a 'DatetimeTZDtype'") + msg = f"Cannot construct a 'DatetimeTZDtype' from '{string}'" + match = cls._match.match(string) + if match: + d = match.groupdict() + try: + return cls(unit=d["unit"], tz=d["tz"]) + except (KeyError, TypeError, ValueError) as err: + # KeyError if maybe_get_tz tries and fails to get a + # pytz timezone (actually pytz.UnknownTimeZoneError). + # TypeError if we pass a nonsense tz; + # ValueError if we pass a unit other than "ns" + raise TypeError(msg) from err + raise TypeError(msg) def __str__(self) -> str_type: return f"datetime64[{self.unit}, {self.tz}]" @@ -1075,7 +1079,9 @@ def construct_from_string(cls, string): if its not possible """ if not isinstance(string, str): - raise TypeError(f"a string needs to be passed, got type {type(string)}") + raise TypeError( + f"'construct_from_string' expects a string, got {type(string)}" + ) if string.lower() == "interval" or cls._match.search(string) is not None: return cls(string) diff --git a/pandas/tests/dtypes/test_dtypes.py b/pandas/tests/dtypes/test_dtypes.py index fddd6239df309..a599a086ae92b 100644 --- a/pandas/tests/dtypes/test_dtypes.py +++ b/pandas/tests/dtypes/test_dtypes.py @@ -244,11 +244,12 @@ def test_construct_from_string_raises(self): with pytest.raises(TypeError, match="notatz"): DatetimeTZDtype.construct_from_string("datetime64[ns, notatz]") - msg = "^Cannot construct a 'DatetimeTZDtype'" - with pytest.raises(TypeError, match=msg): + msg = "'construct_from_string' expects a string, got " + with pytest.raises(TypeError, match=re.escape(msg)): # list instead of string DatetimeTZDtype.construct_from_string(["datetime64[ns, notatz]"]) + msg = "^Cannot construct a 'DatetimeTZDtype'" with pytest.raises(TypeError, match=msg): # non-nano unit DatetimeTZDtype.construct_from_string("datetime64[ps, UTC]") @@ -547,9 +548,9 @@ def test_construction_from_string(self): @pytest.mark.parametrize("string", [0, 3.14, ("a", "b"), None]) def test_construction_from_string_errors(self, string): # these are invalid entirely - msg = "a string needs to be passed, got type" + msg = f"'construct_from_string' expects a string, got {type(string)}" - with pytest.raises(TypeError, match=msg): + with pytest.raises(TypeError, match=re.escape(msg)): IntervalDtype.construct_from_string(string) @pytest.mark.parametrize("string", ["foo", "foo[int64]", "IntervalA"]) diff --git a/pandas/tests/extension/base/dtype.py b/pandas/tests/extension/base/dtype.py index b6c12b5844086..b01867624cb16 100644 --- a/pandas/tests/extension/base/dtype.py +++ b/pandas/tests/extension/base/dtype.py @@ -105,3 +105,10 @@ def test_construct_from_string_another_type_raises(self, dtype): msg = f"Cannot construct a '{type(dtype).__name__}' from 'another_type'" with pytest.raises(TypeError, match=msg): type(dtype).construct_from_string("another_type") + + def test_construct_from_string_wrong_type_raises(self, dtype): + with pytest.raises( + TypeError, + match="'construct_from_string' expects a string, got ", + ): + type(dtype).construct_from_string(0)