From a3dbdfc6870684580a17b11f6b04126303962633 Mon Sep 17 00:00:00 2001 From: Zac Hatfield-Dodds Date: Fri, 25 May 2018 18:32:13 +1000 Subject: [PATCH] Improve error for in_(a_string) with a non-string value (#383) * Improve error for in_(a_string) with a non-string value * Tighten exception handling and test description * Improve changelog for Hynek * Final changelog edit --- changelog.d/383.change.rst | 2 ++ src/attr/validators.py | 7 ++++++- tests/test_validators.py | 13 +++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 changelog.d/383.change.rst diff --git a/changelog.d/383.change.rst b/changelog.d/383.change.rst new file mode 100644 index 00000000..6600b5b3 --- /dev/null +++ b/changelog.d/383.change.rst @@ -0,0 +1,2 @@ +``attr.validators.in_()`` now raises a ``ValueError`` with a useful message even if the options are a string and the value is not a string. +This previously raised a ``TypeError``, like ``1 in "abc"``. diff --git a/src/attr/validators.py b/src/attr/validators.py index f8892fcd..4904e616 100644 --- a/src/attr/validators.py +++ b/src/attr/validators.py @@ -135,7 +135,12 @@ class _InValidator(object): options = attrib() def __call__(self, inst, attr, value): - if value not in self.options: + try: + in_options = value in self.options + except TypeError as e: # e.g. `1 in "abc"` + in_options = False + + if not in_options: raise ValueError( "'{name}' must be in {options!r} (got {value!r})" .format(name=attr.name, options=self.options, value=value) diff --git a/tests/test_validators.py b/tests/test_validators.py index 0e67f991..4d24ce95 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -243,6 +243,19 @@ class TestIn_(object): "'test' must be in [1, 2, 3] (got None)", ) == e.value.args + def test_fail_with_string(self): + """ + Raise ValueError if the value is outside our options when the + options are specified as a string and the value is not a string. + """ + v = in_("abc") + a = simple_attr("test") + with pytest.raises(ValueError) as e: + v(None, a, None) + assert ( + "'test' must be in 'abc' (got None)", + ) == e.value.args + def test_repr(self): """ Returned validator has a useful `__repr__`.