mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Update some libs&tests to 3.14.3 (#7006)
* Update some libs&tests to 3.14.3 * Update argparse from 3.14.3 * Update inspect * Update `configparser.py` from 3.14.3
This commit is contained in:
1 parent
75137f7cdd
commit
3d283bb91f
7 files changed
+360
-192
No files matched your search
Vendored
+117
-110
@@ -337,27 +337,17 @@ class HelpFormatter(object):
|
||||
elif usage is None:
|
||||
prog = '%(prog)s' % dict(prog=self._prog)
|
||||
|
||||
# split optionals from positionals
|
||||
optionals = []
|
||||
positionals = []
|
||||
for action in actions:
|
||||
if action.option_strings:
|
||||
optionals.append(action)
|
||||
else:
|
||||
positionals.append(action)
|
||||
|
||||
parts, pos_start = self._get_actions_usage_parts(actions, groups)
|
||||
# build full usage string
|
||||
format = self._format_actions_usage
|
||||
action_usage = format(optionals + positionals, groups)
|
||||
usage = ' '.join([s for s in [prog, action_usage] if s])
|
||||
usage = ' '.join(filter(None, [prog, *parts]))
|
||||
|
||||
# wrap the usage parts if it's too long
|
||||
text_width = self._width - self._current_indent
|
||||
if len(prefix) + len(self._decolor(usage)) > text_width:
|
||||
|
||||
# break usage into wrappable parts
|
||||
opt_parts = self._get_actions_usage_parts(optionals, groups)
|
||||
pos_parts = self._get_actions_usage_parts(positionals, groups)
|
||||
opt_parts = parts[:pos_start]
|
||||
pos_parts = parts[pos_start:]
|
||||
|
||||
# helper for wrapping lines
|
||||
def get_lines(parts, indent, prefix=None):
|
||||
@@ -414,110 +404,114 @@ class HelpFormatter(object):
|
||||
# prefix with '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()
|
||||
inserts = {}
|
||||
"""Get usage parts with split index for optionals/positionals.
|
||||
|
||||
Returns (parts, pos_start) where pos_start is the index in parts
|
||||
where positionals begin.
|
||||
This preserves mutually exclusive group formatting across the
|
||||
optionals/positionals boundary (gh-75949).
|
||||
"""
|
||||
actions = [action for action in actions if action.help is not SUPPRESS]
|
||||
# group actions by mutually exclusive groups
|
||||
action_groups = dict.fromkeys(actions)
|
||||
for group in groups:
|
||||
if not group._group_actions:
|
||||
raise ValueError(f'empty group {group}')
|
||||
|
||||
if all(action.help is SUPPRESS for action in group._group_actions):
|
||||
continue
|
||||
|
||||
try:
|
||||
start = min(actions.index(item) for item in group._group_actions)
|
||||
except ValueError:
|
||||
continue
|
||||
else:
|
||||
end = start + len(group._group_actions)
|
||||
if set(actions[start:end]) == set(group._group_actions):
|
||||
group_actions.update(group._group_actions)
|
||||
inserts[start, end] = group
|
||||
for action in group._group_actions:
|
||||
if action in action_groups:
|
||||
action_groups[action] = group
|
||||
# positional arguments keep their position
|
||||
positionals = []
|
||||
for action in actions:
|
||||
if not action.option_strings:
|
||||
group = action_groups.pop(action)
|
||||
if group:
|
||||
group_actions = [
|
||||
action2 for action2 in group._group_actions
|
||||
if action2.option_strings and
|
||||
action_groups.pop(action2, None)
|
||||
] + [action]
|
||||
positionals.append((group.required, group_actions))
|
||||
else:
|
||||
positionals.append((None, [action]))
|
||||
# the remaining optional arguments are sorted by the position of
|
||||
# the first option in the group
|
||||
optionals = []
|
||||
for action in actions:
|
||||
if action.option_strings and action in action_groups:
|
||||
group = action_groups.pop(action)
|
||||
if group:
|
||||
group_actions = [action] + [
|
||||
action2 for action2 in group._group_actions
|
||||
if action2.option_strings and
|
||||
action_groups.pop(action2, None)
|
||||
]
|
||||
optionals.append((group.required, group_actions))
|
||||
else:
|
||||
optionals.append((None, [action]))
|
||||
|
||||
# collect all actions format strings
|
||||
parts = []
|
||||
t = self._theme
|
||||
for action in actions:
|
||||
pos_start = None
|
||||
for i, (required, group) in enumerate(optionals + positionals):
|
||||
start = len(parts)
|
||||
if i == len(optionals):
|
||||
pos_start = start
|
||||
in_group = len(group) > 1
|
||||
for action in group:
|
||||
# produce all arg strings
|
||||
if not action.option_strings:
|
||||
default = self._get_default_metavar_for_positional(action)
|
||||
part = self._format_args(action, default)
|
||||
# if it's in a group, strip the outer []
|
||||
if in_group:
|
||||
if part[0] == '[' and part[-1] == ']':
|
||||
part = part[1:-1]
|
||||
part = t.summary_action + part + t.reset
|
||||
|
||||
# suppressed arguments are marked with None
|
||||
if action.help is SUPPRESS:
|
||||
part = None
|
||||
|
||||
# produce all arg strings
|
||||
elif not action.option_strings:
|
||||
default = self._get_default_metavar_for_positional(action)
|
||||
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:
|
||||
if part[0] == '[' and part[-1] == ']':
|
||||
part = part[1:-1]
|
||||
|
||||
# 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
|
||||
# produce the first way to invoke the option in brackets
|
||||
else:
|
||||
option_color = t.summary_short_option
|
||||
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 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 = (
|
||||
f"{option_color}{option_string} "
|
||||
f"{t.summary_label}{args_string}{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 = (
|
||||
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:
|
||||
part = '[%s]' % part
|
||||
# make it look optional if it's not required or in a group
|
||||
if not (action.required or required or in_group):
|
||||
part = '[%s]' % part
|
||||
|
||||
# add the action string to the list
|
||||
parts.append(part)
|
||||
# add the action string to the list
|
||||
parts.append(part)
|
||||
|
||||
# group mutually exclusive actions
|
||||
inserted_separators_indices = set()
|
||||
for start, end in sorted(inserts, reverse=True):
|
||||
group = inserts[start, end]
|
||||
group_parts = [item for item in parts[start:end] if item is not None]
|
||||
group_size = len(group_parts)
|
||||
if group.required:
|
||||
open, close = "()" if group_size > 1 else ("", "")
|
||||
else:
|
||||
open, close = "[]"
|
||||
group_parts[0] = open + group_parts[0]
|
||||
group_parts[-1] = group_parts[-1] + close
|
||||
for i, part in enumerate(group_parts[:-1], start=start):
|
||||
# insert a separator if not already done in a nested group
|
||||
if i not in inserted_separators_indices:
|
||||
parts[i] = part + ' |'
|
||||
inserted_separators_indices.add(i)
|
||||
parts[start + group_size - 1] = group_parts[-1]
|
||||
for i in range(start + group_size, end):
|
||||
parts[i] = None
|
||||
if in_group:
|
||||
parts[start] = ('(' if required else '[') + parts[start]
|
||||
for i in range(start, len(parts) - 1):
|
||||
parts[i] += ' |'
|
||||
parts[-1] += ')' if required else ']'
|
||||
|
||||
# return the usage parts
|
||||
return [item for item in parts if item is not None]
|
||||
if pos_start is None:
|
||||
pos_start = len(parts)
|
||||
return parts, pos_start
|
||||
|
||||
def _format_text(self, text):
|
||||
if '%(prog)' in text:
|
||||
@@ -745,11 +739,14 @@ class ArgumentDefaultsHelpFormatter(HelpFormatter):
|
||||
if help is None:
|
||||
help = ''
|
||||
|
||||
if '%(default)' not in help:
|
||||
if action.default is not SUPPRESS:
|
||||
defaulting_nargs = [OPTIONAL, ZERO_OR_MORE]
|
||||
if action.option_strings or action.nargs in defaulting_nargs:
|
||||
help += _(' (default: %(default)s)')
|
||||
if (
|
||||
'%(default)' not in help
|
||||
and action.default is not SUPPRESS
|
||||
and not action.required
|
||||
):
|
||||
defaulting_nargs = (OPTIONAL, ZERO_OR_MORE)
|
||||
if action.option_strings or action.nargs in defaulting_nargs:
|
||||
help += _(' (default: %(default)s)')
|
||||
return help
|
||||
|
||||
|
||||
@@ -1552,8 +1549,8 @@ class _ActionsContainer(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()
|
||||
if hasattr(self, "_get_validation_formatter"):
|
||||
formatter = self._get_validation_formatter()
|
||||
try:
|
||||
formatter._format_args(action, None)
|
||||
except TypeError:
|
||||
@@ -1741,8 +1738,8 @@ class _ActionsContainer(object):
|
||||
action.container._remove_action(action)
|
||||
|
||||
def _check_help(self, action):
|
||||
if action.help and hasattr(self, "_get_formatter"):
|
||||
formatter = self._get_formatter()
|
||||
if action.help and hasattr(self, "_get_validation_formatter"):
|
||||
formatter = self._get_validation_formatter()
|
||||
try:
|
||||
formatter._expand_help(action)
|
||||
except (ValueError, TypeError, KeyError) as exc:
|
||||
@@ -1897,6 +1894,9 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||
self.suggest_on_error = suggest_on_error
|
||||
self.color = color
|
||||
|
||||
# Cached formatter for validation (avoids repeated _set_color calls)
|
||||
self._cached_formatter = None
|
||||
|
||||
add_group = self.add_argument_group
|
||||
self._positionals = add_group(_('positional arguments'))
|
||||
self._optionals = add_group(_('options'))
|
||||
@@ -2728,6 +2728,13 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||
formatter._set_color(self.color)
|
||||
return formatter
|
||||
|
||||
def _get_validation_formatter(self):
|
||||
# Return cached formatter for read-only validation operations
|
||||
# (_expand_help and _format_args). Avoids repeated slow _set_color calls.
|
||||
if self._cached_formatter is None:
|
||||
self._cached_formatter = self._get_formatter()
|
||||
return self._cached_formatter
|
||||
|
||||
# =====================
|
||||
# Help-printing methods
|
||||
# =====================
|
||||
|
||||
Vendored
+71
-43
@@ -154,14 +154,13 @@ import itertools
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import types
|
||||
|
||||
__all__ = ("NoSectionError", "DuplicateOptionError", "DuplicateSectionError",
|
||||
"NoOptionError", "InterpolationError", "InterpolationDepthError",
|
||||
"InterpolationMissingOptionError", "InterpolationSyntaxError",
|
||||
"ParsingError", "MissingSectionHeaderError",
|
||||
"MultilineContinuationError",
|
||||
"ConfigParser", "RawConfigParser",
|
||||
"MultilineContinuationError", "UnnamedSectionDisabledError",
|
||||
"InvalidWriteError", "ConfigParser", "RawConfigParser",
|
||||
"Interpolation", "BasicInterpolation", "ExtendedInterpolation",
|
||||
"SectionProxy", "ConverterMapping",
|
||||
"DEFAULTSECT", "MAX_INTERPOLATION_DEPTH", "UNNAMED_SECTION")
|
||||
@@ -362,11 +361,27 @@ class MultilineContinuationError(ParsingError):
|
||||
self.line = line
|
||||
self.args = (filename, lineno, line)
|
||||
|
||||
|
||||
class UnnamedSectionDisabledError(Error):
|
||||
"""Raised when an attempt to use UNNAMED_SECTION is made with the
|
||||
feature disabled."""
|
||||
def __init__(self):
|
||||
Error.__init__(self, "Support for UNNAMED_SECTION is disabled.")
|
||||
|
||||
|
||||
class _UnnamedSection:
|
||||
|
||||
def __repr__(self):
|
||||
return "<UNNAMED_SECTION>"
|
||||
|
||||
class InvalidWriteError(Error):
|
||||
"""Raised when attempting to write data that the parser would read back differently.
|
||||
ex: writing a key which begins with the section header pattern would read back as a
|
||||
new section """
|
||||
|
||||
def __init__(self, msg=''):
|
||||
Error.__init__(self, msg)
|
||||
|
||||
|
||||
UNNAMED_SECTION = _UnnamedSection()
|
||||
|
||||
@@ -556,35 +571,36 @@ class _ReadState:
|
||||
|
||||
|
||||
class _Line(str):
|
||||
__slots__ = 'clean', 'has_comments'
|
||||
|
||||
def __new__(cls, val, *args, **kwargs):
|
||||
return super().__new__(cls, val)
|
||||
|
||||
def __init__(self, val, prefixes):
|
||||
self.prefixes = prefixes
|
||||
def __init__(self, val, comments):
|
||||
trimmed = val.strip()
|
||||
self.clean = comments.strip(trimmed)
|
||||
self.has_comments = trimmed != self.clean
|
||||
|
||||
@functools.cached_property
|
||||
def clean(self):
|
||||
return self._strip_full() and self._strip_inline()
|
||||
|
||||
@property
|
||||
def has_comments(self):
|
||||
return self.strip() != self.clean
|
||||
|
||||
def _strip_inline(self):
|
||||
"""
|
||||
Search for the earliest prefix at the beginning of the line or following a space.
|
||||
"""
|
||||
matcher = re.compile(
|
||||
'|'.join(fr'(^|\s)({re.escape(prefix)})' for prefix in self.prefixes.inline)
|
||||
# match nothing if no prefixes
|
||||
or '(?!)'
|
||||
class _CommentSpec:
|
||||
def __init__(self, full_prefixes, inline_prefixes):
|
||||
full_patterns = (
|
||||
# prefix at the beginning of a line
|
||||
fr'^({re.escape(prefix)}).*'
|
||||
for prefix in full_prefixes
|
||||
)
|
||||
match = matcher.search(self)
|
||||
return self[:match.start() if match else None].strip()
|
||||
inline_patterns = (
|
||||
# prefix at the beginning of the line or following a space
|
||||
fr'(^|\s)({re.escape(prefix)}.*)'
|
||||
for prefix in inline_prefixes
|
||||
)
|
||||
self.pattern = re.compile('|'.join(itertools.chain(full_patterns, inline_patterns)))
|
||||
|
||||
def _strip_full(self):
|
||||
return '' if any(map(self.strip().startswith, self.prefixes.full)) else True
|
||||
def strip(self, text):
|
||||
return self.pattern.sub('', text).rstrip()
|
||||
|
||||
def wrap(self, text):
|
||||
return _Line(text, self)
|
||||
|
||||
|
||||
class RawConfigParser(MutableMapping):
|
||||
@@ -653,10 +669,7 @@ class RawConfigParser(MutableMapping):
|
||||
else:
|
||||
self._optcre = re.compile(self._OPT_TMPL.format(delim=d),
|
||||
re.VERBOSE)
|
||||
self._prefixes = types.SimpleNamespace(
|
||||
full=tuple(comment_prefixes or ()),
|
||||
inline=tuple(inline_comment_prefixes or ()),
|
||||
)
|
||||
self._comments = _CommentSpec(comment_prefixes or (), inline_comment_prefixes or ())
|
||||
self._strict = strict
|
||||
self._allow_no_value = allow_no_value
|
||||
self._empty_lines_in_values = empty_lines_in_values
|
||||
@@ -694,6 +707,10 @@ class RawConfigParser(MutableMapping):
|
||||
if section == self.default_section:
|
||||
raise ValueError('Invalid section name: %r' % section)
|
||||
|
||||
if section is UNNAMED_SECTION:
|
||||
if not self._allow_unnamed_section:
|
||||
raise UnnamedSectionDisabledError
|
||||
|
||||
if section in self._sections:
|
||||
raise DuplicateSectionError(section)
|
||||
self._sections[section] = self._dict()
|
||||
@@ -777,7 +794,8 @@ class RawConfigParser(MutableMapping):
|
||||
"""
|
||||
elements_added = set()
|
||||
for section, keys in dictionary.items():
|
||||
section = str(section)
|
||||
if section is not UNNAMED_SECTION:
|
||||
section = str(section)
|
||||
try:
|
||||
self.add_section(section)
|
||||
except (DuplicateSectionError, ValueError):
|
||||
@@ -949,7 +967,7 @@ class RawConfigParser(MutableMapping):
|
||||
if self._defaults:
|
||||
self._write_section(fp, self.default_section,
|
||||
self._defaults.items(), d)
|
||||
if UNNAMED_SECTION in self._sections:
|
||||
if UNNAMED_SECTION in self._sections and self._sections[UNNAMED_SECTION]:
|
||||
self._write_section(fp, UNNAMED_SECTION, self._sections[UNNAMED_SECTION].items(), d, unnamed=True)
|
||||
|
||||
for section in self._sections:
|
||||
@@ -959,10 +977,11 @@ class RawConfigParser(MutableMapping):
|
||||
self._sections[section].items(), d)
|
||||
|
||||
def _write_section(self, fp, section_name, section_items, delimiter, unnamed=False):
|
||||
"""Write a single section to the specified `fp'."""
|
||||
"""Write a single section to the specified 'fp'."""
|
||||
if not unnamed:
|
||||
fp.write("[{}]\n".format(section_name))
|
||||
for key, value in section_items:
|
||||
self._validate_key_contents(key)
|
||||
value = self._interpolation.before_write(self, section_name, key,
|
||||
value)
|
||||
if value is not None or not self._allow_no_value:
|
||||
@@ -1047,7 +1066,6 @@ class RawConfigParser(MutableMapping):
|
||||
in an otherwise empty line or may be entered in lines holding values or
|
||||
section names. Please note that comments get stripped off when reading configuration files.
|
||||
"""
|
||||
|
||||
try:
|
||||
ParsingError._raise_all(self._read_inner(fp, fpname))
|
||||
finally:
|
||||
@@ -1056,8 +1074,7 @@ class RawConfigParser(MutableMapping):
|
||||
def _read_inner(self, fp, fpname):
|
||||
st = _ReadState()
|
||||
|
||||
Line = functools.partial(_Line, prefixes=self._prefixes)
|
||||
for st.lineno, line in enumerate(map(Line, fp), start=1):
|
||||
for st.lineno, line in enumerate(map(self._comments.wrap, fp), start=1):
|
||||
if not line.clean:
|
||||
if self._empty_lines_in_values:
|
||||
# add empty line to the value, but only if there was no
|
||||
@@ -1200,21 +1217,32 @@ class RawConfigParser(MutableMapping):
|
||||
raise ValueError('Not a boolean: %s' % value)
|
||||
return self.BOOLEAN_STATES[value.lower()]
|
||||
|
||||
def _validate_value_types(self, *, section="", option="", value=""):
|
||||
"""Raises a TypeError for non-string values.
|
||||
def _validate_key_contents(self, key):
|
||||
"""Raises an InvalidWriteError for any keys containing
|
||||
delimiters or that begins with the section header pattern"""
|
||||
if re.match(self.SECTCRE, key):
|
||||
raise InvalidWriteError(
|
||||
f"Cannot write key {key}; begins with section pattern")
|
||||
for delim in self._delimiters:
|
||||
if delim in key:
|
||||
raise InvalidWriteError(
|
||||
f"Cannot write key {key}; contains delimiter {delim}")
|
||||
|
||||
The only legal non-string value if we allow valueless
|
||||
options is None, so we need to check if the value is a
|
||||
string if:
|
||||
- we do not allow valueless options, or
|
||||
- we allow valueless options but the value is not None
|
||||
def _validate_value_types(self, *, section="", option="", value=""):
|
||||
"""Raises a TypeError for illegal non-string values.
|
||||
|
||||
Legal non-string values are UNNAMED_SECTION and falsey values if
|
||||
they are allowed.
|
||||
|
||||
For compatibility reasons this method is not used in classic set()
|
||||
for RawConfigParsers. It is invoked in every case for mapping protocol
|
||||
access and in ConfigParser.set().
|
||||
"""
|
||||
if not isinstance(section, str):
|
||||
raise TypeError("section names must be strings")
|
||||
if section is UNNAMED_SECTION:
|
||||
if not self._allow_unnamed_section:
|
||||
raise UnnamedSectionDisabledError
|
||||
elif not isinstance(section, str):
|
||||
raise TypeError("section names must be strings or UNNAMED_SECTION")
|
||||
if not isinstance(option, str):
|
||||
raise TypeError("option keys must be strings")
|
||||
if not self._allow_no_value or value:
|
||||
|
||||
Vendored
+1
@@ -348,6 +348,7 @@ def isgenerator(object):
|
||||
gi_frame frame object or possibly None once the generator has
|
||||
been exhausted
|
||||
gi_running set to 1 when generator is executing, 0 otherwise
|
||||
gi_suspended set to 1 when the generator is suspended at a yield point, 0 otherwise
|
||||
gi_yieldfrom object being iterated by yield from or None
|
||||
|
||||
__iter__() defined to support iteration over container
|
||||
|
||||
Vendored
+2
-2
@@ -1,5 +1,5 @@
|
||||
import _imp
|
||||
import contextlib
|
||||
import _imp
|
||||
import importlib
|
||||
import importlib.machinery
|
||||
import importlib.util
|
||||
@@ -10,7 +10,7 @@ import textwrap
|
||||
import unittest
|
||||
import warnings
|
||||
|
||||
from .os_helper import temp_dir, unlink
|
||||
from .os_helper import unlink, temp_dir
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
|
||||
Vendored
+78
-27
@@ -3307,12 +3307,11 @@ class TestMutuallyExclusiveGroupErrors(TestCase):
|
||||
'''
|
||||
self.assertEqual(cmd_foo.format_help(), textwrap.dedent(expected))
|
||||
|
||||
def test_empty_group(self):
|
||||
def test_usage_empty_group(self):
|
||||
# See issue 26952
|
||||
parser = argparse.ArgumentParser()
|
||||
parser = ErrorRaisingArgumentParser(prog='PROG')
|
||||
group = parser.add_mutually_exclusive_group()
|
||||
with self.assertRaises(ValueError):
|
||||
parser.parse_args(['-h'])
|
||||
self.assertEqual(parser.format_usage(), 'usage: PROG [-h]\n')
|
||||
|
||||
def test_nested_mutex_groups(self):
|
||||
parser = argparse.ArgumentParser(prog='PROG')
|
||||
@@ -3580,25 +3579,29 @@ class TestMutuallyExclusiveOptionalsMixed(MEMixin, TestCase):
|
||||
group.add_argument('-b', action='store_true', help='b help')
|
||||
parser.add_argument('-y', action='store_true', help='y help')
|
||||
group.add_argument('-c', action='store_true', help='c help')
|
||||
parser.add_argument('-z', action='store_true', help='z help')
|
||||
return parser
|
||||
|
||||
failures = ['-a -b', '-b -c', '-a -c', '-a -b -c']
|
||||
successes = [
|
||||
('-a', NS(a=True, b=False, c=False, x=False, y=False)),
|
||||
('-b', NS(a=False, b=True, c=False, x=False, y=False)),
|
||||
('-c', NS(a=False, b=False, c=True, x=False, y=False)),
|
||||
('-a -x', NS(a=True, b=False, c=False, x=True, y=False)),
|
||||
('-y -b', NS(a=False, b=True, c=False, x=False, y=True)),
|
||||
('-x -y -c', NS(a=False, b=False, c=True, x=True, y=True)),
|
||||
('-a', NS(a=True, b=False, c=False, x=False, y=False, z=False)),
|
||||
('-b', NS(a=False, b=True, c=False, x=False, y=False, z=False)),
|
||||
('-c', NS(a=False, b=False, c=True, x=False, y=False, z=False)),
|
||||
('-a -x', NS(a=True, b=False, c=False, x=True, y=False, z=False)),
|
||||
('-y -b', NS(a=False, b=True, c=False, x=False, y=True, z=False)),
|
||||
('-x -y -c', NS(a=False, b=False, c=True, x=True, y=True, z=False)),
|
||||
]
|
||||
successes_when_not_required = [
|
||||
('', NS(a=False, b=False, c=False, x=False, y=False)),
|
||||
('-x', NS(a=False, b=False, c=False, x=True, y=False)),
|
||||
('-y', NS(a=False, b=False, c=False, x=False, y=True)),
|
||||
('', NS(a=False, b=False, c=False, x=False, y=False, z=False)),
|
||||
('-x', NS(a=False, b=False, c=False, x=True, y=False, z=False)),
|
||||
('-y', NS(a=False, b=False, c=False, x=False, y=True, z=False)),
|
||||
]
|
||||
|
||||
usage_when_required = usage_when_not_required = '''\
|
||||
usage: PROG [-h] [-x] [-a] [-b] [-y] [-c]
|
||||
usage_when_not_required = '''\
|
||||
usage: PROG [-h] [-x] [-a | -b | -c] [-y] [-z]
|
||||
'''
|
||||
usage_when_required = '''\
|
||||
usage: PROG [-h] [-x] (-a | -b | -c) [-y] [-z]
|
||||
'''
|
||||
help = '''\
|
||||
|
||||
@@ -3609,6 +3612,7 @@ class TestMutuallyExclusiveOptionalsMixed(MEMixin, TestCase):
|
||||
-b b help
|
||||
-y y help
|
||||
-c c help
|
||||
-z z help
|
||||
'''
|
||||
|
||||
|
||||
@@ -3662,23 +3666,27 @@ class TestMutuallyExclusiveOptionalsAndPositionalsMixed(MEMixin, TestCase):
|
||||
group.add_argument('a', nargs='?', help='a help')
|
||||
group.add_argument('-b', action='store_true', help='b help')
|
||||
group.add_argument('-c', action='store_true', help='c help')
|
||||
parser.add_argument('-z', action='store_true', help='z help')
|
||||
return parser
|
||||
|
||||
failures = ['X A -b', '-b -c', '-c X A']
|
||||
successes = [
|
||||
('X A', NS(a='A', b=False, c=False, x='X', y=False)),
|
||||
('X -b', NS(a=None, b=True, c=False, x='X', y=False)),
|
||||
('X -c', NS(a=None, b=False, c=True, x='X', y=False)),
|
||||
('X A -y', NS(a='A', b=False, c=False, x='X', y=True)),
|
||||
('X -y -b', NS(a=None, b=True, c=False, x='X', y=True)),
|
||||
('X A', NS(a='A', b=False, c=False, x='X', y=False, z=False)),
|
||||
('X -b', NS(a=None, b=True, c=False, x='X', y=False, z=False)),
|
||||
('X -c', NS(a=None, b=False, c=True, x='X', y=False, z=False)),
|
||||
('X A -y', NS(a='A', b=False, c=False, x='X', y=True, z=False)),
|
||||
('X -y -b', NS(a=None, b=True, c=False, x='X', y=True, z=False)),
|
||||
]
|
||||
successes_when_not_required = [
|
||||
('X', NS(a=None, b=False, c=False, x='X', y=False)),
|
||||
('X -y', NS(a=None, b=False, c=False, x='X', y=True)),
|
||||
('X', NS(a=None, b=False, c=False, x='X', y=False, z=False)),
|
||||
('X -y', NS(a=None, b=False, c=False, x='X', y=True, z=False)),
|
||||
]
|
||||
|
||||
usage_when_required = usage_when_not_required = '''\
|
||||
usage: PROG [-h] [-y] [-b] [-c] x [a]
|
||||
usage_when_not_required = '''\
|
||||
usage: PROG [-h] [-y] [-z] x [-b | -c | a]
|
||||
'''
|
||||
usage_when_required = '''\
|
||||
usage: PROG [-h] [-y] [-z] x (-b | -c | a)
|
||||
'''
|
||||
help = '''\
|
||||
|
||||
@@ -3691,6 +3699,7 @@ class TestMutuallyExclusiveOptionalsAndPositionalsMixed(MEMixin, TestCase):
|
||||
-y y help
|
||||
-b b help
|
||||
-c c help
|
||||
-z z help
|
||||
'''
|
||||
|
||||
|
||||
@@ -4885,6 +4894,25 @@ class TestHelpUsageNoWhitespaceCrash(TestCase):
|
||||
''')
|
||||
self.assertEqual(parser.format_usage(), usage)
|
||||
|
||||
def test_mutex_groups_with_mixed_optionals_positionals_wrap(self):
|
||||
# https://github.com/python/cpython/issues/75949
|
||||
# Mutually exclusive groups containing both optionals and positionals
|
||||
# should preserve pipe separators when the usage line wraps.
|
||||
parser = argparse.ArgumentParser(prog='PROG')
|
||||
g = parser.add_mutually_exclusive_group()
|
||||
g.add_argument('-v', '--verbose', action='store_true')
|
||||
g.add_argument('-q', '--quiet', action='store_true')
|
||||
g.add_argument('-x', '--extra-long-option-name', nargs='?')
|
||||
g.add_argument('-y', '--yet-another-long-option', nargs='?')
|
||||
g.add_argument('positional', nargs='?')
|
||||
|
||||
usage = textwrap.dedent('''\
|
||||
usage: PROG [-h]
|
||||
[-v | -q | -x [EXTRA_LONG_OPTION_NAME] |
|
||||
-y [YET_ANOTHER_LONG_OPTION] | positional]
|
||||
''')
|
||||
self.assertEqual(parser.format_usage(), usage)
|
||||
|
||||
|
||||
class TestHelpVariableExpansion(HelpTestCase):
|
||||
"""Test that variables are expanded properly in help messages"""
|
||||
@@ -5300,6 +5328,7 @@ class TestHelpArgumentDefaults(HelpTestCase):
|
||||
argument_signatures = [
|
||||
Sig('--foo', help='foo help - oh and by the way, %(default)s'),
|
||||
Sig('--bar', action='store_true', help='bar help'),
|
||||
Sig('--required', required=True, help='some help'),
|
||||
Sig('--taz', action=argparse.BooleanOptionalAction,
|
||||
help='Whether to taz it', default=True),
|
||||
Sig('--corge', action=argparse.BooleanOptionalAction,
|
||||
@@ -5313,8 +5342,8 @@ class TestHelpArgumentDefaults(HelpTestCase):
|
||||
[Sig('--baz', type=int, default=42, help='baz help')]),
|
||||
]
|
||||
usage = '''\
|
||||
usage: PROG [-h] [--foo FOO] [--bar] [--taz | --no-taz] [--corge | --no-corge]
|
||||
[--quux QUUX] [--baz BAZ]
|
||||
usage: PROG [-h] [--foo FOO] [--bar] --required REQUIRED [--taz | --no-taz]
|
||||
[--corge | --no-corge] [--quux QUUX] [--baz BAZ]
|
||||
spam [badger]
|
||||
'''
|
||||
help = usage + '''\
|
||||
@@ -5329,6 +5358,7 @@ class TestHelpArgumentDefaults(HelpTestCase):
|
||||
-h, --help show this help message and exit
|
||||
--foo FOO foo help - oh and by the way, None
|
||||
--bar bar help (default: False)
|
||||
--required REQUIRED some help
|
||||
--taz, --no-taz Whether to taz it (default: True)
|
||||
--corge, --no-corge Whether to corge it
|
||||
--quux QUUX Set the quux (default: 42)
|
||||
@@ -7256,7 +7286,28 @@ class TestColorized(TestCase):
|
||||
),
|
||||
)
|
||||
|
||||
def test_argparse_color_usage(self):
|
||||
def test_argparse_color_mutually_exclusive_group_usage(self):
|
||||
parser = argparse.ArgumentParser(color=True, prog="PROG")
|
||||
group = parser.add_mutually_exclusive_group()
|
||||
group.add_argument('--foo', action='store_true', help='FOO')
|
||||
group.add_argument('--spam', help='SPAM')
|
||||
group.add_argument('badger', nargs='*', help='BADGER')
|
||||
|
||||
prog = self.theme.prog
|
||||
heading = self.theme.heading
|
||||
long = self.theme.summary_long_option
|
||||
short = self.theme.summary_short_option
|
||||
label = self.theme.summary_label
|
||||
pos = self.theme.summary_action
|
||||
reset = self.theme.reset
|
||||
|
||||
self.assertEqual(parser.format_usage(),
|
||||
f"{heading}usage: {reset}{prog}PROG{reset} [{short}-h{reset}] "
|
||||
f"[{long}--foo{reset} | "
|
||||
f"{long}--spam {label}SPAM{reset} | "
|
||||
f"{pos}badger ...{reset}]\n")
|
||||
|
||||
def test_argparse_color_custom_usage(self):
|
||||
# Arrange
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
|
||||
Vendored
+65
-8
@@ -526,7 +526,7 @@ boolean {0[0]} NO
|
||||
cf.get(self.default_section, "Foo"), "Bar",
|
||||
"could not locate option, expecting case-insensitive defaults")
|
||||
|
||||
@unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON, universal newlines")
|
||||
@unittest.skipIf(os.name == "nt", "TODO: RUSTPYTHON; universal newlines")
|
||||
def test_parse_errors(self):
|
||||
cf = self.newconfig()
|
||||
self.parse_error(cf, configparser.ParsingError,
|
||||
@@ -762,7 +762,6 @@ boolean {0[0]} NO
|
||||
parsed_files = cf.read([], encoding="utf-8")
|
||||
self.assertEqual(parsed_files, [])
|
||||
|
||||
# XXX: RUSTPYTHON; This test might cause CI hang
|
||||
def test_read_returns_file_list_with_bytestring_path(self):
|
||||
if self.delimiters[0] != '=':
|
||||
self.skipTest('incompatible format')
|
||||
@@ -988,12 +987,12 @@ class ConfigParserTestCase(BasicTestCase, unittest.TestCase):
|
||||
|
||||
def test_defaults_keyword(self):
|
||||
"""bpo-23835 fix for ConfigParser"""
|
||||
cf = self.newconfig(defaults={1: 2.4})
|
||||
self.assertEqual(cf[self.default_section]['1'], '2.4')
|
||||
self.assertAlmostEqual(cf[self.default_section].getfloat('1'), 2.4)
|
||||
cf = self.newconfig(defaults={"A": 5.2})
|
||||
self.assertEqual(cf[self.default_section]['a'], '5.2')
|
||||
self.assertAlmostEqual(cf[self.default_section].getfloat('a'), 5.2)
|
||||
cf = self.newconfig(defaults={1: 2.5})
|
||||
self.assertEqual(cf[self.default_section]['1'], '2.5')
|
||||
self.assertAlmostEqual(cf[self.default_section].getfloat('1'), 2.5)
|
||||
cf = self.newconfig(defaults={"A": 5.25})
|
||||
self.assertEqual(cf[self.default_section]['a'], '5.25')
|
||||
self.assertAlmostEqual(cf[self.default_section].getfloat('a'), 5.25)
|
||||
|
||||
|
||||
class ConfigParserTestCaseNoInterpolation(BasicTestCase, unittest.TestCase):
|
||||
@@ -2204,6 +2203,40 @@ class SectionlessTestCase(unittest.TestCase):
|
||||
self.assertEqual('1', cfg2[configparser.UNNAMED_SECTION]['a'])
|
||||
self.assertEqual('2', cfg2[configparser.UNNAMED_SECTION]['b'])
|
||||
|
||||
def test_empty_unnamed_section(self):
|
||||
cfg = configparser.ConfigParser(allow_unnamed_section=True)
|
||||
cfg.add_section(configparser.UNNAMED_SECTION)
|
||||
cfg.add_section('section')
|
||||
output = io.StringIO()
|
||||
cfg.write(output)
|
||||
self.assertEqual(output.getvalue(), '[section]\n\n')
|
||||
|
||||
def test_add_section(self):
|
||||
cfg = configparser.ConfigParser(allow_unnamed_section=True)
|
||||
cfg.add_section(configparser.UNNAMED_SECTION)
|
||||
cfg.set(configparser.UNNAMED_SECTION, 'a', '1')
|
||||
self.assertEqual('1', cfg[configparser.UNNAMED_SECTION]['a'])
|
||||
output = io.StringIO()
|
||||
cfg.write(output)
|
||||
self.assertEqual(output.getvalue(), 'a = 1\n\n')
|
||||
|
||||
cfg = configparser.ConfigParser(allow_unnamed_section=True)
|
||||
cfg[configparser.UNNAMED_SECTION] = {'a': '1'}
|
||||
self.assertEqual('1', cfg[configparser.UNNAMED_SECTION]['a'])
|
||||
output = io.StringIO()
|
||||
cfg.write(output)
|
||||
self.assertEqual(output.getvalue(), 'a = 1\n\n')
|
||||
|
||||
def test_disabled_error(self):
|
||||
with self.assertRaises(configparser.MissingSectionHeaderError):
|
||||
configparser.ConfigParser().read_string("a = 1")
|
||||
|
||||
with self.assertRaises(configparser.UnnamedSectionDisabledError):
|
||||
configparser.ConfigParser().add_section(configparser.UNNAMED_SECTION)
|
||||
|
||||
with self.assertRaises(configparser.UnnamedSectionDisabledError):
|
||||
configparser.ConfigParser()[configparser.UNNAMED_SECTION] = {'a': '1'}
|
||||
|
||||
def test_multiple_configs(self):
|
||||
cfg = configparser.ConfigParser(allow_unnamed_section=True)
|
||||
cfg.read_string('a = 1')
|
||||
@@ -2214,6 +2247,30 @@ class SectionlessTestCase(unittest.TestCase):
|
||||
self.assertEqual('2', cfg[configparser.UNNAMED_SECTION]['b'])
|
||||
|
||||
|
||||
class InvalidInputTestCase(unittest.TestCase):
|
||||
"""Tests for issue #65697, where configparser will write configs
|
||||
it parses back differently. Ex: keys containing delimiters or
|
||||
matching the section pattern"""
|
||||
|
||||
def test_delimiter_in_key(self):
|
||||
cfg = configparser.ConfigParser(delimiters=('='))
|
||||
cfg.add_section('section1')
|
||||
cfg.set('section1', 'a=b', 'c')
|
||||
output = io.StringIO()
|
||||
with self.assertRaises(configparser.InvalidWriteError):
|
||||
cfg.write(output)
|
||||
output.close()
|
||||
|
||||
def test_section_bracket_in_key(self):
|
||||
cfg = configparser.ConfigParser()
|
||||
cfg.add_section('section1')
|
||||
cfg.set('section1', '[this parses back as a section]', 'foo')
|
||||
output = io.StringIO()
|
||||
with self.assertRaises(configparser.InvalidWriteError):
|
||||
cfg.write(output)
|
||||
output.close()
|
||||
|
||||
|
||||
class MiscTestCase(unittest.TestCase):
|
||||
def test__all__(self):
|
||||
support.check__all__(self, configparser, not_exported={"Error"})
|
||||
|
||||
+26
-2
@@ -13,8 +13,7 @@ import linecache
|
||||
import os
|
||||
import dis
|
||||
from os.path import normcase
|
||||
# XXX: RUSTPYTHON
|
||||
# import _pickle
|
||||
# import _pickle # TODO: RUSTPYTHON
|
||||
import pickle
|
||||
import shutil
|
||||
import stat
|
||||
@@ -2786,6 +2785,31 @@ class TestGetGeneratorState(unittest.TestCase):
|
||||
# Running after the first yield
|
||||
next(self.generator)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: '_GeneratorWrapper' object has no attribute 'gi_suspended'
|
||||
def test_types_coroutine_wrapper_state(self):
|
||||
def gen():
|
||||
yield 1
|
||||
yield 2
|
||||
|
||||
@types.coroutine
|
||||
def wrapped_generator_coro():
|
||||
# return a generator iterator so types.coroutine
|
||||
# wraps it into types._GeneratorWrapper.
|
||||
return gen()
|
||||
|
||||
g = wrapped_generator_coro()
|
||||
self.addCleanup(g.close)
|
||||
self.assertIs(type(g), types._GeneratorWrapper)
|
||||
|
||||
# _GeneratorWrapper must provide gi_suspended/cr_suspended
|
||||
# so inspect.get*state() doesn't raise AttributeError.
|
||||
self.assertEqual(inspect.getgeneratorstate(g), inspect.GEN_CREATED)
|
||||
self.assertEqual(inspect.getcoroutinestate(g), inspect.CORO_CREATED)
|
||||
|
||||
next(g)
|
||||
self.assertEqual(inspect.getgeneratorstate(g), inspect.GEN_SUSPENDED)
|
||||
self.assertEqual(inspect.getcoroutinestate(g), inspect.CORO_SUSPENDED)
|
||||
|
||||
def test_easy_debugging(self):
|
||||
# repr() and str() of a generator state should contain the state name
|
||||
names = 'GEN_CREATED GEN_RUNNING GEN_SUSPENDED GEN_CLOSED'.split()
|
||||
|
||||
Reference in new issue
Block a user