diff -r 44f455e6163d Lib/argparse.py --- a/Lib/argparse.py Thu Jun 27 12:23:29 2013 +0200 +++ b/Lib/argparse.py Sat Apr 26 23:06:24 2014 -0700 @@ -530,9 +530,12 @@ 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 - + metavar = self._metavar_formatter(action, default)(1) + if len(metavar)==1: + return metavar[0] # usual case + else: + # positional with tuple metavar needs special handling + return _format_metavars(action, self) else: parts = [] @@ -687,13 +690,30 @@ # Options and Arguments # ===================== +def _format_metavars(action, formatter=None): + # issue14074 - for positional, turn a tuple metavar into a + # string that can be used for both help and error messages + # some alternative versions + return '|'.join(action.metavar) + #return str(action.metavar) + #return action.metavar[0] + if formatter is None: + # if called from a module function + formatter = HelpFormatter('') + # use format used in the usage line + metastr = formatter._format_args(action, action.dest) + return metastr + def _get_action_name(argument): if argument is None: return None elif argument.option_strings: return '/'.join(argument.option_strings) elif argument.metavar not in (None, SUPPRESS): - return argument.metavar + metavar = argument.metavar + if isinstance(metavar, tuple): + metavar = _format_metavars(argument) + return metavar elif argument.dest not in (None, SUPPRESS): return argument.dest else: diff -r 44f455e6163d Lib/test/test_argparse.py --- a/Lib/test/test_argparse.py Thu Jun 27 12:23:29 2013 +0200 +++ b/Lib/test/test_argparse.py Sat Apr 26 23:06:24 2014 -0700 @@ -3859,6 +3859,32 @@ ''' version = '' +class TestHelpPositionalTupleMetavar(HelpTestCase): + """Test specifying metavar of a positional as a tuple""" + + parser_signature = Sig(prog='PROG') + argument_signatures = [ + Sig('w', help='w', nargs='+', metavar=('W1', 'W2')), + Sig('x', help='x', nargs='*', metavar=('X1', 'X2')), + Sig('y', help='y', nargs=3, metavar=('Y1', 'Y2', 'Y3')), + Sig('z', help='z', nargs='?', metavar=('Z1', )), + ] + argument_group_signatures = [] + usage = '''\ + usage: PROG [-h] W1 [W2 ...] [X1 [X2 ...]] Y1 Y2 Y3 [Z1] + ''' + help = usage + '''\ + + positional arguments: + W1|W2 w + X1|X2 x + Y1|Y2|Y3 y + Z1 z + + optional arguments: + -h, --help show this help message and exit + ''' + version = '' class TestHelpRawText(HelpTestCase): """Test the RawTextHelpFormatter""" @@ -4606,6 +4632,22 @@ self.assertRegex(msg, 'req_pos') self.assertNotIn(msg, 'optional_positional') + def test_positional_metavars(self): + """issue 14074 - + bad error message: TypeError: sequence item 0: expected str instance, tuple found + that is, it could not convert a tuple metavar as a string + test that proper 'arguments are required' error + """ + parser = ErrorRaisingArgumentParser(prog='PROG', usage='') + parser.add_argument('pos', nargs=2, metavar=('A', 'B')) + parser.add_argument('star', nargs='*', metavar=('A', 'B')) + with self.assertRaises(ArgumentParserError) as cm: + parser.parse_args([]) + msg = str(cm.exception) + self.assertRegex(msg, 'the following arguments are required') + #self.assertRegex(msg, 'A B') + #self.assertRegex(msg, r'\[A \[B \.\.\.\]\]') + self.assertRegex(msg, r'A\|B') # ================================================ # Check that the type function is called only once