aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/tools/python3/Lib
diff options
context:
space:
mode:
authorAlexSm <alex@ydb.tech>2024-05-06 18:27:11 +0200
committerGitHub <noreply@github.com>2024-05-06 18:27:11 +0200
commit068e8291de67631f063304b76dda3c1fd6601c12 (patch)
treef9058c69ef88f04c55ff9c92949dffa8cd6b83a5 /contrib/tools/python3/Lib
parent653a427438ab0fa69068180c34233b015af0d405 (diff)
parent41f0129e44731de1ba129fbae27008f8a4048fdc (diff)
downloadydb-068e8291de67631f063304b76dda3c1fd6601c12.tar.gz
Merge pull request #4325 from ydb-platform/mergelibs-240506-1255
Library import 240506-1255
Diffstat (limited to 'contrib/tools/python3/Lib')
-rw-r--r--contrib/tools/python3/Lib/_pyio.py3
-rw-r--r--contrib/tools/python3/Lib/argparse.py62
-rw-r--r--contrib/tools/python3/Lib/ast.py15
-rw-r--r--contrib/tools/python3/Lib/asyncio/base_events.py20
-rw-r--r--contrib/tools/python3/Lib/asyncio/tasks.py2
-rw-r--r--contrib/tools/python3/Lib/asyncio/windows_events.py43
-rw-r--r--contrib/tools/python3/Lib/collections/__init__.py5
-rw-r--r--contrib/tools/python3/Lib/configparser.py180
-rw-r--r--contrib/tools/python3/Lib/dataclasses.py4
-rw-r--r--contrib/tools/python3/Lib/doctest.py4
-rw-r--r--contrib/tools/python3/Lib/email/_header_value_parser.py3
-rw-r--r--contrib/tools/python3/Lib/email/generator.py2
-rw-r--r--contrib/tools/python3/Lib/email/message.py2
-rw-r--r--contrib/tools/python3/Lib/enum.py82
-rw-r--r--contrib/tools/python3/Lib/glob.py3
-rw-r--r--contrib/tools/python3/Lib/http/client.py15
-rw-r--r--contrib/tools/python3/Lib/importlib/_bootstrap_external.py3
-rw-r--r--contrib/tools/python3/Lib/importlib/metadata/__init__.py1
-rw-r--r--contrib/tools/python3/Lib/importlib/resources/simple.py2
-rw-r--r--contrib/tools/python3/Lib/importlib/util.py82
-rw-r--r--contrib/tools/python3/Lib/inspect.py171
-rw-r--r--contrib/tools/python3/Lib/json/encoder.py2
-rw-r--r--contrib/tools/python3/Lib/linecache.py12
-rw-r--r--contrib/tools/python3/Lib/logging/__init__.py25
-rw-r--r--contrib/tools/python3/Lib/logging/handlers.py116
-rw-r--r--contrib/tools/python3/Lib/mailbox.py10
-rw-r--r--contrib/tools/python3/Lib/mimetypes.py8
-rw-r--r--contrib/tools/python3/Lib/multiprocessing/connection.py3
-rw-r--r--contrib/tools/python3/Lib/os.py4
-rwxr-xr-xcontrib/tools/python3/Lib/pdb.py50
-rw-r--r--contrib/tools/python3/Lib/pickletools.py2
-rwxr-xr-xcontrib/tools/python3/Lib/pydoc.py164
-rw-r--r--contrib/tools/python3/Lib/pydoc_data/topics.py198
-rw-r--r--contrib/tools/python3/Lib/shutil.py4
-rw-r--r--contrib/tools/python3/Lib/subprocess.py2
-rw-r--r--contrib/tools/python3/Lib/tokenize.py53
-rw-r--r--contrib/tools/python3/Lib/typing.py82
-rw-r--r--contrib/tools/python3/Lib/unittest/mock.py13
-rw-r--r--contrib/tools/python3/Lib/urllib/parse.py54
-rw-r--r--contrib/tools/python3/Lib/urllib/request.py77
-rw-r--r--contrib/tools/python3/Lib/xml/etree/ElementTree.py14
-rw-r--r--contrib/tools/python3/Lib/xml/sax/expatreader.py14
-rw-r--r--contrib/tools/python3/Lib/ya.make4
-rw-r--r--contrib/tools/python3/Lib/zipfile/__init__.py19
44 files changed, 953 insertions, 681 deletions
diff --git a/contrib/tools/python3/Lib/_pyio.py b/contrib/tools/python3/Lib/_pyio.py
index 9641d43101..687076fbe9 100644
--- a/contrib/tools/python3/Lib/_pyio.py
+++ b/contrib/tools/python3/Lib/_pyio.py
@@ -1209,7 +1209,8 @@ class BufferedReader(_BufferedIOMixin):
return written
def tell(self):
- return _BufferedIOMixin.tell(self) - len(self._read_buf) + self._read_pos
+ # GH-95782: Keep return value non-negative
+ return max(_BufferedIOMixin.tell(self) - len(self._read_buf) + self._read_pos, 0)
def seek(self, pos, whence=0):
if whence not in valid_seek_flags:
diff --git a/contrib/tools/python3/Lib/argparse.py b/contrib/tools/python3/Lib/argparse.py
index 484a1efde4..120cb6c845 100644
--- a/contrib/tools/python3/Lib/argparse.py
+++ b/contrib/tools/python3/Lib/argparse.py
@@ -225,7 +225,8 @@ class HelpFormatter(object):
# add the heading if the section was non-empty
if self.heading is not SUPPRESS and self.heading is not None:
current_indent = self.formatter._current_indent
- heading = '%*s%s:\n' % (current_indent, '', self.heading)
+ heading_text = _('%(heading)s:') % dict(heading=self.heading)
+ heading = '%*s%s\n' % (current_indent, '', heading_text)
else:
heading = ''
@@ -415,6 +416,8 @@ class HelpFormatter(object):
suppressed_actions_count += 1
exposed_actions_count = group_action_count - suppressed_actions_count
+ if not exposed_actions_count:
+ continue
if not group.required:
if start in inserts:
@@ -720,7 +723,7 @@ class ArgumentDefaultsHelpFormatter(HelpFormatter):
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)'
+ help += _(' (default: %(default)s)')
return help
@@ -1149,7 +1152,9 @@ class _VersionAction(Action):
version=None,
dest=SUPPRESS,
default=SUPPRESS,
- help="show program's version number and exit"):
+ help=None):
+ if help is None:
+ help = _("show program's version number and exit")
super(_VersionAction, self).__init__(
option_strings=option_strings,
dest=dest,
@@ -2004,7 +2009,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
# get the optional identified at this index
option_tuple = option_string_indices[start_index]
- action, option_string, explicit_arg = option_tuple
+ action, option_string, sep, explicit_arg = option_tuple
# identify additional optionals in the same arg string
# (e.g. -xyz is the same as -x -y -z if no args are required)
@@ -2031,18 +2036,27 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
and option_string[1] not in chars
and explicit_arg != ''
):
+ if sep or explicit_arg[0] in chars:
+ msg = _('ignored explicit argument %r')
+ raise ArgumentError(action, msg % explicit_arg)
action_tuples.append((action, [], option_string))
char = option_string[0]
option_string = char + explicit_arg[0]
- new_explicit_arg = explicit_arg[1:] or None
optionals_map = self._option_string_actions
if option_string in optionals_map:
action = optionals_map[option_string]
- explicit_arg = new_explicit_arg
+ explicit_arg = explicit_arg[1:]
+ if not explicit_arg:
+ sep = explicit_arg = None
+ elif explicit_arg[0] == '=':
+ sep = '='
+ explicit_arg = explicit_arg[1:]
+ else:
+ sep = ''
else:
- msg = _('ignored explicit argument %r')
- raise ArgumentError(action, msg % explicit_arg)
-
+ extras.append(char + explicit_arg)
+ stop = start_index + 1
+ break
# if the action expect exactly one argument, we've
# successfully matched the option; exit the loop
elif arg_count == 1:
@@ -2262,18 +2276,17 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
# if the option string is present in the parser, return the action
if arg_string in self._option_string_actions:
action = self._option_string_actions[arg_string]
- return action, arg_string, None
+ return action, arg_string, None, None
# if it's just a single character, it was meant to be positional
if len(arg_string) == 1:
return None
# if the option string before the "=" is present, return the action
- if '=' in arg_string:
- option_string, explicit_arg = arg_string.split('=', 1)
- if option_string in self._option_string_actions:
- action = self._option_string_actions[option_string]
- return action, option_string, explicit_arg
+ option_string, sep, explicit_arg = arg_string.partition('=')
+ if sep and option_string in self._option_string_actions:
+ action = self._option_string_actions[option_string]
+ return action, option_string, sep, explicit_arg
# search through all possible prefixes of the option string
# and all actions in the parser for possible interpretations
@@ -2282,7 +2295,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
# if multiple actions match, the option string was ambiguous
if len(option_tuples) > 1:
options = ', '.join([option_string
- for action, option_string, explicit_arg in option_tuples])
+ for action, option_string, sep, explicit_arg in option_tuples])
args = {'option': arg_string, 'matches': options}
msg = _('ambiguous option: %(option)s could match %(matches)s')
self.error(msg % args)
@@ -2306,7 +2319,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
# it was meant to be an optional but there is no such option
# in this parser (though it might be a valid option in a subparser)
- return None, arg_string, None
+ return None, arg_string, None, None
def _get_option_tuples(self, option_string):
result = []
@@ -2316,15 +2329,13 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
chars = self.prefix_chars
if option_string[0] in chars and option_string[1] in chars:
if self.allow_abbrev:
- if '=' in option_string:
- option_prefix, explicit_arg = option_string.split('=', 1)
- else:
- option_prefix = option_string
- explicit_arg = None
+ option_prefix, sep, explicit_arg = option_string.partition('=')
+ if not sep:
+ sep = explicit_arg = None
for option_string in self._option_string_actions:
if option_string.startswith(option_prefix):
action = self._option_string_actions[option_string]
- tup = action, option_string, explicit_arg
+ tup = action, option_string, sep, explicit_arg
result.append(tup)
# single character options can be concatenated with their arguments
@@ -2332,18 +2343,17 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
# separate
elif option_string[0] in chars and option_string[1] not in chars:
option_prefix = option_string
- explicit_arg = None
short_option_prefix = option_string[:2]
short_explicit_arg = option_string[2:]
for option_string in self._option_string_actions:
if option_string == short_option_prefix:
action = self._option_string_actions[option_string]
- tup = action, option_string, short_explicit_arg
+ tup = action, option_string, '', short_explicit_arg
result.append(tup)
elif option_string.startswith(option_prefix):
action = self._option_string_actions[option_string]
- tup = action, option_string, explicit_arg
+ tup = action, option_string, None, None
result.append(tup)
# shouldn't ever get here
diff --git a/contrib/tools/python3/Lib/ast.py b/contrib/tools/python3/Lib/ast.py
index de940d2e9c..b0995fa7f1 100644
--- a/contrib/tools/python3/Lib/ast.py
+++ b/contrib/tools/python3/Lib/ast.py
@@ -1268,14 +1268,18 @@ class _Unparser(NodeVisitor):
quote_type = quote_types[0]
self.write(f"{quote_type}{value}{quote_type}")
- def _write_fstring_inner(self, node, scape_newlines=False):
+ def _write_fstring_inner(self, node, is_format_spec=False):
if isinstance(node, JoinedStr):
# for both the f-string itself, and format_spec
for value in node.values:
- self._write_fstring_inner(value, scape_newlines=scape_newlines)
+ self._write_fstring_inner(value, is_format_spec=is_format_spec)
elif isinstance(node, Constant) and isinstance(node.value, str):
value = node.value.replace("{", "{{").replace("}", "}}")
- if scape_newlines:
+
+ if is_format_spec:
+ value = value.replace("\\", "\\\\")
+ value = value.replace("'", "\\'")
+ value = value.replace('"', '\\"')
value = value.replace("\n", "\\n")
self.write(value)
elif isinstance(node, FormattedValue):
@@ -1299,10 +1303,7 @@ class _Unparser(NodeVisitor):
self.write(f"!{chr(node.conversion)}")
if node.format_spec:
self.write(":")
- self._write_fstring_inner(
- node.format_spec,
- scape_newlines=True
- )
+ self._write_fstring_inner(node.format_spec, is_format_spec=True)
def visit_Name(self, node):
self.write(node.id)
diff --git a/contrib/tools/python3/Lib/asyncio/base_events.py b/contrib/tools/python3/Lib/asyncio/base_events.py
index c16c445bde..29eff0499c 100644
--- a/contrib/tools/python3/Lib/asyncio/base_events.py
+++ b/contrib/tools/python3/Lib/asyncio/base_events.py
@@ -45,6 +45,7 @@ from . import protocols
from . import sslproto
from . import staggered
from . import tasks
+from . import timeouts
from . import transports
from . import trsock
from .log import logger
@@ -596,23 +597,24 @@ class BaseEventLoop(events.AbstractEventLoop):
thread = threading.Thread(target=self._do_shutdown, args=(future,))
thread.start()
try:
- await future
- finally:
- thread.join(timeout)
-
- if thread.is_alive():
+ async with timeouts.timeout(timeout):
+ await future
+ except TimeoutError:
warnings.warn("The executor did not finishing joining "
- f"its threads within {timeout} seconds.",
- RuntimeWarning, stacklevel=2)
+ f"its threads within {timeout} seconds.",
+ RuntimeWarning, stacklevel=2)
self._default_executor.shutdown(wait=False)
+ else:
+ thread.join()
def _do_shutdown(self, future):
try:
self._default_executor.shutdown(wait=True)
if not self.is_closed():
- self.call_soon_threadsafe(future.set_result, None)
+ self.call_soon_threadsafe(futures._set_result_unless_cancelled,
+ future, None)
except Exception as ex:
- if not self.is_closed():
+ if not self.is_closed() and not future.cancelled():
self.call_soon_threadsafe(future.set_exception, ex)
def _check_running(self):
diff --git a/contrib/tools/python3/Lib/asyncio/tasks.py b/contrib/tools/python3/Lib/asyncio/tasks.py
index 65f2a6ef80..0b22e28d8e 100644
--- a/contrib/tools/python3/Lib/asyncio/tasks.py
+++ b/contrib/tools/python3/Lib/asyncio/tasks.py
@@ -480,7 +480,7 @@ async def wait_for(fut, timeout):
If the wait is cancelled, the task is also cancelled.
- If the task supresses the cancellation and returns a value instead,
+ If the task suppresses the cancellation and returns a value instead,
that value is returned.
This function is a coroutine.
diff --git a/contrib/tools/python3/Lib/asyncio/windows_events.py b/contrib/tools/python3/Lib/asyncio/windows_events.py
index c9a5fb841c..cb613451a5 100644
--- a/contrib/tools/python3/Lib/asyncio/windows_events.py
+++ b/contrib/tools/python3/Lib/asyncio/windows_events.py
@@ -8,6 +8,7 @@ if sys.platform != 'win32': # pragma: no cover
import _overlapped
import _winapi
import errno
+from functools import partial
import math
import msvcrt
import socket
@@ -323,13 +324,13 @@ class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
if self._self_reading_future is not None:
ov = self._self_reading_future._ov
self._self_reading_future.cancel()
- # self_reading_future was just cancelled so if it hasn't been
- # finished yet, it never will be (it's possible that it has
- # already finished and its callback is waiting in the queue,
- # where it could still happen if the event loop is restarted).
- # Unregister it otherwise IocpProactor.close will wait for it
- # forever
- if ov is not None:
+ # self_reading_future always uses IOCP, so even though it's
+ # been cancelled, we need to make sure that the IOCP message
+ # is received so that the kernel is not holding on to the
+ # memory, possibly causing memory corruption later. Only
+ # unregister it if IO is complete in all respects. Otherwise
+ # we need another _poll() later to complete the IO.
+ if ov is not None and not ov.pending:
self._proactor._unregister(ov)
self._self_reading_future = None
@@ -466,6 +467,18 @@ class IocpProactor:
else:
raise
+ @classmethod
+ def _finish_recvfrom(cls, trans, key, ov, *, empty_result):
+ try:
+ return cls.finish_socket_func(trans, key, ov)
+ except OSError as exc:
+ # WSARecvFrom will report ERROR_PORT_UNREACHABLE when the same
+ # socket is used to send to an address that is not listening.
+ if exc.winerror == _overlapped.ERROR_PORT_UNREACHABLE:
+ return empty_result, None
+ else:
+ raise
+
def recv(self, conn, nbytes, flags=0):
self._register_with_iocp(conn)
ov = _overlapped.Overlapped(NULL)
@@ -500,7 +513,8 @@ class IocpProactor:
except BrokenPipeError:
return self._result((b'', None))
- return self._register(ov, conn, self.finish_socket_func)
+ return self._register(ov, conn, partial(self._finish_recvfrom,
+ empty_result=b''))
def recvfrom_into(self, conn, buf, flags=0):
self._register_with_iocp(conn)
@@ -510,17 +524,8 @@ class IocpProactor:
except BrokenPipeError:
return self._result((0, None))
- def finish_recv(trans, key, ov):
- try:
- return ov.getresult()
- except OSError as exc:
- if exc.winerror in (_overlapped.ERROR_NETNAME_DELETED,
- _overlapped.ERROR_OPERATION_ABORTED):
- raise ConnectionResetError(*exc.args)
- else:
- raise
-
- return self._register(ov, conn, finish_recv)
+ return self._register(ov, conn, partial(self._finish_recvfrom,
+ empty_result=0))
def sendto(self, conn, buf, flags=0, addr=None):
self._register_with_iocp(conn)
diff --git a/contrib/tools/python3/Lib/collections/__init__.py b/contrib/tools/python3/Lib/collections/__init__.py
index 8652dc8a4e..5f000b5f2c 100644
--- a/contrib/tools/python3/Lib/collections/__init__.py
+++ b/contrib/tools/python3/Lib/collections/__init__.py
@@ -638,7 +638,8 @@ class Counter(dict):
>>> sorted(c.elements())
['A', 'A', 'B', 'B', 'C', 'C']
- # Knuth's example for prime factors of 1836: 2**2 * 3**3 * 17**1
+ Knuth's example for prime factors of 1836: 2**2 * 3**3 * 17**1
+
>>> import math
>>> prime_factors = Counter({2: 2, 3: 3, 17: 1})
>>> math.prod(prime_factors.elements())
@@ -679,7 +680,7 @@ class Counter(dict):
'''
# The regular dict.update() operation makes no sense here because the
- # replace behavior results in the some of original untouched counts
+ # replace behavior results in some of the original untouched counts
# being mixed-in with all of the other counts for a mismash that
# doesn't have a straight-forward interpretation in most counting
# contexts. Instead, we implement straight-addition. Both the inputs
diff --git a/contrib/tools/python3/Lib/configparser.py b/contrib/tools/python3/Lib/configparser.py
index e8aae21794..f96704eb45 100644
--- a/contrib/tools/python3/Lib/configparser.py
+++ b/contrib/tools/python3/Lib/configparser.py
@@ -995,100 +995,102 @@ class RawConfigParser(MutableMapping):
lineno = 0
indent_level = 0
e = None # None, or an exception
- for lineno, line in enumerate(fp, start=1):
- comment_start = sys.maxsize
- # strip inline comments
- inline_prefixes = {p: -1 for p in self._inline_comment_prefixes}
- while comment_start == sys.maxsize and inline_prefixes:
- next_prefixes = {}
- for prefix, index in inline_prefixes.items():
- index = line.find(prefix, index+1)
- if index == -1:
- continue
- next_prefixes[prefix] = index
- if index == 0 or (index > 0 and line[index-1].isspace()):
- comment_start = min(comment_start, index)
- inline_prefixes = next_prefixes
- # strip full line comments
- for prefix in self._comment_prefixes:
- if line.strip().startswith(prefix):
- comment_start = 0
- break
- if comment_start == sys.maxsize:
- comment_start = None
- value = line[:comment_start].strip()
- if not value:
- if self._empty_lines_in_values:
- # add empty line to the value, but only if there was no
- # comment on the line
- if (comment_start is None and
- cursect is not None and
- optname and
- cursect[optname] is not None):
- cursect[optname].append('') # newlines added at join
- else:
- # empty line marks end of value
- indent_level = sys.maxsize
- continue
- # continuation line?
- first_nonspace = self.NONSPACECRE.search(line)
- cur_indent_level = first_nonspace.start() if first_nonspace else 0
- if (cursect is not None and optname and
- cur_indent_level > indent_level):
- cursect[optname].append(value)
- # a section header or option header?
- else:
- indent_level = cur_indent_level
- # is it a section header?
- mo = self.SECTCRE.match(value)
- if mo:
- sectname = mo.group('header')
- if sectname in self._sections:
- if self._strict and sectname in elements_added:
- raise DuplicateSectionError(sectname, fpname,
- lineno)
- cursect = self._sections[sectname]
- elements_added.add(sectname)
- elif sectname == self.default_section:
- cursect = self._defaults
+ try:
+ for lineno, line in enumerate(fp, start=1):
+ comment_start = sys.maxsize
+ # strip inline comments
+ inline_prefixes = {p: -1 for p in self._inline_comment_prefixes}
+ while comment_start == sys.maxsize and inline_prefixes:
+ next_prefixes = {}
+ for prefix, index in inline_prefixes.items():
+ index = line.find(prefix, index+1)
+ if index == -1:
+ continue
+ next_prefixes[prefix] = index
+ if index == 0 or (index > 0 and line[index-1].isspace()):
+ comment_start = min(comment_start, index)
+ inline_prefixes = next_prefixes
+ # strip full line comments
+ for prefix in self._comment_prefixes:
+ if line.strip().startswith(prefix):
+ comment_start = 0
+ break
+ if comment_start == sys.maxsize:
+ comment_start = None
+ value = line[:comment_start].strip()
+ if not value:
+ if self._empty_lines_in_values:
+ # add empty line to the value, but only if there was no
+ # comment on the line
+ if (comment_start is None and
+ cursect is not None and
+ optname and
+ cursect[optname] is not None):
+ cursect[optname].append('') # newlines added at join
else:
- cursect = self._dict()
- self._sections[sectname] = cursect
- self._proxies[sectname] = SectionProxy(self, sectname)
- elements_added.add(sectname)
- # So sections can't start with a continuation line
- optname = None
- # no section header in the file?
- elif cursect is None:
- raise MissingSectionHeaderError(fpname, lineno, line)
- # an option line?
+ # empty line marks end of value
+ indent_level = sys.maxsize
+ continue
+ # continuation line?
+ first_nonspace = self.NONSPACECRE.search(line)
+ cur_indent_level = first_nonspace.start() if first_nonspace else 0
+ if (cursect is not None and optname and
+ cur_indent_level > indent_level):
+ cursect[optname].append(value)
+ # a section header or option header?
else:
- mo = self._optcre.match(value)
+ indent_level = cur_indent_level
+ # is it a section header?
+ mo = self.SECTCRE.match(value)
if mo:
- optname, vi, optval = mo.group('option', 'vi', 'value')
- if not optname:
- e = self._handle_error(e, fpname, lineno, line)
- optname = self.optionxform(optname.rstrip())
- if (self._strict and
- (sectname, optname) in elements_added):
- raise DuplicateOptionError(sectname, optname,
- fpname, lineno)
- elements_added.add((sectname, optname))
- # This check is fine because the OPTCRE cannot
- # match if it would set optval to None
- if optval is not None:
- optval = optval.strip()
- cursect[optname] = [optval]
+ sectname = mo.group('header')
+ if sectname in self._sections:
+ if self._strict and sectname in elements_added:
+ raise DuplicateSectionError(sectname, fpname,
+ lineno)
+ cursect = self._sections[sectname]
+ elements_added.add(sectname)
+ elif sectname == self.default_section:
+ cursect = self._defaults
else:
- # valueless option handling
- cursect[optname] = None
+ cursect = self._dict()
+ self._sections[sectname] = cursect
+ self._proxies[sectname] = SectionProxy(self, sectname)
+ elements_added.add(sectname)
+ # So sections can't start with a continuation line
+ optname = None
+ # no section header in the file?
+ elif cursect is None:
+ raise MissingSectionHeaderError(fpname, lineno, line)
+ # an option line?
else:
- # a non-fatal parsing error occurred. set up the
- # exception but keep going. the exception will be
- # raised at the end of the file and will contain a
- # list of all bogus lines
- e = self._handle_error(e, fpname, lineno, line)
- self._join_multiline_values()
+ mo = self._optcre.match(value)
+ if mo:
+ optname, vi, optval = mo.group('option', 'vi', 'value')
+ if not optname:
+ e = self._handle_error(e, fpname, lineno, line)
+ optname = self.optionxform(optname.rstrip())
+ if (self._strict and
+ (sectname, optname) in elements_added):
+ raise DuplicateOptionError(sectname, optname,
+ fpname, lineno)
+ elements_added.add((sectname, optname))
+ # This check is fine because the OPTCRE cannot
+ # match if it would set optval to None
+ if optval is not None:
+ optval = optval.strip()
+ cursect[optname] = [optval]
+ else:
+ # valueless option handling
+ cursect[optname] = None
+ else:
+ # a non-fatal parsing error occurred. set up the
+ # exception but keep going. the exception will be
+ # raised at the end of the file and will contain a
+ # list of all bogus lines
+ e = self._handle_error(e, fpname, lineno, line)
+ finally:
+ self._join_multiline_values()
# if any parsing errors occurred, raise an exception
if e:
raise e
diff --git a/contrib/tools/python3/Lib/dataclasses.py b/contrib/tools/python3/Lib/dataclasses.py
index 3eacba840d..12b2dfd145 100644
--- a/contrib/tools/python3/Lib/dataclasses.py
+++ b/contrib/tools/python3/Lib/dataclasses.py
@@ -1168,8 +1168,10 @@ def _dataclass_setstate(self, state):
def _get_slots(cls):
match cls.__dict__.get('__slots__'):
+ # A class which does not define __slots__ at all is equivalent
+ # to a class defining __slots__ = ('__dict__', '__weakref__')
case None:
- return
+ yield from ('__dict__', '__weakref__')
case str(slot):
yield slot
# Slots may be any iterable, but we cannot handle an iterator
diff --git a/contrib/tools/python3/Lib/doctest.py b/contrib/tools/python3/Lib/doctest.py
index 087c52327f..ee48622031 100644
--- a/contrib/tools/python3/Lib/doctest.py
+++ b/contrib/tools/python3/Lib/doctest.py
@@ -1127,7 +1127,7 @@ class DocTestFinder:
obj = obj.fget
if inspect.isfunction(obj) and getattr(obj, '__doc__', None):
# We don't use `docstring` var here, because `obj` can be changed.
- obj = obj.__code__
+ obj = inspect.unwrap(obj).__code__
if inspect.istraceback(obj): obj = obj.tb_frame
if inspect.isframe(obj): obj = obj.f_code
if inspect.iscode(obj):
@@ -2206,13 +2206,13 @@ class DocTestCase(unittest.TestCase):
unittest.TestCase.__init__(self)
self._dt_optionflags = optionflags
self._dt_checker = checker
- self._dt_globs = test.globs.copy()
self._dt_test = test
self._dt_setUp = setUp
self._dt_tearDown = tearDown
def setUp(self):
test = self._dt_test
+ self._dt_globs = test.globs.copy()
if self._dt_setUp is not None:
self._dt_setUp(test)
diff --git a/contrib/tools/python3/Lib/email/_header_value_parser.py b/contrib/tools/python3/Lib/email/_header_value_parser.py
index 5b653f66c1..e4a342d446 100644
--- a/contrib/tools/python3/Lib/email/_header_value_parser.py
+++ b/contrib/tools/python3/Lib/email/_header_value_parser.py
@@ -949,6 +949,7 @@ class _InvalidEwError(errors.HeaderParseError):
# up other parse trees. Maybe should have tests for that, too.
DOT = ValueTerminal('.', 'dot')
ListSeparator = ValueTerminal(',', 'list-separator')
+ListSeparator.as_ew_allowed = False
RouteComponentMarker = ValueTerminal('@', 'route-component-marker')
#
@@ -2022,7 +2023,7 @@ def get_address_list(value):
address_list.defects.append(errors.InvalidHeaderDefect(
"invalid address in address-list"))
if value: # Must be a , at this point.
- address_list.append(ValueTerminal(',', 'list-separator'))
+ address_list.append(ListSeparator)
value = value[1:]
return address_list, value
diff --git a/contrib/tools/python3/Lib/email/generator.py b/contrib/tools/python3/Lib/email/generator.py
index 7ccbe10eb7..c8056ad47b 100644
--- a/contrib/tools/python3/Lib/email/generator.py
+++ b/contrib/tools/python3/Lib/email/generator.py
@@ -243,7 +243,7 @@ class Generator:
# existing message.
msg = deepcopy(msg)
del msg['content-transfer-encoding']
- msg.set_payload(payload, charset)
+ msg.set_payload(msg._payload, charset)
payload = msg.get_payload()
self._munge_cte = (msg['content-transfer-encoding'],
msg['content-type'])
diff --git a/contrib/tools/python3/Lib/email/message.py b/contrib/tools/python3/Lib/email/message.py
index fe769580fe..a14cca56b3 100644
--- a/contrib/tools/python3/Lib/email/message.py
+++ b/contrib/tools/python3/Lib/email/message.py
@@ -340,7 +340,7 @@ class Message:
return
if not isinstance(charset, Charset):
charset = Charset(charset)
- payload = payload.encode(charset.output_charset)
+ payload = payload.encode(charset.output_charset, 'surrogateescape')
if hasattr(payload, 'decode'):
self._payload = payload.decode('ascii', 'surrogateescape')
else:
diff --git a/contrib/tools/python3/Lib/enum.py b/contrib/tools/python3/Lib/enum.py
index 1502bfe915..af5613838d 100644
--- a/contrib/tools/python3/Lib/enum.py
+++ b/contrib/tools/python3/Lib/enum.py
@@ -166,6 +166,11 @@ def _dedent(text):
lines[j] = l[i:]
return '\n'.join(lines)
+class _not_given:
+ def __repr__(self):
+ return('<not given>')
+_not_given = _not_given()
+
class _auto_null:
def __repr__(self):
return '_auto_null'
@@ -283,9 +288,10 @@ class _proto_member:
enum_member._sort_order_ = len(enum_class._member_names_)
if Flag is not None and issubclass(enum_class, Flag):
- enum_class._flag_mask_ |= value
- if _is_single_bit(value):
- enum_class._singles_mask_ |= value
+ if isinstance(value, int):
+ enum_class._flag_mask_ |= value
+ if _is_single_bit(value):
+ enum_class._singles_mask_ |= value
enum_class._all_bits_ = 2 ** ((enum_class._flag_mask_).bit_length()) - 1
# If another member with the same value was already defined, the
@@ -313,6 +319,7 @@ class _proto_member:
elif (
Flag is not None
and issubclass(enum_class, Flag)
+ and isinstance(value, int)
and _is_single_bit(value)
):
# no other instances found, record this member in _member_names_
@@ -457,10 +464,11 @@ class _EnumDict(dict):
if isinstance(value, auto):
single = True
value = (value, )
- if type(value) is tuple and any(isinstance(v, auto) for v in value):
+ if isinstance(value, tuple) and any(isinstance(v, auto) for v in value):
# insist on an actual tuple, no subclasses, in keeping with only supporting
# top-level auto() usage (not contained in any other data structure)
auto_valued = []
+ t = type(value)
for v in value:
if isinstance(v, auto):
non_auto_store = False
@@ -475,7 +483,12 @@ class _EnumDict(dict):
if single:
value = auto_valued[0]
else:
- value = tuple(auto_valued)
+ try:
+ # accepts iterable as multiple arguments?
+ value = t(auto_valued)
+ except TypeError:
+ # then pass them in singlely
+ value = t(*auto_valued)
self._member_names[key] = None
if non_auto_store:
self._last_values.append(value)
@@ -710,7 +723,7 @@ class EnumType(type):
"""
return True
- def __call__(cls, value, names=None, *values, module=None, qualname=None, type=None, start=1, boundary=None):
+ def __call__(cls, value, names=_not_given, *values, module=None, qualname=None, type=None, start=1, boundary=None):
"""
Either returns an existing member, or creates a new enum class.
@@ -739,18 +752,18 @@ class EnumType(type):
"""
if cls._member_map_:
# simple value lookup if members exist
- if names:
+ if names is not _not_given:
value = (value, names) + values
return cls.__new__(cls, value)
# otherwise, functional API: we're creating a new Enum type
- if names is None and type is None:
+ if names is _not_given and type is None:
# no body? no data-type? possibly wrong usage
raise TypeError(
f"{cls} has no members; specify `names=()` if you meant to create a new, empty, enum"
)
return cls._create_(
class_name=value,
- names=names,
+ names=None if names is _not_given else names,
module=module,
qualname=qualname,
type=type,
@@ -1528,37 +1541,50 @@ class Flag(Enum, boundary=STRICT):
def __bool__(self):
return bool(self._value_)
+ def _get_value(self, flag):
+ if isinstance(flag, self.__class__):
+ return flag._value_
+ elif self._member_type_ is not object and isinstance(flag, self._member_type_):
+ return flag
+ return NotImplemented
+
def __or__(self, other):
- if isinstance(other, self.__class__):
- other = other._value_
- elif self._member_type_ is not object and isinstance(other, self._member_type_):
- other = other
- else:
+ other_value = self._get_value(other)
+ if other_value is NotImplemented:
return NotImplemented
+
+ for flag in self, other:
+ if self._get_value(flag) is None:
+ raise TypeError(f"'{flag}' cannot be combined with other flags with |")
value = self._value_
- return self.__class__(value | other)
+ return self.__class__(value | other_value)
def __and__(self, other):
- if isinstance(other, self.__class__):
- other = other._value_
- elif self._member_type_ is not object and isinstance(other, self._member_type_):
- other = other
- else:
+ other_value = self._get_value(other)
+ if other_value is NotImplemented:
return NotImplemented
+
+ for flag in self, other:
+ if self._get_value(flag) is None:
+ raise TypeError(f"'{flag}' cannot be combined with other flags with &")
value = self._value_
- return self.__class__(value & other)
+ return self.__class__(value & other_value)
def __xor__(self, other):
- if isinstance(other, self.__class__):
- other = other._value_
- elif self._member_type_ is not object and isinstance(other, self._member_type_):
- other = other
- else:
+ other_value = self._get_value(other)
+ if other_value is NotImplemented:
return NotImplemented
+
+ for flag in self, other:
+ if self._get_value(flag) is None:
+ raise TypeError(f"'{flag}' cannot be combined with other flags with ^")
value = self._value_
- return self.__class__(value ^ other)
+ return self.__class__(value ^ other_value)
def __invert__(self):
+ if self._get_value(self) is None:
+ raise TypeError(f"'{self}' cannot be inverted")
+
if self._inverted_ is None:
if self._boundary_ in (EJECT, KEEP):
self._inverted_ = self.__class__(~self._value_)
@@ -1625,7 +1651,7 @@ def global_flag_repr(self):
cls_name = self.__class__.__name__
if self._name_ is None:
return "%s.%s(%r)" % (module, cls_name, self._value_)
- if _is_single_bit(self):
+ if _is_single_bit(self._value_):
return '%s.%s' % (module, self._name_)
if self._boundary_ is not FlagBoundary.KEEP:
return '|'.join(['%s.%s' % (module, name) for name in self.name.split('|')])
diff --git a/contrib/tools/python3/Lib/glob.py b/contrib/tools/python3/Lib/glob.py
index a7256422d5..50beef37f4 100644
--- a/contrib/tools/python3/Lib/glob.py
+++ b/contrib/tools/python3/Lib/glob.py
@@ -132,7 +132,8 @@ def glob1(dirname, pattern):
def _glob2(dirname, pattern, dir_fd, dironly, include_hidden=False):
assert _isrecursive(pattern)
- yield pattern[:0]
+ if not dirname or _isdir(dirname, dir_fd):
+ yield pattern[:0]
yield from _rlistdir(dirname, dir_fd, dironly,
include_hidden=include_hidden)
diff --git a/contrib/tools/python3/Lib/http/client.py b/contrib/tools/python3/Lib/http/client.py
index 5eebfccafb..a353716a85 100644
--- a/contrib/tools/python3/Lib/http/client.py
+++ b/contrib/tools/python3/Lib/http/client.py
@@ -936,17 +936,23 @@ class HTTPConnection:
host = host[:i]
else:
port = self.default_port
- if host and host[0] == '[' and host[-1] == ']':
- host = host[1:-1]
+ if host and host[0] == '[' and host[-1] == ']':
+ host = host[1:-1]
return (host, port)
def set_debuglevel(self, level):
self.debuglevel = level
+ def _wrap_ipv6(self, ip):
+ if b':' in ip and ip[0] != b'['[0]:
+ return b"[" + ip + b"]"
+ return ip
+
def _tunnel(self):
connect = b"CONNECT %s:%d %s\r\n" % (
- self._tunnel_host.encode("idna"), self._tunnel_port,
+ self._wrap_ipv6(self._tunnel_host.encode("idna")),
+ self._tunnel_port,
self._http_vsn_str.encode("ascii"))
headers = [connect]
for header, value in self._tunnel_headers.items():
@@ -1221,9 +1227,8 @@ class HTTPConnection:
# As per RFC 273, IPv6 address should be wrapped with []
# when used as Host header
-
+ host_enc = self._wrap_ipv6(host_enc)
if ":" in host:
- host_enc = b'[' + host_enc + b']'
host_enc = _strip_ipv6_iface(host_enc)
if port == self.default_port:
diff --git a/contrib/tools/python3/Lib/importlib/_bootstrap_external.py b/contrib/tools/python3/Lib/importlib/_bootstrap_external.py
index e6f75a9f6f..61dafc0f4c 100644
--- a/contrib/tools/python3/Lib/importlib/_bootstrap_external.py
+++ b/contrib/tools/python3/Lib/importlib/_bootstrap_external.py
@@ -1450,6 +1450,9 @@ class PathFinder:
# https://bugs.python.org/issue45703
_NamespacePath._epoch += 1
+ from importlib.metadata import MetadataPathFinder
+ MetadataPathFinder.invalidate_caches()
+
@staticmethod
def _path_hooks(path):
"""Search sys.path_hooks for a finder for 'path'."""
diff --git a/contrib/tools/python3/Lib/importlib/metadata/__init__.py b/contrib/tools/python3/Lib/importlib/metadata/__init__.py
index 82e0ce1b28..54156e93af 100644
--- a/contrib/tools/python3/Lib/importlib/metadata/__init__.py
+++ b/contrib/tools/python3/Lib/importlib/metadata/__init__.py
@@ -795,6 +795,7 @@ class MetadataPathFinder(DistributionFinder):
path.search(prepared) for path in map(FastPath, paths)
)
+ @classmethod
def invalidate_caches(cls):
FastPath.__new__.cache_clear()
diff --git a/contrib/tools/python3/Lib/importlib/resources/simple.py b/contrib/tools/python3/Lib/importlib/resources/simple.py
index 7770c922c8..96f117fec6 100644
--- a/contrib/tools/python3/Lib/importlib/resources/simple.py
+++ b/contrib/tools/python3/Lib/importlib/resources/simple.py
@@ -88,7 +88,7 @@ class ResourceHandle(Traversable):
def open(self, mode='r', *args, **kwargs):
stream = self.parent.reader.open_binary(self.name)
if 'b' not in mode:
- stream = io.TextIOWrapper(*args, **kwargs)
+ stream = io.TextIOWrapper(stream, *args, **kwargs)
return stream
def joinpath(self, name):
diff --git a/contrib/tools/python3/Lib/importlib/util.py b/contrib/tools/python3/Lib/importlib/util.py
index f4d6e82331..3743e6aa91 100644
--- a/contrib/tools/python3/Lib/importlib/util.py
+++ b/contrib/tools/python3/Lib/importlib/util.py
@@ -13,6 +13,7 @@ from ._bootstrap_external import spec_from_file_location
import _imp
import sys
+import threading
import types
@@ -145,7 +146,7 @@ class _incompatible_extension_module_restrictions:
You can get the same effect as this function by implementing the
basic interface of multi-phase init (PEP 489) and lying about
- support for mulitple interpreters (or per-interpreter GIL).
+ support for multiple interpreters (or per-interpreter GIL).
"""
def __init__(self, *, disable_check):
@@ -171,36 +172,53 @@ class _LazyModule(types.ModuleType):
def __getattribute__(self, attr):
"""Trigger the load of the module and return the attribute."""
- # All module metadata must be garnered from __spec__ in order to avoid
- # using mutated values.
- # Stop triggering this method.
- self.__class__ = types.ModuleType
- # Get the original name to make sure no object substitution occurred
- # in sys.modules.
- original_name = self.__spec__.name
- # Figure out exactly what attributes were mutated between the creation
- # of the module and now.
- attrs_then = self.__spec__.loader_state['__dict__']
- attrs_now = self.__dict__
- attrs_updated = {}
- for key, value in attrs_now.items():
- # Code that set the attribute may have kept a reference to the
- # assigned object, making identity more important than equality.
- if key not in attrs_then:
- attrs_updated[key] = value
- elif id(attrs_now[key]) != id(attrs_then[key]):
- attrs_updated[key] = value
- self.__spec__.loader.exec_module(self)
- # If exec_module() was used directly there is no guarantee the module
- # object was put into sys.modules.
- if original_name in sys.modules:
- if id(self) != id(sys.modules[original_name]):
- raise ValueError(f"module object for {original_name!r} "
- "substituted in sys.modules during a lazy "
- "load")
- # Update after loading since that's what would happen in an eager
- # loading situation.
- self.__dict__.update(attrs_updated)
+ __spec__ = object.__getattribute__(self, '__spec__')
+ loader_state = __spec__.loader_state
+ with loader_state['lock']:
+ # Only the first thread to get the lock should trigger the load
+ # and reset the module's class. The rest can now getattr().
+ if object.__getattribute__(self, '__class__') is _LazyModule:
+ # Reentrant calls from the same thread must be allowed to proceed without
+ # triggering the load again.
+ # exec_module() and self-referential imports are the primary ways this can
+ # happen, but in any case we must return something to avoid deadlock.
+ if loader_state['is_loading']:
+ return object.__getattribute__(self, attr)
+ loader_state['is_loading'] = True
+
+ __dict__ = object.__getattribute__(self, '__dict__')
+
+ # All module metadata must be gathered from __spec__ in order to avoid
+ # using mutated values.
+ # Get the original name to make sure no object substitution occurred
+ # in sys.modules.
+ original_name = __spec__.name
+ # Figure out exactly what attributes were mutated between the creation
+ # of the module and now.
+ attrs_then = loader_state['__dict__']
+ attrs_now = __dict__
+ attrs_updated = {}
+ for key, value in attrs_now.items():
+ # Code that set an attribute may have kept a reference to the
+ # assigned object, making identity more important than equality.
+ if key not in attrs_then:
+ attrs_updated[key] = value
+ elif id(attrs_now[key]) != id(attrs_then[key]):
+ attrs_updated[key] = value
+ __spec__.loader.exec_module(self)
+ # If exec_module() was used directly there is no guarantee the module
+ # object was put into sys.modules.
+ if original_name in sys.modules:
+ if id(self) != id(sys.modules[original_name]):
+ raise ValueError(f"module object for {original_name!r} "
+ "substituted in sys.modules during a lazy "
+ "load")
+ # Update after loading since that's what would happen in an eager
+ # loading situation.
+ __dict__.update(attrs_updated)
+ # Finally, stop triggering this method.
+ self.__class__ = types.ModuleType
+
return getattr(self, attr)
def __delattr__(self, attr):
@@ -244,5 +262,7 @@ class LazyLoader(Loader):
loader_state = {}
loader_state['__dict__'] = module.__dict__.copy()
loader_state['__class__'] = module.__class__
+ loader_state['lock'] = threading.RLock()
+ loader_state['is_loading'] = False
module.__spec__.loader_state = loader_state
module.__class__ = _LazyModule
diff --git a/contrib/tools/python3/Lib/inspect.py b/contrib/tools/python3/Lib/inspect.py
index a550202bb0..819ce940ee 100644
--- a/contrib/tools/python3/Lib/inspect.py
+++ b/contrib/tools/python3/Lib/inspect.py
@@ -760,18 +760,14 @@ def unwrap(func, *, stop=None):
:exc:`ValueError` is raised if a cycle is encountered.
"""
- if stop is None:
- def _is_wrapper(f):
- return hasattr(f, '__wrapped__')
- else:
- def _is_wrapper(f):
- return hasattr(f, '__wrapped__') and not stop(f)
f = func # remember the original func for error reporting
# Memoise by id to tolerate non-hashable objects, but store objects to
# ensure they aren't destroyed, which would allow their IDs to be reused.
memo = {id(f): f}
recursion_limit = sys.getrecursionlimit()
- while _is_wrapper(func):
+ while not isinstance(func, type) and hasattr(func, '__wrapped__'):
+ if stop is not None and stop(func):
+ break
func = func.__wrapped__
id_func = id(func)
if (id_func in memo) or (len(memo) >= recursion_limit):
@@ -2007,15 +2003,17 @@ def _signature_get_user_defined_method(cls, method_name):
named ``method_name`` and returns it only if it is a
pure python function.
"""
- try:
- meth = getattr(cls, method_name)
- except AttributeError:
- return
+ if method_name == '__new__':
+ meth = getattr(cls, method_name, None)
else:
- if not isinstance(meth, _NonUserDefinedCallables):
- # Once '__signature__' will be added to 'C'-level
- # callables, this check won't be necessary
- return meth
+ meth = getattr_static(cls, method_name, None)
+ if meth is None or isinstance(meth, _NonUserDefinedCallables):
+ # Once '__signature__' will be added to 'C'-level
+ # callables, this check won't be necessary
+ return None
+ if method_name != '__new__':
+ meth = _descriptor_get(meth, cls)
+ return meth
def _signature_get_partial(wrapped_sig, partial, extra_args=()):
@@ -2460,6 +2458,15 @@ def _signature_from_function(cls, func, skip_bound_arg=True,
__validate_parameters__=is_duck_function)
+def _descriptor_get(descriptor, obj):
+ if isclass(descriptor):
+ return descriptor
+ get = getattr(type(descriptor), '__get__', _sentinel)
+ if get is _sentinel:
+ return descriptor
+ return get(descriptor, obj, type(obj))
+
+
def _signature_from_callable(obj, *,
follow_wrapper_chains=True,
skip_bound_arg=True,
@@ -2568,7 +2575,6 @@ def _signature_from_callable(obj, *,
wrapped_sig = _get_signature_of(obj.func)
return _signature_get_partial(wrapped_sig, obj)
- sig = None
if isinstance(obj, type):
# obj is a class or a metaclass
@@ -2576,88 +2582,65 @@ def _signature_from_callable(obj, *,
# in its metaclass
call = _signature_get_user_defined_method(type(obj), '__call__')
if call is not None:
- sig = _get_signature_of(call)
- else:
- factory_method = None
- new = _signature_get_user_defined_method(obj, '__new__')
- init = _signature_get_user_defined_method(obj, '__init__')
-
- # Go through the MRO and see if any class has user-defined
- # pure Python __new__ or __init__ method
- for base in obj.__mro__:
- # Now we check if the 'obj' class has an own '__new__' method
- if new is not None and '__new__' in base.__dict__:
- factory_method = new
- break
- # or an own '__init__' method
- elif init is not None and '__init__' in base.__dict__:
- factory_method = init
- break
+ return _get_signature_of(call)
- if factory_method is not None:
- sig = _get_signature_of(factory_method)
-
- if sig is None:
- # At this point we know, that `obj` is a class, with no user-
- # defined '__init__', '__new__', or class-level '__call__'
-
- for base in obj.__mro__[:-1]:
- # Since '__text_signature__' is implemented as a
- # descriptor that extracts text signature from the
- # class docstring, if 'obj' is derived from a builtin
- # class, its own '__text_signature__' may be 'None'.
- # Therefore, we go through the MRO (except the last
- # class in there, which is 'object') to find the first
- # class with non-empty text signature.
- try:
- text_sig = base.__text_signature__
- except AttributeError:
- pass
- else:
- if text_sig:
- # If 'base' class has a __text_signature__ attribute:
- # return a signature based on it
- return _signature_fromstr(sigcls, base, text_sig)
-
- # No '__text_signature__' was found for the 'obj' class.
- # Last option is to check if its '__init__' is
- # object.__init__ or type.__init__.
- if type not in obj.__mro__:
- # We have a class (not metaclass), but no user-defined
- # __init__ or __new__ for it
- if (obj.__init__ is object.__init__ and
- obj.__new__ is object.__new__):
- # Return a signature of 'object' builtin.
- return sigcls.from_callable(object)
- else:
- raise ValueError(
- 'no signature found for builtin type {!r}'.format(obj))
+ new = _signature_get_user_defined_method(obj, '__new__')
+ init = _signature_get_user_defined_method(obj, '__init__')
- elif not isinstance(obj, _NonUserDefinedCallables):
- # An object with __call__
- # We also check that the 'obj' is not an instance of
- # types.WrapperDescriptorType or types.MethodWrapperType to avoid
- # infinite recursion (and even potential segfault)
- call = _signature_get_user_defined_method(type(obj), '__call__')
- if call is not None:
+ # Go through the MRO and see if any class has user-defined
+ # pure Python __new__ or __init__ method
+ for base in obj.__mro__:
+ # Now we check if the 'obj' class has an own '__new__' method
+ if new is not None and '__new__' in base.__dict__:
+ sig = _get_signature_of(new)
+ if skip_bound_arg:
+ sig = _signature_bound_method(sig)
+ return sig
+ # or an own '__init__' method
+ elif init is not None and '__init__' in base.__dict__:
+ return _get_signature_of(init)
+
+ # At this point we know, that `obj` is a class, with no user-
+ # defined '__init__', '__new__', or class-level '__call__'
+
+ for base in obj.__mro__[:-1]:
+ # Since '__text_signature__' is implemented as a
+ # descriptor that extracts text signature from the
+ # class docstring, if 'obj' is derived from a builtin
+ # class, its own '__text_signature__' may be 'None'.
+ # Therefore, we go through the MRO (except the last
+ # class in there, which is 'object') to find the first
+ # class with non-empty text signature.
try:
- sig = _get_signature_of(call)
- except ValueError as ex:
- msg = 'no signature found for {!r}'.format(obj)
- raise ValueError(msg) from ex
-
- if sig is not None:
- # For classes and objects we skip the first parameter of their
- # __call__, __new__, or __init__ methods
- if skip_bound_arg:
- return _signature_bound_method(sig)
- else:
- return sig
+ text_sig = base.__text_signature__
+ except AttributeError:
+ pass
+ else:
+ if text_sig:
+ # If 'base' class has a __text_signature__ attribute:
+ # return a signature based on it
+ return _signature_fromstr(sigcls, base, text_sig)
+
+ # No '__text_signature__' was found for the 'obj' class.
+ # Last option is to check if its '__init__' is
+ # object.__init__ or type.__init__.
+ if type not in obj.__mro__:
+ # We have a class (not metaclass), but no user-defined
+ # __init__ or __new__ for it
+ if (obj.__init__ is object.__init__ and
+ obj.__new__ is object.__new__):
+ # Return a signature of 'object' builtin.
+ return sigcls.from_callable(object)
+ else:
+ raise ValueError(
+ 'no signature found for builtin type {!r}'.format(obj))
- if isinstance(obj, types.BuiltinFunctionType):
- # Raise a nicer error message for builtins
- msg = 'no signature found for builtin function {!r}'.format(obj)
- raise ValueError(msg)
+ else:
+ # An object with __call__
+ call = getattr_static(type(obj), '__call__', None)
+ if call is not None:
+ call = _descriptor_get(call, obj)
+ return _get_signature_of(call)
raise ValueError('callable {!r} is not supported by signature'.format(obj))
diff --git a/contrib/tools/python3/Lib/json/encoder.py b/contrib/tools/python3/Lib/json/encoder.py
index 45f5477418..597849eca0 100644
--- a/contrib/tools/python3/Lib/json/encoder.py
+++ b/contrib/tools/python3/Lib/json/encoder.py
@@ -174,7 +174,7 @@ class JSONEncoder(object):
else:
return list(iterable)
# Let the base class default method raise the TypeError
- return JSONEncoder.default(self, o)
+ return super().default(o)
"""
raise TypeError(f'Object of type {o.__class__.__name__} '
diff --git a/contrib/tools/python3/Lib/linecache.py b/contrib/tools/python3/Lib/linecache.py
index b164e727c6..5585216d2b 100644
--- a/contrib/tools/python3/Lib/linecache.py
+++ b/contrib/tools/python3/Lib/linecache.py
@@ -178,13 +178,11 @@ def lazycache(filename, module_globals):
return False
# Try for a __loader__, if available
if module_globals and '__name__' in module_globals:
- name = module_globals['__name__']
- if (loader := module_globals.get('__loader__')) is None:
- if spec := module_globals.get('__spec__'):
- try:
- loader = spec.loader
- except AttributeError:
- pass
+ spec = module_globals.get('__spec__')
+ name = getattr(spec, 'name', None) or module_globals['__name__']
+ loader = getattr(spec, 'loader', None)
+ if loader is None:
+ loader = module_globals.get('__loader__')
get_source = getattr(loader, 'get_source', None)
if name and get_source:
diff --git a/contrib/tools/python3/Lib/logging/__init__.py b/contrib/tools/python3/Lib/logging/__init__.py
index 056380fb22..22d3198332 100644
--- a/contrib/tools/python3/Lib/logging/__init__.py
+++ b/contrib/tools/python3/Lib/logging/__init__.py
@@ -1521,7 +1521,7 @@ class Logger(Filterer):
To pass exception information, use the keyword argument exc_info with
a true value, e.g.
- logger.debug("Houston, we have a %s", "thorny problem", exc_info=1)
+ logger.debug("Houston, we have a %s", "thorny problem", exc_info=True)
"""
if self.isEnabledFor(DEBUG):
self._log(DEBUG, msg, args, **kwargs)
@@ -1533,7 +1533,7 @@ class Logger(Filterer):
To pass exception information, use the keyword argument exc_info with
a true value, e.g.
- logger.info("Houston, we have a %s", "notable problem", exc_info=1)
+ logger.info("Houston, we have a %s", "notable problem", exc_info=True)
"""
if self.isEnabledFor(INFO):
self._log(INFO, msg, args, **kwargs)
@@ -1545,7 +1545,7 @@ class Logger(Filterer):
To pass exception information, use the keyword argument exc_info with
a true value, e.g.
- logger.warning("Houston, we have a %s", "bit of a problem", exc_info=1)
+ logger.warning("Houston, we have a %s", "bit of a problem", exc_info=True)
"""
if self.isEnabledFor(WARNING):
self._log(WARNING, msg, args, **kwargs)
@@ -1562,7 +1562,7 @@ class Logger(Filterer):
To pass exception information, use the keyword argument exc_info with
a true value, e.g.
- logger.error("Houston, we have a %s", "major problem", exc_info=1)
+ logger.error("Houston, we have a %s", "major problem", exc_info=True)
"""
if self.isEnabledFor(ERROR):
self._log(ERROR, msg, args, **kwargs)
@@ -1580,7 +1580,7 @@ class Logger(Filterer):
To pass exception information, use the keyword argument exc_info with
a true value, e.g.
- logger.critical("Houston, we have a %s", "major disaster", exc_info=1)
+ logger.critical("Houston, we have a %s", "major disaster", exc_info=True)
"""
if self.isEnabledFor(CRITICAL):
self._log(CRITICAL, msg, args, **kwargs)
@@ -1598,7 +1598,7 @@ class Logger(Filterer):
To pass exception information, use the keyword argument exc_info with
a true value, e.g.
- logger.log(level, "We have a %s", "mysterious problem", exc_info=1)
+ logger.log(level, "We have a %s", "mysterious problem", exc_info=True)
"""
if not isinstance(level, int):
if raiseExceptions:
@@ -1985,18 +1985,11 @@ class LoggerAdapter(object):
"""
return self.logger.hasHandlers()
- def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False):
+ def _log(self, level, msg, args, **kwargs):
"""
Low-level log implementation, proxied to allow nested logger adapters.
"""
- return self.logger._log(
- level,
- msg,
- args,
- exc_info=exc_info,
- extra=extra,
- stack_info=stack_info,
- )
+ return self.logger._log(level, msg, args, **kwargs)
@property
def manager(self):
@@ -2056,7 +2049,7 @@ def basicConfig(**kwargs):
that this argument is incompatible with 'filename' - if both
are present, 'stream' is ignored.
handlers If specified, this should be an iterable of already created
- handlers, which will be added to the root handler. Any handler
+ handlers, which will be added to the root logger. Any handler
in the list which does not have a formatter assigned will be
assigned the formatter created in this function.
force If this keyword is specified as true, any existing handlers
diff --git a/contrib/tools/python3/Lib/logging/handlers.py b/contrib/tools/python3/Lib/logging/handlers.py
index 6e88184b51..1ae6bb8443 100644
--- a/contrib/tools/python3/Lib/logging/handlers.py
+++ b/contrib/tools/python3/Lib/logging/handlers.py
@@ -232,19 +232,19 @@ class TimedRotatingFileHandler(BaseRotatingHandler):
if self.when == 'S':
self.interval = 1 # one second
self.suffix = "%Y-%m-%d_%H-%M-%S"
- self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}(\.\w+)?$"
+ extMatch = r"(?<!\d)\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}(?!\d)"
elif self.when == 'M':
self.interval = 60 # one minute
self.suffix = "%Y-%m-%d_%H-%M"
- self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}(\.\w+)?$"
+ extMatch = r"(?<!\d)\d{4}-\d{2}-\d{2}_\d{2}-\d{2}(?!\d)"
elif self.when == 'H':
self.interval = 60 * 60 # one hour
self.suffix = "%Y-%m-%d_%H"
- self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}(\.\w+)?$"
+ extMatch = r"(?<!\d)\d{4}-\d{2}-\d{2}_\d{2}(?!\d)"
elif self.when == 'D' or self.when == 'MIDNIGHT':
self.interval = 60 * 60 * 24 # one day
self.suffix = "%Y-%m-%d"
- self.extMatch = r"^\d{4}-\d{2}-\d{2}(\.\w+)?$"
+ extMatch = r"(?<!\d)\d{4}-\d{2}-\d{2}(?!\d)"
elif self.when.startswith('W'):
self.interval = 60 * 60 * 24 * 7 # one week
if len(self.when) != 2:
@@ -253,11 +253,17 @@ class TimedRotatingFileHandler(BaseRotatingHandler):
raise ValueError("Invalid day specified for weekly rollover: %s" % self.when)
self.dayOfWeek = int(self.when[1])
self.suffix = "%Y-%m-%d"
- self.extMatch = r"^\d{4}-\d{2}-\d{2}(\.\w+)?$"
+ extMatch = r"(?<!\d)\d{4}-\d{2}-\d{2}(?!\d)"
else:
raise ValueError("Invalid rollover interval specified: %s" % self.when)
- self.extMatch = re.compile(self.extMatch, re.ASCII)
+ # extMatch is a pattern for matching a datetime suffix in a file name.
+ # After custom naming, it is no longer guaranteed to be separated by
+ # periods from other parts of the filename. The lookup statements
+ # (?<!\d) and (?!\d) ensure that the datetime suffix (which itself
+ # starts and ends with digits) is not preceded or followed by digits.
+ # This reduces the number of false matches and improves performance.
+ self.extMatch = re.compile(extMatch, re.ASCII)
self.interval = self.interval * interval # multiply by units requested
# The following line added because the filename passed in could be a
# path object (see Issue #27493), but self.baseFilename will be a string
@@ -299,7 +305,7 @@ class TimedRotatingFileHandler(BaseRotatingHandler):
r = rotate_ts - ((currentHour * 60 + currentMinute) * 60 +
currentSecond)
- if r < 0:
+ if r <= 0:
# Rotate time is before the current time (for example when
# self.rotateAt is 13:45 and it now 14:15), rotation is
# tomorrow.
@@ -328,17 +334,21 @@ class TimedRotatingFileHandler(BaseRotatingHandler):
daysToWait = self.dayOfWeek - day
else:
daysToWait = 6 - day + self.dayOfWeek + 1
- newRolloverAt = result + (daysToWait * (60 * 60 * 24))
- if not self.utc:
- dstNow = t[-1]
- dstAtRollover = time.localtime(newRolloverAt)[-1]
- if dstNow != dstAtRollover:
- if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour
- addend = -3600
- else: # DST bows out before next rollover, so we need to add an hour
- addend = 3600
- newRolloverAt += addend
- result = newRolloverAt
+ result += daysToWait * _MIDNIGHT
+ result += self.interval - _MIDNIGHT * 7
+ else:
+ result += self.interval - _MIDNIGHT
+ if not self.utc:
+ dstNow = t[-1]
+ dstAtRollover = time.localtime(result)[-1]
+ if dstNow != dstAtRollover:
+ if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour
+ addend = -3600
+ if not time.localtime(result-3600)[-1]:
+ addend = 0
+ else: # DST bows out before next rollover, so we need to add an hour
+ addend = 3600
+ result += addend
return result
def shouldRollover(self, record):
@@ -369,32 +379,28 @@ class TimedRotatingFileHandler(BaseRotatingHandler):
dirName, baseName = os.path.split(self.baseFilename)
fileNames = os.listdir(dirName)
result = []
- # See bpo-44753: Don't use the extension when computing the prefix.
- n, e = os.path.splitext(baseName)
- prefix = n + '.'
- plen = len(prefix)
- for fileName in fileNames:
- if self.namer is None:
- # Our files will always start with baseName
- if not fileName.startswith(baseName):
- continue
- else:
- # Our files could be just about anything after custom naming, but
- # likely candidates are of the form
- # foo.log.DATETIME_SUFFIX or foo.DATETIME_SUFFIX.log
- if (not fileName.startswith(baseName) and fileName.endswith(e) and
- len(fileName) > (plen + 1) and not fileName[plen+1].isdigit()):
- continue
-
- if fileName[:plen] == prefix:
- suffix = fileName[plen:]
- # See bpo-45628: The date/time suffix could be anywhere in the
- # filename
- parts = suffix.split('.')
- for part in parts:
- if self.extMatch.match(part):
+ if self.namer is None:
+ prefix = baseName + '.'
+ plen = len(prefix)
+ for fileName in fileNames:
+ if fileName[:plen] == prefix:
+ suffix = fileName[plen:]
+ if self.extMatch.fullmatch(suffix):
+ result.append(os.path.join(dirName, fileName))
+ else:
+ for fileName in fileNames:
+ # Our files could be just about anything after custom naming,
+ # but they should contain the datetime suffix.
+ # Try to find the datetime suffix in the file name and verify
+ # that the file name can be generated by this handler.
+ m = self.extMatch.search(fileName)
+ while m:
+ dfn = self.namer(self.baseFilename + "." + m[0])
+ if os.path.basename(dfn) == fileName:
result.append(os.path.join(dirName, fileName))
break
+ m = self.extMatch.search(fileName, m.start() + 1)
+
if len(result) < self.backupCount:
result = []
else:
@@ -410,17 +416,14 @@ class TimedRotatingFileHandler(BaseRotatingHandler):
then we have to get a list of matching filenames, sort them and remove
the one with the oldest suffix.
"""
- if self.stream:
- self.stream.close()
- self.stream = None
# get the time that this sequence started at and make it a TimeTuple
currentTime = int(time.time())
- dstNow = time.localtime(currentTime)[-1]
t = self.rolloverAt - self.interval
if self.utc:
timeTuple = time.gmtime(t)
else:
timeTuple = time.localtime(t)
+ dstNow = time.localtime(currentTime)[-1]
dstThen = timeTuple[-1]
if dstNow != dstThen:
if dstNow:
@@ -431,26 +434,19 @@ class TimedRotatingFileHandler(BaseRotatingHandler):
dfn = self.rotation_filename(self.baseFilename + "." +
time.strftime(self.suffix, timeTuple))
if os.path.exists(dfn):
- os.remove(dfn)
+ # Already rolled over.
+ return
+
+ if self.stream:
+ self.stream.close()
+ self.stream = None
self.rotate(self.baseFilename, dfn)
if self.backupCount > 0:
for s in self.getFilesToDelete():
os.remove(s)
if not self.delay:
self.stream = self._open()
- newRolloverAt = self.computeRollover(currentTime)
- while newRolloverAt <= currentTime:
- newRolloverAt = newRolloverAt + self.interval
- #If DST changes and midnight or weekly rollover, adjust for this.
- if (self.when == 'MIDNIGHT' or self.when.startswith('W')) and not self.utc:
- dstAtRollover = time.localtime(newRolloverAt)[-1]
- if dstNow != dstAtRollover:
- if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour
- addend = -3600
- else: # DST bows out before next rollover, so we need to add an hour
- addend = 3600
- newRolloverAt += addend
- self.rolloverAt = newRolloverAt
+ self.rolloverAt = self.computeRollover(currentTime)
class WatchedFileHandler(logging.FileHandler):
"""
diff --git a/contrib/tools/python3/Lib/mailbox.py b/contrib/tools/python3/Lib/mailbox.py
index c8b3444f64..c7f060ce52 100644
--- a/contrib/tools/python3/Lib/mailbox.py
+++ b/contrib/tools/python3/Lib/mailbox.py
@@ -698,9 +698,13 @@ class _singlefileMailbox(Mailbox):
_sync_close(new_file)
# self._file is about to get replaced, so no need to sync.
self._file.close()
- # Make sure the new file's mode is the same as the old file's
- mode = os.stat(self._path).st_mode
- os.chmod(new_file.name, mode)
+ # Make sure the new file's mode and owner are the same as the old file's
+ info = os.stat(self._path)
+ os.chmod(new_file.name, info.st_mode)
+ try:
+ os.chown(new_file.name, info.st_uid, info.st_gid)
+ except (AttributeError, OSError):
+ pass
try:
os.rename(new_file.name, self._path)
except FileExistsError:
diff --git a/contrib/tools/python3/Lib/mimetypes.py b/contrib/tools/python3/Lib/mimetypes.py
index 37228de482..3cc027aa36 100644
--- a/contrib/tools/python3/Lib/mimetypes.py
+++ b/contrib/tools/python3/Lib/mimetypes.py
@@ -120,7 +120,13 @@ class MimeTypes:
but non-standard types.
"""
url = os.fspath(url)
- scheme, url = urllib.parse._splittype(url)
+ p = urllib.parse.urlparse(url)
+ if p.scheme and len(p.scheme) > 1:
+ scheme = p.scheme
+ url = p.path
+ else:
+ scheme = None
+ url = os.path.splitdrive(url)[1]
if scheme == 'data':
# syntax of data URLs:
# dataurl := "data:" [ mediatype ] [ ";base64" ] "," data
diff --git a/contrib/tools/python3/Lib/multiprocessing/connection.py b/contrib/tools/python3/Lib/multiprocessing/connection.py
index dbbf106f68..d0582e3cd5 100644
--- a/contrib/tools/python3/Lib/multiprocessing/connection.py
+++ b/contrib/tools/python3/Lib/multiprocessing/connection.py
@@ -476,8 +476,9 @@ class Listener(object):
'''
if self._listener is None:
raise OSError('listener is closed')
+
c = self._listener.accept()
- if self._authkey:
+ if self._authkey is not None:
deliver_challenge(c, self._authkey)
answer_challenge(c, self._authkey)
return c
diff --git a/contrib/tools/python3/Lib/os.py b/contrib/tools/python3/Lib/os.py
index 598c9e5023..7ee7d695d9 100644
--- a/contrib/tools/python3/Lib/os.py
+++ b/contrib/tools/python3/Lib/os.py
@@ -473,7 +473,7 @@ if {open, stat} <= supports_dir_fd and {scandir, stat} <= supports_fd:
# lstat()/open()/fstat() trick.
if not follow_symlinks:
orig_st = stat(top, follow_symlinks=False, dir_fd=dir_fd)
- topfd = open(top, O_RDONLY, dir_fd=dir_fd)
+ topfd = open(top, O_RDONLY | O_NONBLOCK, dir_fd=dir_fd)
try:
if (follow_symlinks or (st.S_ISDIR(orig_st.st_mode) and
path.samestat(orig_st, stat(topfd)))):
@@ -522,7 +522,7 @@ if {open, stat} <= supports_dir_fd and {scandir, stat} <= supports_fd:
assert entries is not None
name, entry = name
orig_st = entry.stat(follow_symlinks=False)
- dirfd = open(name, O_RDONLY, dir_fd=topfd)
+ dirfd = open(name, O_RDONLY | O_NONBLOCK, dir_fd=topfd)
except OSError as err:
if onerror is not None:
onerror(err)
diff --git a/contrib/tools/python3/Lib/pdb.py b/contrib/tools/python3/Lib/pdb.py
index a838a26b03..225c9f253e 100755
--- a/contrib/tools/python3/Lib/pdb.py
+++ b/contrib/tools/python3/Lib/pdb.py
@@ -154,6 +154,7 @@ class _ScriptTarget(str):
__name__='__main__',
__file__=self,
__builtins__=__builtins__,
+ __spec__=None,
)
@property
@@ -298,26 +299,13 @@ class Pdb(bdb.Bdb, cmd.Cmd):
# cache it here to ensure that modifications are not overwritten.
self.curframe_locals = self.curframe.f_locals
self.set_convenience_variable(self.curframe, '_frame', self.curframe)
- return self.execRcLines()
- # Can be executed earlier than 'setup' if desired
- def execRcLines(self):
- if not self.rcLines:
- return
- # local copy because of recursion
- rcLines = self.rcLines
- rcLines.reverse()
- # execute every line only once
- self.rcLines = []
- while rcLines:
- line = rcLines.pop().strip()
- if line and line[0] != '#':
- if self.onecmd(line):
- # if onecmd returns True, the command wants to exit
- # from the interaction, save leftover rc lines
- # to execute before next interaction
- self.rcLines += reversed(rcLines)
- return True
+ if self.rcLines:
+ self.cmdqueue = [
+ line for line in self.rcLines
+ if line.strip() and not line.strip().startswith("#")
+ ]
+ self.rcLines = []
# Override Bdb methods
@@ -430,12 +418,10 @@ class Pdb(bdb.Bdb, cmd.Cmd):
pass
else:
Pdb._previous_sigint_handler = None
- if self.setup(frame, traceback):
- # no interaction desired at this time (happens if .pdbrc contains
- # a command like "continue")
- self.forget()
- return
- self.print_stack_entry(self.stack[self.curindex])
+ self.setup(frame, traceback)
+ # if we have more commands to process, do not show the stack entry
+ if not self.cmdqueue:
+ self.print_stack_entry(self.stack[self.curindex])
self._cmdloop()
self.forget()
@@ -522,7 +508,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
if marker >= 0:
# queue up everything after marker
next = line[marker+2:].lstrip()
- self.cmdqueue.append(next)
+ self.cmdqueue.insert(0, next)
line = line[:marker].rstrip()
# Replace all the convenience variables
@@ -546,13 +532,12 @@ class Pdb(bdb.Bdb, cmd.Cmd):
"""Handles one command line during command list definition."""
cmd, arg, line = self.parseline(line)
if not cmd:
- return
+ return False
if cmd == 'silent':
self.commands_silent[self.commands_bnum] = True
- return # continue to handle other cmd def in the cmd list
+ return False # continue to handle other cmd def in the cmd list
elif cmd == 'end':
- self.cmdqueue = []
- return 1 # end of cmd list
+ return True # end of cmd list
cmdlist = self.commands[self.commands_bnum]
if arg:
cmdlist.append(cmd+' '+arg)
@@ -566,9 +551,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
# one of the resuming commands
if func.__name__ in self.commands_resuming:
self.commands_doprompt[self.commands_bnum] = False
- self.cmdqueue = []
- return 1
- return
+ return True
+ return False
# interface abstraction functions
diff --git a/contrib/tools/python3/Lib/pickletools.py b/contrib/tools/python3/Lib/pickletools.py
index 95a77aeb2a..51ee4a7a26 100644
--- a/contrib/tools/python3/Lib/pickletools.py
+++ b/contrib/tools/python3/Lib/pickletools.py
@@ -1253,7 +1253,7 @@ opcodes = [
stack_before=[],
stack_after=[pyint],
proto=2,
- doc="""Long integer using found-byte length.
+ doc="""Long integer using four-byte length.
A more efficient encoding of a Python long; the long4 encoding
says it all."""),
diff --git a/contrib/tools/python3/Lib/pydoc.py b/contrib/tools/python3/Lib/pydoc.py
index 84bbf588dc..9a8812392a 100755
--- a/contrib/tools/python3/Lib/pydoc.py
+++ b/contrib/tools/python3/Lib/pydoc.py
@@ -204,6 +204,19 @@ def classname(object, modname):
name = object.__module__ + '.' + name
return name
+def parentname(object, modname):
+ """Get a name of the enclosing class (qualified it with a module name
+ if necessary) or module."""
+ if '.' in object.__qualname__:
+ name = object.__qualname__.rpartition('.')[0]
+ if object.__module__ != modname:
+ return object.__module__ + '.' + name
+ else:
+ return name
+ else:
+ if object.__module__ != modname:
+ return object.__module__
+
def isdata(object):
"""Check if an object is of a type that probably means it's data."""
return not (inspect.ismodule(object) or inspect.isclass(object) or
@@ -298,13 +311,15 @@ def visiblename(name, all=None, obj=None):
return not name.startswith('_')
def classify_class_attrs(object):
- """Wrap inspect.classify_class_attrs, with fixup for data descriptors."""
+ """Wrap inspect.classify_class_attrs, with fixup for data descriptors and bound methods."""
results = []
for (name, kind, cls, value) in inspect.classify_class_attrs(object):
if inspect.isdatadescriptor(value):
kind = 'data descriptor'
if isinstance(value, property) and value.fset is None:
kind = 'readonly property'
+ elif kind == 'method' and _is_bound_method(value):
+ kind = 'static method'
results.append((name, kind, cls, value))
return results
@@ -514,7 +529,7 @@ class Doc:
'_thread', 'zipimport') or
(file.startswith(basedir) and
not file.startswith(os.path.join(basedir, 'site-packages')))) and
- object.__name__ not in ('xml.etree', 'test.pydoc_mod')):
+ object.__name__ not in ('xml.etree', 'test.test_pydoc.pydoc_mod')):
if docloc.startswith(("http://", "https://")):
docloc = "{}/{}.html".format(docloc.rstrip("/"), object.__name__.lower())
else:
@@ -658,6 +673,25 @@ class HTMLDoc(Doc):
module.__name__, name, classname(object, modname))
return classname(object, modname)
+ def parentlink(self, object, modname):
+ """Make a link for the enclosing class or module."""
+ link = None
+ name, module = object.__name__, sys.modules.get(object.__module__)
+ if hasattr(module, name) and getattr(module, name) is object:
+ if '.' in object.__qualname__:
+ name = object.__qualname__.rpartition('.')[0]
+ if object.__module__ != modname:
+ link = '%s.html#%s' % (module.__name__, name)
+ else:
+ link = '#%s' % name
+ else:
+ if object.__module__ != modname:
+ link = '%s.html' % module.__name__
+ if link:
+ return '<a href="%s">%s</a>' % (link, parentname(object, modname))
+ else:
+ return parentname(object, modname)
+
def modulelink(self, object):
"""Make a link for a module."""
return '<a href="%s.html">%s</a>' % (object.__name__, object.__name__)
@@ -902,7 +936,7 @@ class HTMLDoc(Doc):
push(self.docdata(value, name, mod))
else:
push(self.document(value, name, mod,
- funcs, classes, mdict, object))
+ funcs, classes, mdict, object, homecls))
push('\n')
return attrs
@@ -1025,24 +1059,44 @@ class HTMLDoc(Doc):
return self.grey('=' + self.repr(object))
def docroutine(self, object, name=None, mod=None,
- funcs={}, classes={}, methods={}, cl=None):
+ funcs={}, classes={}, methods={}, cl=None, homecls=None):
"""Produce HTML documentation for a function or method object."""
realname = object.__name__
name = name or realname
- anchor = (cl and cl.__name__ or '') + '-' + name
+ if homecls is None:
+ homecls = cl
+ anchor = ('' if cl is None else cl.__name__) + '-' + name
note = ''
- skipdocs = 0
+ skipdocs = False
+ imfunc = None
if _is_bound_method(object):
- imclass = object.__self__.__class__
- if cl:
- if imclass is not cl:
- note = ' from ' + self.classlink(imclass, mod)
+ imself = object.__self__
+ if imself is cl:
+ imfunc = getattr(object, '__func__', None)
+ elif inspect.isclass(imself):
+ note = ' class method of %s' % self.classlink(imself, mod)
else:
- if object.__self__ is not None:
- note = ' method of %s instance' % self.classlink(
- object.__self__.__class__, mod)
- else:
- note = ' unbound %s method' % self.classlink(imclass,mod)
+ note = ' method of %s instance' % self.classlink(
+ imself.__class__, mod)
+ elif (inspect.ismethoddescriptor(object) or
+ inspect.ismethodwrapper(object)):
+ try:
+ objclass = object.__objclass__
+ except AttributeError:
+ pass
+ else:
+ if cl is None:
+ note = ' unbound %s method' % self.classlink(objclass, mod)
+ elif objclass is not homecls:
+ note = ' from ' + self.classlink(objclass, mod)
+ else:
+ imfunc = object
+ if inspect.isfunction(imfunc) and homecls is not None and (
+ imfunc.__module__ != homecls.__module__ or
+ imfunc.__qualname__ != homecls.__qualname__ + '.' + realname):
+ pname = self.parentlink(imfunc, mod)
+ if pname:
+ note = ' from %s' % pname
if (inspect.iscoroutinefunction(object) or
inspect.isasyncgenfunction(object)):
@@ -1053,10 +1107,13 @@ class HTMLDoc(Doc):
if name == realname:
title = '<a name="%s"><strong>%s</strong></a>' % (anchor, realname)
else:
- if cl and inspect.getattr_static(cl, realname, []) is object:
+ if (cl is not None and
+ inspect.getattr_static(cl, realname, []) is object):
reallink = '<a href="#%s">%s</a>' % (
cl.__name__ + '-' + realname, realname)
- skipdocs = 1
+ skipdocs = True
+ if note.startswith(' from '):
+ note = ''
else:
reallink = realname
title = '<a name="%s"><strong>%s</strong></a> = %s' % (
@@ -1074,7 +1131,8 @@ class HTMLDoc(Doc):
# XXX lambda's won't usually have func_annotations['return']
# since the syntax doesn't support but it is possible.
# So removing parentheses isn't truly safe.
- argspec = argspec[1:-1] # remove parentheses
+ if not object.__annotations__:
+ argspec = argspec[1:-1] # remove parentheses
if not argspec:
argspec = '(...)'
@@ -1089,7 +1147,7 @@ class HTMLDoc(Doc):
doc = doc and '<dd><span class="code">%s</span></dd>' % doc
return '<dl><dt>%s</dt>%s</dl>\n' % (decl, doc)
- def docdata(self, object, name=None, mod=None, cl=None):
+ def docdata(self, object, name=None, mod=None, cl=None, *ignored):
"""Produce html documentation for a data descriptor."""
results = []
push = results.append
@@ -1200,7 +1258,7 @@ class TextDoc(Doc):
entry, modname, c, prefix + ' ')
return result
- def docmodule(self, object, name=None, mod=None):
+ def docmodule(self, object, name=None, mod=None, *ignored):
"""Produce text documentation for a given module object."""
name = object.__name__ # ignore the passed-in name
synop, desc = splitdoc(getdoc(object))
@@ -1384,7 +1442,7 @@ location listed above.
push(self.docdata(value, name, mod))
else:
push(self.document(value,
- name, mod, object))
+ name, mod, object, homecls))
return attrs
def spilldescriptors(msg, attrs, predicate):
@@ -1459,23 +1517,43 @@ location listed above.
"""Format an argument default value as text."""
return '=' + self.repr(object)
- def docroutine(self, object, name=None, mod=None, cl=None):
+ def docroutine(self, object, name=None, mod=None, cl=None, homecls=None):
"""Produce text documentation for a function or method object."""
realname = object.__name__
name = name or realname
+ if homecls is None:
+ homecls = cl
note = ''
- skipdocs = 0
+ skipdocs = False
+ imfunc = None
if _is_bound_method(object):
- imclass = object.__self__.__class__
- if cl:
- if imclass is not cl:
- note = ' from ' + classname(imclass, mod)
+ imself = object.__self__
+ if imself is cl:
+ imfunc = getattr(object, '__func__', None)
+ elif inspect.isclass(imself):
+ note = ' class method of %s' % classname(imself, mod)
else:
- if object.__self__ is not None:
- note = ' method of %s instance' % classname(
- object.__self__.__class__, mod)
- else:
- note = ' unbound %s method' % classname(imclass,mod)
+ note = ' method of %s instance' % classname(
+ imself.__class__, mod)
+ elif (inspect.ismethoddescriptor(object) or
+ inspect.ismethodwrapper(object)):
+ try:
+ objclass = object.__objclass__
+ except AttributeError:
+ pass
+ else:
+ if cl is None:
+ note = ' unbound %s method' % classname(objclass, mod)
+ elif objclass is not homecls:
+ note = ' from ' + classname(objclass, mod)
+ else:
+ imfunc = object
+ if inspect.isfunction(imfunc) and homecls is not None and (
+ imfunc.__module__ != homecls.__module__ or
+ imfunc.__qualname__ != homecls.__qualname__ + '.' + realname):
+ pname = parentname(imfunc, mod)
+ if pname:
+ note = ' from %s' % pname
if (inspect.iscoroutinefunction(object) or
inspect.isasyncgenfunction(object)):
@@ -1486,8 +1564,11 @@ location listed above.
if name == realname:
title = self.bold(realname)
else:
- if cl and inspect.getattr_static(cl, realname, []) is object:
- skipdocs = 1
+ if (cl is not None and
+ inspect.getattr_static(cl, realname, []) is object):
+ skipdocs = True
+ if note.startswith(' from '):
+ note = ''
title = self.bold(name) + ' = ' + realname
argspec = None
@@ -1503,7 +1584,8 @@ location listed above.
# XXX lambda's won't usually have func_annotations['return']
# since the syntax doesn't support but it is possible.
# So removing parentheses isn't truly safe.
- argspec = argspec[1:-1] # remove parentheses
+ if not object.__annotations__:
+ argspec = argspec[1:-1] # remove parentheses
if not argspec:
argspec = '(...)'
decl = asyncqualifier + title + argspec + note
@@ -1514,7 +1596,7 @@ location listed above.
doc = getdoc(object) or ''
return decl + '\n' + (doc and self.indent(doc).rstrip() + '\n')
- def docdata(self, object, name=None, mod=None, cl=None):
+ def docdata(self, object, name=None, mod=None, cl=None, *ignored):
"""Produce text documentation for a data descriptor."""
results = []
push = results.append
@@ -1530,7 +1612,8 @@ location listed above.
docproperty = docdata
- def docother(self, object, name=None, mod=None, parent=None, maxlen=None, doc=None):
+ def docother(self, object, name=None, mod=None, parent=None, *ignored,
+ maxlen=None, doc=None):
"""Produce text documentation for a data object."""
repr = self.repr(object)
if maxlen:
@@ -2410,6 +2493,7 @@ def _start_server(urlhandler, hostname, port):
threading.Thread.__init__(self)
self.serving = False
self.error = None
+ self.docserver = None
def run(self):
"""Start the server."""
@@ -2442,9 +2526,9 @@ def _start_server(urlhandler, hostname, port):
thread = ServerThread(urlhandler, hostname, port)
thread.start()
- # Wait until thread.serving is True to make sure we are
- # really up before returning.
- while not thread.error and not thread.serving:
+ # Wait until thread.serving is True and thread.docserver is set
+ # to make sure we are really up before returning.
+ while not thread.error and not (thread.serving and thread.docserver):
time.sleep(.01)
return thread
diff --git a/contrib/tools/python3/Lib/pydoc_data/topics.py b/contrib/tools/python3/Lib/pydoc_data/topics.py
index e97e543a87..0eb0e7dce5 100644
--- a/contrib/tools/python3/Lib/pydoc_data/topics.py
+++ b/contrib/tools/python3/Lib/pydoc_data/topics.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# Autogenerated by Sphinx on Tue Feb 6 21:16:37 2024
+# Autogenerated by Sphinx on Tue Apr 9 09:17:41 2024
# as part of the release process.
topics = {'assert': 'The "assert" statement\n'
'**********************\n'
@@ -722,9 +722,9 @@ topics = {'assert': 'The "assert" statement\n'
'\n'
'object.__dir__(self)\n'
'\n'
- ' Called when "dir()" is called on the object. A '
- 'sequence must be\n'
- ' returned. "dir()" converts the returned sequence to a '
+ ' Called when "dir()" is called on the object. An '
+ 'iterable must be\n'
+ ' returned. "dir()" converts the returned iterable to a '
'list and\n'
' sorts it.\n'
'\n'
@@ -751,8 +751,8 @@ topics = {'assert': 'The "assert" statement\n'
'returned.\n'
'\n'
'The "__dir__" function should accept no arguments, and '
- 'return a\n'
- 'sequence of strings that represents the names accessible '
+ 'return an\n'
+ 'iterable of strings that represents the names accessible '
'on module. If\n'
'present, this function overrides the standard "dir()" '
'search on a\n'
@@ -4724,7 +4724,7 @@ topics = {'assert': 'The "assert" statement\n'
'reflection,\n'
' and "__eq__()" and "__ne__()" are their own reflection. '
'If the\n'
- ' operands are of different types, and right operand’s '
+ ' operands are of different types, and the right operand’s '
'type is a\n'
' direct or indirect subclass of the left operand’s type, '
'the\n'
@@ -4734,6 +4734,11 @@ topics = {'assert': 'The "assert" statement\n'
'is not\n'
' considered.\n'
'\n'
+ ' When no appropriate method returns any value other than\n'
+ ' "NotImplemented", the "==" and "!=" operators will fall '
+ 'back to\n'
+ ' "is" and "is not", respectively.\n'
+ '\n'
'object.__hash__(self)\n'
'\n'
' Called by built-in function "hash()" and for operations '
@@ -5212,22 +5217,23 @@ topics = {'assert': 'The "assert" statement\n'
'the\n'
'current directory, it is read with "\'utf-8\'" encoding and '
'executed as\n'
- 'if it had been typed at the debugger prompt. This is '
- 'particularly\n'
- 'useful for aliases. If both files exist, the one in the home\n'
- 'directory is read first and aliases defined there can be '
- 'overridden by\n'
- 'the local file.\n'
- '\n'
- 'Changed in version 3.11: ".pdbrc" is now read with "\'utf-8\'" '
- 'encoding.\n'
- 'Previously, it was read with the system locale encoding.\n'
+ 'if it had been typed at the debugger prompt, with the exception '
+ 'that\n'
+ 'empty lines and lines starting with "#" are ignored. This is\n'
+ 'particularly useful for aliases. If both files exist, the one '
+ 'in the\n'
+ 'home directory is read first and aliases defined there can be\n'
+ 'overridden by the local file.\n'
'\n'
'Changed in version 3.2: ".pdbrc" can now contain commands that\n'
'continue debugging, such as "continue" or "next". Previously, '
'these\n'
'commands had no effect.\n'
'\n'
+ 'Changed in version 3.11: ".pdbrc" is now read with "\'utf-8\'" '
+ 'encoding.\n'
+ 'Previously, it was read with the system locale encoding.\n'
+ '\n'
'h(elp) [command]\n'
'\n'
' Without argument, print the list of available commands. With '
@@ -8559,32 +8565,36 @@ topics = {'assert': 'The "assert" statement\n'
'\n'
' nonlocal_stmt ::= "nonlocal" identifier ("," identifier)*\n'
'\n'
- 'The "nonlocal" statement causes the listed identifiers to refer '
- 'to\n'
- 'previously bound variables in the nearest enclosing scope '
- 'excluding\n'
- 'globals. This is important because the default behavior for '
- 'binding is\n'
- 'to search the local namespace first. The statement allows\n'
- 'encapsulated code to rebind variables outside of the local '
- 'scope\n'
- 'besides the global (module) scope.\n'
- '\n'
- 'Names listed in a "nonlocal" statement, unlike those listed in '
- 'a\n'
- '"global" statement, must refer to pre-existing bindings in an\n'
- 'enclosing scope (the scope in which a new binding should be '
- 'created\n'
- 'cannot be determined unambiguously).\n'
- '\n'
- 'Names listed in a "nonlocal" statement must not collide with '
- 'pre-\n'
- 'existing bindings in the local scope.\n'
+ 'When the definition of a function or class is nested (enclosed) '
+ 'within\n'
+ 'the definitions of other functions, its nonlocal scopes are the '
+ 'local\n'
+ 'scopes of the enclosing functions. The "nonlocal" statement '
+ 'causes the\n'
+ 'listed identifiers to refer to names previously bound in '
+ 'nonlocal\n'
+ 'scopes. It allows encapsulated code to rebind such nonlocal\n'
+ 'identifiers. If a name is bound in more than one nonlocal '
+ 'scope, the\n'
+ 'nearest binding is used. If a name is not bound in any nonlocal '
+ 'scope,\n'
+ 'or if there is no nonlocal scope, a "SyntaxError" is raised.\n'
+ '\n'
+ 'The nonlocal statement applies to the entire scope of a function '
+ 'or\n'
+ 'class body. A "SyntaxError" is raised if a variable is used or\n'
+ 'assigned to prior to its nonlocal declaration in the scope.\n'
'\n'
'See also:\n'
'\n'
' **PEP 3104** - Access to Names in Outer Scopes\n'
- ' The specification for the "nonlocal" statement.\n',
+ ' The specification for the "nonlocal" statement.\n'
+ '\n'
+ '**Programmer’s note:** "nonlocal" is a directive to the parser '
+ 'and\n'
+ 'applies only to code parsed along with it. See the note for '
+ 'the\n'
+ '"global" statement.\n',
'numbers': 'Numeric literals\n'
'****************\n'
'\n'
@@ -8680,7 +8690,7 @@ topics = {'assert': 'The "assert" statement\n'
'"__rsub__()"\n'
' method, "type(y).__rsub__(y, x)" is called if '
'"type(x).__sub__(x,\n'
- ' y)" returns *NotImplemented*.\n'
+ ' y)" returns "NotImplemented".\n'
'\n'
' Note that ternary "pow()" will not try calling '
'"__rpow__()" (the\n'
@@ -8723,14 +8733,18 @@ topics = {'assert': 'The "assert" statement\n'
'the result\n'
' (which could be, but does not have to be, *self*). If a '
'specific\n'
- ' method is not defined, the augmented assignment falls '
- 'back to the\n'
- ' normal methods. For instance, if *x* is an instance of '
- 'a class\n'
- ' with an "__iadd__()" method, "x += y" is equivalent to '
- '"x =\n'
- ' x.__iadd__(y)" . Otherwise, "x.__add__(y)" and '
- '"y.__radd__(x)" are\n'
+ ' method is not defined, or if that method returns '
+ '"NotImplemented",\n'
+ ' the augmented assignment falls back to the normal '
+ 'methods. For\n'
+ ' instance, if *x* is an instance of a class with an '
+ '"__iadd__()"\n'
+ ' method, "x += y" is equivalent to "x = x.__iadd__(y)" . '
+ 'If\n'
+ ' "__iadd__()" does not exist, or if "x.__iadd__(y)" '
+ 'returns\n'
+ ' "NotImplemented", "x.__add__(y)" and "y.__radd__(x)" '
+ 'are\n'
' considered, as with the evaluation of "x + y". In '
'certain\n'
' situations, augmented assignment can result in '
@@ -8811,7 +8825,7 @@ topics = {'assert': 'The "assert" statement\n'
'Every object has an identity, a type and a value. An object’s\n'
'*identity* never changes once it has been created; you may think '
'of it\n'
- 'as the object’s address in memory. The ‘"is"’ operator compares '
+ 'as the object’s address in memory. The "is" operator compares '
'the\n'
'identity of two objects; the "id()" function returns an integer\n'
'representing its identity.\n'
@@ -8876,7 +8890,7 @@ topics = {'assert': 'The "assert" statement\n'
'Note that the use of the implementation’s tracing or debugging\n'
'facilities may keep objects alive that would normally be '
'collectable.\n'
- 'Also note that catching an exception with a ‘"try"…"except"’ '
+ 'Also note that catching an exception with a "try"…"except" '
'statement\n'
'may keep objects alive.\n'
'\n'
@@ -8891,8 +8905,9 @@ topics = {'assert': 'The "assert" statement\n'
'release the external resource, usually a "close()" method. '
'Programs\n'
'are strongly recommended to explicitly close such objects. The\n'
- '‘"try"…"finally"’ statement and the ‘"with"’ statement provide\n'
- 'convenient ways to do this.\n'
+ '"try"…"finally" statement and the "with" statement provide '
+ 'convenient\n'
+ 'ways to do this.\n'
'\n'
'Some objects contain references to other objects; these are '
'called\n'
@@ -9269,10 +9284,7 @@ topics = {'assert': 'The "assert" statement\n'
'The try statement.\n'
'\n'
'Changed in version 3.3: "None" is now permitted as "Y" in "raise X\n'
- 'from Y".\n'
- '\n'
- 'New in version 3.3: The "__suppress_context__" attribute to '
- 'suppress\n'
+ 'from Y".Added the "__suppress_context__" attribute to suppress\n'
'automatic display of the exception context.\n'
'\n'
'Changed in version 3.11: If the traceback of the active exception '
@@ -10057,8 +10069,8 @@ topics = {'assert': 'The "assert" statement\n'
'reflection,\n'
' and "__eq__()" and "__ne__()" are their own reflection. '
'If the\n'
- ' operands are of different types, and right operand’s type '
- 'is a\n'
+ ' operands are of different types, and the right operand’s '
+ 'type is a\n'
' direct or indirect subclass of the left operand’s type, '
'the\n'
' reflected method of the right operand has priority, '
@@ -10067,6 +10079,11 @@ topics = {'assert': 'The "assert" statement\n'
'is not\n'
' considered.\n'
'\n'
+ ' When no appropriate method returns any value other than\n'
+ ' "NotImplemented", the "==" and "!=" operators will fall '
+ 'back to\n'
+ ' "is" and "is not", respectively.\n'
+ '\n'
'object.__hash__(self)\n'
'\n'
' Called by built-in function "hash()" and for operations '
@@ -10308,9 +10325,9 @@ topics = {'assert': 'The "assert" statement\n'
'\n'
'object.__dir__(self)\n'
'\n'
- ' Called when "dir()" is called on the object. A sequence '
+ ' Called when "dir()" is called on the object. An iterable '
'must be\n'
- ' returned. "dir()" converts the returned sequence to a '
+ ' returned. "dir()" converts the returned iterable to a '
'list and\n'
' sorts it.\n'
'\n'
@@ -10337,8 +10354,8 @@ topics = {'assert': 'The "assert" statement\n'
'returned.\n'
'\n'
'The "__dir__" function should accept no arguments, and '
- 'return a\n'
- 'sequence of strings that represents the names accessible on '
+ 'return an\n'
+ 'iterable of strings that represents the names accessible on '
'module. If\n'
'present, this function overrides the standard "dir()" search '
'on a\n'
@@ -11606,7 +11623,7 @@ topics = {'assert': 'The "assert" statement\n'
'"__rsub__()"\n'
' method, "type(y).__rsub__(y, x)" is called if '
'"type(x).__sub__(x,\n'
- ' y)" returns *NotImplemented*.\n'
+ ' y)" returns "NotImplemented".\n'
'\n'
' Note that ternary "pow()" will not try calling '
'"__rpow__()" (the\n'
@@ -11649,14 +11666,17 @@ topics = {'assert': 'The "assert" statement\n'
'the result\n'
' (which could be, but does not have to be, *self*). If a '
'specific\n'
- ' method is not defined, the augmented assignment falls '
- 'back to the\n'
- ' normal methods. For instance, if *x* is an instance of a '
- 'class\n'
- ' with an "__iadd__()" method, "x += y" is equivalent to "x '
- '=\n'
- ' x.__iadd__(y)" . Otherwise, "x.__add__(y)" and '
- '"y.__radd__(x)" are\n'
+ ' method is not defined, or if that method returns '
+ '"NotImplemented",\n'
+ ' the augmented assignment falls back to the normal '
+ 'methods. For\n'
+ ' instance, if *x* is an instance of a class with an '
+ '"__iadd__()"\n'
+ ' method, "x += y" is equivalent to "x = x.__iadd__(y)" . '
+ 'If\n'
+ ' "__iadd__()" does not exist, or if "x.__iadd__(y)" '
+ 'returns\n'
+ ' "NotImplemented", "x.__add__(y)" and "y.__radd__(x)" are\n'
' considered, as with the evaluation of "x + y". In '
'certain\n'
' situations, augmented assignment can result in unexpected '
@@ -12998,9 +13018,8 @@ topics = {'assert': 'The "assert" statement\n'
'\n'
'New in version 3.3: The "\'rb\'" prefix of raw bytes literals has '
'been\n'
- 'added as a synonym of "\'br\'".\n'
- '\n'
- 'New in version 3.3: Support for the unicode legacy literal\n'
+ 'added as a synonym of "\'br\'".Support for the unicode legacy '
+ 'literal\n'
'("u\'value\'") was reintroduced to simplify the maintenance of '
'dual\n'
'Python 2.x and 3.x codebases. See **PEP 414** for more '
@@ -13709,14 +13728,18 @@ topics = {'assert': 'The "assert" statement\n'
'contains\n'
'the numbers 0, 1, …, *n*-1. Item *i* of sequence *a* is selected '
'by\n'
- '"a[i]".\n'
+ '"a[i]". Some sequences, including built-in sequences, interpret\n'
+ 'negative subscripts by adding the sequence length. For example,\n'
+ '"a[-2]" equals "a[n-2]", the second to last item of sequence a '
+ 'with\n'
+ 'length "n".\n'
'\n'
'Sequences also support slicing: "a[i:j]" selects all items with '
'index\n'
'*k* such that *i* "<=" *k* "<" *j*. When used as an expression, a\n'
- 'slice is a sequence of the same type. This implies that the index '
- 'set\n'
- 'is renumbered so that it starts at 0.\n'
+ 'slice is a sequence of the same type. The comment above about '
+ 'negative\n'
+ 'indexes also applies to negative slice positions.\n'
'\n'
'Some sequences also support “extended slicing” with a third “step”\n'
'parameter: "a[i:j:k]" selects all items of *a* with index *x* where '
@@ -14461,7 +14484,9 @@ topics = {'assert': 'The "assert" statement\n'
'name |\n'
'+----------------------------------------------------+----------------------------------------------------+\n'
'| codeobject.co_qualname | The fully '
- 'qualified function name |\n'
+ 'qualified function name New in version |\n'
+ '| | '
+ '3.11. |\n'
'+----------------------------------------------------+----------------------------------------------------+\n'
'| codeobject.co_argcount | The total '
'number of positional *parameters* |\n'
@@ -14681,6 +14706,14 @@ topics = {'assert': 'The "assert" statement\n'
'tools.\n'
' The PEP that introduced the "co_lines()" method.\n'
'\n'
+ 'codeobject.replace(**kwargs)\n'
+ '\n'
+ ' Return a copy of the code object with new values for the '
+ 'specified\n'
+ ' fields.\n'
+ '\n'
+ ' New in version 3.8.\n'
+ '\n'
'\n'
'Frame objects\n'
'-------------\n'
@@ -16019,7 +16052,7 @@ topics = {'assert': 'The "assert" statement\n'
'\n'
' For sorting examples and a brief sorting tutorial, see '
'Sorting\n'
- ' HOW TO.\n'
+ ' Techniques.\n'
'\n'
' **CPython implementation detail:** While a list is being '
'sorted,\n'
@@ -16234,9 +16267,8 @@ topics = {'assert': 'The "assert" statement\n'
'objects\n'
'based on the sequence of values they define (instead of '
'comparing\n'
- 'based on object identity).\n'
- '\n'
- 'New in version 3.3: The "start", "stop" and "step" attributes.\n'
+ 'based on object identity).Added the "start", "stop" and "step"\n'
+ 'attributes.\n'
'\n'
'See also:\n'
'\n'
diff --git a/contrib/tools/python3/Lib/shutil.py b/contrib/tools/python3/Lib/shutil.py
index 96463007d1..3a2b6be39b 100644
--- a/contrib/tools/python3/Lib/shutil.py
+++ b/contrib/tools/python3/Lib/shutil.py
@@ -676,7 +676,7 @@ def _rmtree_safe_fd(topfd, path, onexc):
continue
if is_dir:
try:
- dirfd = os.open(entry.name, os.O_RDONLY, dir_fd=topfd)
+ dirfd = os.open(entry.name, os.O_RDONLY | os.O_NONBLOCK, dir_fd=topfd)
dirfd_closed = False
except OSError as err:
onexc(os.open, fullname, err)
@@ -775,7 +775,7 @@ def rmtree(path, ignore_errors=False, onerror=None, *, onexc=None, dir_fd=None):
onexc(os.lstat, path, err)
return
try:
- fd = os.open(path, os.O_RDONLY, dir_fd=dir_fd)
+ fd = os.open(path, os.O_RDONLY | os.O_NONBLOCK, dir_fd=dir_fd)
fd_closed = False
except Exception as err:
onexc(os.open, path, err)
diff --git a/contrib/tools/python3/Lib/subprocess.py b/contrib/tools/python3/Lib/subprocess.py
index 3264d9afc7..1d17ae3608 100644
--- a/contrib/tools/python3/Lib/subprocess.py
+++ b/contrib/tools/python3/Lib/subprocess.py
@@ -1581,6 +1581,8 @@ class Popen:
"""Internal implementation of wait() on Windows."""
if timeout is None:
timeout_millis = _winapi.INFINITE
+ elif timeout <= 0:
+ timeout_millis = 0
else:
timeout_millis = int(timeout * 1000)
if self.returncode is None:
diff --git a/contrib/tools/python3/Lib/tokenize.py b/contrib/tools/python3/Lib/tokenize.py
index 49e8144edd..7af7a5cc1c 100644
--- a/contrib/tools/python3/Lib/tokenize.py
+++ b/contrib/tools/python3/Lib/tokenize.py
@@ -170,6 +170,7 @@ class Untokenizer:
self.tokens = []
self.prev_row = 1
self.prev_col = 0
+ self.prev_type = None
self.encoding = None
def add_whitespace(self, start):
@@ -185,6 +186,29 @@ class Untokenizer:
if col_offset:
self.tokens.append(" " * col_offset)
+ def escape_brackets(self, token):
+ characters = []
+ consume_until_next_bracket = False
+ for character in token:
+ if character == "}":
+ if consume_until_next_bracket:
+ consume_until_next_bracket = False
+ else:
+ characters.append(character)
+ if character == "{":
+ n_backslashes = sum(
+ 1 for char in _itertools.takewhile(
+ "\\".__eq__,
+ characters[-2::-1]
+ )
+ )
+ if n_backslashes % 2 == 0:
+ characters.append(character)
+ else:
+ consume_until_next_bracket = True
+ characters.append(character)
+ return "".join(characters)
+
def untokenize(self, iterable):
it = iter(iterable)
indents = []
@@ -216,11 +240,13 @@ class Untokenizer:
startline = False
elif tok_type == FSTRING_MIDDLE:
if '{' in token or '}' in token:
+ token = self.escape_brackets(token)
+ last_line = token.splitlines()[-1]
end_line, end_col = end
- end = (end_line, end_col + token.count('{') + token.count('}'))
- token = re.sub('{', '{{', token)
- token = re.sub('}', '}}', token)
-
+ extra_chars = last_line.count("{{") + last_line.count("}}")
+ end = (end_line, end_col + extra_chars)
+ elif tok_type in (STRING, FSTRING_START) and self.prev_type in (STRING, FSTRING_END):
+ self.tokens.append(" ")
self.add_whitespace(start)
self.tokens.append(token)
@@ -228,6 +254,7 @@ class Untokenizer:
if tok_type in (NEWLINE, NL):
self.prev_row += 1
self.prev_col = 0
+ self.prev_type = tok_type
return "".join(self.tokens)
def compat(self, token, iterable):
@@ -235,6 +262,7 @@ class Untokenizer:
toks_append = self.tokens.append
startline = token[0] in (NEWLINE, NL)
prevstring = False
+ in_fstring = 0
for tok in _itertools.chain([token], iterable):
toknum, tokval = tok[:2]
@@ -253,6 +281,10 @@ class Untokenizer:
else:
prevstring = False
+ if toknum == FSTRING_START:
+ in_fstring += 1
+ elif toknum == FSTRING_END:
+ in_fstring -= 1
if toknum == INDENT:
indents.append(tokval)
continue
@@ -265,11 +297,18 @@ class Untokenizer:
toks_append(indents[-1])
startline = False
elif toknum == FSTRING_MIDDLE:
- if '{' in tokval or '}' in tokval:
- tokval = re.sub('{', '{{', tokval)
- tokval = re.sub('}', '}}', tokval)
+ tokval = self.escape_brackets(tokval)
+
+ # Insert a space between two consecutive brackets if we are in an f-string
+ if tokval in {"{", "}"} and self.tokens and self.tokens[-1] == tokval and in_fstring:
+ tokval = ' ' + tokval
+
+ # Insert a space between two consecutive f-strings
+ if toknum in (STRING, FSTRING_START) and self.prev_type in (STRING, FSTRING_END):
+ self.tokens.append(" ")
toks_append(tokval)
+ self.prev_type = toknum
def untokenize(iterable):
diff --git a/contrib/tools/python3/Lib/typing.py b/contrib/tools/python3/Lib/typing.py
index ffe7ce8d8a..b58c2d3064 100644
--- a/contrib/tools/python3/Lib/typing.py
+++ b/contrib/tools/python3/Lib/typing.py
@@ -314,19 +314,33 @@ def _unpack_args(args):
newargs.append(arg)
return newargs
-def _deduplicate(params):
+def _deduplicate(params, *, unhashable_fallback=False):
# Weed out strict duplicates, preserving the first of each occurrence.
- all_params = set(params)
- if len(all_params) < len(params):
- new_params = []
- for t in params:
- if t in all_params:
- new_params.append(t)
- all_params.remove(t)
- params = new_params
- assert not all_params, all_params
- return params
-
+ try:
+ return dict.fromkeys(params)
+ except TypeError:
+ if not unhashable_fallback:
+ raise
+ # Happens for cases like `Annotated[dict, {'x': IntValidator()}]`
+ return _deduplicate_unhashable(params)
+
+def _deduplicate_unhashable(unhashable_params):
+ new_unhashable = []
+ for t in unhashable_params:
+ if t not in new_unhashable:
+ new_unhashable.append(t)
+ return new_unhashable
+
+def _compare_args_orderless(first_args, second_args):
+ first_unhashable = _deduplicate_unhashable(first_args)
+ second_unhashable = _deduplicate_unhashable(second_args)
+ t = list(second_unhashable)
+ try:
+ for elem in first_unhashable:
+ t.remove(elem)
+ except ValueError:
+ return False
+ return not t
def _remove_dups_flatten(parameters):
"""Internal helper for Union creation and substitution.
@@ -341,7 +355,7 @@ def _remove_dups_flatten(parameters):
else:
params.append(p)
- return tuple(_deduplicate(params))
+ return tuple(_deduplicate(params, unhashable_fallback=True))
def _flatten_literal_params(parameters):
@@ -530,7 +544,7 @@ class Any(metaclass=_AnyMeta):
def __new__(cls, *args, **kwargs):
if cls is Any:
raise TypeError("Any cannot be instantiated")
- return super().__new__(cls, *args, **kwargs)
+ return super().__new__(cls)
@_SpecialForm
@@ -832,22 +846,25 @@ def TypeGuard(self, parameters):
2. If the return value is ``True``, the type of its argument
is the type inside ``TypeGuard``.
- For example::
+ For example::
+
+ def is_str_list(val: list[object]) -> TypeGuard[list[str]]:
+ '''Determines whether all objects in the list are strings'''
+ return all(isinstance(x, str) for x in val)
- def is_str(val: Union[str, float]):
- # "isinstance" type guard
- if isinstance(val, str):
- # Type of ``val`` is narrowed to ``str``
- ...
- else:
- # Else, type of ``val`` is narrowed to ``float``.
- ...
+ def func1(val: list[object]):
+ if is_str_list(val):
+ # Type of ``val`` is narrowed to ``list[str]``.
+ print(" ".join(val))
+ else:
+ # Type of ``val`` remains as ``list[object]``.
+ print("Not a list of strings!")
Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower
form of ``TypeA`` (it can even be a wider form) and this may lead to
type-unsafe results. The main reason is to allow for things like
- narrowing ``List[object]`` to ``List[str]`` even though the latter is not
- a subtype of the former, since ``List`` is invariant. The responsibility of
+ narrowing ``list[object]`` to ``list[str]`` even though the latter is not
+ a subtype of the former, since ``list`` is invariant. The responsibility of
writing type-safe type guards is left to the user.
``TypeGuard`` also works with type variables. For more information, see
@@ -872,7 +889,7 @@ class ForwardRef(_Final, _root=True):
# If we do `def f(*args: *Ts)`, then we'll have `arg = '*Ts'`.
# Unfortunately, this isn't a valid expression on its own, so we
# do the unpacking manually.
- if arg[0] == '*':
+ if arg.startswith('*'):
arg_to_compile = f'({arg},)[0]' # E.g. (*Ts,)[0] or (*tuple[int, int],)[0]
else:
arg_to_compile = arg
@@ -1140,7 +1157,9 @@ class _BaseGenericAlias(_Final, _root=True):
result = self.__origin__(*args, **kwargs)
try:
result.__orig_class__ = self
- except AttributeError:
+ # Some objects raise TypeError (or something even more exotic)
+ # if you try to set attributes on them; we guard against that here
+ except Exception:
pass
return result
@@ -1546,7 +1565,10 @@ class _UnionGenericAlias(_NotIterable, _GenericAlias, _root=True):
def __eq__(self, other):
if not isinstance(other, (_UnionGenericAlias, types.UnionType)):
return NotImplemented
- return set(self.__args__) == set(other.__args__)
+ try: # fast path
+ return set(self.__args__) == set(other.__args__)
+ except TypeError: # not hashable, slow path
+ return _compare_args_orderless(self.__args__, other.__args__)
def __hash__(self):
return hash(frozenset(self.__args__))
@@ -3254,11 +3276,11 @@ class TextIO(IO[str]):
class _DeprecatedType(type):
def __getattribute__(cls, name):
- if name not in ("__dict__", "__module__") and name in cls.__dict__:
+ if name not in {"__dict__", "__module__", "__doc__"} and name in cls.__dict__:
warnings.warn(
f"{cls.__name__} is deprecated, import directly "
f"from typing instead. {cls.__name__} will be removed "
- "in Python 3.12.",
+ "in Python 3.13.",
DeprecationWarning,
stacklevel=2,
)
diff --git a/contrib/tools/python3/Lib/unittest/mock.py b/contrib/tools/python3/Lib/unittest/mock.py
index a2187580af..0e1b9ace7b 100644
--- a/contrib/tools/python3/Lib/unittest/mock.py
+++ b/contrib/tools/python3/Lib/unittest/mock.py
@@ -543,7 +543,7 @@ class NonCallableMock(Base):
if self._mock_delegate is not None:
ret = self._mock_delegate.return_value
- if ret is DEFAULT:
+ if ret is DEFAULT and self._mock_wraps is None:
ret = self._get_child_mock(
_new_parent=self, _new_name='()'
)
@@ -1204,6 +1204,9 @@ class CallableMixin(Base):
if self._mock_return_value is not DEFAULT:
return self.return_value
+ if self._mock_delegate and self._mock_delegate.return_value is not DEFAULT:
+ return self.return_value
+
if self._mock_wraps is not None:
return self._mock_wraps(*args, **kwargs)
@@ -2754,9 +2757,12 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
if _parent is not None and not instance:
_parent._mock_children[_name] = mock
+ wrapped = kwargs.get('wraps')
+
if is_type and not instance and 'return_value' not in kwargs:
mock.return_value = create_autospec(spec, spec_set, instance=True,
- _name='()', _parent=mock)
+ _name='()', _parent=mock,
+ wraps=wrapped)
for entry in dir(spec):
if _is_magic(entry):
@@ -2778,6 +2784,9 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
continue
kwargs = {'spec': original}
+ # Wrap child attributes also.
+ if wrapped and hasattr(wrapped, entry):
+ kwargs.update(wraps=original)
if spec_set:
kwargs = {'spec_set': original}
diff --git a/contrib/tools/python3/Lib/urllib/parse.py b/contrib/tools/python3/Lib/urllib/parse.py
index c129b0d797..fc9e7c99f2 100644
--- a/contrib/tools/python3/Lib/urllib/parse.py
+++ b/contrib/tools/python3/Lib/urllib/parse.py
@@ -763,42 +763,48 @@ def parse_qsl(qs, keep_blank_values=False, strict_parsing=False,
Returns a list, as G-d intended.
"""
- qs, _coerce_result = _coerce_args(qs)
- separator, _ = _coerce_args(separator)
- if not separator or (not isinstance(separator, (str, bytes))):
+ if not separator or not isinstance(separator, (str, bytes)):
raise ValueError("Separator must be of type string or bytes.")
+ if isinstance(qs, str):
+ if not isinstance(separator, str):
+ separator = str(separator, 'ascii')
+ eq = '='
+ def _unquote(s):
+ return unquote_plus(s, encoding=encoding, errors=errors)
+ else:
+ if not qs:
+ return []
+ # Use memoryview() to reject integers and iterables,
+ # acceptable by the bytes constructor.
+ qs = bytes(memoryview(qs))
+ if isinstance(separator, str):
+ separator = bytes(separator, 'ascii')
+ eq = b'='
+ def _unquote(s):
+ return unquote_to_bytes(s.replace(b'+', b' '))
+
+ if not qs:
+ return []
# If max_num_fields is defined then check that the number of fields
# is less than max_num_fields. This prevents a memory exhaustion DOS
# attack via post bodies with many fields.
if max_num_fields is not None:
- num_fields = 1 + qs.count(separator) if qs else 0
+ num_fields = 1 + qs.count(separator)
if max_num_fields < num_fields:
raise ValueError('Max number of fields exceeded')
r = []
- query_args = qs.split(separator) if qs else []
- for name_value in query_args:
- if not name_value and not strict_parsing:
- continue
- nv = name_value.split('=', 1)
- if len(nv) != 2:
- if strict_parsing:
+ for name_value in qs.split(separator):
+ if name_value or strict_parsing:
+ name, has_eq, value = name_value.partition(eq)
+ if not has_eq and strict_parsing:
raise ValueError("bad query field: %r" % (name_value,))
- # Handle case of a control-name with no equal sign
- if keep_blank_values:
- nv.append('')
- else:
- continue
- if len(nv[1]) or keep_blank_values:
- name = nv[0].replace('+', ' ')
- name = unquote(name, encoding=encoding, errors=errors)
- name = _coerce_result(name)
- value = nv[1].replace('+', ' ')
- value = unquote(value, encoding=encoding, errors=errors)
- value = _coerce_result(value)
- r.append((name, value))
+ if value or keep_blank_values:
+ name = _unquote(name)
+ value = _unquote(value)
+ r.append((name, value))
return r
def unquote_plus(string, encoding='utf-8', errors='replace'):
diff --git a/contrib/tools/python3/Lib/urllib/request.py b/contrib/tools/python3/Lib/urllib/request.py
index 5314b3f260..7228a35534 100644
--- a/contrib/tools/python3/Lib/urllib/request.py
+++ b/contrib/tools/python3/Lib/urllib/request.py
@@ -2589,6 +2589,7 @@ def _proxy_bypass_macosx_sysconf(host, proxy_settings):
}
"""
from fnmatch import fnmatch
+ from ipaddress import AddressValueError, IPv4Address
hostonly, port = _splitport(host)
@@ -2605,20 +2606,17 @@ def _proxy_bypass_macosx_sysconf(host, proxy_settings):
return True
hostIP = None
+ try:
+ hostIP = int(IPv4Address(hostonly))
+ except AddressValueError:
+ pass
for value in proxy_settings.get('exceptions', ()):
# Items in the list are strings like these: *.local, 169.254/16
if not value: continue
m = re.match(r"(\d+(?:\.\d+)*)(/\d+)?", value)
- if m is not None:
- if hostIP is None:
- try:
- hostIP = socket.gethostbyname(hostonly)
- hostIP = ip2num(hostIP)
- except OSError:
- continue
-
+ if m is not None and hostIP is not None:
base = ip2num(m.group(1))
mask = m.group(2)
if mask is None:
@@ -2641,6 +2639,31 @@ def _proxy_bypass_macosx_sysconf(host, proxy_settings):
return False
+# Same as _proxy_bypass_macosx_sysconf, testable on all platforms
+def _proxy_bypass_winreg_override(host, override):
+ """Return True if the host should bypass the proxy server.
+
+ The proxy override list is obtained from the Windows
+ Internet settings proxy override registry value.
+
+ An example of a proxy override value is:
+ "www.example.com;*.example.net; 192.168.0.1"
+ """
+ from fnmatch import fnmatch
+
+ host, _ = _splitport(host)
+ proxy_override = override.split(';')
+ for test in proxy_override:
+ test = test.strip()
+ # "<local>" should bypass the proxy server for all intranet addresses
+ if test == '<local>':
+ if '.' not in host:
+ return True
+ elif fnmatch(host, test):
+ return True
+ return False
+
+
if sys.platform == 'darwin':
from _scproxy import _get_proxy_settings, _get_proxies
@@ -2739,7 +2762,7 @@ elif os.name == 'nt':
import winreg
except ImportError:
# Std modules, so should be around - but you never know!
- return 0
+ return False
try:
internetSettings = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
r'Software\Microsoft\Windows\CurrentVersion\Internet Settings')
@@ -2749,40 +2772,10 @@ elif os.name == 'nt':
'ProxyOverride')[0])
# ^^^^ Returned as Unicode but problems if not converted to ASCII
except OSError:
- return 0
+ return False
if not proxyEnable or not proxyOverride:
- return 0
- # try to make a host list from name and IP address.
- rawHost, port = _splitport(host)
- host = [rawHost]
- try:
- addr = socket.gethostbyname(rawHost)
- if addr != rawHost:
- host.append(addr)
- except OSError:
- pass
- try:
- fqdn = socket.getfqdn(rawHost)
- if fqdn != rawHost:
- host.append(fqdn)
- except OSError:
- pass
- # make a check value list from the registry entry: replace the
- # '<local>' string by the localhost entry and the corresponding
- # canonical entry.
- proxyOverride = proxyOverride.split(';')
- # now check if we match one of the registry values.
- for test in proxyOverride:
- if test == '<local>':
- if '.' not in rawHost:
- return 1
- test = test.replace(".", r"\.") # mask dots
- test = test.replace("*", r".*") # change glob sequence
- test = test.replace("?", r".") # change glob char
- for val in host:
- if re.match(test, val, re.I):
- return 1
- return 0
+ return False
+ return _proxy_bypass_winreg_override(host, proxyOverride)
def proxy_bypass(host):
"""Return True, if host should be bypassed.
diff --git a/contrib/tools/python3/Lib/xml/etree/ElementTree.py b/contrib/tools/python3/Lib/xml/etree/ElementTree.py
index bb7362d163..fd2cc8704e 100644
--- a/contrib/tools/python3/Lib/xml/etree/ElementTree.py
+++ b/contrib/tools/python3/Lib/xml/etree/ElementTree.py
@@ -1313,6 +1313,11 @@ class XMLPullParser:
else:
yield event
+ def flush(self):
+ if self._parser is None:
+ raise ValueError("flush() called after end of stream")
+ self._parser.flush()
+
def XML(text, parser=None):
"""Parse XML document from string constant.
@@ -1719,6 +1724,15 @@ class XMLParser:
del self.parser, self._parser
del self.target, self._target
+ def flush(self):
+ was_enabled = self.parser.GetReparseDeferralEnabled()
+ try:
+ self.parser.SetReparseDeferralEnabled(False)
+ self.parser.Parse(b"", False)
+ except self._error as v:
+ self._raiseerror(v)
+ finally:
+ self.parser.SetReparseDeferralEnabled(was_enabled)
# --------------------------------------------------------------------
# C14N 2.0
diff --git a/contrib/tools/python3/Lib/xml/sax/expatreader.py b/contrib/tools/python3/Lib/xml/sax/expatreader.py
index b9ad52692d..ba3c1e9851 100644
--- a/contrib/tools/python3/Lib/xml/sax/expatreader.py
+++ b/contrib/tools/python3/Lib/xml/sax/expatreader.py
@@ -214,6 +214,20 @@ class ExpatParser(xmlreader.IncrementalParser, xmlreader.Locator):
# FIXME: when to invoke error()?
self._err_handler.fatalError(exc)
+ def flush(self):
+ if self._parser is None:
+ return
+
+ was_enabled = self._parser.GetReparseDeferralEnabled()
+ try:
+ self._parser.SetReparseDeferralEnabled(False)
+ self._parser.Parse(b"", False)
+ except expat.error as e:
+ exc = SAXParseException(expat.ErrorString(e.code), e, self)
+ self._err_handler.fatalError(exc)
+ finally:
+ self._parser.SetReparseDeferralEnabled(was_enabled)
+
def _close_source(self):
source = self._source
try:
diff --git a/contrib/tools/python3/Lib/ya.make b/contrib/tools/python3/Lib/ya.make
index 407fd45aaf..81d0f7ca7d 100644
--- a/contrib/tools/python3/Lib/ya.make
+++ b/contrib/tools/python3/Lib/ya.make
@@ -4,9 +4,9 @@ ENABLE(PYBUILD_NO_PY)
PY3_LIBRARY()
-VERSION(3.12.2)
+VERSION(3.12.3)
-ORIGINAL_SOURCE(https://github.com/python/cpython/archive/v3.12.2.tar.gz)
+ORIGINAL_SOURCE(https://github.com/python/cpython/archive/v3.12.3.tar.gz)
LICENSE(Python-2.0)
diff --git a/contrib/tools/python3/Lib/zipfile/__init__.py b/contrib/tools/python3/Lib/zipfile/__init__.py
index 8918484207..91358156bc 100644
--- a/contrib/tools/python3/Lib/zipfile/__init__.py
+++ b/contrib/tools/python3/Lib/zipfile/__init__.py
@@ -582,7 +582,15 @@ class ZipInfo (object):
def is_dir(self):
"""Return True if this archive member is a directory."""
- return self.filename.endswith('/')
+ if self.filename.endswith('/'):
+ return True
+ # The ZIP format specification requires to use forward slashes
+ # as the directory separator, but in practice some ZIP files
+ # created on Windows can use backward slashes. For compatibility
+ # with the extraction code which already handles this:
+ if os.path.altsep:
+ return self.filename.endswith((os.path.sep, os.path.altsep))
+ return False
# ZIP encryption uses the CRC32 one-byte primitive for scrambling some
@@ -1554,7 +1562,8 @@ class ZipFile:
self._didModify = True
def read(self, name, pwd=None):
- """Return file bytes for name."""
+ """Return file bytes for name. 'pwd' is the password to decrypt
+ encrypted files."""
with self.open(name, "r", pwd) as fp:
return fp.read()
@@ -1706,7 +1715,8 @@ class ZipFile:
"""Extract a member from the archive to the current working directory,
using its full name. Its file information is extracted as accurately
as possible. `member' may be a filename or a ZipInfo object. You can
- specify a different directory using `path'.
+ specify a different directory using `path'. You can specify the
+ password to decrypt the file using 'pwd'.
"""
if path is None:
path = os.getcwd()
@@ -1719,7 +1729,8 @@ class ZipFile:
"""Extract all members from the archive to the current working
directory. `path' specifies a different directory to extract to.
`members' is optional and must be a subset of the list returned
- by namelist().
+ by namelist(). You can specify the password to decrypt all files
+ using 'pwd'.
"""
if members is None:
members = self.namelist()