@@ -284,6 +284,7 @@ def do_build(self, target: str, common: CommonArgs):
284284 NamedTuple ,
285285 ParamSpec ,
286286 Protocol ,
287+ TypeAlias ,
287288 TypedDict ,
288289 TypeGuard ,
289290 TypeVar ,
@@ -312,12 +313,16 @@ def do_build(self, target: str, common: CommonArgs):
312313 BoundCommandFunc ,
313314 CmdOrSetT ,
314315 UnboundChoicesProvider ,
316+ UnboundCommandFunc ,
315317 UnboundCompleter ,
316318)
317319
318320#: ``nargs`` values accepted by cmd2's patched ``add_argument`` (incl. ranged tuples).
319321_NargsValue = int | str | tuple [int ] | tuple [int , int ] | tuple [int , float ]
320322
323+ # Parser construction works with a command method before or after descriptor binding.
324+ _CommandFunc : TypeAlias = BoundCommandFunc | UnboundCommandFunc [Any , ...]
325+
321326
322327class Cmd2ParserKwargs (TypedDict , total = False ):
323328 """Forwarded ctor kwargs for [`Cmd2ArgumentParser`][cmd2.argparse_utils.Cmd2ArgumentParser] (PEP 692 ``Unpack``).
@@ -2119,7 +2124,7 @@ def _link_mutex_group_membership(
21192124 by_name [name ].mutex_group_indices .append (index )
21202125
21212126
2122- def _resolve_func_hints (func : BoundCommandFunc , * , skip_params : frozenset [str ] = _SKIP_PARAMS ) -> dict [str , Any ]:
2127+ def _resolve_func_hints (func : _CommandFunc , * , skip_params : frozenset [str ] = _SKIP_PARAMS ) -> dict [str , Any ]:
21232128 """Resolve the type hints for the parameters that become arguments.
21242129
21252130 The bound first parameter (self/cls), the injected ``skip_params``, and the ``return`` annotation
@@ -2297,7 +2302,7 @@ def _block_field_dest(spec: _BlockSpec, field_name: str) -> str:
22972302 return _shared_field_dest (spec .dc_type , field_name ) if spec .shared else field_name
22982303
22992304
2300- def _dataclass_blocks (func : BoundCommandFunc , * , skip_params : frozenset [str ] = _SKIP_PARAMS ) -> dict [str , _BlockSpec ]:
2305+ def _dataclass_blocks (func : _CommandFunc , * , skip_params : frozenset [str ] = _SKIP_PARAMS ) -> dict [str , _BlockSpec ]:
23012306 """Map each dataclass-block parameter name to its :class:`_BlockSpec`.
23022307
23032308 Used by the runtime handler to reconstruct the dataclass instance from the parsed namespace. A
@@ -2321,7 +2326,7 @@ def _dataclass_blocks(func: BoundCommandFunc, *, skip_params: frozenset[str] = _
23212326
23222327
23232328def _lazy_block_resolver (
2324- func : BoundCommandFunc ,
2329+ func : _CommandFunc ,
23252330 * ,
23262331 base_accepted : set [str ],
23272332 skip_params : frozenset [str ],
@@ -2378,7 +2383,7 @@ def _reconstruct_dataclass_blocks(func_kwargs: dict[str, Any], blocks: dict[str,
23782383
23792384
23802385def _resolve_parameters (
2381- func : BoundCommandFunc ,
2386+ func : _CommandFunc ,
23822387 * ,
23832388 skip_params : frozenset [str ] = _SKIP_PARAMS ,
23842389 base_command : bool = False ,
@@ -2722,7 +2727,7 @@ def _docstring_first_paragraph(doc: str | None) -> str | None:
27222727
27232728
27242729def build_parser_from_function (
2725- func : BoundCommandFunc ,
2730+ func : _CommandFunc ,
27262731 * ,
27272732 skip_params : frozenset [str ] = _SKIP_PARAMS ,
27282733 groups : tuple [Group , ...] | None = None ,
@@ -2797,7 +2802,7 @@ def build_parser_from_function(
27972802 return parser
27982803
27992804
2800- def _derive_subcommand_name (func : BoundCommandFunc , subcommand_to : str ) -> str :
2805+ def _derive_subcommand_name (func : _CommandFunc , subcommand_to : str ) -> str :
28012806 """Derive the subcommand name from the function name and validate the naming convention.
28022807
28032808 ``subcommand_to='team member'`` + ``func.__name__='team_member_add'`` -> ``'add'``.
@@ -2833,7 +2838,7 @@ class _ParserBuildOptions:
28332838
28342839
28352840def _make_parser_builder (
2836- func : BoundCommandFunc ,
2841+ func : _CommandFunc ,
28372842 * ,
28382843 skip_params : frozenset [str ],
28392844 base_command : bool ,
@@ -2872,12 +2877,12 @@ def parser_builder() -> Cmd2ArgumentParser:
28722877
28732878
28742879def _build_subcommand_handler (
2875- func : BoundCommandFunc ,
2880+ func : _CommandFunc ,
28762881 subcommand_to : str ,
28772882 * ,
28782883 base_command : bool = False ,
28792884 options : _ParserBuildOptions ,
2880- ) -> tuple [BoundCommandFunc , str , Callable [[], Cmd2ArgumentParser ]]:
2885+ ) -> tuple [_CommandFunc , str , Callable [[], Cmd2ArgumentParser ]]:
28812886 """Build a subcommand's parser and a handler that unpacks the Namespace into typed kwargs.
28822887
28832888 :param func: the subcommand handler function
@@ -2923,9 +2928,12 @@ def handler(self_arg: Any, ns: Any) -> Any:
29232928_CommandParams = ParamSpec ("_CommandParams" )
29242929
29252930
2926- class _AnnotatedCommand (BoundCommandFunc , Protocol [_CommandParams ]):
2931+ class _AnnotatedCommand (Protocol [_CommandParams ]):
29272932 """A callable command method with the metadata used by ``with_annotated``."""
29282933
2934+ __name__ : str
2935+ __qualname__ : str
2936+
29292937 def __call__ (self , * args : _CommandParams .args , ** kwargs : _CommandParams .kwargs ) -> bool | None : ...
29302938
29312939
0 commit comments