0

I'm using python argparse to manage commandline options. When the number of options becomes large, the help message (what argparse prints when you pass --help) is getting intimidating and hard to read, simply because it is to long. Other programs sometimes solve this issue by modularizing the help message: they only show "core options" with --help and have options like --help-modulea, --help-moduleb, etc.

I think that argparse allows for something similar in the presence of subcommands, essentially like git does: ./script.py command --help. My application however has no such thing as subcommands, only a big set of options (that can be grouped nicely, though).

Is there any (reasonable) way to do this with argparse?

3
  • You may just be looking for argument groups, though they don't help with suppressing some of the output. For that, you'll need to define your own option handlers. Commented Dec 3, 2020 at 21:27
  • I'm already using argument groups to group the arguments. (obviously) I'm not sure what you mean by "option handlers", though... Commented Dec 4, 2020 at 6:29
  • Look at the code for print_help and format_help. You'll see how the HelpFormatter is called, and how the groups are passed to it. Also look at the _help_action class (or some such name). I can imagine creating similar Action subclasses and/or formatters that display a subset of your argument_groups. Commented Dec 4, 2020 at 21:56

1 Answer 1

1

Thanks to @hpaulj answer, I had a proper look at the argparse internals and came up with the following. While it has some caveats, I feel it's a good start:

class ModularHelpEnabler(argparse.Action):
    def __call__(self, parser, namespace, values, option_string = None):
        parser.enable_modular_help(self.const)

class ModularArgumentParser(argparse.ArgumentParser):
    def __init__(self, *args, **kwargs):
        self._modular_action_groups = {}
        modular_help_groups = kwargs.pop('modular_help_groups', [])
        super().__init__(*args, **kwargs)
        self._modular_help_groups = {}
        for name in modular_help_groups:
            self._modular_help_groups[name] = self.add_argument_group(name)

    def add_argument_group(self, *args, **kwargs):
        name = kwargs.pop('help_name', None)
        help_group = kwargs.pop('help_group', None)
        help_text = kwargs.pop('help_group', 'show help for {}')
        grp = super().add_argument_group(*args, **kwargs)
        if name is not None:
            self._modular_action_groups[grp] = name
            parser = self
            if help_group is not None:
                parser = self._modular_help_groups[help_group]
            parser.add_argument('--help-{}'.format(name), action=ModularHelpEnabler, nargs=0, const=grp, help = help_text.format(name))
        return grp
    def enable_modular_help(self, grp):
        del self._modular_action_groups[grp]
    def format_help(self):
        self._action_groups = [
            ag for ag in self._action_groups
            if ag not in self._modular_action_groups
        ]
        return super().format_help()

Caveats (that are not trivial to fix, AFAIK):

  • --help-foo does not imply --help.
  • --help-foo needs to be given before --help.

Unfortunately, --help directly triggers printing the help and exits. If we retain this behavior, everything that modifies the help message needs to be specified before --help itself. We could defer printing the help until after parsing has finished, however this would mean that --help no longer shadows parsing errors (as it does right now), changing the current behavior significantly.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.