Update argparse from CPython 3.14.2

This commit is contained in:
CPython Devleopers
2026-01-16 20:10:26 +09:00
committed by Jeong, YunWon
parent ef22bf4774
commit d75f272c8b
2 changed files with 1048 additions and 326 deletions

379
Lib/argparse.py vendored
View File

@@ -18,11 +18,12 @@ command-line and writes the result to a file::
'integers', metavar='int', nargs='+', type=int,
help='an integer to be summed')
parser.add_argument(
'--log', default=sys.stdout, type=argparse.FileType('w'),
'--log',
help='the file where the sum should be written')
args = parser.parse_args()
args.log.write('%s' % sum(args.integers))
args.log.close()
with (open(args.log, 'w') if args.log is not None
else contextlib.nullcontext(sys.stdout)) as log:
log.write('%s' % sum(args.integers))
The module contains the following public classes:
@@ -39,7 +40,8 @@ The module contains the following public classes:
- FileType -- A factory for defining types of files to be created. As the
example above shows, instances of FileType are typically passed as
the type= argument of add_argument() calls.
the type= argument of add_argument() calls. Deprecated since
Python 3.14.
- Action -- The base class for parser actions. Typically actions are
selected by passing strings like 'store_true' or 'append_const' to
@@ -159,18 +161,21 @@ class HelpFormatter(object):
provided by the class are considered an implementation detail.
"""
def __init__(self,
prog,
indent_increment=2,
max_help_position=24,
width=None):
def __init__(
self,
prog,
indent_increment=2,
max_help_position=24,
width=None,
color=True,
):
# default setting for width
if width is None:
import shutil
width = shutil.get_terminal_size().columns
width -= 2
self._set_color(color)
self._prog = prog
self._indent_increment = indent_increment
self._max_help_position = min(max_help_position,
@@ -187,6 +192,16 @@ class HelpFormatter(object):
self._whitespace_matcher = _re.compile(r'\s+', _re.ASCII)
self._long_break_matcher = _re.compile(r'\n\n\n+')
def _set_color(self, color):
from _colorize import can_colorize, decolor, get_theme
if color and can_colorize():
self._theme = get_theme(force_color=True).argparse
self._decolor = decolor
else:
self._theme = get_theme(force_no_color=True).argparse
self._decolor = lambda text: text
# ===============================
# Section and indentation methods
# ===============================
@@ -225,7 +240,11 @@ class HelpFormatter(object):
if self.heading is not SUPPRESS and self.heading is not None:
current_indent = self.formatter._current_indent
heading_text = _('%(heading)s:') % dict(heading=self.heading)
heading = '%*s%s\n' % (current_indent, '', heading_text)
t = self.formatter._theme
heading = (
f'{" " * current_indent}'
f'{t.heading}{heading_text}{t.reset}\n'
)
else:
heading = ''
@@ -262,7 +281,7 @@ class HelpFormatter(object):
if action.help is not SUPPRESS:
# find all invocations
get_invocation = self._format_action_invocation
get_invocation = lambda x: self._decolor(self._format_action_invocation(x))
invocation_lengths = [len(get_invocation(action)) + self._current_indent]
for subaction in self._iter_indented_subactions(action):
invocation_lengths.append(len(get_invocation(subaction)) + self._current_indent)
@@ -296,16 +315,23 @@ class HelpFormatter(object):
if part and part is not SUPPRESS])
def _format_usage(self, usage, actions, groups, prefix):
t = self._theme
if prefix is None:
prefix = _('usage: ')
# if usage is specified, use that
if usage is not None:
usage = usage % dict(prog=self._prog)
usage = (
t.prog_extra
+ usage
% {"prog": f"{t.prog}{self._prog}{t.reset}{t.prog_extra}"}
+ t.reset
)
# if no optionals or positionals are available, usage is just prog
elif usage is None and not actions:
usage = '%(prog)s' % dict(prog=self._prog)
usage = f"{t.prog}{self._prog}{t.reset}"
# if optionals and positionals are available, calculate usage
elif usage is None:
@@ -327,7 +353,7 @@ class HelpFormatter(object):
# wrap the usage parts if it's too long
text_width = self._width - self._current_indent
if len(prefix) + len(usage) > text_width:
if len(prefix) + len(self._decolor(usage)) > text_width:
# break usage into wrappable parts
opt_parts = self._get_actions_usage_parts(optionals, groups)
@@ -343,12 +369,13 @@ class HelpFormatter(object):
else:
line_len = indent_length - 1
for part in parts:
if line_len + 1 + len(part) > text_width and line:
part_len = len(self._decolor(part))
if line_len + 1 + part_len > text_width and line:
lines.append(indent + ' '.join(line))
line = []
line_len = indent_length - 1
line.append(part)
line_len += len(part) + 1
line_len += part_len + 1
if line:
lines.append(indent + ' '.join(line))
if prefix is not None:
@@ -356,8 +383,9 @@ class HelpFormatter(object):
return lines
# if prog is short, follow it with optionals or positionals
if len(prefix) + len(prog) <= 0.75 * text_width:
indent = ' ' * (len(prefix) + len(prog) + 1)
prog_len = len(self._decolor(prog))
if len(prefix) + prog_len <= 0.75 * text_width:
indent = ' ' * (len(prefix) + prog_len + 1)
if opt_parts:
lines = get_lines([prog] + opt_parts, indent, prefix)
lines.extend(get_lines(pos_parts, indent))
@@ -380,12 +408,18 @@ class HelpFormatter(object):
# join lines into usage
usage = '\n'.join(lines)
usage = usage.removeprefix(prog)
usage = f"{t.prog}{prog}{t.reset}{usage}"
# prefix with 'usage:'
return '%s%s\n\n' % (prefix, usage)
return f'{t.usage}{prefix}{t.reset}{usage}\n\n'
def _format_actions_usage(self, actions, groups):
return ' '.join(self._get_actions_usage_parts(actions, groups))
def _is_long_option(self, string):
return len(string) > 2
def _get_actions_usage_parts(self, actions, groups):
# find group indices and identify actions in groups
group_actions = set()
@@ -398,17 +432,18 @@ class HelpFormatter(object):
continue
try:
start = actions.index(group._group_actions[0])
start = min(actions.index(item) for item in group._group_actions)
except ValueError:
continue
else:
end = start + len(group._group_actions)
if actions[start:end] == group._group_actions:
if set(actions[start:end]) == set(group._group_actions):
group_actions.update(group._group_actions)
inserts[start, end] = group
# collect all actions format strings
parts = []
t = self._theme
for action in actions:
# suppressed arguments are marked with None
@@ -418,7 +453,11 @@ class HelpFormatter(object):
# produce all arg strings
elif not action.option_strings:
default = self._get_default_metavar_for_positional(action)
part = self._format_args(action, default)
part = (
t.summary_action
+ self._format_args(action, default)
+ t.reset
)
# if it's in a group, strip the outer []
if action in group_actions:
@@ -428,18 +467,26 @@ class HelpFormatter(object):
# produce the first way to invoke the option in brackets
else:
option_string = action.option_strings[0]
if self._is_long_option(option_string):
option_color = t.summary_long_option
else:
option_color = t.summary_short_option
# if the Optional doesn't take a value, format is:
# -s or --long
if action.nargs == 0:
part = action.format_usage()
part = f"{option_color}{part}{t.reset}"
# if the Optional takes a value, format is:
# -s ARGS or --long ARGS
else:
default = self._get_default_metavar_for_optional(action)
args_string = self._format_args(action, default)
part = '%s %s' % (option_string, args_string)
part = (
f"{option_color}{option_string} "
f"{t.summary_label}{args_string}{t.reset}"
)
# make it look optional if it's not required or in a group
if not action.required and action not in group_actions:
@@ -486,6 +533,7 @@ class HelpFormatter(object):
help_width = max(self._width - help_position, 11)
action_width = help_position - self._current_indent - 2
action_header = self._format_action_invocation(action)
action_header_no_color = self._decolor(action_header)
# no help; start on same line and add a final newline
if not action.help:
@@ -493,9 +541,15 @@ class HelpFormatter(object):
action_header = '%*s%s\n' % tup
# short action name; start on the same line and pad two spaces
elif len(action_header) <= action_width:
tup = self._current_indent, '', action_width, action_header
elif len(action_header_no_color) <= action_width:
# calculate widths without color codes
action_header_color = action_header
tup = self._current_indent, '', action_width, action_header_no_color
action_header = '%*s%-*s ' % tup
# swap in the colored header
action_header = action_header.replace(
action_header_no_color, action_header_color
)
indent_first = 0
# long action name; start on the next line
@@ -528,23 +582,42 @@ class HelpFormatter(object):
return self._join_parts(parts)
def _format_action_invocation(self, action):
t = self._theme
if not action.option_strings:
default = self._get_default_metavar_for_positional(action)
return ' '.join(self._metavar_formatter(action, default)(1))
return (
t.action
+ ' '.join(self._metavar_formatter(action, default)(1))
+ t.reset
)
else:
def color_option_strings(strings):
parts = []
for s in strings:
if self._is_long_option(s):
parts.append(f"{t.long_option}{s}{t.reset}")
else:
parts.append(f"{t.short_option}{s}{t.reset}")
return parts
# if the Optional doesn't take a value, format is:
# -s, --long
if action.nargs == 0:
return ', '.join(action.option_strings)
option_strings = color_option_strings(action.option_strings)
return ', '.join(option_strings)
# if the Optional takes a value, format is:
# -s, --long ARGS
else:
default = self._get_default_metavar_for_optional(action)
args_string = self._format_args(action, default)
return ', '.join(action.option_strings) + ' ' + args_string
option_strings = color_option_strings(action.option_strings)
args_string = (
f"{t.label}{self._format_args(action, default)}{t.reset}"
)
return ', '.join(option_strings) + ' ' + args_string
def _metavar_formatter(self, action, default_metavar):
if action.metavar is not None:
@@ -590,16 +663,19 @@ class HelpFormatter(object):
return result
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)
for name in list(params):
if params[name] is SUPPRESS:
value = params[name]
if value is SUPPRESS:
del params[name]
for name in list(params):
if hasattr(params[name], '__name__'):
params[name] = params[name].__name__
elif hasattr(value, '__name__'):
params[name] = value.__name__
if params.get('choices') is not None:
params['choices'] = ', '.join(map(str, params['choices']))
return self._get_help_string(action) % params
return help_string % params
def _iter_indented_subactions(self, action):
try:
@@ -844,22 +920,16 @@ class Action(_AttributeHolder):
return self.option_strings[0]
def __call__(self, parser, namespace, values, option_string=None):
raise NotImplementedError(_('.__call__() not defined'))
raise NotImplementedError('.__call__() not defined')
# FIXME: remove together with `BooleanOptionalAction` deprecated arguments.
_deprecated_default = object()
class BooleanOptionalAction(Action):
def __init__(self,
option_strings,
dest,
default=None,
type=_deprecated_default,
choices=_deprecated_default,
required=False,
help=None,
metavar=_deprecated_default,
deprecated=False):
_option_strings = []
@@ -867,38 +937,19 @@ class BooleanOptionalAction(Action):
_option_strings.append(option_string)
if option_string.startswith('--'):
if option_string.startswith('--no-'):
raise ValueError(f'invalid option name {option_string!r} '
f'for BooleanOptionalAction')
option_string = '--no-' + option_string[2:]
_option_strings.append(option_string)
# We need `_deprecated` special value to ban explicit arguments that
# match default value. Like:
# parser.add_argument('-f', action=BooleanOptionalAction, type=int)
for field_name in ('type', 'choices', 'metavar'):
if locals()[field_name] is not _deprecated_default:
import warnings
warnings._deprecated(
field_name,
"{name!r} is deprecated as of Python 3.12 and will be "
"removed in Python {remove}.",
remove=(3, 14))
if type is _deprecated_default:
type = None
if choices is _deprecated_default:
choices = None
if metavar is _deprecated_default:
metavar = None
super().__init__(
option_strings=_option_strings,
dest=dest,
nargs=0,
default=default,
type=type,
choices=choices,
required=required,
help=help,
metavar=metavar,
deprecated=deprecated)
@@ -1180,6 +1231,7 @@ class _SubParsersAction(Action):
self._name_parser_map = {}
self._choices_actions = []
self._deprecated = set()
self._color = True
super(_SubParsersAction, self).__init__(
option_strings=option_strings,
@@ -1195,23 +1247,30 @@ class _SubParsersAction(Action):
if kwargs.get('prog') is None:
kwargs['prog'] = '%s %s' % (self._prog_prefix, name)
# set color
if kwargs.get('color') is None:
kwargs['color'] = self._color
aliases = kwargs.pop('aliases', ())
if name in self._name_parser_map:
raise ArgumentError(self, _('conflicting subparser: %s') % name)
raise ValueError(f'conflicting subparser: {name}')
for alias in aliases:
if alias in self._name_parser_map:
raise ArgumentError(
self, _('conflicting subparser alias: %s') % alias)
raise ValueError(f'conflicting subparser alias: {alias}')
# create a pseudo-action to hold the choice help
if 'help' in kwargs:
help = kwargs.pop('help')
choice_action = self._ChoicesPseudoAction(name, aliases, help)
self._choices_actions.append(choice_action)
else:
choice_action = None
# create the parser and add it to the map
parser = self._parser_class(**kwargs)
if choice_action is not None:
parser._check_help(choice_action)
self._name_parser_map[name] = parser
# make parser available under aliases also
@@ -1276,7 +1335,7 @@ class _ExtendAction(_AppendAction):
# ==============
class FileType(object):
"""Factory for creating file object types
"""Deprecated factory for creating file object types
Instances of FileType are typically passed as type= arguments to the
ArgumentParser add_argument() method.
@@ -1293,6 +1352,12 @@ class FileType(object):
"""
def __init__(self, mode='r', bufsize=-1, encoding=None, errors=None):
import warnings
warnings.warn(
"FileType is deprecated. Simply open files after parsing arguments.",
category=PendingDeprecationWarning,
stacklevel=2
)
self._mode = mode
self._bufsize = bufsize
self._encoding = encoding
@@ -1396,7 +1461,7 @@ class _ActionsContainer(object):
self._defaults = {}
# determines whether an "option" looks like a negative number
self._negative_number_matcher = _re.compile(r'^-\d+$|^-\d*\.\d+$')
self._negative_number_matcher = _re.compile(r'-\.?\d')
# whether or not there are any optionals that look like negative
# numbers -- uses a list so it can be shared and edited
@@ -1449,7 +1514,8 @@ class _ActionsContainer(object):
chars = self.prefix_chars
if not args or len(args) == 1 and args[0][0] not in chars:
if args and 'dest' in kwargs:
raise ValueError('dest supplied twice for positional argument')
raise TypeError('dest supplied twice for positional argument,'
' did you mean metavar?')
kwargs = self._get_positional_kwargs(*args, **kwargs)
# otherwise, we're adding an optional argument
@@ -1465,27 +1531,34 @@ class _ActionsContainer(object):
kwargs['default'] = self.argument_default
# create the action object, and add it to the parser
action_name = kwargs.get('action')
action_class = self._pop_action_class(kwargs)
if not callable(action_class):
raise ValueError('unknown action "%s"' % (action_class,))
raise ValueError(f'unknown action {action_class!r}')
action = action_class(**kwargs)
# raise an error if action for positional argument does not
# consume arguments
if not action.option_strings and action.nargs == 0:
raise ValueError(f'action {action_name!r} is not valid for positional arguments')
# raise an error if the action type is not callable
type_func = self._registry_get('type', action.type, action.type)
if not callable(type_func):
raise ValueError('%r is not callable' % (type_func,))
raise TypeError(f'{type_func!r} is not callable')
if type_func is FileType:
raise ValueError('%r is a FileType class object, instance of it'
' must be passed' % (type_func,))
raise TypeError(f'{type_func!r} is a FileType class object, '
f'instance of it must be passed')
# raise an error if the metavar does not match the type
if hasattr(self, "_get_formatter"):
formatter = self._get_formatter()
try:
self._get_formatter()._format_args(action, None)
formatter._format_args(action, None)
except TypeError:
raise ValueError("length of metavar tuple does not match nargs")
self._check_help(action)
return self._add_action(action)
def add_argument_group(self, *args, **kwargs):
@@ -1529,8 +1602,8 @@ class _ActionsContainer(object):
if group.title in title_group_map:
# This branch could happen if a derived class added
# groups with duplicated titles in __init__
msg = _('cannot merge actions - two groups are named %r')
raise ValueError(msg % (group.title))
msg = f'cannot merge actions - two groups are named {group.title!r}'
raise ValueError(msg)
title_group_map[group.title] = group
# map each action to its group
@@ -1571,13 +1644,15 @@ class _ActionsContainer(object):
def _get_positional_kwargs(self, dest, **kwargs):
# make sure required is not specified
if 'required' in kwargs:
msg = _("'required' is an invalid argument for positionals")
msg = "'required' is an invalid argument for positionals"
raise TypeError(msg)
# mark positional arguments as required if at least one is
# always required
nargs = kwargs.get('nargs')
if nargs not in [OPTIONAL, ZERO_OR_MORE, REMAINDER, SUPPRESS, 0]:
if nargs == 0:
raise ValueError('nargs for positionals must be != 0')
if nargs not in [OPTIONAL, ZERO_OR_MORE, REMAINDER, SUPPRESS]:
kwargs['required'] = True
# return the keyword arguments with no option strings
@@ -1590,11 +1665,9 @@ class _ActionsContainer(object):
for option_string in args:
# error on strings that don't start with an appropriate prefix
if not option_string[0] in self.prefix_chars:
args = {'option': option_string,
'prefix_chars': self.prefix_chars}
msg = _('invalid option string %(option)r: '
'must start with a character %(prefix_chars)r')
raise ValueError(msg % args)
raise ValueError(
f'invalid option string {option_string!r}: '
f'must start with a character {self.prefix_chars!r}')
# strings starting with two prefix characters are long options
option_strings.append(option_string)
@@ -1610,8 +1683,8 @@ class _ActionsContainer(object):
dest_option_string = option_strings[0]
dest = dest_option_string.lstrip(self.prefix_chars)
if not dest:
msg = _('dest= is required for options like %r')
raise ValueError(msg % option_string)
msg = f'dest= is required for options like {option_string!r}'
raise TypeError(msg)
dest = dest.replace('-', '_')
# return the updated keyword arguments
@@ -1627,8 +1700,8 @@ class _ActionsContainer(object):
try:
return getattr(self, handler_func_name)
except AttributeError:
msg = _('invalid conflict_resolution value: %r')
raise ValueError(msg % self.conflict_handler)
msg = f'invalid conflict_resolution value: {self.conflict_handler!r}'
raise ValueError(msg)
def _check_conflict(self, action):
@@ -1667,10 +1740,26 @@ class _ActionsContainer(object):
if not action.option_strings:
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):
def __init__(self, container, title=None, description=None, **kwargs):
if 'prefix_chars' in kwargs:
import warnings
depr_msg = (
"The use of the undocumented 'prefix_chars' parameter in "
"ArgumentParser.add_argument_group() is deprecated."
)
warnings.warn(depr_msg, DeprecationWarning, stacklevel=3)
# add any missing keyword arguments by checking the container
update = kwargs.setdefault
update('conflict_handler', container.conflict_handler)
@@ -1702,14 +1791,7 @@ class _ArgumentGroup(_ActionsContainer):
self._group_actions.remove(action)
def add_argument_group(self, *args, **kwargs):
import warnings
warnings.warn(
"Nesting argument groups is deprecated.",
category=DeprecationWarning,
stacklevel=2
)
return super().add_argument_group(*args, **kwargs)
raise ValueError('argument groups cannot be nested')
class _MutuallyExclusiveGroup(_ArgumentGroup):
@@ -1720,7 +1802,7 @@ class _MutuallyExclusiveGroup(_ArgumentGroup):
def _add_action(self, action):
if action.required:
msg = _('mutually exclusive arguments must be optional')
msg = 'mutually exclusive arguments must be optional'
raise ValueError(msg)
action = self._container._add_action(action)
self._group_actions.append(action)
@@ -1730,14 +1812,29 @@ class _MutuallyExclusiveGroup(_ArgumentGroup):
self._container._remove_action(action)
self._group_actions.remove(action)
def add_mutually_exclusive_group(self, *args, **kwargs):
import warnings
warnings.warn(
"Nesting mutually exclusive groups is deprecated.",
category=DeprecationWarning,
stacklevel=2
)
return super().add_mutually_exclusive_group(*args, **kwargs)
def add_mutually_exclusive_group(self, **kwargs):
raise ValueError('mutually exclusive groups cannot be nested')
def _prog_name(prog=None):
if prog is not None:
return prog
arg0 = _sys.argv[0]
try:
modspec = _sys.modules['__main__'].__spec__
except (KeyError, AttributeError):
# possibly PYTHONSTARTUP or -X presite or other weird edge case
# no good answer here, so fall back to the default
modspec = None
if modspec is None:
# simple script
return _os.path.basename(arg0)
py = _os.path.basename(_sys.executable)
if modspec.name != '__main__':
# imported module or package
modname = modspec.name.removesuffix('.__main__')
return f'{py} -m {modname}'
# directory or ZIP file
return f'{py} {arg0}'
class ArgumentParser(_AttributeHolder, _ActionsContainer):
@@ -1760,6 +1857,9 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
- 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``)
- color - Allow color output in help messages (default: ``False``)
"""
def __init__(self,
@@ -1775,19 +1875,18 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
conflict_handler='error',
add_help=True,
allow_abbrev=True,
exit_on_error=True):
exit_on_error=True,
*,
suggest_on_error=False,
color=True,
):
superinit = super(ArgumentParser, self).__init__
superinit(description=description,
prefix_chars=prefix_chars,
argument_default=argument_default,
conflict_handler=conflict_handler)
# default setting for prog
if prog is None:
prog = _os.path.basename(_sys.argv[0])
self.prog = prog
self.prog = _prog_name(prog)
self.usage = usage
self.epilog = epilog
self.formatter_class = formatter_class
@@ -1795,6 +1894,8 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
self.add_help = add_help
self.allow_abbrev = allow_abbrev
self.exit_on_error = exit_on_error
self.suggest_on_error = suggest_on_error
self.color = color
add_group = self.add_argument_group
self._positionals = add_group(_('positional arguments'))
@@ -1844,7 +1945,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
def add_subparsers(self, **kwargs):
if self._subparsers is not None:
raise ArgumentError(None, _('cannot have multiple subparser arguments'))
raise ValueError('cannot have multiple subparser arguments')
# add the parser class to the arguments if it's not present
kwargs.setdefault('parser_class', type(self))
@@ -1859,15 +1960,19 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
# prog defaults to the usage message of this parser, skipping
# optional arguments and with no "usage:" prefix
if kwargs.get('prog') is None:
formatter = self._get_formatter()
# Create formatter without color to avoid storing ANSI codes in prog
formatter = self.formatter_class(prog=self.prog)
formatter._set_color(False)
positionals = self._get_positional_actions()
groups = self._mutually_exclusive_groups
formatter.add_usage(self.usage, positionals, groups, '')
formatter.add_usage(None, positionals, groups, '')
kwargs['prog'] = formatter.format_help().strip()
# create the parsers action and add it to the positionals list
parsers_class = self._pop_action_class(kwargs, 'parsers')
action = parsers_class(option_strings=[], **kwargs)
action._color = self.color
self._check_help(action)
self._subparsers._add_action(action)
# return the created parsers action
@@ -2498,7 +2603,6 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
value = action.default
if isinstance(value, str) and value is not SUPPRESS:
value = self._get_value(action, value)
self._check_value(action, value)
# when nargs='*' on a positional, if there were no command-line
# args, use the default if it is anything other than None
@@ -2506,11 +2610,8 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
not action.option_strings):
if action.default is not None:
value = action.default
self._check_value(action, value)
else:
# since arg_strings is always [] at this point
# there is no need to use self._check_value(action, value)
value = arg_strings
value = []
# single argument or optional argument produces a single value
elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]:
@@ -2543,8 +2644,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
def _get_value(self, action, arg_string):
type_func = self._registry_get('type', action.type, action.type)
if not callable(type_func):
msg = _('%r is not callable')
raise ArgumentError(action, msg % type_func)
raise TypeError(f'{type_func!r} is not callable')
# convert the value to the appropriate type
try:
@@ -2568,14 +2668,27 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
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
@@ -2611,7 +2724,9 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
return formatter.format_help()
def _get_formatter(self):
return self.formatter_class(prog=self.prog)
formatter = self.formatter_class(prog=self.prog)
formatter._set_color(self.color)
return formatter
# =====================
# Help-printing methods

File diff suppressed because it is too large Load Diff