click icon indicating copy to clipboard operation
click copied to clipboard

FuncParamType should use ValueError for self.fail(message)

Open toppk opened this issue 4 months ago • 1 comments

FuncParamType currently just calls self.fail(value,...) if it self.func(value) raise a ValueError. This is missing the point of the self.fail which is to give better feedback.
Using typer so something like


@dataclass
class MyClass:
    myvalue: str
     @staticmethod
      def from_str(cls, input:Any) -> 'MyClass':
              if input != 'works':
                  raise ValueError(f'input was  {input=}, should be works')
              return cls(input)
@app.command('test', help='get snapshot')
async def test(
    mydata: Annotated[
        MyClass, typer.Argument(parser=MyClass.from_str)
    ],
) -> None:

should give str(error) not str(input)

fix would be

class FuncParamType(ParamType):
    def __init__(self, func: t.Callable[[t.Any], t.Any]) -> None:
        self.name: str = func.__name__
        self.func = func

    def to_info_dict(self) -> dict[str, t.Any]:
        info_dict = super().to_info_dict()
        info_dict["func"] = self.func
        return info_dict

    def convert(
        self, value: t.Any, param: Parameter | None, ctx: Context | None
    ) -> t.Any:
        try:
            return self.func(value)
        except ValueError as e:
            self.fail(str(e), param, ctx)

happy to submit a PR if this makes sense. Rather not have to create a customclass when FuncParamType is so close. Not sure what else FuncParamType there there for, as there isn't really any documentation of why it exists, but typer uses it.

toppk avatar Oct 11 '25 22:10 toppk

in case any else finds this, enjoy my monkey patch:

def patch_click_func_param() -> None:
    """Patch click.FuncParamType to forward ValueError text to Click."""
    from click.types import FuncParamType  # noqa: PLC0415

    if getattr(FuncParamType, '_rt_original_convert', None) is not None:
        return

    def _patched_convert(
        self: FuncParamType,
        value: Any,
        param: 'Parameter | None',
        ctx: 'Context | None',
    ) -> Any:
        try:
            return self.func(value)
        except ValueError as exc:
            self.fail(str(exc), param, ctx)

    FuncParamType._rt_original_convert = FuncParamType.convert  # noqa: SLF001 # pyright: ignore[reportAttributeAccessIssue]
    FuncParamType.convert = _patched_convert


patch_click_func_param()

toppk avatar Oct 12 '25 00:10 toppk