diff --git a/voluptuous/tests/tests.py b/voluptuous/tests/tests.py index 0608548..b3e6f17 100644 --- a/voluptuous/tests/tests.py +++ b/voluptuous/tests/tests.py @@ -1745,6 +1745,20 @@ def test_coerce_in_set_is_applied(): assert Schema({int})({1, 2}) == {1, 2} +def test_coerce_callable_marks_invalid_without_msg(): + # Coerce accepts any callable (not just a class). When such a callable + # raises on bad input, the value must be marked Invalid, as the docstring + # promises -- the Enum-message branch used to call issubclass() on the + # callable and leak a raw TypeError. See the class twin Coerce(int), which + # already behaves this way. + def parse_int(v): + return int(v) + + validate = Schema(Coerce(parse_int)) + with raises(MultipleInvalid, 'expected parse_int'): + validate('foo') + + def test_lower_util_handles_various_inputs(): assert Lower(3) == "3" assert Lower(u"3") == u"3" diff --git a/voluptuous/validators.py b/voluptuous/validators.py index a69bb8a..510ab34 100644 --- a/voluptuous/validators.py +++ b/voluptuous/validators.py @@ -145,7 +145,12 @@ def __call__(self, v): return self.type(v) except (ValueError, TypeError, InvalidOperation): msg = self.msg or ('expected %s' % self.type_name) - if not self.msg and Enum and issubclass(self.type, Enum): + if ( + not self.msg + and Enum + and isinstance(self.type, type) + and issubclass(self.type, Enum) + ): msg += " or one of %s" % str([e.value for e in self.type])[1:-1] raise CoerceInvalid(msg)