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:
Shahar Naveh authored and GitHub committed 2026-02-07 19:09:42 +09:00
1 parent 75137f7cdd
commit 3d283bb91f
7 files changed
+360 -192

No files matched your search

+117 -110
View File
@@ -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
# =====================
+71 -43
View File
@@ -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:
+1
View File
@@ -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
+2 -2
View File
@@ -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
+78 -27
View File
@@ -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,
+65 -8
View File
@@ -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
View File
@@ -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()