mirror of
https://github.com/python/cpython.git
synced 2025-11-01 06:01:29 +00:00
gh-65865: Raise early errors for invalid help strings in argparse (GH-124899)
This commit is contained in:
parent
4a943c3251
commit
eb2d268ac7
3 changed files with 58 additions and 7 deletions
|
|
@ -588,17 +588,20 @@ def _format_args(self, action, default_metavar):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _expand_help(self, action):
|
def _expand_help(self, action):
|
||||||
|
help_string = self._get_help_string(action)
|
||||||
|
if '%' not in help_string:
|
||||||
|
return help_string
|
||||||
params = dict(vars(action), prog=self._prog)
|
params = dict(vars(action), prog=self._prog)
|
||||||
for name in list(params):
|
for name in list(params):
|
||||||
if params[name] is SUPPRESS:
|
value = params[name]
|
||||||
|
if value is SUPPRESS:
|
||||||
del params[name]
|
del params[name]
|
||||||
for name in list(params):
|
elif hasattr(value, '__name__'):
|
||||||
if hasattr(params[name], '__name__'):
|
params[name] = value.__name__
|
||||||
params[name] = params[name].__name__
|
|
||||||
if params.get('choices') is not None:
|
if params.get('choices') is not None:
|
||||||
choices_str = ', '.join([str(c) for c in params['choices']])
|
choices_str = ', '.join([str(c) for c in params['choices']])
|
||||||
params['choices'] = choices_str
|
params['choices'] = choices_str
|
||||||
return self._get_help_string(action) % params
|
return help_string % params
|
||||||
|
|
||||||
def _iter_indented_subactions(self, action):
|
def _iter_indented_subactions(self, action):
|
||||||
try:
|
try:
|
||||||
|
|
@ -1180,9 +1183,13 @@ def add_parser(self, name, *, deprecated=False, **kwargs):
|
||||||
help = kwargs.pop('help')
|
help = kwargs.pop('help')
|
||||||
choice_action = self._ChoicesPseudoAction(name, aliases, help)
|
choice_action = self._ChoicesPseudoAction(name, aliases, help)
|
||||||
self._choices_actions.append(choice_action)
|
self._choices_actions.append(choice_action)
|
||||||
|
else:
|
||||||
|
choice_action = None
|
||||||
|
|
||||||
# create the parser and add it to the map
|
# create the parser and add it to the map
|
||||||
parser = self._parser_class(**kwargs)
|
parser = self._parser_class(**kwargs)
|
||||||
|
if choice_action is not None:
|
||||||
|
parser._check_help(choice_action)
|
||||||
self._name_parser_map[name] = parser
|
self._name_parser_map[name] = parser
|
||||||
|
|
||||||
# make parser available under aliases also
|
# make parser available under aliases also
|
||||||
|
|
@ -1449,11 +1456,12 @@ def add_argument(self, *args, **kwargs):
|
||||||
|
|
||||||
# raise an error if the metavar does not match the type
|
# raise an error if the metavar does not match the type
|
||||||
if hasattr(self, "_get_formatter"):
|
if hasattr(self, "_get_formatter"):
|
||||||
|
formatter = self._get_formatter()
|
||||||
try:
|
try:
|
||||||
self._get_formatter()._format_args(action, None)
|
formatter._format_args(action, None)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
raise ValueError("length of metavar tuple does not match nargs")
|
raise ValueError("length of metavar tuple does not match nargs")
|
||||||
|
self._check_help(action)
|
||||||
return self._add_action(action)
|
return self._add_action(action)
|
||||||
|
|
||||||
def add_argument_group(self, *args, **kwargs):
|
def add_argument_group(self, *args, **kwargs):
|
||||||
|
|
@ -1635,6 +1643,14 @@ def _handle_conflict_resolve(self, action, conflicting_actions):
|
||||||
if not action.option_strings:
|
if not action.option_strings:
|
||||||
action.container._remove_action(action)
|
action.container._remove_action(action)
|
||||||
|
|
||||||
|
def _check_help(self, action):
|
||||||
|
if action.help and hasattr(self, "_get_formatter"):
|
||||||
|
formatter = self._get_formatter()
|
||||||
|
try:
|
||||||
|
formatter._expand_help(action)
|
||||||
|
except (ValueError, TypeError, KeyError) as exc:
|
||||||
|
raise ValueError('badly formed help string') from exc
|
||||||
|
|
||||||
|
|
||||||
class _ArgumentGroup(_ActionsContainer):
|
class _ArgumentGroup(_ActionsContainer):
|
||||||
|
|
||||||
|
|
@ -1852,6 +1868,7 @@ def add_subparsers(self, **kwargs):
|
||||||
# create the parsers action and add it to the positionals list
|
# create the parsers action and add it to the positionals list
|
||||||
parsers_class = self._pop_action_class(kwargs, 'parsers')
|
parsers_class = self._pop_action_class(kwargs, 'parsers')
|
||||||
action = parsers_class(option_strings=[], **kwargs)
|
action = parsers_class(option_strings=[], **kwargs)
|
||||||
|
self._check_help(action)
|
||||||
self._subparsers._add_action(action)
|
self._subparsers._add_action(action)
|
||||||
|
|
||||||
# return the created parsers action
|
# return the created parsers action
|
||||||
|
|
|
||||||
|
|
@ -2623,6 +2623,29 @@ def test_parser_command_help(self):
|
||||||
--foo foo help
|
--foo foo help
|
||||||
'''))
|
'''))
|
||||||
|
|
||||||
|
def assert_bad_help(self, context_type, func, *args, **kwargs):
|
||||||
|
with self.assertRaisesRegex(ValueError, 'badly formed help string') as cm:
|
||||||
|
func(*args, **kwargs)
|
||||||
|
self.assertIsInstance(cm.exception.__context__, context_type)
|
||||||
|
|
||||||
|
def test_invalid_subparsers_help(self):
|
||||||
|
parser = ErrorRaisingArgumentParser(prog='PROG')
|
||||||
|
self.assert_bad_help(ValueError, parser.add_subparsers, help='%Y-%m-%d')
|
||||||
|
parser = ErrorRaisingArgumentParser(prog='PROG')
|
||||||
|
self.assert_bad_help(KeyError, parser.add_subparsers, help='%(spam)s')
|
||||||
|
parser = ErrorRaisingArgumentParser(prog='PROG')
|
||||||
|
self.assert_bad_help(TypeError, parser.add_subparsers, help='%(prog)d')
|
||||||
|
|
||||||
|
def test_invalid_subparser_help(self):
|
||||||
|
parser = ErrorRaisingArgumentParser(prog='PROG')
|
||||||
|
subparsers = parser.add_subparsers()
|
||||||
|
self.assert_bad_help(ValueError, subparsers.add_parser, '1',
|
||||||
|
help='%Y-%m-%d')
|
||||||
|
self.assert_bad_help(KeyError, subparsers.add_parser, '1',
|
||||||
|
help='%(spam)s')
|
||||||
|
self.assert_bad_help(TypeError, subparsers.add_parser, '1',
|
||||||
|
help='%(prog)d')
|
||||||
|
|
||||||
def test_subparser_title_help(self):
|
def test_subparser_title_help(self):
|
||||||
parser = ErrorRaisingArgumentParser(prog='PROG',
|
parser = ErrorRaisingArgumentParser(prog='PROG',
|
||||||
description='main description')
|
description='main description')
|
||||||
|
|
@ -5375,6 +5398,14 @@ def test_invalid_action(self):
|
||||||
self.assertValueError('--foo', action="store-true",
|
self.assertValueError('--foo', action="store-true",
|
||||||
errmsg='unknown action')
|
errmsg='unknown action')
|
||||||
|
|
||||||
|
def test_invalid_help(self):
|
||||||
|
self.assertValueError('--foo', help='%Y-%m-%d',
|
||||||
|
errmsg='badly formed help string')
|
||||||
|
self.assertValueError('--foo', help='%(spam)s',
|
||||||
|
errmsg='badly formed help string')
|
||||||
|
self.assertValueError('--foo', help='%(prog)d',
|
||||||
|
errmsg='badly formed help string')
|
||||||
|
|
||||||
def test_multiple_dest(self):
|
def test_multiple_dest(self):
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument(dest='foo')
|
parser.add_argument(dest='foo')
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
:mod:`argparse` now raises early error for invalid ``help`` arguments to
|
||||||
|
:meth:`~argparse.ArgumentParser.add_argument`,
|
||||||
|
:meth:`~argparse.ArgumentParser.add_subparsers` and :meth:`!add_parser`.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue