diff --git a/src/click/types.py b/src/click/types.py index e71c1c21e4..5177cdb022 100644 --- a/src/click/types.py +++ b/src/click/types.py @@ -183,13 +183,16 @@ def convert( ) -> t.Any: try: return self.func(value) - except ValueError: - try: - value = str(value) - except UnicodeError: - value = value.decode("utf-8", "replace") + except ValueError as exc: + message = str(exc) + + if not message: + try: + message = str(value) + except UnicodeError: + message = value.decode("utf-8", "replace") - self.fail(value, param, ctx) + self.fail(message, param, ctx) class UnprocessedParamType(ParamType): diff --git a/tests/test_types.py b/tests/test_types.py index 75434f1042..2e1f902879 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -51,6 +51,18 @@ def test_range_fail(type, value, expect): assert expect in exc_info.value.message +def test_func_param_type_uses_value_error_message(): + def parse(value): + raise ValueError(f"bad value: {value}") + + func_type = click.types.FuncParamType(parse) + + with pytest.raises(click.BadParameter) as exc_info: + func_type.convert("nope", None, None) + + assert "bad value: nope" in exc_info.value.message + + def test_float_range_no_clamp_open(): with pytest.raises(TypeError): click.FloatRange(0, 1, max_open=True, clamp=True)