From d4dd572047f0ff485eb6826bcd93e38451f1bc82 Mon Sep 17 00:00:00 2001 From: "stephenonyango65@gmail.com" Date: Tue, 17 Feb 2026 11:49:34 +0300 Subject: [PATCH] FuncParamType should use ValueError message in self.fail() When FuncParamType.convert() caught a ValueError, it discarded the exception message and only passed the input value to self.fail(). This meant users lost context about why the conversion failed. This fix captures and uses the ValueError message, providing better error feedback to users. Falls back to the input value only if the message is empty (backward compatibility). Includes regression test ensuring the message is surfaced in the BadParameter exception. --- src/click/types.py | 15 +++++++++------ tests/test_types.py | 12 ++++++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) 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)