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
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
@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
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
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
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
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
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
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
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
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
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
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
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
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
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
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_formatter"):
        formatter = self._get_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
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
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
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:
        formatter = self._get_formatter()
        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
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
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
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
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
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
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
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
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
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
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