Search code examples
pythonformattingposixargparse

Python argparse proper formatting of choice arguments with flag


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?


Solution

  • 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)