Pavel Chernikov
Pavel Chernikov

Reputation: 2266

Grouping argparse subparser arguments

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

Answers (2)

mxrch
mxrch

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

hpaulj
hpaulj

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

Related Questions