volitank
volitank

Reputation: 27

Argparse subparser help displayed before options

With argparse, currently my main.py --help looks like this

usage: main.py [--options] <command>

options:
  -h, --help  show this help message and exit
  --debug     Logs extra information for debugging
  --version   show program's version number and exit
  --license   reads the GPLv3

commands:

    install   install packages
    remove    remove packages
    update    update package list and upgrade the system

is there an easy way to make the subparser commands display before the global options?

my configuration is a bit long to post at the moment but I can if context is needed. I don't have anything crazy going on, this is the important stuff.

formatter = lambda prog: argparse.RawDescriptionHelpFormatter(prog,
                                                max_help_position=64)
bin_name = Path(argv[0]).name
version = __version__
parser = nalaParser(    formatter_class=formatter,
                        usage=f'{bin_name} [--options] <command>', 
                        )

subparsers = parser.add_subparsers(title='commands', metavar='', dest='command')
parser._optionals.title = "options"

install_parser = subparsers.add_parser('install', help='install packages')
remove_parser = subparsers.add_parser('remove', help='remove packages')
update_parser = subparsers.add_parser('update', help='update package list and upgrade the system')

parser.add_argument('--debug', action='store_true', help='Logs extra information for debugging')
parser.add_argument('--version', action='version', version=f'{bin_name} {version}')
parser.add_argument('--license', action=GPLv3)

Since I was already subclassing I just added this method below to nalaParser

def format_help(self):
    formatter = self._get_formatter()

    # usage
    formatter.add_usage(self.usage, self._actions,
                        self._mutually_exclusive_groups)

    # description
    formatter.add_text(self.description)

    index = -1
    # Put options last in the group
    for action_group in self._action_groups[:]:
        index = + 1
        if action_group.title == 'options':
            self._action_groups.append(self._action_groups.pop(index))

    # positionals, optionals and user-defined groups
    for action_group in self._action_groups:
        formatter.start_section(action_group.title)
        formatter.add_text(action_group.description)
        formatter.add_arguments(action_group._group_actions)
        formatter.end_section()


    # epilog
    formatter.add_text(self.epilog)

    # determine help from format above
    return formatter.format_help()

Upvotes: 0

Views: 387

Answers (2)

David Gard
David Gard

Reputation: 12047

You could override the format_help method of argparse.ArgumentParser and rearrange the order of the action groups before calling super().

class MyParser(ArgumentParser):

    def format_help(self):

        # Put options last in the group
        opitions_action_group = [a for a in self._action_groups if a.title == 'options']
        self._action_groups = [a for a in self._action_groups if a.title != 'options']
        if opitions_action_group:
            self._action_groups.append(opitions_action_group[0])

        return super().format_help()


# Parent parser (so 'help' is available for all sub-parsers).
parent_parser = MyParser()

# Main parser.
desc = ('Take some action as described below.')
main_parser = MyParser(description=desc, add_help=False, formatter_class=HelpFormatter, parents=[parent_parser])

# Sub-parsers.
subparsers = main_parser.add_subparsers(title='action')
subparsers.add_parser('action1', add_help=False, parents=[parent_parser], description='Description of action1.')
subparsers.add_parser('action2', add_help=False, parents=[parent_parser], description='Description of action2.')
subparsers.add_parser('action3', add_help=False, parents=[parent_parser], description='Description of action3.')

main_parser.parse_args()

Parsing the above with -h results in the following...

usage: cs-aws [-h] {action1,action2,action3} ...

Take some action as described below.

action:
  {action1,action2,action3}

options:
  -h, --help            show this help message and exit

Upvotes: 0

hpaulj
hpaulj

Reputation: 231355

parser.format_help passes the action_groups to the formatter in the order that they were created.

In [24]: parser._action_groups
Out[24]: 
[<argparse._ArgumentGroup at 0x7f8167ac02e0>,
 <argparse._ArgumentGroup at 0x7f8167ac0850>,
 <argparse._ArgumentGroup at 0x7f8167ac0490>]
In [25]: [a.title for a in parser._action_groups]
Out[25]: ['positional arguments', 'options', 'commands']

The default groups are positional and options. add_subparsers puts its Action in the positional group, except when given a title. In that case it makes a new group.

You could tweak the format_help to reorder the groups that it passes to the formatter.

Or it might work to change the 'positional arguments' title as you did with the optionals (instead of giving subparsers the title parameter).

Anyways, the key to the behavior is in these two methods.

Upvotes: 1

Related Questions