Skip to content

# cmd2.argparse_utils

## `` cmd2.argparse_utils

Module adds capabilities to argparse by patching a few of its functions.

It also defines a parser class called Cmd2ArgumentParser which improves error and help output over normal argparse. All cmd2 code uses this parser and it is required that developers of cmd2-based apps either use it or write their own parser that inherits from it. If you wish to override the parser used by cmd2's built-in commands, see custom_parser.py example.

Added capabilities

Extends argparse nargs functionality by allowing tuples which specify a range (min, max). To specify a max value with no upper bound, use a 1-item tuple (min,)

Example::

    # -f argument expects at least 3 values
    parser.add_argument("-f", nargs=(3,))

    # -f argument expects 3 to 5 values
    parser.add_argument("-f", nargs=(3, 5))

Completion

cmd2 uses its ArgparseCompleter class to enable argparse-based completion on all commands that use the @with_argparser decorator. Out of the box you get completion of commands, subcommands, and flag names, as well as instructive hints about the current argument that print when tab is pressed. In addition, you can add completion for each argument's values using parameters passed to add_argument().

Below are the 3 add_argument() parameters for enabling completion of an argument's value. Only one can be used at a time.

`choices` - pass a list of values to the choices parameter.

    Example::

        my_list = ["An Option", "SomeOtherOption"]
        parser.add_argument("-o", "--options", choices=my_list)

`choices_provider` - pass a function that returns a Choices object. This is good in cases where the choices are dynamically generated when the user hits tab.

    Example::

        def my_choices_provider(self) -> Choices:
            ...
            return my_choices

        parser.add_argument("arg", choices_provider=my_choices_provider)

`completer` - pass a function that does custom completion and returns a Completions object.

cmd2 provides a few completer methods for convenience (e.g., path_complete, delimiter_complete)

    Example::

        # This adds file-path completion to an argument
        parser.add_argument("-o", "--options", completer=cmd2.Cmd.path_complete)

    You can use functools.partial() to prepopulate values of the underlying
    choices and completer functions/methods.

    Example::

        # This says to call path_complete with a preset value for its path_filter argument
        dir_completer = functools.partial(path_complete, path_filter=lambda path: os.path.isdir(path))
        parser.add_argument("-o", "--options", completer=dir_completer)

For `choices_provider` and `completer`, do not set them to a bound method. This is because ArgparseCompleter passes the `self` argument explicitly to these functions. When ArgparseCompleter calls one, it will detect whether it is bound to a `Cmd` subclass or `CommandSet`. If bound to a `cmd2.Cmd subclass`, it will pass the app instance as the `self` argument. If bound to a `cmd2.CommandSet` subclass, it will pass the `CommandSet` instance as the `self` argument. Therefore instead of passing something like `self.path_complete`, pass `cmd2.Cmd.path_complete`.

`choices_provider` and `completer` functions can also be implemented as standalone functions (i.e. not a member of a class). In this case, ArgparseCompleter will pass its `cmd2.Cmd` app instance as the first positional argument.

Of the 3 completion parameters, `choices` is the only one where argparse validates user input against items in the choices list. This is because the other 2 parameters are meant to complete data sets that are viewed as dynamic. Therefore it is up to the developer to validate if the user has typed an acceptable value for these arguments.

There are times when what's being completed is determined by a previous argument on the command line. In these cases, ArgparseCompleter can pass a dictionary that maps the command line tokens up through the one being completed to their argparse argument name. To receive this dictionary, your choices/completer function should have an argument called arg_tokens.

    Example::

        def my_choices_provider(self, arg_tokens) -> Choices
        def my_completer(self, text, line, begidx, endidx, arg_tokens) -> Completions

All values of the arg_tokens dictionary are lists, even if a particular argument expects only 1 token. Since ArgparseCompleter is for completion, it does not convert the tokens to their actual argument types or validate their values. All tokens are stored in the dictionary as the raw strings provided on the command line. It is up to the developer to determine if the user entered the correct argument type (e.g. int) and validate their values.

CompletionItem Class

This class represents a single completion result and what the `Choices` and `Completion` classes contain.

`CompletionItem` provides the following optional metadata fields which enhance completion results displayed to the screen.

1. display - string for displaying the completion differently in the completion menu
2. display_meta - meta information about completion which displays in the completion menu
3. table_data - supplemental data for completion tables

They can also be used as argparse choices. When a `CompletionItem` is created, it stores the original value (e.g. ID number) and makes it accessible through a property called `value`. cmd2 has patched argparse so that when evaluating choices, input is compared to `CompletionItem.value` instead of the `CompletionItem` instance.

Completion Tables

These were added to help in cases where uninformative data is being completed. For instance, completing ID numbers isn't very helpful to a user without context.

Providing `table_data` in your `CompletionItem` signals ArgparseCompleter to output the completion results in a table with supplemental data instead of just a table of tokens::

    Instead of this:
        1     2     3

    The user sees this:
         ITEM_ID   Description
        ────────────────────────────
               1   My item
               2   Another item
               3   Yet another item

The left-most column is the actual value being completed and its header is that value's name. Any additional column headers are defined using the `table_columns` parameter of add_argument(), which is a list of header names. The supplemental column values come from the `table_data` argument to `CompletionItem`. It's a `Sequence` with the same number of items as `table_columns`.

Example::

    Add an argument and define its table_columns.

        parser.add_argument(
            "item_id",
            type=int,
            choices_provider=get_choices,
            table_columns=["Item Name", "Checked Out", "Due Date"],
        )

    Implement the choices_provider to return Choices.

        def get_choices(self) -> Choices:
            """choices_provider which returns CompletionItems"""

            # Populate CompletionItem's table_data argument.
            # Its item count should match that of table_columns.
            items = [
                CompletionItem(1, table_data=["My item", True, "02/02/2022"]),
                CompletionItem(2, table_data=["Another item", False, ""]),
                CompletionItem(3, table_data=["Yet another item", False, ""]),
            ]
            return Choices(items)

    This is what the user will see during completion.

        ITEM_ID   Item Name          Checked Out   Due Date
        ───────────────────────────────────────────────────────
              1   My item            True          02/02/2022
              2   Another item       False
              3   Yet another item   False

`table_columns` can be strings or `Rich.table.Columns` for more control over things like alignment.

- If a header is a string, it will render as a left-aligned column with its overflow behavior set to "fold". This means a long string will wrap within its cell, creating as many new lines as required to fit.

- If a header is a `Column`, it defaults to "ellipsis" overflow behavior. This means a long string which exceeds the width of its column will be truncated with an ellipsis at the end. You can override this and other settings when you create the `Column`.

`table_data` items can include Rich objects, including styled Text and Tables.

To avoid printing excessive information to the screen at once when a user presses tab, there is a maximum threshold for the number of `CompletionItems` that will be shown. Its value is defined in `cmd2.Cmd.max_completion_table_items`. It defaults to 50, but can be changed. If the number of completion suggestions exceeds this number, then a completion table won't be displayed.

Custom Argument Parameters

`argparse._ActionsContainer.add_argument` has been patched to support several custom parameters used for tab completion and nargs range parsing. These parameters are registered using `register_argparse_argument_parameter()`. See `_ActionsContainer_add_argument` for more details on these parameters.

Registering a parameter whitelists it for use in `add_argument()` and automatically adds getter and setter accessor methods to the `argparse.Action` class. For any registered parameter named `<name>`, the following methods are available on the resulting `Action` object to access its underlying attribute:

- `action.get_<name>()`
- `action.set_<name>(value)`

### `` NoParamParserFactory `module-attribute`

    NoParamParserFactory = Callable[[], 'Cmd2ArgumentParser'] | _StaticParserFactory

### `` ClassParamParserFactory `module-attribute`

    ClassParamParserFactory = Union[Callable[[type[CmdOrSetT]], 'Cmd2ArgumentParser'], '_ClassParserFactory[CmdOrSetT]']

### `` ParserSource `module-attribute`

    ParserSource = Union['Cmd2ArgumentParser', NoParamParserFactory, ClassParamParserFactory[CmdOrSetT]]

### `` orig_actions_container_add_argument `module-attribute`

    orig_actions_container_add_argument = add_argument

### `` DEFAULT_ARGUMENT_PARSER `module-attribute`

    DEFAULT_ARGUMENT_PARSER = Cmd2ArgumentParser

### `` ArgparseCommandSpec `dataclass`

    ArgparseCommandSpec(*, parser_source, preserve_quotes=False)

Metadata for an argparse-based command function.

PARAMETER | DESCRIPTION
--- | ---
`parser_source` | an existing Cmd2ArgumentParser instance or a factory (callable, staticmethod, or classmethod) that returns one. TYPE: `ParserSource[Any]`
`preserve_quotes` | if True, then arguments passed to argparse maintain their quotes TYPE: `bool` DEFAULT: `False`

#### `` parser_source `instance-attribute`

    parser_source

#### `` preserve_quotes `class-attribute` `instance-attribute`

    preserve_quotes = False

### `` SubcommandSpec `dataclass`

    SubcommandSpec(*, name, command, help=None, aliases=(), deprecated=False, parser_source)

Bases: `_SubcommandBase`

Metadata used to build and register a subcommand.

PARAMETER | DESCRIPTION
--- | ---
`parser_source` | an existing Cmd2ArgumentParser instance or a factory (callable, staticmethod, or classmethod) that returns one. TYPE: `ParserSource[Any]`

#### `` parser_source `instance-attribute`

    parser_source

#### `` name `instance-attribute`

    name

#### `` command `instance-attribute`

    command

#### `` help `class-attribute` `instance-attribute`

    help = None

#### `` aliases `class-attribute` `instance-attribute`

    aliases = ()

#### `` deprecated `class-attribute` `instance-attribute`

    deprecated = False

### `` SubcommandRecord `dataclass`

    SubcommandRecord(*, name, command, help=None, aliases=(), deprecated=False, parser)

Bases: `_SubcommandBase`

A record of a subcommand's configuration and parser.

Used primarily for attaching and detaching subcommands.

PARAMETER | DESCRIPTION
--- | ---
`parser` | the built Cmd2ArgumentParser instance for this subcommand TYPE: `Cmd2ArgumentParser`

#### `` parser `instance-attribute`

    parser

#### `` name `instance-attribute`

    name

#### `` command `instance-attribute`

    command

#### `` help `class-attribute` `instance-attribute`

    help = None

#### `` aliases `class-attribute` `instance-attribute`

    aliases = ()

#### `` deprecated `class-attribute` `instance-attribute`

    deprecated = False

### `` Cmd2ArgumentParser

    Cmd2ArgumentParser(prog=None, usage=None, description=None, epilog=None, parents=(), formatter_class=Cmd2HelpFormatter, prefix_chars='-', fromfile_prefix_chars=None, argument_default=None, conflict_handler='error', add_help=True, allow_abbrev=True, exit_on_error=True, suggest_on_error=False, color=False, *, completer_class=None)

Bases: `ArgumentParser`

Custom ArgumentParser class that improves error and help output.

Initialize the Cmd2ArgumentParser instance.

PARAMETER | DESCRIPTION
--- | ---
`completer_class` | optional parameter which specifies a subclass of ArgparseCompleter for custom completion behavior on this parser. If this is None, then it will be set to argparse_completer.DEFAULT_ARGPARSE_COMPLETER. TYPE: `type[ArgparseCompleter] | None` DEFAULT: `None`

Source code in `cmd2/argparse_utils.py`

    713
    714
    715
    716
    717
    718
    719
    720
    721
    722
    723
    724
    725
    726
    727
    728
    729
    730
    731
    732
    733
    734
    735
    736
    737
    738
    739
    740
    741
    742
    743
    744
    745
    746
    747
    748
    749
    750
    751
    752
    753
    754
    755
    756
    757
    758
    759
    760
    761
    762
    763
    764
    765
    766
    767
    768
    769
    770
    771
    772
    773

    def __init__(
        self,
        prog: str | None = None,
        usage: str | None = None,
        description: HelpContent | None = None,
        epilog: HelpContent | None = None,
        parents: Sequence[argparse.ArgumentParser] = (),
        formatter_class: type[Cmd2HelpFormatter] = Cmd2HelpFormatter,
        prefix_chars: str = "-",
        fromfile_prefix_chars: str | None = None,
        argument_default: str | None = None,
        conflict_handler: str = "error",
        add_help: bool = True,
        allow_abbrev: bool = True,
        exit_on_error: bool = True,
        suggest_on_error: bool = False,
        color: bool = False,
        *,
        completer_class: type["ArgparseCompleter"] | None = None,
    ) -> None:
        """Initialize the Cmd2ArgumentParser instance.

        :param completer_class: optional parameter which specifies a subclass of ArgparseCompleter
                                for custom completion behavior on this parser. If this is None, then
                                it will be set to argparse_completer.DEFAULT_ARGPARSE_COMPLETER.
        """
        kwargs: dict[str, bool] = {}
        if sys.version_info >= (3, 14):
            # Python >= 3.14 so pass new arguments to parent argparse.ArgumentParser class
            kwargs = {
                "suggest_on_error": suggest_on_error,
                "color": color,
            }

        super().__init__(
            prog=prog,
            usage=usage,
            description=description,  # type: ignore[arg-type]
            epilog=epilog,  # type: ignore[arg-type]
            parents=parents,
            formatter_class=formatter_class,
            prefix_chars=prefix_chars,
            fromfile_prefix_chars=fromfile_prefix_chars,
            argument_default=argument_default,
            conflict_handler=conflict_handler,
            add_help=add_help,
            allow_abbrev=allow_abbrev,
            exit_on_error=exit_on_error,
            **kwargs,
        )

        if completer_class is None:
            from . import argparse_completer

            completer_class = argparse_completer.DEFAULT_ARGPARSE_COMPLETER
        self.completer_class = completer_class

        # To assist type checkers, recast these to reflect our usage of rich-argparse.
        self.formatter_class: type[Cmd2HelpFormatter]
        self.description: HelpContent | None  # type: ignore[assignment]
        self.epilog: HelpContent | None  # type: ignore[assignment]

#### `` completer_class `instance-attribute`

    completer_class = completer_class

#### `` formatter_class `instance-attribute`

    formatter_class

#### `` description `instance-attribute`

    description

#### `` epilog `instance-attribute`

    epilog

#### `` argument_default `instance-attribute`

    argument_default = argument_default

#### `` prefix_chars `instance-attribute`

    prefix_chars = prefix_chars

#### `` conflict_handler `instance-attribute`

    conflict_handler = conflict_handler

#### `` prog `instance-attribute`

    prog = _prog_name(prog)

#### `` usage `instance-attribute`

    usage = usage

#### `` fromfile_prefix_chars `instance-attribute`

    fromfile_prefix_chars = fromfile_prefix_chars

#### `` add_help `instance-attribute`

    add_help = add_help

#### `` allow_abbrev `instance-attribute`

    allow_abbrev = allow_abbrev

#### `` exit_on_error `instance-attribute`

    exit_on_error = exit_on_error

#### `` suggest_on_error `instance-attribute`

    suggest_on_error = suggest_on_error

#### `` color `instance-attribute`

    color = color

#### `` output_to

    output_to(file)

Context manager to temporarily set the output stream during argparse operations.

This is helpful for directing output for functions like `parse_args()`, which default to `sys.stdout` and lack a `file` argument.

PARAMETER | DESCRIPTION
--- | ---
`file` | the file stream to use for output TYPE: `IO[str] | None`

Source code in `cmd2/argparse_utils.py`

    697
    698
    699
    700
    701
    702
    703
    704
    705
    706
    707
    708
    709
    710
    711

    @contextlib.contextmanager
    def output_to(self, file: IO[str] | None) -> Iterator[None]:
        """Context manager to temporarily set the output stream during argparse operations.

        This is helpful for directing output for functions like `parse_args()`, which
        default to `sys.stdout` and lack a `file` argument.

        :param file: the file stream to use for output
        """
        previous = self._thread_locals.current_output_file
        self._thread_locals.current_output_file = file
        try:
            yield
        finally:
            self._thread_locals.current_output_file = previous

#### `` print_usage

    print_usage(file=None)

Override to ensure the formatter is aware of the target file.

Source code in `cmd2/argparse_utils.py`

    775
    776
    777
    778
    779
    780
    781

    def print_usage(self, file: IO[str] | None = None) -> None:  # type:ignore[override]
        """Override to ensure the formatter is aware of the target file."""
        if file is None:
            file = self._thread_locals.current_output_file

        with self.output_to(file):
            super().print_usage(file)

#### `` print_help

    print_help(file=None)

Override to ensure the formatter is aware of the target file.

Source code in `cmd2/argparse_utils.py`

    783
    784
    785
    786
    787
    788
    789

    def print_help(self, file: IO[str] | None = None) -> None:  # type:ignore[override]
        """Override to ensure the formatter is aware of the target file."""
        if file is None:
            file = self._thread_locals.current_output_file

        with self.output_to(file):
            super().print_help(file)

#### `` get_subparsers_action

    get_subparsers_action()

Get the _SubParsersAction for this parser if it exists.

RETURNS | DESCRIPTION
--- | ---
`_SubParsersAction[Cmd2ArgumentParser]` | the _SubParsersAction for this parser

RAISES | DESCRIPTION
--- | ---
`ValueError` | if this parser does not support subcommands

Source code in `cmd2/argparse_utils.py`

    791
    792
    793
    794
    795
    796
    797
    798
    799
    800
    801

    def get_subparsers_action(self) -> "argparse._SubParsersAction[Cmd2ArgumentParser]":
        """Get the _SubParsersAction for this parser if it exists.

        :return: the _SubParsersAction for this parser
        :raises ValueError: if this parser does not support subcommands
        """
        if self._subparsers is not None:
            for action in self._subparsers._group_actions:
                if isinstance(action, argparse._SubParsersAction):
                    return action
        raise ValueError(f"Command '{self.prog}' does not support subcommands")

#### `` update_prog

    update_prog(prog)

Recursively update the prog attribute of this parser and all of its subparsers.

PARAMETER | DESCRIPTION
--- | ---
`prog` | new value for this parser's prog attribute TYPE: `str`

Source code in `cmd2/argparse_utils.py`

    845
    846
    847
    848
    849
    850
    851
    852
    853
    854
    855
    856
    857
    858
    859
    860
    861
    862
    863
    864
    865
    866
    867
    868
    869
    870
    871
    872
    873
    874
    875
    876
    877
    878
    879
    880
    881
    882
    883
    884
    885
    886

    def update_prog(self, prog: str) -> None:
        """Recursively update the prog attribute of this parser and all of its subparsers.

        :param prog: new value for this parser's prog attribute
        """
        # Set the prog value for this parser
        self.prog = prog

        try:
            subparsers_action = self.get_subparsers_action()
        except ValueError:
            # This parser has no subcommands
            return

        # Get all positional arguments which appear before the subcommand.
        positionals: list[argparse.Action] = []
        for action in self._actions:
            if action is subparsers_action:
                break

            # Save positional argument
            if not action.option_strings:
                positionals.append(action)

        # Update _prog_prefix. This ensures that any subcommands added later via
        # add_parser() will have the correct prog value.
        subparsers_action._prog_prefix = self._build_subparsers_prog_prefix(positionals)

        # subparsers_action._name_parser_map includes aliases. Since primary names are inserted
        # first, we skip already updated parsers to ensure primary names are used in 'prog'.
        # We can't rely on subparsers_action._choices_actions to filter out aliases because while
        # it contains only primary names, it omits any subcommands that lack help text.
        updated_parsers: set[Cmd2ArgumentParser] = set()

        # Set the prog value for each subcommand's parser
        for subcmd_name, subcmd_parser in subparsers_action._name_parser_map.items():
            if subcmd_parser in updated_parsers:
                continue

            subcmd_prog = f"{subparsers_action._prog_prefix} {subcmd_name}"
            subcmd_parser.update_prog(subcmd_prog)
            updated_parsers.add(subcmd_parser)

#### `` find_parser

    find_parser(subcommand_path)

Find a parser in the hierarchy based on a sequence of subcommand names.

PARAMETER | DESCRIPTION
--- | ---
`subcommand_path` | sequence of subcommand names leading to the target parser TYPE: `Iterable[str]`

RETURNS | DESCRIPTION
--- | ---
`Cmd2ArgumentParser` | the discovered parser

RAISES | DESCRIPTION
--- | ---
`ValueError` | if any subcommand in the path is not found or a level doesn't support subcommands

Source code in `cmd2/argparse_utils.py`

    888
    889
    890
    891
    892
    893
    894
    895
    896
    897
    898
    899
    900
    901

    def find_parser(self, subcommand_path: Iterable[str]) -> "Cmd2ArgumentParser":
        """Find a parser in the hierarchy based on a sequence of subcommand names.

        :param subcommand_path: sequence of subcommand names leading to the target parser
        :return: the discovered parser
        :raises ValueError: if any subcommand in the path is not found or a level doesn't support subcommands
        """
        parser = self
        for name in subcommand_path:
            subparsers_action = parser.get_subparsers_action()
            if name not in subparsers_action._name_parser_map:
                raise ValueError(f"Subcommand '{name}' does not exist for '{parser.prog}'")
            parser = subparsers_action._name_parser_map[name]
        return parser

#### `` attach_subcommand

    attach_subcommand(record, subcommand_path=())

Attach a parser as a subcommand to a command at the specified path.

Note: `record.command` is not used for navigation here. It is assumed you are attaching relative to `self` using `subcommand_path`. However, `record.command` will be updated to reflect the final, absolute path of the parent parser this subcommand is attached to.

PARAMETER | DESCRIPTION
--- | ---
`record` | SubcommandRecord object describing the subcommand TYPE: `SubcommandRecord`
`subcommand_path` | sequence of subcommand names leading to the parser that will host the new subcommand. An empty sequence indicates this parser. TYPE: `Iterable[str]` DEFAULT: `()`

RAISES | DESCRIPTION
--- | ---
`TypeError` | if record.parser is not an instance of Cmd2ArgumentParser (or subclass)
`ValueError` | if the command path is invalid, doesn't support subcommands, or the subcommand already exists

Source code in `cmd2/argparse_utils.py`

    903
    904
    905
    906
    907
    908
    909
    910
    911
    912
    913
    914
    915
    916
    917
    918
    919
    920
    921
    922
    923
    924
    925
    926
    927
    928
    929
    930
    931
    932
    933
    934
    935
    936
    937
    938
    939
    940
    941
    942
    943
    944
    945
    946
    947
    948
    949
    950
    951
    952
    953
    954
    955
    956
    957
    958
    959
    960
    961
    962
    963
    964
    965
    966
    967
    968
    969
    970

    def attach_subcommand(
        self,
        record: SubcommandRecord,
        subcommand_path: Iterable[str] = (),
    ) -> None:
        """Attach a parser as a subcommand to a command at the specified path.

        Note: `record.command` is not used for navigation here. It is assumed you
        are attaching relative to `self` using `subcommand_path`. However,
        `record.command` will be updated to reflect the final, absolute path
        of the parent parser this subcommand is attached to.

        :param record: SubcommandRecord object describing the subcommand
        :param subcommand_path: sequence of subcommand names leading to the parser that will
                                host the new subcommand. An empty sequence indicates this parser.
        :raises TypeError: if record.parser is not an instance of Cmd2ArgumentParser (or subclass)
        :raises ValueError: if the command path is invalid, doesn't support subcommands, or the
                            subcommand already exists
        """
        if not isinstance(record.parser, Cmd2ArgumentParser):
            raise TypeError(
                f"The attached parser must be an instance of 'Cmd2ArgumentParser' (or subclass). "
                f"Received: '{type(record.parser).__name__}'."
            )

        target_parser = self.find_parser(subcommand_path)
        subparsers_action = target_parser.get_subparsers_action()

        # Verify the parser is compatible with the 'parser_class' configured for this
        # subcommand group. We use isinstance() here to allow for subclasses, providing
        # more flexibility than the standard add_parser() factory approach which enforces
        # a specific class.
        if not isinstance(record.parser, subparsers_action._parser_class):
            raise TypeError(
                f"The attached parser must be an instance of '{subparsers_action._parser_class.__name__}' "
                f"(or subclass) to match the 'parser_class' configured for this subcommand group. "
                f"Received: '{type(record.parser).__name__}'."
            )

        # Do not overwrite existing subcommands or aliases
        all_names = (record.name, *record.aliases)
        for name in all_names:
            if name in subparsers_action._name_parser_map:
                raise ValueError(f"Subcommand '{name}' already exists for '{target_parser.prog}'")

        # Registration kwargs
        kwargs: dict[str, Any] = {"aliases": record.aliases}
        if record.help is not None:
            kwargs["help"] = record.help
        if record.deprecated:
            kwargs["deprecated"] = record.deprecated

        # Use add_parser to register the subcommand name and any aliases
        placeholder_parser = subparsers_action.add_parser(record.name, **kwargs)

        # To ensure accurate usage strings, recursively update 'prog' values
        # within the injected parser to match its new location in the command hierarchy.
        record.parser.update_prog(placeholder_parser.prog)

        # Replace the parser created by add_parser() with our pre-configured one
        subparsers_action._name_parser_map[record.name] = record.parser

        # Remap any aliases to our pre-configured parser
        for alias in record.aliases:
            subparsers_action._name_parser_map[alias] = record.parser

        # Update command to reflect the parent parser's absolute path
        record.command = target_parser.prog

#### `` detach_subcommand

    detach_subcommand(subcommand_path, subcommand)

Detach a subcommand from a command at the specified path.

PARAMETER | DESCRIPTION
--- | ---
`subcommand_path` | sequence of subcommand names leading to the parser hosting the subcommand to be detached. An empty sequence indicates this parser. TYPE: `Iterable[str]`
`subcommand` | name of the subcommand to detach TYPE: `str`

RETURNS | DESCRIPTION
--- | ---
`SubcommandRecord` | a SubcommandRecord object describing the detached subcommand

RAISES | DESCRIPTION
--- | ---
`ValueError` | if the command path is invalid or the subcommand doesn't exist

Source code in `cmd2/argparse_utils.py`

    972
    973
    974
    975
    976
    977
    978
    979
    980
    981
    982
    983
    984
    985
    986
    987
    988
    989
    990
    991
    992
    993
    994

    def detach_subcommand(self, subcommand_path: Iterable[str], subcommand: str) -> SubcommandRecord:
        """Detach a subcommand from a command at the specified path.

        :param subcommand_path: sequence of subcommand names leading to the parser hosting the
                                subcommand to be detached. An empty sequence indicates this parser.
        :param subcommand: name of the subcommand to detach
        :return: a SubcommandRecord object describing the detached subcommand
        :raises ValueError: if the command path is invalid or the subcommand doesn't exist
        """
        target_parser = self.find_parser(subcommand_path)
        subparsers_action = target_parser.get_subparsers_action()

        try:
            record = cast(
                SubcommandRecord,
                subparsers_action.remove_parser(subcommand),  # type: ignore[attr-defined]
            )
        except ValueError:
            raise ValueError(f"Subcommand '{subcommand}' does not exist for '{target_parser.prog}'") from None

        # Update command to reflect the parent parser's absolute path
        record.command = target_parser.prog
        return record

#### `` detach_all_subcommands

    detach_all_subcommands(subcommand_path)

Detach all subcommands from a command at the specified path.

PARAMETER | DESCRIPTION
--- | ---
`subcommand_path` | sequence of subcommand names leading to the parser hosting the subcommands to be detached. An empty sequence indicates this parser. TYPE: `Iterable[str]`

RETURNS | DESCRIPTION
--- | ---
`list[SubcommandRecord]` | a list of SubcommandRecord objects describing the detached subcommands

RAISES | DESCRIPTION
--- | ---
`ValueError` | if the command path is invalid or the command doesn't support subcommands

Source code in `cmd2/argparse_utils.py`

     996
     997
     998
     999
    1000
    1001
    1002
    1003
    1004
    1005
    1006
    1007
    1008
    1009
    1010
    1011
    1012
    1013
    1014

    def detach_all_subcommands(self, subcommand_path: Iterable[str]) -> list[SubcommandRecord]:
        """Detach all subcommands from a command at the specified path.

        :param subcommand_path: sequence of subcommand names leading to the parser hosting the
                                subcommands to be detached. An empty sequence indicates this parser.
        :return: a list of SubcommandRecord objects describing the detached subcommands
        :raises ValueError: if the command path is invalid or the command doesn't support subcommands
        """
        target_parser = self.find_parser(subcommand_path)
        subparsers_action = target_parser.get_subparsers_action()

        records = cast(
            list[SubcommandRecord],
            subparsers_action.remove_all_parsers(),  # type: ignore[attr-defined]
        )
        # Update command for each detached subcommand
        for record in records:
            record.command = target_parser.prog
        return records

#### `` error

    error(message)

Override that applies custom formatting to the error message.

Source code in `cmd2/argparse_utils.py`

    1016
    1017
    1018
    1019
    1020
    1021
    1022
    1023
    1024
    1025
    1026
    1027
    1028
    1029
    1030
    1031
    1032
    1033
    1034
    1035
    1036

    def error(self, message: str) -> NoReturn:
        """Override that applies custom formatting to the error message."""
        lines = message.split("\n")
        formatted_message = ""
        for linum, line in enumerate(lines):
            if linum == 0:
                formatted_message = "Error: " + line
            else:
                formatted_message += "\n       " + line

        with self.output_to(sys.stderr):
            self.print_usage(sys.stderr)

            # Use console to add style since it will respect ALLOW_STYLE's value.
            # Now _get_formatter() will return a formatter bound to stderr.
            console = self._get_formatter().console
            with console.capture() as capture:
                console.print(formatted_message, style=Cmd2Style.ERROR)
            formatted_message = f"{capture.get()}"

        self.exit(2, f"{formatted_message}\n")

#### `` format_help

    format_help()

Override to add a newline.

Source code in `cmd2/argparse_utils.py`

    1042
    1043
    1044

    def format_help(self) -> str:
        """Override to add a newline."""
        return super().format_help() + "\n"

#### `` register

    register(registry_name, value, object)

Source code in `python3.14/argparse.py`

    1486
    1487
    1488

    def register(self, registry_name, value, object):
        registry = self._registries.setdefault(registry_name, {})
        registry[value] = object

#### `` set_defaults

    set_defaults(**kwargs)

Source code in `python3.14/argparse.py`

    1497
    1498
    1499
    1500
    1501
    1502
    1503
    1504

    def set_defaults(self, **kwargs):
        self._defaults.update(kwargs)

        # if these defaults match any existing arguments, replace
        # the previous default on the object with the new one
        for action in self._actions:
            if action.dest in kwargs:
                action.default = kwargs[action.dest]

#### `` get_default

    get_default(dest)

Source code in `python3.14/argparse.py`

    1506
    1507
    1508
    1509
    1510

    def get_default(self, dest):
        for action in self._actions:
            if action.dest == dest and action.default is not None:
                return action.default
        return self._defaults.get(dest, None)

#### `` add_argument

    add_argument(*args, **kwargs)

add_argument(dest, ..., name=value, ...) add_argument(option_string, option_string, ..., name=value, ...)

Source code in `python3.14/argparse.py`

    1517
    1518
    1519
    1520
    1521
    1522
    1523
    1524
    1525
    1526
    1527
    1528
    1529
    1530
    1531
    1532
    1533
    1534
    1535
    1536
    1537
    1538
    1539
    1540
    1541
    1542
    1543
    1544
    1545
    1546
    1547
    1548
    1549
    1550
    1551
    1552
    1553
    1554
    1555
    1556
    1557
    1558
    1559
    1560
    1561
    1562
    1563
    1564
    1565
    1566
    1567
    1568
    1569
    1570
    1571
    1572
    1573
    1574

    def add_argument(self, *args, **kwargs):
        """
        add_argument(dest, ..., name=value, ...)
        add_argument(option_string, option_string, ..., name=value, ...)
        """

        # if no positional args are supplied or only one is supplied and
        # it doesn't look like an option string, parse a positional
        # argument
        chars = self.prefix_chars
        if not args or len(args) == 1 and args[0][0] not in chars:
            if args and 'dest' in kwargs:
                raise TypeError('dest supplied twice for positional argument,'
                                ' did you mean metavar?')
            kwargs = self._get_positional_kwargs(*args, **kwargs)

        # otherwise, we're adding an optional argument
        else:
            kwargs = self._get_optional_kwargs(*args, **kwargs)

        # if no default was supplied, use the parser-level default
        if 'default' not in kwargs:
            dest = kwargs['dest']
            if dest in self._defaults:
                kwargs['default'] = self._defaults[dest]
            elif self.argument_default is not None:
                kwargs['default'] = self.argument_default

        # create the action object, and add it to the parser
        action_name = kwargs.get('action')
        action_class = self._pop_action_class(kwargs)
        if not callable(action_class):
            raise ValueError(f'unknown action {action_class!r}')
        action = action_class(**kwargs)

        # raise an error if action for positional argument does not
        # consume arguments
        if not action.option_strings and action.nargs == 0:
            raise ValueError(f'action {action_name!r} is not valid for positional arguments')

        # raise an error if the action type is not callable
        type_func = self._registry_get('type', action.type, action.type)
        if not callable(type_func):
            raise TypeError(f'{type_func!r} is not callable')

        if type_func is FileType:
            raise TypeError(f'{type_func!r} is a FileType class object, '
                            f'instance of it must be passed')

        # raise an error if the metavar does not match the type
        if hasattr(self, "_get_validation_formatter"):
            formatter = self._get_validation_formatter()
            try:
                formatter._format_args(action, None)
            except TypeError:
                raise ValueError("length of metavar tuple does not match nargs")
        self._check_help(action)
        return self._add_action(action)

#### `` add_argument_group

    add_argument_group(*args, **kwargs)

Source code in `python3.14/argparse.py`

    1576
    1577
    1578
    1579

    def add_argument_group(self, *args, **kwargs):
        group = _ArgumentGroup(self, *args, **kwargs)
        self._action_groups.append(group)
        return group

#### `` add_mutually_exclusive_group

    add_mutually_exclusive_group(**kwargs)

Source code in `python3.14/argparse.py`

    1581
    1582
    1583
    1584

    def add_mutually_exclusive_group(self, **kwargs):
        group = _MutuallyExclusiveGroup(self, **kwargs)
        self._mutually_exclusive_groups.append(group)
        return group

#### `` add_subparsers

    add_subparsers(**kwargs)

Source code in `python3.14/argparse.py`

    1959
    1960
    1961
    1962
    1963
    1964
    1965
    1966
    1967
    1968
    1969
    1970
    1971
    1972
    1973
    1974
    1975
    1976
    1977
    1978
    1979
    1980
    1981
    1982
    1983
    1984
    1985
    1986
    1987
    1988
    1989
    1990
    1991
    1992

    def add_subparsers(self, **kwargs):
        if self._subparsers is not None:
            raise ValueError('cannot have multiple subparser arguments')

        # add the parser class to the arguments if it's not present
        kwargs.setdefault('parser_class', type(self))

        if 'title' in kwargs or 'description' in kwargs:
            title = kwargs.pop('title', _('subcommands'))
            description = kwargs.pop('description', None)
            self._subparsers = self.add_argument_group(title, description)
        else:
            self._subparsers = self._positionals

        # prog defaults to the usage message of this parser, skipping
        # optional arguments and with no "usage:" prefix
        if kwargs.get('prog') is None:
            # Create formatter without color to avoid storing ANSI codes in prog
            formatter = self.formatter_class(prog=self.prog)
            formatter._set_color(False)
            positionals = self._get_positional_actions()
            groups = self._mutually_exclusive_groups
            formatter.add_usage(None, positionals, groups, '')
            kwargs['prog'] = formatter.format_help().strip()

        # create the parsers action and add it to the positionals list
        parsers_class = self._pop_action_class(kwargs, 'parsers')
        action = parsers_class(option_strings=[], **kwargs)
        action._color = self.color
        self._check_help(action)
        self._subparsers._add_action(action)

        # return the created parsers action
        return action

#### `` parse_args

    parse_args(args=None, namespace=None)

Source code in `python3.14/argparse.py`

    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2022
    2023

    def parse_args(self, args=None, namespace=None):
        args, argv = self.parse_known_args(args, namespace)
        if argv:
            msg = _('unrecognized arguments: %s') % ' '.join(argv)
            if self.exit_on_error:
                self.error(msg)
            else:
                raise ArgumentError(None, msg)
        return args

#### `` parse_known_args

    parse_known_args(args=None, namespace=None)

Source code in `python3.14/argparse.py`

    2025
    2026

    def parse_known_args(self, args=None, namespace=None):
        return self._parse_known_args2(args, namespace, intermixed=False)

#### `` convert_arg_line_to_args

    convert_arg_line_to_args(arg_line)

Source code in `python3.14/argparse.py`

    2400
    2401

    def convert_arg_line_to_args(self, arg_line):
        return [arg_line]

#### `` parse_intermixed_args

    parse_intermixed_args(args=None, namespace=None)

Source code in `python3.14/argparse.py`

    2578
    2579
    2580
    2581
    2582
    2583
    2584
    2585
    2586

    def parse_intermixed_args(self, args=None, namespace=None):
        args, argv = self.parse_known_intermixed_args(args, namespace)
        if argv:
            msg = _('unrecognized arguments: %s') % ' '.join(argv)
            if self.exit_on_error:
                self.error(msg)
            else:
                raise ArgumentError(None, msg)
        return args

#### `` parse_known_intermixed_args

    parse_known_intermixed_args(args=None, namespace=None)

Source code in `python3.14/argparse.py`

    2588
    2589
    2590
    2591
    2592
    2593
    2594
    2595
    2596
    2597
    2598
    2599
    2600
    2601
    2602
    2603
    2604

    def parse_known_intermixed_args(self, args=None, namespace=None):
        # returns a namespace and list of extras
        #
        # positional can be freely intermixed with optionals.  optionals are
        # first parsed with all positional arguments deactivated.  The 'extras'
        # are then parsed.  If the parser definition is incompatible with the
        # intermixed assumptions (e.g. use of REMAINDER, subparsers) a
        # TypeError is raised.

        positionals = self._get_positional_actions()
        a = [action for action in positionals
             if action.nargs in [PARSER, REMAINDER]]
        if a:
            raise TypeError('parse_intermixed_args: positional arg'
                            ' with nargs=%s'%a[0].nargs)

        return self._parse_known_args2(args, namespace, intermixed=True)

#### `` format_usage

    format_usage()

Source code in `python3.14/argparse.py`

    2714
    2715
    2716
    2717
    2718

    def format_usage(self):
        formatter = self._get_formatter()
        formatter.add_usage(self.usage, self._actions,
                            self._mutually_exclusive_groups)
        return formatter.format_help()

#### `` exit

    exit(status=0, message=None)

Source code in `python3.14/argparse.py`

    2781
    2782
    2783
    2784

    def exit(self, status=0, message=None):
        if message:
            self._print_message(message, _sys.stderr)
        _sys.exit(status)

### `` build_range_error

    build_range_error(range_min, range_max)

Build an error message when the number of arguments provided is not within the expected range.

Source code in `cmd2/argparse_utils.py`

    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364

    def build_range_error(range_min: int, range_max: float) -> str:
        """Build an error message when the number of arguments provided is not within the expected range."""
        err_msg = "expected "

        if range_max == constants.INFINITY:
            plural = "" if range_min == 1 else "s"
            err_msg += f"at least {range_min}"
        else:
            plural = "" if range_max == 1 else "s"
            if range_min == range_max:
                err_msg += f"{range_min}"
            else:
                err_msg += f"{range_min} to {range_max}"

        err_msg += f" argument{plural}"

        return err_msg

### `` register_argparse_argument_parameter

    register_argparse_argument_parameter(param_name, *, validator=None)

Register a custom parameter for argparse.Action and add accessors to the Action class.

PARAMETER | DESCRIPTION
--- | ---
`param_name` | Name of the parameter. This must be a valid Python identifier. TYPE: `str`
`validator` | Optional function to validate and/or transform the parameter value. It accepts the Action instance and the value as arguments. TYPE: `Callable[[Action, Any], Any] | None` DEFAULT: `None`

RAISES | DESCRIPTION
--- | ---
`ValueError` | if the parameter name is invalid
`KeyError` | if the new parameter collides with any existing attributes

Source code in `cmd2/argparse_utils.py`

    376
    377
    378
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    391
    392
    393
    394
    395
    396
    397
    398
    399
    400
    401
    402
    403
    404
    405
    406
    407
    408
    409
    410
    411
    412
    413
    414
    415
    416
    417
    418
    419
    420
    421
    422
    423
    424
    425

    def register_argparse_argument_parameter(
        param_name: str,
        *,
        validator: Callable[[argparse.Action, Any], Any] | None = None,
    ) -> None:
        """Register a custom parameter for argparse.Action and add accessors to the Action class.

        :param param_name: Name of the parameter. This must be a valid Python identifier.
        :param validator: Optional function to validate and/or transform the parameter value.
                          It accepts the Action instance and the value as arguments.
        :raises ValueError: if the parameter name is invalid
        :raises KeyError: if the new parameter collides with any existing attributes
        """
        if not param_name.isidentifier():
            raise ValueError(f"Invalid parameter name '{param_name}': must be a valid Python identifier")

        if param_name in _CUSTOM_ACTION_ATTRIBS:
            raise KeyError(f"Custom parameter '{param_name}' is already registered")

        # Ensure we don't hijack standard argparse.Action attributes or existing methods
        if hasattr(argparse.Action, param_name):
            raise KeyError(f"'{param_name}' conflicts with an existing attribute on argparse.Action")

        # Check if accessors already exist (e.g., from manual patching or previous registration)
        getter_name = f"get_{param_name}"
        setter_name = f"set_{param_name}"
        if hasattr(argparse.Action, getter_name) or hasattr(argparse.Action, setter_name):
            raise KeyError(f"Accessor methods for '{param_name}' already exist on argparse.Action")

        # Check for the prefixed internal attribute name collision (e.g., _cmd2_<param_name>)
        attr_name = constants.cmd2_private_attr_name(param_name)
        if hasattr(argparse.Action, attr_name):
            raise KeyError(f"The internal attribute '{attr_name}' already exists on argparse.Action")

        def _action_get_custom_parameter(self: argparse.Action) -> Any:
            """Get the custom attribute of an argparse Action."""
            return getattr(self, attr_name, None)

        setattr(argparse.Action, getter_name, _action_get_custom_parameter)

        def _action_set_custom_parameter(self: argparse.Action, value: Any) -> None:
            """Set the custom attribute of an argparse Action."""
            if validator is not None:
                value = validator(self, value)

            setattr(self, attr_name, value)

        setattr(argparse.Action, setter_name, _action_set_custom_parameter)

        _CUSTOM_ACTION_ATTRIBS.add(param_name)

### `` set_default_argument_parser

    set_default_argument_parser(parser_class)

Set the default Cmd2ArgumentParser class for cmd2's built-in commands.

Since built-in commands rely on customizations made in Cmd2ArgumentParser, your custom parser class should inherit from Cmd2ArgumentParser.

This should be called prior to instantiating your CLI object.

See examples/custom_parser.py.

Source code in `cmd2/argparse_utils.py`

    1098
    1099
    1100
    1101
    1102
    1103
    1104
    1105
    1106
    1107
    1108
    1109

    def set_default_argument_parser(parser_class: type[Cmd2ArgumentParser]) -> None:
        """Set the default Cmd2ArgumentParser class for cmd2's built-in commands.

        Since built-in commands rely on customizations made in Cmd2ArgumentParser,
        your custom parser class should inherit from Cmd2ArgumentParser.

        This should be called prior to instantiating your CLI object.

        See examples/custom_parser.py.
        """
        global DEFAULT_ARGUMENT_PARSER  # noqa: PLW0603
        DEFAULT_ARGUMENT_PARSER = parser_class