Reputation: 2266
I have a script that has multiple commands, with each command taking it's own set of required and/or optional arguments, by using add_subparser.
=->test.py -h
usage: test.py [-h] <command> ...
positional arguments:
<command> Available Commands
cmd1 Command 1
cmd2 Command 2
cmd3 Command 3
cmd4 Command 4
optional arguments:
-h, --help show this help message and exit
=->test.py cmd1 -h
usage: test.py cmd1 [-h] --flag1 FLAG1
optional arguments:
-h, --help show this help message and exit
--flag1 FLAG1 Test flag
=->test.py cmd2 -h
usage: test.py cmd2 [-h] [--flag2 FLAG2]
optional arguments:
-h, --help show this help message and exit
--flag2 FLAG2 Test flag
I'd like to somehow separate these commands into groups so that users see something like the following:
=->test.py -h
usage: test.py [-h] <command> ...
First Group:
cmd1 Command 1
cmd2 Command 2
Second Group:
cmd3 Command 3
cmd4 Command 4
optional arguments:
-h, --help show this help message and exit
But, doesn't look like add_argument_group and add_subparsers work together.
Any way to achieve this?
Upvotes: 8
Views: 6154
Reputation: 205
I had this problem, and resolved it by creating a group in the parent parser, and a function where it creates a dummy group for each subparser of a given subparsers, and it replaces the object of the dummy group by the wanted group, while keeping its address, so the group does belong to each subparser !
Here is an example with a little bit of context :
import argparse
import ctypes
parent_parser = argparse.ArgumentParser(description="Redacted")
subparsers = parent_parser.add_subparsers()
subparser_email = subparsers.add_parser("email", parents=[parent_parser], add_help=False)
subparser_gen = subparsers.add_parser("gen", parents=[parent_parser], add_help=False)
group_emails = subparser_email.add_argument_group("Main inputs")
group_gen = subparser_gen.add_argument_group("Emails generation")
group_matchers = parent_parser.add_argument_group("Matchers") # The group we want on each subparser
[...] # add the arguments to your groups here
def add_group_to_subparsers(group: argparse._ArgumentGroup, subparsers: argparse._SubParsersAction, name: str):
for name, subparser in subparsers._name_parser_map.items():
dummy_group = subparser.add_argument_group(name)
ctypes.memmove(id(dummy_group), id(group), object.__sizeof__(dummy_group))
add_group_to_subparsers(group_matchers, subparsers, "Matchers")
parent_parser.parse_args()
Upvotes: 0
Reputation: 231395
You are right, argument groups and subparsers don't work together. That's because subparsers (or rather their names) are not arguments.
The sp = parser.add_subparsers(...)
command creates an argument, or technically an instance of a subclass of argparse.Action
. This is a positional argument. The add_parser
command creates a parser
object (i.e. calls argparse.ArgumentParser
), and adds it, along with its name (and aliases) to a dictionary owned by this action. And the names populate the choices
attribute of the Action.
That subparsers
Action could belong to an argument group, but since there can only be one such action, it doesn't help you group the help lines.
You can control, to some extent, the help by using a description, and omitting the help for subparsers
import argparse
description = """
First Group:
cmd1 Command 1
cmd2 Command 2
Second Group:
cmd3 Command 3
cmd4 Command 4"""
parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter)
sp = parser.add_subparsers(title='commands',description=description)
sp.add_parser('cmd1')
sp.add_parser('cmd2')
sp.add_parser('cmd3')
sp.add_parser('cmd4')
parser.print_help()
produces
1343:~/mypy$ python stack32017020.py
usage: stack32017020.py [-h] {cmd1,cmd2,cmd3,cmd4} ...
optional arguments:
-h, --help show this help message and exit
commands:
First Group:
cmd1 Command 1
cmd2 Command 2
Second Group:
cmd3 Command 3
cmd4 Command 4
{cmd1,cmd2,cmd3,cmd4}
http://bugs.python.org/issue9341 - allow argparse subcommands to be grouped
talks about doing what you want. The patch that I proposed isn't trivial. But you are welcome to test it.
Upvotes: 10