@@ -240,6 +240,7 @@ def my_completer(self, text, line, begidx, endidx, arg_tokens)
240240 NoReturn ,
241241 Optional ,
242242 Sequence ,
243+ Set ,
243244 Tuple ,
244245 Type ,
245246 Union ,
@@ -640,10 +641,75 @@ def _action_set_suppress_tab_hint(self: argparse.Action, suppress_tab_hint: bool
640641setattr (argparse .Action , 'set_suppress_tab_hint' , _action_set_suppress_tab_hint )
641642
642643
644+ ############################################################################################################
645+ # Allow developers to add custom action attributes
646+ ############################################################################################################
647+
648+ CUSTOM_ACTION_ATTRIBS : Set [str ] = set ()
649+ _CUSTOM_ATTRIB_PFX = '_attr_'
650+
651+
652+ def register_argparse_argument_parameter (param_name : str , param_type : Optional [Type [Any ]]) -> None :
653+ """
654+ Registers a custom argparse argument parameter.
655+
656+ The registered name will then be a recognized keyword parameter to the parser's `add_argument()` function.
657+
658+ An accessor functions will be added to the parameter's Action object in the form of: ``get_{param_name}()``
659+ and ``set_{param_name}(value)``.
660+
661+ :param param_name: Name of the parameter to add.
662+ """
663+ attr_name = f'{ _CUSTOM_ATTRIB_PFX } { param_name } '
664+ if param_name in CUSTOM_ACTION_ATTRIBS or hasattr (argparse .Action , attr_name ):
665+ raise KeyError (f'Custom parameter { param_name } already exists' )
666+ if not re .search ('^[A-Za-z_][A-Za-z0-9_]*$' , param_name ):
667+ raise KeyError (f'Invalid parameter name { param_name } - cannot be used as a python identifier' )
668+
669+ getter_name = f'get_{ param_name } '
670+
671+ def _action_get_custom_parameter (self : argparse .Action ) -> Any :
672+ f"""
673+ Get the custom { param_name } attribute of an argparse Action.
674+
675+ This function is added by cmd2 as a method called ``{ getter_name } ()`` to ``argparse.Action`` class.
676+
677+ To call: ``action.{ getter_name } ()``
678+
679+ :param self: argparse Action being queried
680+ :return: The value of { param_name } or None if attribute does not exist
681+ """
682+ return getattr (self , attr_name , None )
683+
684+ setattr (argparse .Action , getter_name , _action_get_custom_parameter )
685+
686+ setter_name = f'set_{ param_name } '
687+
688+ def _action_set_custom_parameter (self : argparse .Action , value : Any ) -> None :
689+ f"""
690+ Set the custom { param_name } attribute of an argparse Action.
691+
692+ This function is added by cmd2 as a method called ``{ setter_name } ()`` to ``argparse.Action`` class.
693+
694+ To call: ``action.{ setter_name } ({ param_name } )``
695+
696+ :param self: argparse Action being updated
697+ :param value: value being assigned
698+ """
699+ if param_type and not isinstance (value , param_type ):
700+ raise TypeError (f'{ param_name } must be of type { param_type } , got: { value } ({ type (value )} )' )
701+ setattr (self , attr_name , value )
702+
703+ setattr (argparse .Action , setter_name , _action_set_custom_parameter )
704+
705+ CUSTOM_ACTION_ATTRIBS .add (param_name )
706+
707+
643708############################################################################################################
644709# Patch _ActionsContainer.add_argument with our wrapper to support more arguments
645710############################################################################################################
646711
712+
647713# Save original _ActionsContainer.add_argument so we can call it in our wrapper
648714# noinspection PyProtectedMember
649715orig_actions_container_add_argument = argparse ._ActionsContainer .add_argument
@@ -753,6 +819,14 @@ def _add_argument_wrapper(
753819 # Add the argparse-recognized version of nargs to kwargs
754820 kwargs ['nargs' ] = nargs_adjusted
755821
822+ # Extract registered custom keyword arguments
823+ custom_attribs : Dict [str , Any ] = {}
824+ for keyword , value in kwargs .items ():
825+ if keyword in CUSTOM_ACTION_ATTRIBS :
826+ custom_attribs [keyword ] = value
827+ for keyword in custom_attribs :
828+ del kwargs [keyword ]
829+
756830 # Create the argument using the original add_argument function
757831 new_arg = orig_actions_container_add_argument (self , * args , ** kwargs )
758832
@@ -767,6 +841,11 @@ def _add_argument_wrapper(
767841 new_arg .set_suppress_tab_hint (suppress_tab_hint ) # type: ignore[attr-defined]
768842 new_arg .set_descriptive_header (descriptive_header ) # type: ignore[attr-defined]
769843
844+ for keyword , value in custom_attribs .items ():
845+ attr_setter = getattr (new_arg , f'set_{ keyword } ' , None )
846+ if attr_setter is not None :
847+ attr_setter (value )
848+
770849 return new_arg
771850
772851
@@ -1243,6 +1322,9 @@ def set(self, new_val: Any) -> None:
12431322
12441323
12451324def set_default_argument_parser (parser : Type [argparse .ArgumentParser ]) -> None :
1246- """Set the default ArgumentParser class for a cmd2 app"""
1325+ """
1326+ Set the default ArgumentParser class for a cmd2 app. This must be called prior to loading cmd2.py if
1327+ you want to override the parser for cmd2's built-in commands. See examples/override_parser.py.
1328+ """
12471329 global DEFAULT_ARGUMENT_PARSER
12481330 DEFAULT_ARGUMENT_PARSER = parser
0 commit comments