From 0ef6fb7c1ee7d22f76321e44edd1379492077cc2 Mon Sep 17 00:00:00 2001 From: Kelvin Chung Date: Wed, 10 Jun 2026 22:10:40 +0100 Subject: [PATCH 1/2] chore: fix annotated typing c --- cmd2/annotated.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/cmd2/annotated.py b/cmd2/annotated.py index 268a71f09..d893e34f3 100644 --- a/cmd2/annotated.py +++ b/cmd2/annotated.py @@ -190,6 +190,8 @@ def do_paint( Any, ClassVar, Literal, + ParamSpec, + Protocol, TypedDict, TypeVar, Union, @@ -2117,8 +2119,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 @@ -2141,7 +2153,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( From beeacedbb0ff2e57a0d8643e8d5d7f6ceb4e6cde Mon Sep 17 00:00:00 2001 From: Kelvin Chung Date: Thu, 11 Jun 2026 11:05:58 +0100 Subject: [PATCH 2/2] fix(annotated): drop unnecessary string annotations, add changelog Address review feedback on PR #1692: - Remove forward-reference quotes from _WithAnnotatedDecorator.__call__ since Callable, _CommandParams, and _CommandReturn are already in scope - Add a Bug Fixes changelog entry for the with_annotated type-hint fix --- CHANGELOG.md | 3 +++ cmd2/annotated.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b67cf797e..4c3ed2d6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ - **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. ## 4.0.0 (June 5, 2026) diff --git a/cmd2/annotated.py b/cmd2/annotated.py index 17fe7b2c6..ecf4cea78 100644 --- a/cmd2/annotated.py +++ b/cmd2/annotated.py @@ -2145,7 +2145,7 @@ def handler(self_arg: Any, ns: Any) -> Any: class _WithAnnotatedDecorator(Protocol): """The signature-preserving decorator ``with_annotated(...)`` returns (generic per call).""" - def __call__(self, fn: "Callable[_CommandParams, _CommandReturn]", /) -> "Callable[_CommandParams, _CommandReturn]": ... + def __call__(self, fn: Callable[_CommandParams, _CommandReturn], /) -> Callable[_CommandParams, _CommandReturn]: ... @overload