@@ -772,25 +772,6 @@ def __init__(
772772 self .description : RenderableType | None # type: ignore[assignment]
773773 self .epilog : RenderableType | None # type: ignore[assignment]
774774
775- def add_subparsers ( # type: ignore[override]
776- self ,
777- ** kwargs : Any ,
778- ) -> "argparse._SubParsersAction[Cmd2ArgumentParser]" :
779- """Override for improved defaults and type safety.
780-
781- This override does two things.
782- 1. Sets a default title if one was not given.
783- 2. Narrows the return type to provide better IDE autocompletion
784- and type safety for `Cmd2ArgumentParser` instances.
785-
786- :param kwargs: additional keyword arguments
787- :return: _SubParsersAction which stores Cmd2ArgumentParsers
788- """
789- if 'title' not in kwargs :
790- kwargs ['title' ] = 'subcommands'
791-
792- return super ().add_subparsers (** kwargs )
793-
794775 def _get_subparsers_action (self ) -> "argparse._SubParsersAction[Cmd2ArgumentParser]" :
795776 """Get the _SubParsersAction for this parser if it exists.
796777
@@ -890,7 +871,7 @@ def _find_parser(self, subcommand_path: Iterable[str]) -> 'Cmd2ArgumentParser':
890871 """Find a parser in the hierarchy based on a sequence of subcommand names.
891872
892873 :param subcommand_path: sequence of subcommand names leading to the target parser
893- :return: the discovered Cmd2ArgumentParser
874+ :return: the discovered parser
894875 :raises ValueError: if any subcommand in the path is not found or a level doesn't support subcommands
895876 """
896877 parser = self
@@ -905,34 +886,54 @@ def attach_subcommand(
905886 self ,
906887 subcommand_path : Iterable [str ],
907888 subcommand : str ,
908- parser : 'Cmd2ArgumentParser' ,
889+ subcommand_parser : 'Cmd2ArgumentParser' ,
909890 ** add_parser_kwargs : Any ,
910891 ) -> None :
911892 """Attach a parser as a subcommand to a command at the specified path.
912893
913894 :param subcommand_path: sequence of subcommand names leading to the parser that will
914895 host the new subcommand. An empty sequence indicates this parser.
915896 :param subcommand: name of the new subcommand
916- :param parser : the parser to attach
897+ :param subcommand_parser : the parser to attach
917898 :param add_parser_kwargs: additional arguments for the subparser registration (e.g. help, aliases)
899+ :raises TypeError: if subcommand_parser is not an instance of the following or their subclasses:
900+ 1. Cmd2ArgumentParser
901+ 2. The parser_class configured for the target subcommand group
918902 :raises ValueError: if the command path is invalid or doesn't support subcommands
919903 """
904+ if not isinstance (subcommand_parser , Cmd2ArgumentParser ):
905+ raise TypeError (
906+ f"The attached parser must be an instance of 'Cmd2ArgumentParser' (or a subclass). "
907+ f"Received: '{ type (subcommand_parser ).__name__ } '."
908+ )
909+
920910 target_parser = self ._find_parser (subcommand_path )
921911 subparsers_action = target_parser ._get_subparsers_action ()
922912
913+ # Verify the parser is compatible with the 'parser_class' configured for this
914+ # subcommand group. We use isinstance() here to allow for subclasses, providing
915+ # more flexibility than the standard add_parser() factory approach which enforces
916+ # a specific class.
917+ if not isinstance (subcommand_parser , subparsers_action ._parser_class ):
918+ raise TypeError (
919+ f"The attached parser must be an instance of '{ subparsers_action ._parser_class .__name__ } ' "
920+ f"(or a subclass) to match the 'parser_class' configured for this subcommand group. "
921+ f"Received: '{ type (subcommand_parser ).__name__ } '."
922+ )
923+
923924 # Use add_parser to register the subcommand name and any aliases
924- new_parser = subparsers_action .add_parser (subcommand , ** add_parser_kwargs )
925+ placeholder_parser = subparsers_action .add_parser (subcommand , ** add_parser_kwargs )
925926
926927 # To ensure accurate usage strings, recursively update 'prog' values
927928 # within the injected parser to match its new location in the command hierarchy.
928- parser .update_prog (new_parser .prog )
929+ subcommand_parser .update_prog (placeholder_parser .prog )
929930
930931 # Replace the parser created by add_parser() with our pre-configured one
931- subparsers_action ._name_parser_map [subcommand ] = parser
932+ subparsers_action ._name_parser_map [subcommand ] = subcommand_parser
932933
933934 # Remap any aliases to our pre-configured parser
934935 for alias in add_parser_kwargs .get ("aliases" , ()):
935- subparsers_action ._name_parser_map [alias ] = parser
936+ subparsers_action ._name_parser_map [alias ] = subcommand_parser
936937
937938 def detach_subcommand (self , subcommand_path : Iterable [str ], subcommand : str ) -> 'Cmd2ArgumentParser' :
938939 """Detach a subcommand from a command at the specified path.
0 commit comments