diff --git a/CHANGES.rst b/CHANGES.rst index 76f2b0066..77f4b4b4b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -49,6 +49,8 @@ Unreleased :issue:`3043` - Add :class:`NoSuchCommand` exception with suggestions for misspelled commands. :issue:`3107` :pr:`3228` +- Use :class:`ValueError` message when conversion in :class:`FuncParamType` would + fail. :issue:`3105` :pr:`3211` - Add ``click.get_pager_file`` for file-like access to an output pager. :pr:`1572` diff --git a/src/click/types.py b/src/click/types.py index 355e98423..ecce4a959 100644 --- a/src/click/types.py +++ b/src/click/types.py @@ -195,13 +195,16 @@ def convert( ) -> ParamTypeValue: 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[t.Any]): diff --git a/tests/test_types.py b/tests/test_types.py index e633be4a2..0f78ea9d3 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)