0

I'm trying to create well formatted help messages for 'choice' type command line arguments with Python's argparse. For the command I allow the name '--operation' and the alias '-o'. Currently, argparse is printing the list of options next to both in the help message.

Please note that this question is different to the question of formatting the help messages of the options (That problem has a good answer here by Anthon: Python argparse: How to insert newline in the help text?)

>>> import argparse
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('-o', '--operation', help="operation to perform", type=str, choices=["create", "update", "delete"])
_StoreAction(option_strings=['-o', '--operation'], dest='operation', nargs=None, const=None, default=None, type=<class 'str'>, choices=['create', 'update', 'delete'], help='operation to perform', metavar=None)
>>> parser.print_help()
usage: [-h] [-o {create,update,delete}]

optional arguments:
  -h, --help            show this help message and exit
  -o {create,update,delete}, --operation {create,update,delete}
                        operation to perform
>>> 

My problem is this line:

  -o {create,update,delete}, --operation {create,update,delete}

It's very clunky how the list of options is repeated twice. Especially since I will have lists that are even longer. It would be better I think to have this:

-o, --operation {create,update,delete}

This is assuming of course that there isn't some POSIX rule about how this has to work. I don't think there is.

How can I achieve the desired output? Or is there a good reason that I shouldn't be trying to?

Neil
  • 3,020
  • 4
  • 25
  • 48
  • 1
    You might want to check [`click`](https://click.palletsprojects.com/en/5.x/) or [`docopt`](https://github.com/docopt/docopt) packages – Thom Oct 15 '19 at 14:15
  • 1
    An alternative is to use `metavar` to replace the `choices` with a shorter string, and display the `choices` as part of the `help` expression. – hpaulj Oct 15 '19 at 15:48

1 Answers1

2

This is quite a hack, but there doesn't appear to be a good place to hook into this.

Define your own formatter, which overrides (by basically copying) the _format_action_invocation method. The only change you'll make is to add the choices only to the last option string.

class MyHelpFormatter(HelpFormatter):

    def _format_action_invocation(self, action):
        if not action.option_strings:
            default = self._get_default_metavar_for_positional(action)
            metavar, = self._metavar_formatter(action, default)(1)
            return metavar

        else:
            parts = []

            # if the Optional doesn't take a value, format is:
            #    -s, --long
            if action.nargs == 0:
                parts.extend(action.option_strings)

            # if the Optional takes a value, format is:
            #    -s ARGS, --long ARGS
            else:
                default = self._get_default_metavar_for_optional(action)
                args_string = self._format_args(action, default)
                for option_string in action.option_strings[:-1]:
                    parts.append('%s' % (option_string,))
                parts.append('%s %s' % (action.option_strings[-1], args_string)
            return ', '.join(parts)
chepner
  • 497,756
  • 71
  • 530
  • 681
  • You were close. Unfortunately your simplification causes 2 issues (1) action.nargs can be None, which is why they did ==0 not >0 (though that seems like bad code!) and (2) you're removed all of the options printing now not just the duplication. I'll upvote for the help, and this plays nicely with the other link in my question. But code has bugs as-is so I don't think I can accept. – Neil Oct 15 '19 at 14:42
  • I have got it working my side. Just did 'action.nargs is not None and action.nargs > 0' for the first issue and added 'parts[-1] + self._format_args(action, default)' to fix the second issue. – Neil Oct 15 '19 at 14:43
  • I'll update. Stupid me, it looks like I didn't post the longer, correct answer first, so I can't just rollback to it. – chepner Oct 15 '19 at 14:44