formatter_class=argparse.HelpFormatter, \
prefix_chars='-', fromfile_prefix_chars=None, \
argument_default=None, conflict_handler='error', \
- add_help=True, allow_abbrev=True, exit_on_error=True)
+ add_help=True, allow_abbrev=True, exit_on_error=True, \
+ suggest_on_error=False)
Create a new :class:`ArgumentParser` object. All parameters should be passed
as keyword arguments. Each parameter has its own more detailed description
* exit_on_error_ - Determines whether or not ArgumentParser exits with
error info when an error occurs. (default: ``True``)
+ * suggest_on_error_ - Enables suggestions for mistyped argument choices
+ and subparser names (default: ``False``)
+
+
.. versionchanged:: 3.5
*allow_abbrev* parameter was added.
.. versionadded:: 3.9
+suggest_on_error
+^^^^^^^^^^^^^^^^
+
+By default, when a user passes an invalid argument choice or subparser name,
+:class:`ArgumentParser` will exit with error info and list the permissible
+argument choices (if specified) or subparser names as part of the error message.
+
+If the user would like to enable suggestions for mistyped argument choices and
+subparser names, the feature can be enabled by setting ``suggest_on_error`` to
+``True``. Note that this only applies for arguments when the choices specified
+are strings::
+
+ >>> parser = argparse.ArgumentParser(description='Process some integers.', suggest_on_error=True)
+ >>> parser.add_argument('--action', choices=['sum', 'max'])
+ >>> parser.add_argument('integers', metavar='N', type=int, nargs='+',
+ ... help='an integer for the accumulator')
+ >>> parser.parse_args(['--action', 'sumn', 1, 2, 3])
+ tester.py: error: argument --action: invalid choice: 'sumn', maybe you meant 'sum'? (choose from 'sum', 'max')
+
+.. versionadded:: 3.14
+
The add_argument() method
-------------------------
- allow_abbrev -- Allow long options to be abbreviated unambiguously
- exit_on_error -- Determines whether or not ArgumentParser exits with
error info when an error occurs
+ - suggest_on_error - Enables suggestions for mistyped argument choices
+ and subparser names. (default: ``False``)
"""
def __init__(self,
conflict_handler='error',
add_help=True,
allow_abbrev=True,
- exit_on_error=True):
+ exit_on_error=True,
+ suggest_on_error=False):
superinit = super(ArgumentParser, self).__init__
superinit(description=description,
self.add_help = add_help
self.allow_abbrev = allow_abbrev
self.exit_on_error = exit_on_error
+ self.suggest_on_error = suggest_on_error
add_group = self.add_argument_group
self._positionals = add_group(_('positional arguments'))
def _check_value(self, action, value):
# converted value must be one of the choices (if specified)
choices = action.choices
- if choices is not None:
- if isinstance(choices, str):
- choices = iter(choices)
- if value not in choices:
- args = {'value': str(value),
- 'choices': ', '.join(map(str, action.choices))}
- msg = _('invalid choice: %(value)r (choose from %(choices)s)')
- raise ArgumentError(action, msg % args)
+ if choices is None:
+ return
+
+ if isinstance(choices, str):
+ choices = iter(choices)
+
+ if value not in choices:
+ args = {'value': str(value),
+ 'choices': ', '.join(map(str, action.choices))}
+ msg = _('invalid choice: %(value)r (choose from %(choices)s)')
+
+ if self.suggest_on_error and isinstance(value, str):
+ if all(isinstance(choice, str) for choice in action.choices):
+ import difflib
+ suggestions = difflib.get_close_matches(value, action.choices, 1)
+ if suggestions:
+ args['closest'] = suggestions[0]
+ msg = _('invalid choice: %(value)r, maybe you meant %(closest)r? '
+ '(choose from %(choices)s)')
+
+ raise ArgumentError(action, msg % args)
# =======================
# Help-formatting methods
('--complex -1e-3j', NS(int=None, float=None, complex=-0.001j)),
]
+class TestArgumentAndSubparserSuggestions(TestCase):
+ """Test error handling and suggestion when a user makes a typo"""
+
+ def test_wrong_argument_error_with_suggestions(self):
+ parser = ErrorRaisingArgumentParser(suggest_on_error=True)
+ parser.add_argument('foo', choices=['bar', 'baz'])
+ with self.assertRaises(ArgumentParserError) as excinfo:
+ parser.parse_args(('bazz',))
+ self.assertIn(
+ "error: argument foo: invalid choice: 'bazz', maybe you meant 'baz'? (choose from bar, baz)",
+ excinfo.exception.stderr
+ )
+
+ def test_wrong_argument_error_no_suggestions(self):
+ parser = ErrorRaisingArgumentParser(suggest_on_error=False)
+ parser.add_argument('foo', choices=['bar', 'baz'])
+ with self.assertRaises(ArgumentParserError) as excinfo:
+ parser.parse_args(('bazz',))
+ self.assertIn(
+ "error: argument foo: invalid choice: 'bazz' (choose from bar, baz)",
+ excinfo.exception.stderr,
+ )
+
+ def test_wrong_argument_subparsers_with_suggestions(self):
+ parser = ErrorRaisingArgumentParser(suggest_on_error=True)
+ subparsers = parser.add_subparsers(required=True)
+ subparsers.add_parser('foo')
+ subparsers.add_parser('bar')
+ with self.assertRaises(ArgumentParserError) as excinfo:
+ parser.parse_args(('baz',))
+ self.assertIn(
+ "error: argument {foo,bar}: invalid choice: 'baz', maybe you meant"
+ " 'bar'? (choose from foo, bar)",
+ excinfo.exception.stderr,
+ )
+
+ def test_wrong_argument_subparsers_no_suggestions(self):
+ parser = ErrorRaisingArgumentParser(suggest_on_error=False)
+ subparsers = parser.add_subparsers(required=True)
+ subparsers.add_parser('foo')
+ subparsers.add_parser('bar')
+ with self.assertRaises(ArgumentParserError) as excinfo:
+ parser.parse_args(('baz',))
+ self.assertIn(
+ "error: argument {foo,bar}: invalid choice: 'baz' (choose from foo, bar)",
+ excinfo.exception.stderr,
+ )
+
+ def test_wrong_argument_no_suggestion_implicit(self):
+ parser = ErrorRaisingArgumentParser()
+ parser.add_argument('foo', choices=['bar', 'baz'])
+ with self.assertRaises(ArgumentParserError) as excinfo:
+ parser.parse_args(('bazz',))
+ self.assertIn(
+ "error: argument foo: invalid choice: 'bazz' (choose from bar, baz)",
+ excinfo.exception.stderr,
+ )
+
+ def test_suggestions_choices_empty(self):
+ parser = ErrorRaisingArgumentParser(suggest_on_error=True)
+ parser.add_argument('foo', choices=[])
+ with self.assertRaises(ArgumentParserError) as excinfo:
+ parser.parse_args(('bazz',))
+ self.assertIn(
+ "error: argument foo: invalid choice: 'bazz' (choose from )",
+ excinfo.exception.stderr,
+ )
+
+ def test_suggestions_choices_int(self):
+ parser = ErrorRaisingArgumentParser(suggest_on_error=True)
+ parser.add_argument('foo', choices=[1, 2])
+ with self.assertRaises(ArgumentParserError) as excinfo:
+ parser.parse_args(('3',))
+ self.assertIn(
+ "error: argument foo: invalid choice: '3' (choose from 1, 2)",
+ excinfo.exception.stderr,
+ )
+
+ def test_suggestions_choices_mixed_types(self):
+ parser = ErrorRaisingArgumentParser(suggest_on_error=True)
+ parser.add_argument('foo', choices=[1, '2'])
+ with self.assertRaises(ArgumentParserError) as excinfo:
+ parser.parse_args(('3',))
+ self.assertIn(
+ "error: argument foo: invalid choice: '3' (choose from 1, 2)",
+ excinfo.exception.stderr,
+ )
+
+
class TestInvalidAction(TestCase):
"""Test invalid user defined Action"""
'error: the following arguments are required: {foo,bar}\n$'
)
- def test_wrong_argument_subparsers_no_destination_error(self):
- parser = ErrorRaisingArgumentParser()
- subparsers = parser.add_subparsers(required=True)
- subparsers.add_parser('foo')
- subparsers.add_parser('bar')
- with self.assertRaises(ArgumentParserError) as excinfo:
- parser.parse_args(('baz',))
- self.assertRegex(
- excinfo.exception.stderr,
- r"error: argument {foo,bar}: invalid choice: 'baz' \(choose from foo, bar\)\n$"
- )
-
def test_optional_subparsers(self):
parser = ErrorRaisingArgumentParser()
subparsers = parser.add_subparsers(dest='command', required=False)
parser = ErrorRaisingArgumentParser(parents=[self.ab_mutex_parent])
self._test_mutex_ab(parser.parse_args)
- def test_single_granparent_mutex(self):
+ def test_single_grandparent_mutex(self):
parents = [self.ab_mutex_parent]
parser = ErrorRaisingArgumentParser(add_help=False, parents=parents)
parser = ErrorRaisingArgumentParser(parents=[parser])