99from typing import (
1010 TYPE_CHECKING ,
1111 Any ,
12- ParamSpec ,
1312 TypeAlias ,
13+ TypeVar ,
1414 overload ,
1515)
1616
2828if TYPE_CHECKING : # pragma: no cover
2929 from .cmd2 import Cmd
3030
31- P = ParamSpec ( "P" )
31+ F = TypeVar ( "F" , bound = Callable [..., Any ] )
3232
3333
3434def with_category (
3535 category : str ,
36- ) -> Callable [[UnboundCommandFunc [ CmdOrSetT , P ]], UnboundCommandFunc [ CmdOrSetT , P ] ]:
36+ ) -> Callable [[F ], F ]:
3737 """Decorate a ``do_*`` command function to apply a category.
3838
39+ This decorator has permissive type hints to allow for order-independent stacking
40+ with other decorators that may modify the function signature or return type of the
41+ command function.
42+
3943 :param category: the name of the category in which this command should
4044 be grouped when displaying the list of commands.
4145 :return: a decorator that assigns the specified category to the command function
@@ -53,7 +57,7 @@ def do_echo(self, args)
5357
5458 """
5559
56- def cat_decorator (func : UnboundCommandFunc [ CmdOrSetT , P ] ) -> UnboundCommandFunc [ CmdOrSetT , P ] :
60+ def cat_decorator (func : F ) -> F :
5761 from .utils import categorize
5862
5963 categorize (func , category )
@@ -62,10 +66,6 @@ def cat_decorator(func: UnboundCommandFunc[CmdOrSetT, P]) -> UnboundCommandFunc[
6266 return cat_decorator
6367
6468
65- # The standard cmd2 command function signature (e.g. do_command(self, statement))
66- RawCommandFunc : TypeAlias = UnboundCommandFunc [CmdOrSetT , [Statement | str ]]
67-
68-
6969##########################
7070# The _parse_positionals and _arg_swap functions allow for additional positional args to be preserved
7171# in cmd2 command functions/callables. As long as the 2-ple of arguments we expect to be there can be
@@ -108,6 +108,10 @@ def _arg_swap(args: Sequence[Any], search_arg: Any, *replace_arg: Any) -> list[A
108108 return args_list
109109
110110
111+ # The standard cmd2 command function signature (e.g. do_command(self, statement))
112+ RawCommandFunc : TypeAlias = UnboundCommandFunc [CmdOrSetT , [Statement | str ]]
113+
114+
111115# Function signature for a command function that accepts a pre-processed argument list from user input
112116ArgListCommandFunc : TypeAlias = UnboundCommandFunc [CmdOrSetT , [list [str ]]]
113117
@@ -340,8 +344,12 @@ def as_subcommand_to(
340344 help : str | None = None , # noqa: A002
341345 aliases : Sequence [str ] | None = None ,
342346 ** add_parser_kwargs : Any ,
343- ) -> Callable [[ArgparseCommandFunc [CmdOrSetT ]], ArgparseCommandFunc [CmdOrSetT ]]:
344- """Tag this function as a subcommand to an existing argparse decorated command.
347+ ) -> Callable [[F ], F ]:
348+ """Tag a function as a subcommand to an existing argparse decorated command.
349+
350+ This decorator has permissive type hints to allow for order-independent stacking
351+ with other decorators that may modify the function signature or return type of the
352+ subcommand function.
345353
346354 :param command: Command Name. Space-delimited subcommands may optionally be specified
347355 :param subcommand: Subcommand name
@@ -355,7 +363,7 @@ def as_subcommand_to(
355363 :return: Wrapper function that can receive an argparse.Namespace
356364 """
357365
358- def arg_decorator (func : ArgparseCommandFunc [ CmdOrSetT ] ) -> ArgparseCommandFunc [ CmdOrSetT ] :
366+ def arg_decorator (func : F ) -> F :
359367 # Set some custom attributes for this command
360368 setattr (func , constants .SUBCMD_ATTR_COMMAND , command )
361369 setattr (func , constants .CMD_ATTR_ARGPARSER , parser )
0 commit comments