diff --git a/CHANGELOG.md b/CHANGELOG.md index 406bf16ba..0273033eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ - **complete_in_thread**: (boolean) if `True`, then completion will run in a separate thread. If `False` then completion runs in the main thread and causes it to block if slow. Defaults to `True`. + +- Bug Fixes + - Fixed type hinting so that methods decorated with `with_annotated` no longer trigger spurious + mypy errors and preserve their original signature. + - Experimental features - `@with_annotated` now supports `frozenset[T]` collection parameters, alongside the existing `list[T]`, `set[T]`, and `tuple[T, ...]` collection types. diff --git a/cmd2/annotated.py b/cmd2/annotated.py index 6b4a2efe5..63a27f392 100644 --- a/cmd2/annotated.py +++ b/cmd2/annotated.py @@ -198,6 +198,8 @@ def do_paint( Any, ClassVar, Literal, + ParamSpec, + Protocol, TypedDict, TypeVar, Union, @@ -2137,8 +2139,18 @@ def handler(self_arg: Any, ns: Any) -> Any: return handler, subcmd_name, parser_builder +_CommandParams = ParamSpec("_CommandParams") +_CommandReturn = TypeVar("_CommandReturn") + + +class _WithAnnotatedDecorator(Protocol): + """The signature-preserving decorator ``with_annotated(...)`` returns (generic per call).""" + + def __call__(self, fn: Callable[_CommandParams, _CommandReturn], /) -> Callable[_CommandParams, _CommandReturn]: ... + + @overload -def with_annotated(func: Callable[..., Any]) -> Callable[..., Any]: ... +def with_annotated(func: Callable[_CommandParams, _CommandReturn]) -> Callable[_CommandParams, _CommandReturn]: ... @overload @@ -2161,7 +2173,7 @@ def with_annotated( subcommand_title: str | None = ..., subcommand_description: str | None = ..., **parser_kwargs: Unpack[Cmd2ParserKwargs], -) -> Callable[[Callable[..., Any]], Callable[..., Any]]: ... +) -> _WithAnnotatedDecorator: ... def with_annotated(