classification
Title: argparse explodes with nargs='*' and a tuple metavar
Type: Stage:
Components: Library (Lib) Versions: Python 3.4, Python 2.7
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: paul.j3, vvas
Priority: normal Keywords:

Created on 2014-05-30 22:01 by vvas, last changed 2014-06-09 00:05 by vvas.

Messages (3)
msg219429 - (view) Author: Vasilis Vasaitis (vvas) Date: 2014-05-30 22:01
The title says it all really, but to demostrate: Let's say I'm building a program which takes another command as its argument(s) on the command line, to execute it in a special way or whatever. The natural way to do this then would be something like the following:

    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('cmd', nargs='*')
    parser.parse_args()

Which gives the following output with -h:

    usage: nargs.py [-h] [cmd [cmd ...]]

    positional arguments:
      cmd

    optional arguments:
      -h, --help  show this help message and exit

Now, let's say I want to have 'command' printed in the help output, but still write the shorter 'cmd' in the code. Naturally I would make use of the metavar feature:

    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('cmd', nargs='*', metavar='command')
    parser.parse_args()

So now the help output is:

    usage: nargs.py [-h] [command [command ...]]

    positional arguments:
      command

    optional arguments:
      -h, --help  show this help message and exit

That's better, but I don't really want it to say 'command' twice there, it's more like a command and then its arguments. So what about a tuple instead?

    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('cmd', nargs='*', metavar=('command', 'arguments'))
    parser.parse_args()

So let's see what happens now with -h:

    Traceback (most recent call last):
      File "/Users/Vasilis/Sources/Tests/nargs.py", line 6, in <module>
        parser.parse_args()
      File "/usr/local/Cellar/python3/3.4.0/Frameworks/Python.framework/Versions/3.4/lib/python3.4/argparse.py", line 1717, in parse_args
        args, argv = self.parse_known_args(args, namespace)
      File "/usr/local/Cellar/python3/3.4.0/Frameworks/Python.framework/Versions/3.4/lib/python3.4/argparse.py", line 1749, in parse_known_args
        namespace, args = self._parse_known_args(args, namespace)
      File "/usr/local/Cellar/python3/3.4.0/Frameworks/Python.framework/Versions/3.4/lib/python3.4/argparse.py", line 1955, in _parse_known_args
        start_index = consume_optional(start_index)
      File "/usr/local/Cellar/python3/3.4.0/Frameworks/Python.framework/Versions/3.4/lib/python3.4/argparse.py", line 1895, in consume_optional
        take_action(action, args, option_string)
      File "/usr/local/Cellar/python3/3.4.0/Frameworks/Python.framework/Versions/3.4/lib/python3.4/argparse.py", line 1823, in take_action
        action(self, namespace, argument_values, option_string)
      File "/usr/local/Cellar/python3/3.4.0/Frameworks/Python.framework/Versions/3.4/lib/python3.4/argparse.py", line 1016, in __call__
        parser.print_help()
      File "/usr/local/Cellar/python3/3.4.0/Frameworks/Python.framework/Versions/3.4/lib/python3.4/argparse.py", line 2348, in print_help
        self._print_message(self.format_help(), file)
      File "/usr/local/Cellar/python3/3.4.0/Frameworks/Python.framework/Versions/3.4/lib/python3.4/argparse.py", line 2325, in format_help
        formatter.add_arguments(action_group._group_actions)
      File "/usr/local/Cellar/python3/3.4.0/Frameworks/Python.framework/Versions/3.4/lib/python3.4/argparse.py", line 272, in add_arguments
        self.add_argument(action)
      File "/usr/local/Cellar/python3/3.4.0/Frameworks/Python.framework/Versions/3.4/lib/python3.4/argparse.py", line 257, in add_argument
        invocations = [get_invocation(action)]
      File "/usr/local/Cellar/python3/3.4.0/Frameworks/Python.framework/Versions/3.4/lib/python3.4/argparse.py", line 535, in _format_action_invocation
        metavar, = self._metavar_formatter(action, default)(1)
    ValueError: too many values to unpack (expected 1)

Hm, that didn't go very well. Perhaps I can try with a single element in the tuple, as the exception seems to suggest:

    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('cmd', nargs='*', metavar=('command',))
    parser.parse_args()

Any better?

    Traceback (most recent call last):
      File "/usr/local/Cellar/python3/3.4.0/Frameworks/Python.framework/Versions/3.4/lib/python3.4/argparse.py", line 1334, in add_argument
        self._get_formatter()._format_args(action, None)
      File "/usr/local/Cellar/python3/3.4.0/Frameworks/Python.framework/Versions/3.4/lib/python3.4/argparse.py", line 579, in _format_args
        result = '[%s [%s ...]]' % get_metavar(2)
    TypeError: not enough arguments for format string

    During handling of the above exception, another exception occurred:

    Traceback (most recent call last):
      File "/Users/Vasilis/Sources/Tests/nargs.py", line 5, in <module>
        parser.add_argument('cmd', nargs='*', metavar=('command',))
      File "/usr/local/Cellar/python3/3.4.0/Frameworks/Python.framework/Versions/3.4/lib/python3.4/argparse.py", line 1336, in add_argument
        raise ValueError("length of metavar tuple does not match nargs")
    ValueError: length of metavar tuple does not match nargs

No, not really.

So there you have it. I think the two-element-tuple example should do the right thing (print "[command [arguments ...]]"). But if not, at least *some* kind of tuple should work without raising an exception.

Apart from 3.4, I see precisely the same behaviour with Python 2.7 too. I haven't tested with any other version I'm afraid.
msg219646 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2014-06-02 22:21
I think is issue was already raised in
http://bugs.python.org/issue14074
argparse allows nargs>1 for positional arguments but doesn't allow metavar to be a tuple
msg220072 - (view) Author: Vasilis Vasaitis (vvas) Date: 2014-06-09 00:05
Ah, I did come across that issue when I was searching for prior reports, but somehow it didn't register in my head as being the same thing. Does the fix for that address the behaviour I'm seeing too? If so, feel free to close as a duplicate. Additionally, if that fix is indeed relevant, is there an ETA for it?
History
Date User Action Args
2014-06-09 00:05:42vvassetmessages: + msg220072
2014-06-02 22:21:46paul.j3setnosy: + paul.j3
messages: + msg219646
2014-05-30 22:02:30vvassetversions: + Python 2.7
2014-05-30 22:01:40vvascreate