1

I'm trying to achieve a program that makes both use of sub-commands (e.g.: program sub-command [options]) and groups (which makes for a fancy help dialog).

I have achieved this goal with one minor exception: In order to get the help dialog in its own group I must add the flag add_help=False when creating the sub-command parser, which removes the help message when running the top-level help dialog (e.g.: program -h).

Here's the code I've developed:

# imports
import argparse

# create the top-level parser
parser = argparse.ArgumentParser(prog="example", add_help=False, epilog="A very cool program")

# add top-level groups
toplevel = parser.add_argument_group("Global arguments")
toplevel.add_argument("-g", "--global", action="store_true", help="A global argument.")

help = parser.add_argument_group("Help dialog")
help.add_argument("-h", "--help", action="help", default=argparse.SUPPRESS, help="Show this help message and exit.")

# create subparser
subparsers = parser.add_subparsers(title="Available subcommands", dest="subcommand")

# create the parser for the "a" subcommand
parser_a = subparsers.add_parser("a", add_help=False)

# add groups for subcommand "a"
required_a = parser_a.add_argument_group("Required arguments")
required_a.add_argument("--bar", type=int, help="Flag bar help", required=True)

help_a = parser_a.add_argument_group("Help dialog")
help_a.add_argument("-h", "--help", action="help", default=argparse.SUPPRESS, help="Show this help message and exit.")

# create the parser for the "b" command
parser_b = subparsers.add_parser("b", add_help=False)

# add groups for subcommand "b"
required_b = parser_b.add_argument_group("Required arguments")
required_b.add_argument("--baz", help="Flag baz help", required=True)

optional_b = parser_b.add_argument_group("Optional arguments")
optional_b.add_argument("--tas", help="Flag tas help")

help_b = parser_b.add_argument_group("Help dialog")
help_b.add_argument("-h", "--help", action="help", default=argparse.SUPPRESS, help="Show this help message and exit.")

# parse arguments
args = parser.parse_args()

# provide args to main
print(args)

The top-level help is as follows:

$ example -h
usage: example [-g] [-h] {a,b} ...

Global arguments:
  -g, --global  A global argument.

Help dialog:
  -h, --help    Show this help message and exit.

Available sub-commands:
  {a,b}

A very cool program

Which as you can see doesn't show the help message on the sub-commands. To have them show up I would have to get rid of add_help=False when creating parser_a and parser_b but then, as expected, it would raise an issue when I would define my own help flag.

Essentially I would like to have the best of both worlds, where my main dialog would be:

$ example -h
usage: example [-g] [-h] {a,b} ...

Global arguments:
  -g, --global  A global argument.

Help dialog:
  -h, --help    Show this help message and exit.

Available sub-commands:
  {a,b}
    a           Help for sub-command a.
    b           Help for sub-command b.

A very cool program

And the sub-commands would be:

$ example a -h
usage: example a --bar BAR [-h]

Required arguments:
  --bar BAR   Flag bar help

Help dialog:
  -h, --help  Show this help message and exit.

After going through the argparse source code, would the option conflict_handler be a possible solution? Would it be possible to tell it to ignore the original help dialog, which shows under positional arguments which I do not want, and have it instead showing in my own group but without disabling it completely?

TL;DR: Looking for modifications required to my Python script such that argparse outputs the two previous code blocks.

Note: The reason why "help-dialog" is written in the title is because stack overflow does not allow me to write the world "help" on the title, regardless of where it is written in the sentence.

  • With the normal subparsers setup, `example -h` will show the main parser's groups and the sub-commands (`subparsers` is actually a positional argument of the main parser). `example a -h`, passes parsing to `parser_a`, and it in turn responds to the `-h`, showing its own groups. The main parser knows nothing about the subparser's arguments, and same for the subparser. – hpaulj Oct 19 '21 at 16:41
  • I imagine that your 'help-dialog' substitutes behave the same way, but I can't say for sure. It isn't clear what you are missing or expect. – hpaulj Oct 19 '21 at 16:42
  • I've edited the post to make myself more clear, because the character limit in the comments is quite low. What I'm expecting is to have a help message below the sub-command section in the main help dialog and, in the sub-command help dialog, have the help command inside its own group. This is quite hard to explain for some reason, but if you see the last 2 code blocks that's exactly what I'm trying to achieve. – José Ferreira Oct 19 '21 at 17:33

1 Answers1

0

Your code produces:

1138:~/mypy$ python3 stack69633930.py -h
usage: example [-g] [-h] {a,b} ...

Global arguments:
  -g, --global  A global argument.

Help dialog:
  -h, --help    Show this help message and exit.

Available subcommands:
  {a,b}

A very cool program
1140:~/mypy$ python3 stack69633930.py a -h
usage: example a --bar BAR [-h]

Required arguments:
  --bar BAR   Flag bar help

Help dialog:
  -h, --help  Show this help message and exit.

With the conventional help

1140:~/mypy$ python3 stack69633930-1.py -h
usage: example [-h] [-g] {a,b} ...

optional arguments:
  -h, --help    show this help message and exit

Global arguments:
  -g, --global  A global argument.

Available subcommands:
  {a,b}

A very cool program
1142:~/mypy$ python3 stack69633930-1.py a -h
usage: example a [-h] --bar BAR

optional arguments:
  -h, --help  show this help message and exit

Required arguments:
  --bar BAR   Flag bar help

The only differences I see are "Help dialog" group instead of "optional arguments", and the place of the [-h] in the usage.

To get 'help' for the subcommands:

1155:~/mypy$ python3 stack69633930-1.py -h
usage: example [-h] [-g] {a,b} ...

optional arguments:
  -h, --help    show this help message and exit

Global arguments:
  -g, --global  A global argument.

Available subcommands:
  {a,b}
    a           help for subcommand a
    b           help for subcommand b

A very cool program

add

parser_a = subparsers.add_parser("a", help='help for subcommand a')

That works with your help-group as well.

===

The add_parser method handles the "help" keyword in a special way. "add_help" is passed on the ArgumentParser creation function. They have to very different functions.

def add_parser(self, name, **kwargs):
    # set prog from the existing prefix
    if kwargs.get('prog') is None:
        kwargs['prog'] = '%s %s' % (self._prog_prefix, name)

    aliases = kwargs.pop('aliases', ())

    # create a pseudo-action to hold the choice help
    if 'help' in kwargs:
        help = kwargs.pop('help')
        choice_action = self._ChoicesPseudoAction(name, aliases, help)
        self._choices_actions.append(choice_action)

    # create the parser and add it to the map
    parser = self._parser_class(**kwargs)
    self._name_parser_map[name] = parser

    # make parser available under aliases also
    for alias in aliases:
        self._name_parser_map[alias] = parser

    return parser
hpaulj
  • 221,503
  • 14
  • 230
  • 353
  • The difference is that you forgot to add the `help="something"` instead of just removing the `add_help=False` from `parser_a = subparsers.add_parser("a", add_help=False)`. If you add a `help="something"` dialog and remove the `add_help=False` you will see that when using the global `-h` you will get that same help message, in this case "something", in the "Available subcommands:" section. Perhaps my request was not as specific as I thought. – José Ferreira Oct 19 '21 at 18:55
  • `add_help=False` is a parameter for the subparser itself. The `help='something' is used by the `subparsers` Action. – hpaulj Oct 19 '21 at 19:00
  • Yes, but it also shows on the top level help dialog, under the "available subcommands" if provided, that's my whole point: having it show on the "available subcommands" on top level help dialog and on its own group when inside the sub-command help dialog. – José Ferreira Oct 19 '21 at 19:14
  • I don't see that "something" in your sub-command hlep dialog. The " a help for subcommand a" line has nothing to do help displayed by the `subpareser_a` – hpaulj Oct 19 '21 at 19:28
  • I've used an online compiler to exemplify what I mean, go to [https://trinket.io/python3/d21a721232](https://trinket.io/python3/d21a721232) and when you run the script you will see that there is a help dialog on the right of the sub-commands, in the "Available sub-commands:" section. Now try and adding the help dialog in the sub-commands to its own group by commenting out lines 24, 25, 37 and 38. If you run the script again it will give you an error. This is quite hard to explain by words so maybe this makes what I'm trying to achieve more clear. – José Ferreira Oct 19 '21 at 21:18
  • With your help_dialog you do need the `add_help=False` parameter. But that has nothing to do with the `Available sub-commands` section. – hpaulj Oct 19 '21 at 23:35
  • It has, because adding `add_help=False` makes the help dialog on the `Available sub-commands` section not show up, and I would like for it to show up. If you try to use that online compiler and comment out the lines I've indicated you'll be able to see the error. – José Ferreira Oct 20 '21 at 14:24