@@ -284,13 +284,11 @@ def do_build(self, target: str, common: CommonArgs):
284284 NamedTuple ,
285285 ParamSpec ,
286286 Protocol ,
287- TypeAlias ,
288287 TypedDict ,
289288 TypeGuard ,
290289 TypeVar ,
291290 Union ,
292291 Unpack ,
293- cast ,
294292 get_args ,
295293 get_origin ,
296294 get_type_hints ,
@@ -311,19 +309,27 @@ def do_build(self, target: str, common: CommonArgs):
311309from .exceptions import Cmd2ArgparseError
312310from .rich_utils import Cmd2HelpFormatter , HelpContent
313311from .types import (
314- BoundCommandFunc ,
315- CmdOrSet ,
316312 CmdOrSetT ,
317313 UnboundChoicesProvider ,
318- UnboundCommandFunc ,
319314 UnboundCompleter ,
320315)
321316
322317#: ``nargs`` values accepted by cmd2's patched ``add_argument`` (incl. ranged tuples).
323318_NargsValue = int | str | tuple [int ] | tuple [int , int ] | tuple [int , float ]
324319
325320
326- _CommandFunc : TypeAlias = BoundCommandFunc | UnboundCommandFunc [CmdOrSet , Any ]
321+ class _CommandFunc (Protocol ):
322+ """The command-function interface required by the annotation machinery.
323+
324+ The parser builders only depend on a command's name metadata and its
325+ boolean command result; they never depend on whether the function is
326+ bound or unbound, nor on the types of its parsed arguments.
327+ """
328+
329+ __name__ : str
330+ __qualname__ : str
331+
332+ def __call__ (self , * args : Any , ** kwargs : Any ) -> bool | None : ...
327333
328334
329335class Cmd2ParserKwargs (TypedDict , total = False ):
@@ -2928,17 +2934,22 @@ def handler(self_arg: Any, ns: Any) -> Any:
29282934
29292935
29302936_CommandParams = ParamSpec ("_CommandParams" )
2931- _CommandReturn = TypeVar ("_CommandReturn" )
2937+
2938+
2939+ class _AnnotatedCommand (_CommandFunc , Protocol [_CommandParams ]):
2940+ """A callable command method with the metadata used by ``with_annotated``."""
2941+
2942+ def __call__ (self , * args : _CommandParams .args , ** kwargs : _CommandParams .kwargs ) -> bool | None : ...
29322943
29332944
29342945class _WithAnnotatedDecorator (Protocol ):
29352946 """The signature-preserving decorator ``with_annotated(...)`` returns (generic per call)."""
29362947
2937- def __call__ (self , fn : Callable [_CommandParams , _CommandReturn ], / ) -> Callable [_CommandParams , _CommandReturn ]: ...
2948+ def __call__ (self , fn : _AnnotatedCommand [_CommandParams ], / ) -> _AnnotatedCommand [_CommandParams ]: ...
29382949
29392950
29402951@overload
2941- def with_annotated (func : Callable [_CommandParams , _CommandReturn ] ) -> Callable [_CommandParams , _CommandReturn ]: ...
2952+ def with_annotated (func : _AnnotatedCommand [_CommandParams ], / ) -> _AnnotatedCommand [_CommandParams ]: ...
29422953
29432954
29442955@overload
@@ -2965,7 +2976,7 @@ def with_annotated(
29652976
29662977
29672978def with_annotated (
2968- func : Callable [_CommandParams , _CommandReturn ] | None = None ,
2979+ func : _AnnotatedCommand [_CommandParams ] | None = None ,
29692980 * ,
29702981 ns_provider : Callable [..., argparse .Namespace ] | None = None ,
29712982 preserve_quotes : bool = False ,
@@ -2983,7 +2994,7 @@ def with_annotated(
29832994 subcommand_title : str | None = None ,
29842995 subcommand_description : str | None = None ,
29852996 ** parser_kwargs : Unpack [Cmd2ParserKwargs ],
2986- ) -> Callable [_CommandParams , _CommandReturn ] | _WithAnnotatedDecorator :
2997+ ) -> _AnnotatedCommand [_CommandParams ] | _WithAnnotatedDecorator :
29872998 """Decorate a ``do_*`` method to build its argparse parser from type annotations.
29882999
29893000 :param func: the command function (when used without parentheses)
@@ -3044,11 +3055,9 @@ def with_annotated(
30443055 subcommand_description = subcommand_description ,
30453056 )
30463057
3047- def decorator (fn : Callable [_CommandParams , _CommandReturn ]) -> Callable [_CommandParams , _CommandReturn ]:
3048- # ``with_annotated`` is publicly typed as signature-preserving. Its runtime contract is
3049- # narrower: decorated functions must be cmd2 command methods. Keep that narrowing local
3050- # to the parser-building machinery while retaining the callable's public signature.
3051- command_func = cast (_CommandFunc , fn )
3058+ def decorator (
3059+ command_func : _AnnotatedCommand [_CommandParams ],
3060+ ) -> _AnnotatedCommand [_CommandParams ]:
30523061 if with_unknown_args :
30533062 unknown_param = inspect .signature (command_func ).parameters .get ("_unknown" )
30543063 if unknown_param is None :
@@ -3078,7 +3087,7 @@ def decorator(fn: Callable[_CommandParams, _CommandReturn]) -> Callable[_Command
30783087 parser_source = subcmd_parser_builder ,
30793088 )
30803089 setattr (handler , constants .SUBCOMMAND_ATTR_SPEC , subcommand_spec )
3081- return cast ( Callable [ _CommandParams , _CommandReturn ], handler )
3090+ return handler
30823091
30833092 command_name = command_func .__name__ [len (constants .COMMAND_FUNC_PREFIX ) :]
30843093
@@ -3155,9 +3164,9 @@ def cmd_wrapper(*args: Any, **kwargs: Any) -> bool | None:
31553164 )
31563165 setattr (cmd_wrapper , constants .ARGPARSE_COMMAND_ATTR_SPEC , argparse_command_spec )
31573166
3158- return cast ( Callable [ _CommandParams , _CommandReturn ], cmd_wrapper )
3167+ return cmd_wrapper
31593168
31603169 # Support both @with_annotated and @with_annotated(...)
31613170 if func is not None :
31623171 return decorator (func )
3163- return cast ( _WithAnnotatedDecorator , decorator )
3172+ return decorator
0 commit comments