111111from . import rich_utils as ru
112112from . import string_utils as su
113113from .argparse_utils import (
114+ ApCommandSpec ,
114115 Cmd2ArgumentParser ,
115116 ParserSource ,
116117 SubcommandRecord ,
@@ -280,12 +281,12 @@ def get(self, command_method: BoundCommandFunc) -> Cmd2ArgumentParser | None:
280281 return None
281282 command = command_method .__name__ [len (COMMAND_FUNC_PREFIX ) :]
282283
283- parser_source = getattr (command_method , constants .CMD_ATTR_PARSER_SOURCE , None )
284- if parser_source is None :
284+ spec : ApCommandSpec | None = getattr (command_method , constants .AP_COMMAND_ATTR_SPEC , None )
285+ if spec is None :
285286 return None
286287
287288 owner = self ._cmd_app .find_commandset_for_command (command ) or self ._cmd_app
288- parser = self ._cmd_app ._build_parser (owner , parser_source )
289+ parser = self ._cmd_app ._build_parser (owner , spec . parser_source )
289290
290291 # To ensure accurate usage strings, recursively update 'prog' values
291292 # within the parser to match the command name.
@@ -1063,9 +1064,21 @@ def unregister_command_set(self, cmdset: CommandSet[Any]) -> None:
10631064 self ._installed_command_sets .remove (cmdset )
10641065
10651066 def _check_uninstallable (self , cmdset : CommandSet [Any ]) -> None :
1066- cmdset_id = id (cmdset )
1067+ """Verify if a CommandSet can be safely uninstalled from the application.
1068+
1069+ This method acts as a safety guard before unregistration. It inspects all
1070+ command parsers provided by the CommandSet and recursively checks their
1071+ subcommand hierarchies to ensure no other registrant (another CommandSet
1072+ or the main application) has attached subcommands to them.
1073+
1074+ :param cmdset: the CommandSet instance to check for uninstallation safety
1075+ :raises CommandSetRegistrationError: if any parser in the CommandSet is
1076+ required by another registrant
1077+ """
1078+ registrant_id = id (cmdset )
10671079
10681080 def check_parser_uninstallable (parser : Cmd2ArgumentParser ) -> None :
1081+ # Recursively verify no subcommands belong to a different registrant
10691082 try :
10701083 subparsers_action = parser .get_subparsers_action ()
10711084 except ValueError :
@@ -1080,10 +1093,10 @@ def check_parser_uninstallable(parser: Cmd2ArgumentParser) -> None:
10801093 continue
10811094 checked_parsers .add (subparser )
10821095
1083- attached_cmdset_id = getattr (subparser , constants .PARSER_ATTR_OWNER_ID , None )
1084- if attached_cmdset_id is not None and attached_cmdset_id != cmdset_id :
1096+ attached_registrant_id = getattr (subparser , constants .PARSER_ATTR_REGISTRANT_ID , None )
1097+ if attached_registrant_id is not None and attached_registrant_id != registrant_id :
10851098 raise CommandSetRegistrationError (
1086- f"Cannot uninstall CommandSet: '{ subparser .prog } ' is required by another CommandSet "
1099+ f"Cannot uninstall CommandSet: '{ subparser .prog } ' is required by another registrant "
10871100 )
10881101 check_parser_uninstallable (subparser )
10891102
@@ -1117,13 +1130,13 @@ def _register_subcommands(self, owner: CmdOrSet) -> None:
11171130 owner ,
11181131 predicate = lambda meth : (
11191132 isinstance (meth , Callable ) # type: ignore[arg-type]
1120- and hasattr (meth , constants .SUBCMD_ATTR_SPEC )
1133+ and hasattr (meth , constants .SUBCOMMAND_ATTR_SPEC )
11211134 ),
11221135 )
11231136
11241137 # iterate through all matching methods
11251138 for _method_name , method in methods :
1126- spec : SubcommandSpec = getattr (method , constants .SUBCMD_ATTR_SPEC )
1139+ spec : SubcommandSpec = getattr (method , constants .SUBCOMMAND_ATTR_SPEC )
11271140
11281141 subcommand_valid , errmsg = self .statement_parser .is_valid_command (spec .name , is_subcommand = True )
11291142 if not subcommand_valid :
@@ -1134,12 +1147,12 @@ def _register_subcommands(self, owner: CmdOrSet) -> None:
11341147 if subcmd_parser .description is None and method .__doc__ :
11351148 subcmd_parser .description = strip_doc_annotations (method .__doc__ )
11361149
1137- # Set the subcommand handler
1138- defaults = {constants .NS_ATTR_SUBCMD_HANDLER : method }
1150+ # Set the subcommand function
1151+ defaults = {constants .NS_ATTR_SUBCOMMAND_FUNC : method }
11391152 subcmd_parser .set_defaults (** defaults )
11401153
1141- # Set what instance the handler is bound to
1142- setattr (subcmd_parser , constants .PARSER_ATTR_OWNER_ID , id (owner ))
1154+ # Record the ID of the instance that registered this subcommand parser
1155+ setattr (subcmd_parser , constants .PARSER_ATTR_REGISTRANT_ID , id (owner ))
11431156
11441157 # Attach this subcommand
11451158 record = SubcommandRecord (
@@ -1169,13 +1182,13 @@ def _unregister_subcommands(self, owner: CmdOrSet) -> None:
11691182 owner ,
11701183 predicate = lambda meth : (
11711184 isinstance (meth , Callable ) # type: ignore[arg-type]
1172- and hasattr (meth , constants .SUBCMD_ATTR_SPEC )
1185+ and hasattr (meth , constants .SUBCOMMAND_ATTR_SPEC )
11731186 ),
11741187 )
11751188
11761189 # iterate through all matching methods
11771190 for _method_name , method in methods :
1178- spec : SubcommandSpec = getattr (method , constants .SUBCMD_ATTR_SPEC )
1191+ spec : SubcommandSpec = getattr (method , constants .SUBCOMMAND_ATTR_SPEC )
11791192
11801193 with contextlib .suppress (ValueError ):
11811194 self .detach_subcommand (spec .command , spec .name )
@@ -2517,15 +2530,15 @@ def _perform_completion(
25172530
25182531 if command_func is not None and argparser is not None :
25192532 # Get arguments for complete()
2520- preserve_quotes = getattr (command_func , constants .CMD_ATTR_PRESERVE_QUOTES )
2533+ spec : ApCommandSpec = getattr (command_func , constants .AP_COMMAND_ATTR_SPEC )
25212534 cmd_set = self .find_commandset_for_command (command )
25222535
25232536 # Create the argparse completer
25242537 completer_type = self ._determine_ap_completer_type (argparser )
25252538 completer = completer_type (argparser , self )
25262539
25272540 completer_func = functools .partial (
2528- completer .complete , tokens = raw_tokens [1 :] if preserve_quotes else tokens [1 :], cmd_set = cmd_set
2541+ completer .complete , tokens = raw_tokens [1 :] if spec . preserve_quotes else tokens [1 :], cmd_set = cmd_set
25292542 )
25302543 else :
25312544 completer_func = self .completedefault # type: ignore[assignment]
@@ -3380,8 +3393,8 @@ def _get_command_category(self, func: BoundCommandFunc) -> str:
33803393 :return: category name
33813394 """
33823395 # Check if the command function has a category.
3383- if hasattr (func , constants .CMD_ATTR_HELP_CATEGORY ):
3384- category : str = getattr (func , constants .CMD_ATTR_HELP_CATEGORY )
3396+ if hasattr (func , constants .COMMAND_ATTR_HELP_CATEGORY ):
3397+ category : str = getattr (func , constants .COMMAND_ATTR_HELP_CATEGORY )
33853398
33863399 # Otherwise get the category from its defining class.
33873400 else :
@@ -3784,8 +3797,8 @@ def _build_alias_parser() -> Cmd2ArgumentParser:
37843797 @with_argparser (_build_alias_parser , preserve_quotes = True )
37853798 def do_alias (self , args : argparse .Namespace ) -> None :
37863799 """Manage aliases."""
3787- # Call handler for whatever subcommand was selected
3788- args .cmd2_subcmd_handler (args )
3800+ # Call function for whatever subcommand was selected
3801+ args .cmd2_subcommand_func (args )
37893802
37903803 # alias -> create
37913804 @classmethod
@@ -3998,8 +4011,8 @@ def _build_macro_parser() -> Cmd2ArgumentParser:
39984011 @with_argparser (_build_macro_parser , preserve_quotes = True )
39994012 def do_macro (self , args : argparse .Namespace ) -> None :
40004013 """Manage macros."""
4001- # Call handler for whatever subcommand was selected
4002- args .cmd2_subcmd_handler (args )
4014+ # Call function for whatever subcommand was selected
4015+ args .cmd2_subcommand_func (args )
40034016
40044017 # macro -> create
40054018 @classmethod
0 commit comments