Upgrade python prebuilts to 3.8.2 [Windows] Built at: http://fusion/c5148098-d49a-43d5-9fcb-bf3ed9234d9b Change-Id: I48483caedcfbe76f1c28730d010f766366fe5e8a
diff --git a/DLLs/_asyncio.pyd b/DLLs/_asyncio.pyd index eb08b48..7caf7bb 100644 --- a/DLLs/_asyncio.pyd +++ b/DLLs/_asyncio.pyd Binary files differ
diff --git a/DLLs/_bz2.pyd b/DLLs/_bz2.pyd index 369e689..e592894 100644 --- a/DLLs/_bz2.pyd +++ b/DLLs/_bz2.pyd Binary files differ
diff --git a/DLLs/_ctypes.pyd b/DLLs/_ctypes.pyd index a4efd7e..4ffdd77 100644 --- a/DLLs/_ctypes.pyd +++ b/DLLs/_ctypes.pyd Binary files differ
diff --git a/DLLs/_decimal.pyd b/DLLs/_decimal.pyd index a5cb2f4..a645a55 100644 --- a/DLLs/_decimal.pyd +++ b/DLLs/_decimal.pyd Binary files differ
diff --git a/DLLs/_elementtree.pyd b/DLLs/_elementtree.pyd index a0ea4ac..bd0076c 100644 --- a/DLLs/_elementtree.pyd +++ b/DLLs/_elementtree.pyd Binary files differ
diff --git a/DLLs/_hashlib.pyd b/DLLs/_hashlib.pyd index 6747342..0dfc8c6 100644 --- a/DLLs/_hashlib.pyd +++ b/DLLs/_hashlib.pyd Binary files differ
diff --git a/DLLs/_lzma.pyd b/DLLs/_lzma.pyd index e8b86ad..3aec02a 100644 --- a/DLLs/_lzma.pyd +++ b/DLLs/_lzma.pyd Binary files differ
diff --git a/DLLs/_msi.pyd b/DLLs/_msi.pyd index a23d19d..0ec78c3 100644 --- a/DLLs/_msi.pyd +++ b/DLLs/_msi.pyd Binary files differ
diff --git a/DLLs/_multiprocessing.pyd b/DLLs/_multiprocessing.pyd index bc2b318..20a7df4 100644 --- a/DLLs/_multiprocessing.pyd +++ b/DLLs/_multiprocessing.pyd Binary files differ
diff --git a/DLLs/_overlapped.pyd b/DLLs/_overlapped.pyd index b7ff675..1ec8aaa 100644 --- a/DLLs/_overlapped.pyd +++ b/DLLs/_overlapped.pyd Binary files differ
diff --git a/DLLs/_queue.pyd b/DLLs/_queue.pyd index 990a089..121760d 100644 --- a/DLLs/_queue.pyd +++ b/DLLs/_queue.pyd Binary files differ
diff --git a/DLLs/_socket.pyd b/DLLs/_socket.pyd index 28ff4fa..8469fdc 100644 --- a/DLLs/_socket.pyd +++ b/DLLs/_socket.pyd Binary files differ
diff --git a/DLLs/_sqlite3.pyd b/DLLs/_sqlite3.pyd index 18a4b51..cd474f6 100644 --- a/DLLs/_sqlite3.pyd +++ b/DLLs/_sqlite3.pyd Binary files differ
diff --git a/DLLs/_ssl.pyd b/DLLs/_ssl.pyd index c17c911..d98c9b4 100644 --- a/DLLs/_ssl.pyd +++ b/DLLs/_ssl.pyd Binary files differ
diff --git a/DLLs/pyexpat.pyd b/DLLs/pyexpat.pyd index b57035e..0ebb8a2 100644 --- a/DLLs/pyexpat.pyd +++ b/DLLs/pyexpat.pyd Binary files differ
diff --git a/DLLs/select.pyd b/DLLs/select.pyd index 3abc288..3c3d02b 100644 --- a/DLLs/select.pyd +++ b/DLLs/select.pyd Binary files differ
diff --git a/DLLs/sqlite3.dll b/DLLs/sqlite3.dll index f2191d8..c253fea 100644 --- a/DLLs/sqlite3.dll +++ b/DLLs/sqlite3.dll Binary files differ
diff --git a/DLLs/unicodedata.pyd b/DLLs/unicodedata.pyd index a930b77..4268cf0 100644 --- a/DLLs/unicodedata.pyd +++ b/DLLs/unicodedata.pyd Binary files differ
diff --git a/DLLs/winsound.pyd b/DLLs/winsound.pyd index 512f033..28e7b77 100644 --- a/DLLs/winsound.pyd +++ b/DLLs/winsound.pyd Binary files differ
diff --git a/LICENSE.txt b/LICENSE.txt index 7c22e0e..edfeb1c 100644 --- a/LICENSE.txt +++ b/LICENSE.txt
@@ -73,7 +73,7 @@ distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 Python Software Foundation; +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Python Software Foundation; All Rights Reserved" are retained in Python alone or in any derivative version prepared by Licensee.
diff --git a/Lib/argparse.py b/Lib/argparse.py index fd61bc7..2dad5f1 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py
@@ -2144,24 +2144,23 @@ action = self._option_string_actions[option_string] return action, option_string, explicit_arg - if self.allow_abbrev or not arg_string.startswith('--'): - # search through all possible prefixes of the option string - # and all actions in the parser for possible interpretations - option_tuples = self._get_option_tuples(arg_string) + # search through all possible prefixes of the option string + # and all actions in the parser for possible interpretations + option_tuples = self._get_option_tuples(arg_string) - # 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]) - args = {'option': arg_string, 'matches': options} - msg = _('ambiguous option: %(option)s could match %(matches)s') - self.error(msg % args) + # 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]) + args = {'option': arg_string, 'matches': options} + msg = _('ambiguous option: %(option)s could match %(matches)s') + self.error(msg % args) - # if exactly one action matched, this segmentation is good, - # so return the parsed action - elif len(option_tuples) == 1: - option_tuple, = option_tuples - return option_tuple + # if exactly one action matched, this segmentation is good, + # so return the parsed action + elif len(option_tuples) == 1: + option_tuple, = option_tuples + return option_tuple # if it was not found as an option, but it looks like a negative # number, it was meant to be positional @@ -2185,16 +2184,17 @@ # split at the '=' chars = self.prefix_chars if option_string[0] in chars and option_string[1] in chars: - if '=' in option_string: - option_prefix, explicit_arg = option_string.split('=', 1) - else: - option_prefix = option_string - 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 - result.append(tup) + if self.allow_abbrev: + if '=' in option_string: + option_prefix, explicit_arg = option_string.split('=', 1) + else: + option_prefix = option_string + 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 + result.append(tup) # single character options can be concatenated with their arguments # but multiple character options always have to have their argument
diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index bfd4011..799013d 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py
@@ -547,14 +547,17 @@ 'asyncgen': agen }) - def run_forever(self): - """Run until stop() is called.""" - self._check_closed() + def _check_running(self): if self.is_running(): raise RuntimeError('This event loop is already running') if events._get_running_loop() is not None: raise RuntimeError( 'Cannot run the event loop while another loop is running') + + def run_forever(self): + """Run until stop() is called.""" + self._check_closed() + self._check_running() self._set_coroutine_origin_tracking(self._debug) self._thread_id = threading.get_ident() @@ -586,6 +589,7 @@ Return the Future's result, or raise its exception. """ self._check_closed() + self._check_running() new_task = not futures.isfuture(future) future = tasks.ensure_future(future, loop=self)
diff --git a/Lib/asyncio/staggered.py b/Lib/asyncio/staggered.py index 27c665a..451a53a 100644 --- a/Lib/asyncio/staggered.py +++ b/Lib/asyncio/staggered.py
@@ -6,7 +6,7 @@ import typing from . import events -from . import futures +from . import exceptions as exceptions_mod from . import locks from . import tasks @@ -83,7 +83,7 @@ previous_failed: typing.Optional[locks.Event]) -> None: # Wait for the previous task to finish, or for delay seconds if previous_failed is not None: - with contextlib.suppress(futures.TimeoutError): + with contextlib.suppress(exceptions_mod.TimeoutError): # Use asyncio.wait_for() instead of asyncio.wait() here, so # that if we get cancelled at this point, Event.wait() is also # cancelled, otherwise there will be a "Task destroyed but it is
diff --git a/Lib/asyncio/unix_events.py b/Lib/asyncio/unix_events.py index d8f6530..8c0a574 100644 --- a/Lib/asyncio/unix_events.py +++ b/Lib/asyncio/unix_events.py
@@ -1266,7 +1266,14 @@ return True def close(self): - pass + self._join_threads() + + def _join_threads(self): + """Internal: Join all non-daemon threads""" + threads = [thread for thread in list(self._threads.values()) + if thread.is_alive() and not thread.daemon] + for thread in threads: + thread.join() def __enter__(self): return self
diff --git a/Lib/base64.py b/Lib/base64.py index 2be9c39..2e70223 100644 --- a/Lib/base64.py +++ b/Lib/base64.py
@@ -82,7 +82,7 @@ altchars = _bytes_from_decode_data(altchars) assert len(altchars) == 2, repr(altchars) s = s.translate(bytes.maketrans(altchars, b'+/')) - if validate and not re.match(b'^[A-Za-z0-9+/]*={0,2}$', s): + if validate and not re.fullmatch(b'[A-Za-z0-9+/]*={0,2}', s): raise binascii.Error('Non-base64 digit found') return binascii.a2b_base64(s)
diff --git a/Lib/bdb.py b/Lib/bdb.py index 50d9eec..18491da 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py
@@ -548,14 +548,7 @@ s += frame.f_code.co_name else: s += "<lambda>" - if '__args__' in frame.f_locals: - args = frame.f_locals['__args__'] - else: - args = None - if args: - s += reprlib.repr(args) - else: - s += '()' + s += '()' if '__return__' in frame.f_locals: rv = frame.f_locals['__return__'] s += '->'
diff --git a/Lib/code.py b/Lib/code.py index d8106ae..76000f8 100644 --- a/Lib/code.py +++ b/Lib/code.py
@@ -40,7 +40,7 @@ Arguments are as for compile_command(). - One several things can happen: + One of several things can happen: 1) The input is incorrect; compile_command() raised an exception (SyntaxError or OverflowError). A syntax traceback
diff --git a/Lib/copy.py b/Lib/copy.py index f53cd8c..41873f2 100644 --- a/Lib/copy.py +++ b/Lib/copy.py
@@ -107,7 +107,7 @@ def _copy_immutable(x): return x for t in (type(None), int, float, bool, complex, str, tuple, - bytes, frozenset, type, range, slice, + bytes, frozenset, type, range, slice, property, types.BuiltinFunctionType, type(Ellipsis), type(NotImplemented), types.FunctionType, weakref.ref): d[t] = _copy_immutable @@ -195,6 +195,7 @@ d[types.BuiltinFunctionType] = _deepcopy_atomic d[types.FunctionType] = _deepcopy_atomic d[weakref.ref] = _deepcopy_atomic +d[property] = _deepcopy_atomic def _deepcopy_list(x, memo, deepcopy=deepcopy): y = []
diff --git a/Lib/distutils/command/build_ext.py b/Lib/distutils/command/build_ext.py index 2d7cdf0..dbcd9d1 100644 --- a/Lib/distutils/command/build_ext.py +++ b/Lib/distutils/command/build_ext.py
@@ -688,7 +688,15 @@ provided, "PyInit_" + module_name. Only relevant on Windows, where the .pyd file (DLL) must export the module "PyInit_" function. """ - initfunc_name = "PyInit_" + ext.name.split('.')[-1] + suffix = '_' + ext.name.split('.')[-1] + try: + # Unicode module name support as defined in PEP-489 + # https://www.python.org/dev/peps/pep-0489/#export-hook-name + suffix.encode('ascii') + except UnicodeEncodeError: + suffix = 'U' + suffix.encode('punycode').replace(b'-', b'_').decode('ascii') + + initfunc_name = "PyInit" + suffix if initfunc_name not in ext.export_symbols: ext.export_symbols.append(initfunc_name) return ext.export_symbols
diff --git a/Lib/fractions.py b/Lib/fractions.py index e774d58..e4fcc89 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py
@@ -636,7 +636,9 @@ def __bool__(a): """a != 0""" - return a._numerator != 0 + # bpo-39274: Use bool() because (a._numerator != 0) can return an + # object which is not a bool. + return bool(a._numerator) # support for pickling, copy, and deepcopy
diff --git a/Lib/glob.py b/Lib/glob.py index 0b3fcc6..0dd2f8b 100644 --- a/Lib/glob.py +++ b/Lib/glob.py
@@ -31,6 +31,7 @@ If recursive is true, the pattern '**' will match any files and zero or more directories and subdirectories. """ + sys.audit("glob.glob", pathname, recursive) it = _iglob(pathname, recursive, False) if recursive and _isrecursive(pathname): s = next(it) # skip empty string @@ -38,7 +39,6 @@ return it def _iglob(pathname, recursive, dironly): - sys.audit("glob.glob", pathname, recursive) dirname, basename = os.path.split(pathname) if not has_magic(pathname): assert not dironly
diff --git a/Lib/gzip.py b/Lib/gzip.py index 2968f47..87b553d 100644 --- a/Lib/gzip.py +++ b/Lib/gzip.py
@@ -201,7 +201,7 @@ self.fileobj = fileobj if self.mode == WRITE: - self._write_gzip_header() + self._write_gzip_header(compresslevel) @property def filename(self): @@ -228,7 +228,7 @@ self.bufsize = 0 self.offset = 0 # Current file offset for seek(), tell(), etc - def _write_gzip_header(self): + def _write_gzip_header(self, compresslevel): self.fileobj.write(b'\037\213') # magic header self.fileobj.write(b'\010') # compression method try: @@ -249,7 +249,13 @@ if mtime is None: mtime = time.time() write32u(self.fileobj, int(mtime)) - self.fileobj.write(b'\002') + if compresslevel == _COMPRESS_LEVEL_BEST: + xfl = b'\002' + elif compresslevel == _COMPRESS_LEVEL_FAST: + xfl = b'\004' + else: + xfl = b'\000' + self.fileobj.write(xfl) self.fileobj.write(b'\377') if fname: self.fileobj.write(fname + b'\000')
diff --git a/Lib/http/server.py b/Lib/http/server.py index b247675..38f7acc 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py
@@ -103,6 +103,7 @@ import sys import time import urllib.parse +import contextlib from functools import partial from http import HTTPStatus @@ -1280,4 +1281,19 @@ else: handler_class = partial(SimpleHTTPRequestHandler, directory=args.directory) - test(HandlerClass=handler_class, port=args.port, bind=args.bind) + + # ensure dual-stack is not disabled; ref #38907 + class DualStackServer(ThreadingHTTPServer): + def server_bind(self): + # suppress exception when protocol is IPv4 + with contextlib.suppress(Exception): + self.socket.setsockopt( + socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) + return super().server_bind() + + test( + HandlerClass=handler_class, + ServerClass=DualStackServer, + port=args.port, + bind=args.bind, + )
diff --git a/Lib/importlib/metadata.py b/Lib/importlib/metadata.py index 53f9fb5..831f593 100644 --- a/Lib/importlib/metadata.py +++ b/Lib/importlib/metadata.py
@@ -10,6 +10,7 @@ import operator import functools import itertools +import posixpath import collections from configparser import ConfigParser @@ -371,10 +372,6 @@ """ return vars(self).get('path', sys.path) - @property - def pattern(self): - return '.*' if self.name is None else re.escape(self.name) - @abc.abstractmethod def find_distributions(self, context=Context()): """ @@ -386,6 +383,75 @@ """ +class FastPath: + """ + Micro-optimized class for searching a path for + children. + """ + + def __init__(self, root): + self.root = root + self.base = os.path.basename(root).lower() + + def joinpath(self, child): + return pathlib.Path(self.root, child) + + def children(self): + with suppress(Exception): + return os.listdir(self.root or '') + with suppress(Exception): + return self.zip_children() + return [] + + def zip_children(self): + zip_path = zipfile.Path(self.root) + names = zip_path.root.namelist() + self.joinpath = zip_path.joinpath + + return ( + posixpath.split(child)[0] + for child in names + ) + + def is_egg(self, search): + base = self.base + return ( + base == search.versionless_egg_name + or base.startswith(search.prefix) + and base.endswith('.egg')) + + def search(self, name): + for child in self.children(): + n_low = child.lower() + if (n_low in name.exact_matches + or n_low.startswith(name.prefix) + and n_low.endswith(name.suffixes) + # legacy case: + or self.is_egg(name) and n_low == 'egg-info'): + yield self.joinpath(child) + + +class Prepared: + """ + A prepared search for metadata on a possibly-named package. + """ + normalized = '' + prefix = '' + suffixes = '.dist-info', '.egg-info' + exact_matches = [''][:0] + versionless_egg_name = '' + + def __init__(self, name): + self.name = name + if name is None: + return + self.normalized = name.lower().replace('-', '_') + self.prefix = self.normalized + '-' + self.exact_matches = [ + self.normalized + suffix for suffix in self.suffixes] + self.versionless_egg_name = self.normalized + '.egg' + + class MetadataPathFinder(DistributionFinder): @classmethod def find_distributions(cls, context=DistributionFinder.Context()): @@ -397,45 +463,17 @@ (or all names if ``None`` indicated) along the paths in the list of directories ``context.path``. """ - found = cls._search_paths(context.pattern, context.path) + found = cls._search_paths(context.name, context.path) return map(PathDistribution, found) @classmethod - def _search_paths(cls, pattern, paths): + def _search_paths(cls, name, paths): """Find metadata directories in paths heuristically.""" return itertools.chain.from_iterable( - cls._search_path(path, pattern) - for path in map(cls._switch_path, paths) + path.search(Prepared(name)) + for path in map(FastPath, paths) ) - @staticmethod - def _switch_path(path): - PYPY_OPEN_BUG = False - if not PYPY_OPEN_BUG or os.path.isfile(path): # pragma: no branch - with suppress(Exception): - return zipfile.Path(path) - return pathlib.Path(path) - - @classmethod - def _matches_info(cls, normalized, item): - template = r'{pattern}(-.*)?\.(dist|egg)-info' - manifest = template.format(pattern=normalized) - return re.match(manifest, item.name, flags=re.IGNORECASE) - - @classmethod - def _matches_legacy(cls, normalized, item): - template = r'{pattern}-.*\.egg[\\/]EGG-INFO' - manifest = template.format(pattern=normalized) - return re.search(manifest, str(item), flags=re.IGNORECASE) - - @classmethod - def _search_path(cls, root, pattern): - if not root.is_dir(): - return () - normalized = pattern.replace('-', '_') - return (item for item in root.iterdir() - if cls._matches_info(normalized, item) - or cls._matches_legacy(normalized, item)) class PathDistribution(Distribution):
diff --git a/Lib/lib2to3/fixes/fix_filter.py b/Lib/lib2to3/fixes/fix_filter.py index a7a5a15..38e9078 100644 --- a/Lib/lib2to3/fixes/fix_filter.py +++ b/Lib/lib2to3/fixes/fix_filter.py
@@ -17,7 +17,7 @@ from .. import fixer_base from ..pytree import Node from ..pygram import python_symbols as syms -from ..fixer_util import Name, ArgList, ListComp, in_special_context +from ..fixer_util import Name, ArgList, ListComp, in_special_context, parenthesize class FixFilter(fixer_base.ConditionalFix): @@ -65,10 +65,14 @@ trailers.append(t.clone()) if "filter_lambda" in results: + xp = results.get("xp").clone() + if xp.type == syms.test: + xp.prefix = "" + xp = parenthesize(xp) + new = ListComp(results.get("fp").clone(), results.get("fp").clone(), - results.get("it").clone(), - results.get("xp").clone()) + results.get("it").clone(), xp) new = Node(syms.power, [new] + trailers, prefix="") elif "none" in results:
diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index 16812ec..0cfaec8 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py
@@ -1685,12 +1685,15 @@ return self._cache[level] except KeyError: _acquireLock() - if self.manager.disable >= level: - is_enabled = self._cache[level] = False - else: - is_enabled = self._cache[level] = level >= self.getEffectiveLevel() - _releaseLock() - + try: + if self.manager.disable >= level: + is_enabled = self._cache[level] = False + else: + is_enabled = self._cache[level] = ( + level >= self.getEffectiveLevel() + ) + finally: + _releaseLock() return is_enabled def getChild(self, suffix):
diff --git a/Lib/logging/config.py b/Lib/logging/config.py index 9dd35e1..3cd5fea 100644 --- a/Lib/logging/config.py +++ b/Lib/logging/config.py
@@ -447,7 +447,7 @@ value = ConvertingList(value) value.configurator = self elif not isinstance(value, ConvertingTuple) and\ - isinstance(value, tuple): + isinstance(value, tuple) and not hasattr(value, '_fields'): value = ConvertingTuple(value) value.configurator = self elif isinstance(value, str): # str for py3k
diff --git a/Lib/mimetypes.py b/Lib/mimetypes.py index 9b42bf6..f33b658 100644 --- a/Lib/mimetypes.py +++ b/Lib/mimetypes.py
@@ -563,7 +563,7 @@ _default_mime_types() -if __name__ == '__main__': +def _main(): import getopt USAGE = """\ @@ -607,3 +607,7 @@ guess, encoding = guess_type(gtype, strict) if not guess: print("I don't know anything about type", gtype) else: print('type:', guess, 'encoding:', encoding) + + +if __name__ == '__main__': + _main()
diff --git a/Lib/multiprocessing/spawn.py b/Lib/multiprocessing/spawn.py index 075f345..7cc129e 100644 --- a/Lib/multiprocessing/spawn.py +++ b/Lib/multiprocessing/spawn.py
@@ -36,7 +36,7 @@ if WINSERVICE: _python_exe = os.path.join(sys.exec_prefix, 'python.exe') else: - _python_exe = sys._base_executable + _python_exe = sys.executable def set_executable(exe): global _python_exe
diff --git a/Lib/nntplib.py b/Lib/nntplib.py index 1b7e83a..9036f36 100644 --- a/Lib/nntplib.py +++ b/Lib/nntplib.py
@@ -1107,7 +1107,7 @@ nntplib built-in demo - display the latest articles in a newsgroup""") parser.add_argument('-g', '--group', default='gmane.comp.python.general', help='group to fetch messages from (default: %(default)s)') - parser.add_argument('-s', '--server', default='news.gmane.org', + parser.add_argument('-s', '--server', default='news.gmane.io', help='NNTP server hostname (default: %(default)s)') parser.add_argument('-p', '--port', default=-1, type=int, help='NNTP port number (default: %s / %s)' % (NNTP_PORT, NNTP_SSL_PORT))
diff --git a/Lib/os.py b/Lib/os.py index 52d3f1d..253cad1 100644 --- a/Lib/os.py +++ b/Lib/os.py
@@ -26,6 +26,8 @@ import sys import stat as st +from _collections_abc import _check_methods + _names = sys.builtin_module_names # Note: more names are added to __all__ later. @@ -1070,7 +1072,9 @@ @classmethod def __subclasshook__(cls, subclass): - return hasattr(subclass, '__fspath__') + if cls is PathLike: + return _check_methods(subclass, '__fspath__') + return NotImplemented if name == 'nt':
diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 8ed3c88..015370a 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py
@@ -253,9 +253,7 @@ return 'file:' + urlquote_from_bytes(path.as_posix().encode('utf-8')) def gethomedir(self, username): - if 'HOME' in os.environ: - userhome = os.environ['HOME'] - elif 'USERPROFILE' in os.environ: + if 'USERPROFILE' in os.environ: userhome = os.environ['USERPROFILE'] elif 'HOMEPATH' in os.environ: try:
diff --git a/Lib/pickle.py b/Lib/pickle.py index 71aa57d..515cb8a 100644 --- a/Lib/pickle.py +++ b/Lib/pickle.py
@@ -409,9 +409,9 @@ """This takes a binary file for writing a pickle data stream. The optional *protocol* argument tells the pickler to use the - given protocol; supported protocols are 0, 1, 2, 3 and 4. The - default protocol is 4. It was introduced in Python 3.4, it is - incompatible with previous versions. + given protocol; supported protocols are 0, 1, 2, 3, 4 and 5. + The default protocol is 4. It was introduced in Python 3.4, and + is incompatible with previous versions. Specifying a negative protocol version selects the highest protocol version supported. The higher the protocol used, the
diff --git a/Lib/pty.py b/Lib/pty.py index e841f12..a324320 100644 --- a/Lib/pty.py +++ b/Lib/pty.py
@@ -8,6 +8,7 @@ from select import select import os +import sys import tty __all__ = ["openpty","fork","spawn"] @@ -151,6 +152,7 @@ """Create a spawned process.""" if type(argv) == type(''): argv = (argv,) + sys.audit('pty.spawn', argv) pid, master_fd = fork() if pid == CHILD: os.execlp(argv[0], *argv)
diff --git a/Lib/pydoc.py b/Lib/pydoc.py index 9a22e56..dc3377d 100644 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py
@@ -66,6 +66,7 @@ import platform import re import sys +import sysconfig import time import tokenize import urllib.parse @@ -392,9 +393,7 @@ docmodule = docclass = docroutine = docother = docproperty = docdata = fail - def getdocloc(self, object, - basedir=os.path.join(sys.base_exec_prefix, "lib", - "python%d.%d" % sys.version_info[:2])): + def getdocloc(self, object, basedir=sysconfig.get_path('stdlib')): """Return the location of module docs or None""" try:
diff --git a/Lib/pydoc_data/topics.py b/Lib/pydoc_data/topics.py index 9d779d1..f1fdb7f 100644 --- a/Lib/pydoc_data/topics.py +++ b/Lib/pydoc_data/topics.py
@@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Autogenerated by Sphinx on Wed Dec 18 18:17:58 2019 +# Autogenerated by Sphinx on Mon Feb 24 21:52:17 2020 topics = {'assert': 'The "assert" statement\n' '**********************\n' '\n' @@ -470,24 +470,25 @@ 'The following code:\n' '\n' ' async for TARGET in ITER:\n' - ' BLOCK\n' + ' SUITE\n' ' else:\n' - ' BLOCK2\n' + ' SUITE2\n' '\n' 'Is semantically equivalent to:\n' '\n' ' iter = (ITER)\n' ' iter = type(iter).__aiter__(iter)\n' ' running = True\n' + '\n' ' while running:\n' ' try:\n' ' TARGET = await type(iter).__anext__(iter)\n' ' except StopAsyncIteration:\n' ' running = False\n' ' else:\n' - ' BLOCK\n' + ' SUITE\n' ' else:\n' - ' BLOCK2\n' + ' SUITE2\n' '\n' 'See also "__aiter__()" and "__anext__()" for details.\n' '\n' @@ -507,23 +508,27 @@ '\n' 'The following code:\n' '\n' - ' async with EXPR as VAR:\n' - ' BLOCK\n' + ' async with EXPRESSION as TARGET:\n' + ' SUITE\n' '\n' - 'Is semantically equivalent to:\n' + 'is semantically equivalent to:\n' '\n' - ' mgr = (EXPR)\n' - ' aexit = type(mgr).__aexit__\n' - ' aenter = type(mgr).__aenter__(mgr)\n' + ' manager = (EXPRESSION)\n' + ' aexit = type(manager).__aexit__\n' + ' aenter = type(manager).__aenter__\n' + ' value = await aenter(manager)\n' + ' hit_except = False\n' '\n' - ' VAR = await aenter\n' ' try:\n' - ' BLOCK\n' + ' TARGET = value\n' + ' SUITE\n' ' except:\n' - ' if not await aexit(mgr, *sys.exc_info()):\n' + ' hit_except = True\n' + ' if not await aexit(manager, *sys.exc_info()):\n' ' raise\n' - ' else:\n' - ' await aexit(mgr, None, None, None)\n' + ' finally:\n' + ' if not hit_except:\n' + ' await aexit(manager, None, None, None)\n' '\n' 'See also "__aenter__()" and "__aexit__()" for details.\n' '\n' @@ -2518,11 +2523,13 @@ '"with_item")\n' ' is evaluated to obtain a context manager.\n' '\n' - '2. The context manager’s "__exit__()" is loaded for later use.\n' + '2. The context manager’s "__enter__()" is loaded for later use.\n' '\n' - '3. The context manager’s "__enter__()" method is invoked.\n' + '3. The context manager’s "__exit__()" is loaded for later use.\n' '\n' - '4. If a target was included in the "with" statement, the return\n' + '4. The context manager’s "__enter__()" method is invoked.\n' + '\n' + '5. If a target was included in the "with" statement, the return\n' ' value from "__enter__()" is assigned to it.\n' '\n' ' Note: The "with" statement guarantees that if the ' @@ -2535,9 +2542,9 @@ 'occurring\n' ' within the suite would be. See step 6 below.\n' '\n' - '5. The suite is executed.\n' + '6. The suite is executed.\n' '\n' - '6. The context manager’s "__exit__()" method is invoked. If an\n' + '7. The context manager’s "__exit__()" method is invoked. If an\n' ' exception caused the suite to be exited, its type, value, ' 'and\n' ' traceback are passed as arguments to "__exit__()". Otherwise, ' @@ -2559,18 +2566,42 @@ 'proceeds\n' ' at the normal location for the kind of exit that was taken.\n' '\n' + 'The following code:\n' + '\n' + ' with EXPRESSION as TARGET:\n' + ' SUITE\n' + '\n' + 'is semantically equivalent to:\n' + '\n' + ' manager = (EXPRESSION)\n' + ' enter = type(manager).__enter__\n' + ' exit = type(manager).__exit__\n' + ' value = enter(manager)\n' + ' hit_except = False\n' + '\n' + ' try:\n' + ' TARGET = value\n' + ' SUITE\n' + ' except:\n' + ' hit_except = True\n' + ' if not exit(manager, *sys.exc_info()):\n' + ' raise\n' + ' finally:\n' + ' if not hit_except:\n' + ' exit(manager, None, None, None)\n' + '\n' 'With more than one item, the context managers are processed as ' 'if\n' 'multiple "with" statements were nested:\n' '\n' ' with A() as a, B() as b:\n' - ' suite\n' + ' SUITE\n' '\n' - 'is equivalent to\n' + 'is semantically equivalent to:\n' '\n' ' with A() as a:\n' ' with B() as b:\n' - ' suite\n' + ' SUITE\n' '\n' 'Changed in version 3.1: Support for multiple context ' 'expressions.\n' @@ -2934,24 +2965,25 @@ 'The following code:\n' '\n' ' async for TARGET in ITER:\n' - ' BLOCK\n' + ' SUITE\n' ' else:\n' - ' BLOCK2\n' + ' SUITE2\n' '\n' 'Is semantically equivalent to:\n' '\n' ' iter = (ITER)\n' ' iter = type(iter).__aiter__(iter)\n' ' running = True\n' + '\n' ' while running:\n' ' try:\n' ' TARGET = await type(iter).__anext__(iter)\n' ' except StopAsyncIteration:\n' ' running = False\n' ' else:\n' - ' BLOCK\n' + ' SUITE\n' ' else:\n' - ' BLOCK2\n' + ' SUITE2\n' '\n' 'See also "__aiter__()" and "__anext__()" for details.\n' '\n' @@ -2971,23 +3003,27 @@ '\n' 'The following code:\n' '\n' - ' async with EXPR as VAR:\n' - ' BLOCK\n' + ' async with EXPRESSION as TARGET:\n' + ' SUITE\n' '\n' - 'Is semantically equivalent to:\n' + 'is semantically equivalent to:\n' '\n' - ' mgr = (EXPR)\n' - ' aexit = type(mgr).__aexit__\n' - ' aenter = type(mgr).__aenter__(mgr)\n' + ' manager = (EXPRESSION)\n' + ' aexit = type(manager).__aexit__\n' + ' aenter = type(manager).__aenter__\n' + ' value = await aenter(manager)\n' + ' hit_except = False\n' '\n' - ' VAR = await aenter\n' ' try:\n' - ' BLOCK\n' + ' TARGET = value\n' + ' SUITE\n' ' except:\n' - ' if not await aexit(mgr, *sys.exc_info()):\n' + ' hit_except = True\n' + ' if not await aexit(manager, *sys.exc_info()):\n' ' raise\n' - ' else:\n' - ' await aexit(mgr, None, None, None)\n' + ' finally:\n' + ' if not hit_except:\n' + ' await aexit(manager, None, None, None)\n' '\n' 'See also "__aenter__()" and "__aexit__()" for details.\n' '\n' @@ -5250,9 +5286,12 @@ 'Changed in version 3.6: Added the "\'_\'" option (see also ' '**PEP 515**).\n' '\n' - '*width* is a decimal integer defining the minimum field ' - 'width. If not\n' - 'specified, then the field width will be determined by the ' + '*width* is a decimal integer defining the minimum total ' + 'field width,\n' + 'including any prefixes, separators, and other formatting ' + 'characters.\n' + 'If not specified, then the field width will be determined ' + 'by the\n' 'content.\n' '\n' 'When no explicit alignment is given, preceding the *width* ' @@ -6803,7 +6842,7 @@ 'object.__rfloordiv__(self, other)\n' 'object.__rmod__(self, other)\n' 'object.__rdivmod__(self, other)\n' - 'object.__rpow__(self, other)\n' + 'object.__rpow__(self, other[, modulo])\n' 'object.__rlshift__(self, other)\n' 'object.__rrshift__(self, other)\n' 'object.__rand__(self, other)\n' @@ -8963,11 +9002,17 @@ 'bases,\n' '**kwds)" (where the additional keyword arguments, if any, ' 'come from\n' - 'the class definition).\n' + 'the class definition). The "__prepare__" method should be ' + 'implemented\n' + 'as a "classmethod()". The namespace returned by ' + '"__prepare__" is\n' + 'passed in to "__new__", but when the final class object is ' + 'created the\n' + 'namespace is copied into a new "dict".\n' '\n' 'If the metaclass has no "__prepare__" attribute, then the ' 'class\n' - 'namespace is initialised as an empty ordered mapping.\n' + 'namespace is initialised as an empty "dict()".\n' '\n' 'See also:\n' '\n' @@ -9477,7 +9522,7 @@ 'object.__rfloordiv__(self, other)\n' 'object.__rmod__(self, other)\n' 'object.__rdivmod__(self, other)\n' - 'object.__rpow__(self, other)\n' + 'object.__rpow__(self, other[, modulo])\n' 'object.__rlshift__(self, other)\n' 'object.__rrshift__(self, other)\n' 'object.__rand__(self, other)\n' @@ -11918,8 +11963,9 @@ ' bytecode offsets to line numbers (for details see the source\n' ' code of the interpreter); "co_stacksize" is the required ' 'stack\n' - ' size (including local variables); "co_flags" is an integer\n' - ' encoding a number of flags for the interpreter.\n' + ' size; "co_flags" is an integer encoding a number of flags ' + 'for\n' + ' the interpreter.\n' '\n' ' The following flag bits are defined for "co_flags": bit ' '"0x04"\n' @@ -12372,6 +12418,8 @@ 'dictionary. This\n' ' is a shortcut for "reversed(d.keys())".\n' '\n' + ' New in version 3.8.\n' + '\n' ' setdefault(key[, default])\n' '\n' ' If *key* is in the dictionary, return its value. If ' @@ -13577,11 +13625,13 @@ '1. The context expression (the expression given in the "with_item")\n' ' is evaluated to obtain a context manager.\n' '\n' - '2. The context manager’s "__exit__()" is loaded for later use.\n' + '2. The context manager’s "__enter__()" is loaded for later use.\n' '\n' - '3. The context manager’s "__enter__()" method is invoked.\n' + '3. The context manager’s "__exit__()" is loaded for later use.\n' '\n' - '4. If a target was included in the "with" statement, the return\n' + '4. The context manager’s "__enter__()" method is invoked.\n' + '\n' + '5. If a target was included in the "with" statement, the return\n' ' value from "__enter__()" is assigned to it.\n' '\n' ' Note: The "with" statement guarantees that if the "__enter__()"\n' @@ -13591,9 +13641,9 @@ ' target list, it will be treated the same as an error occurring\n' ' within the suite would be. See step 6 below.\n' '\n' - '5. The suite is executed.\n' + '6. The suite is executed.\n' '\n' - '6. The context manager’s "__exit__()" method is invoked. If an\n' + '7. The context manager’s "__exit__()" method is invoked. If an\n' ' exception caused the suite to be exited, its type, value, and\n' ' traceback are passed as arguments to "__exit__()". Otherwise, ' 'three\n' @@ -13613,17 +13663,41 @@ 'proceeds\n' ' at the normal location for the kind of exit that was taken.\n' '\n' + 'The following code:\n' + '\n' + ' with EXPRESSION as TARGET:\n' + ' SUITE\n' + '\n' + 'is semantically equivalent to:\n' + '\n' + ' manager = (EXPRESSION)\n' + ' enter = type(manager).__enter__\n' + ' exit = type(manager).__exit__\n' + ' value = enter(manager)\n' + ' hit_except = False\n' + '\n' + ' try:\n' + ' TARGET = value\n' + ' SUITE\n' + ' except:\n' + ' hit_except = True\n' + ' if not exit(manager, *sys.exc_info()):\n' + ' raise\n' + ' finally:\n' + ' if not hit_except:\n' + ' exit(manager, None, None, None)\n' + '\n' 'With more than one item, the context managers are processed as if\n' 'multiple "with" statements were nested:\n' '\n' ' with A() as a, B() as b:\n' - ' suite\n' + ' SUITE\n' '\n' - 'is equivalent to\n' + 'is semantically equivalent to:\n' '\n' ' with A() as a:\n' ' with B() as b:\n' - ' suite\n' + ' SUITE\n' '\n' 'Changed in version 3.1: Support for multiple context expressions.\n' '\n'
diff --git a/Lib/shutil.py b/Lib/shutil.py index 755ce39..1f05d80 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py
@@ -235,6 +235,8 @@ symlink will be created instead of copying the file it points to. """ + sys.audit("shutil.copyfile", src, dst) + if _samefile(src, dst): raise SameFileError("{!r} and {!r} are the same file".format(src, dst)) @@ -289,6 +291,8 @@ (e.g. Linux) this method does nothing. """ + sys.audit("shutil.copymode", src, dst) + if not follow_symlinks and _islink(src) and os.path.islink(dst): if hasattr(os, 'lchmod'): stat_func, chmod_func = os.lstat, os.lchmod @@ -340,6 +344,8 @@ If the optional flag `follow_symlinks` is not set, symlinks aren't followed if and only if both `src` and `dst` are symlinks. """ + sys.audit("shutil.copystat", src, dst) + def _nop(*args, ns=None, follow_symlinks=None): pass @@ -442,7 +448,7 @@ def _copytree(entries, src, dst, symlinks, ignore, copy_function, ignore_dangling_symlinks, dirs_exist_ok=False): if ignore is not None: - ignored_names = ignore(src, {x.name for x in entries}) + ignored_names = ignore(os.fspath(src), [x.name for x in entries]) else: ignored_names = set() @@ -766,6 +772,7 @@ the issues this implementation glosses over. """ + sys.audit("shutil.move", src, dst) real_dst = dst if os.path.isdir(dst): if _samefile(src, dst): @@ -1193,6 +1200,8 @@ In case none is found, a ValueError is raised. """ + sys.audit("shutil.unpack_archive", filename, extract_dir, format) + if extract_dir is None: extract_dir = os.getcwd() @@ -1260,6 +1269,7 @@ user and group can be the uid/gid or the user/group names, and in that case, they are converted to their respective uid/gid. """ + sys.audit('shutil.chown', path, user, group) if user is None and group is None: raise ValueError("user and/or group must be set")
diff --git a/Lib/socket.py b/Lib/socket.py index 5b17906..f83f36d 100644 --- a/Lib/socket.py +++ b/Lib/socket.py
@@ -843,7 +843,7 @@ connections. When false it will explicitly disable this option on platforms that enable it by default (e.g. Linux). - >>> with create_server((None, 8000)) as server: + >>> with create_server(('', 8000)) as server: ... while True: ... conn, addr = server.accept() ... # handle new connection
diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 85b9ea0..5c2c2f0 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py
@@ -1848,9 +1848,9 @@ with _PopenSelector() as selector: if self.stdin and input: selector.register(self.stdin, selectors.EVENT_WRITE) - if self.stdout: + if self.stdout and not self.stdout.closed: selector.register(self.stdout, selectors.EVENT_READ) - if self.stderr: + if self.stderr and not self.stderr.closed: selector.register(self.stderr, selectors.EVENT_READ) while selector.get_map():
diff --git a/Lib/tarfile.py b/Lib/tarfile.py index 2c06f91..d31b9cb 100644 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py
@@ -1655,13 +1655,12 @@ raise ValueError("mode must be 'r', 'w' or 'x'") try: - import gzip - gzip.GzipFile - except (ImportError, AttributeError): + from gzip import GzipFile + except ImportError: raise CompressionError("gzip module is not available") try: - fileobj = gzip.GzipFile(name, mode + "b", compresslevel, fileobj) + fileobj = GzipFile(name, mode + "b", compresslevel, fileobj) except OSError: if fileobj is not None and mode == 'r': raise ReadError("not a gzip file") @@ -1689,12 +1688,11 @@ raise ValueError("mode must be 'r', 'w' or 'x'") try: - import bz2 + from bz2 import BZ2File except ImportError: raise CompressionError("bz2 module is not available") - fileobj = bz2.BZ2File(fileobj or name, mode, - compresslevel=compresslevel) + fileobj = BZ2File(fileobj or name, mode, compresslevel=compresslevel) try: t = cls.taropen(name, mode, fileobj, **kwargs) @@ -1718,15 +1716,15 @@ raise ValueError("mode must be 'r', 'w' or 'x'") try: - import lzma + from lzma import LZMAFile, LZMAError except ImportError: raise CompressionError("lzma module is not available") - fileobj = lzma.LZMAFile(fileobj or name, mode, preset=preset) + fileobj = LZMAFile(fileobj or name, mode, preset=preset) try: t = cls.taropen(name, mode, fileobj, **kwargs) - except (lzma.LZMAError, EOFError): + except (LZMAError, EOFError): fileobj.close() if mode == 'r': raise ReadError("not an lzma file")
diff --git a/Lib/trace.py b/Lib/trace.py index 62325d3..a447357 100644 --- a/Lib/trace.py +++ b/Lib/trace.py
@@ -52,6 +52,7 @@ import linecache import os import sys +import sysconfig import token import tokenize import inspect @@ -676,9 +677,8 @@ opts = parser.parse_args() if opts.ignore_dir: - rel_path = 'lib', 'python{0.major}.{0.minor}'.format(sys.version_info) - _prefix = os.path.join(sys.base_prefix, *rel_path) - _exec_prefix = os.path.join(sys.base_exec_prefix, *rel_path) + _prefix = sysconfig.get_path("stdlib") + _exec_prefix = sysconfig.get_path("platstdlib") def parse_ignore_dir(s): s = os.path.expanduser(os.path.expandvars(s))
diff --git a/Lib/typing.py b/Lib/typing.py index 69f6d98..7aab8db 100644 --- a/Lib/typing.py +++ b/Lib/typing.py
@@ -13,7 +13,7 @@ * Public helper functions: get_type_hints, overload, cast, no_type_check, no_type_check_decorator. * Generic aliases for collections.abc ABCs and few additional protocols. -* Special types: NewType, NamedTuple, TypedDict (may be added soon). +* Special types: NewType, NamedTuple, TypedDict. * Wrapper submodules for re and io related types. """ @@ -1779,6 +1779,19 @@ Point2D = TypedDict('Point2D', x=int, y=int, label=str) Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str}) + By default, all keys must be present in a TypedDict. It is possible + to override this by specifying totality. + Usage:: + + class point2D(TypedDict, total=False): + x: int + y: int + + This means that a point2D TypedDict can have any of the keys omitted.A type + checker is only expected to support a literal False or True as the value of + the total argument. True is the default, and makes all items defined in the + class body be required. + The class syntax is only supported in Python 3.6+, while two other syntax forms work for Python 2.7 and 3.2+ """ @@ -1848,6 +1861,7 @@ def close(self) -> None: pass + @property @abstractmethod def closed(self) -> bool: pass
diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index b639c64..e5734b6 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py
@@ -529,7 +529,7 @@ the specified test method's docstring. """ doc = self._testMethodDoc - return doc and doc.split("\n")[0].strip() or None + return doc.strip().split("\n")[0].strip() if doc else None def id(self):
diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index e92ccf1..204b3e7 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py
@@ -48,6 +48,8 @@ def _is_async_obj(obj): if _is_instance_mock(obj) and not isinstance(obj, AsyncMock): return False + if hasattr(obj, '__func__'): + obj = getattr(obj, '__func__') return asyncio.iscoroutinefunction(obj) or inspect.isawaitable(obj) @@ -825,6 +827,10 @@ if child is None or isinstance(child, _SpecState): break else: + # If an autospecced object is attached using attach_mock the + # child would be a function with mock object as attribute from + # which signature has to be derived. + child = _extract_mock(child) children = child._mock_children sig = child._spec_signature @@ -2709,7 +2715,7 @@ continue if isinstance(result, (staticmethod, classmethod)): return False - elif isinstance(getattr(result, '__get__', None), MethodWrapperTypes): + elif isinstance(result, FunctionTypes): # Normal method => skip if looked up on type # (if looked up on instance, self is already skipped) return is_type @@ -2739,10 +2745,6 @@ type(ANY.__eq__), ) -MethodWrapperTypes = ( - type(ANY.__eq__.__get__), -) - file_spec = None
diff --git a/Lib/urllib/parse.py b/Lib/urllib/parse.py index d497925..e2b6f13 100644 --- a/Lib/urllib/parse.py +++ b/Lib/urllib/parse.py
@@ -431,11 +431,31 @@ netloc = query = fragment = '' i = url.find(':') if i > 0: + if url[:i] == 'http': # optimize the common case + url = url[i+1:] + if url[:2] == '//': + netloc, url = _splitnetloc(url, 2) + if (('[' in netloc and ']' not in netloc) or + (']' in netloc and '[' not in netloc)): + raise ValueError("Invalid IPv6 URL") + if allow_fragments and '#' in url: + url, fragment = url.split('#', 1) + if '?' in url: + url, query = url.split('?', 1) + _checknetloc(netloc) + v = SplitResult('http', netloc, url, query, fragment) + _parse_cache[key] = v + return _coerce_result(v) for c in url[:i]: if c not in scheme_chars: break else: - scheme, url = url[:i].lower(), url[i+1:] + # make sure "url" is not actually a port number (in which case + # "scheme" is really part of the path) + rest = url[i+1:] + if not rest or any(c not in '0123456789' for c in rest): + # not a port number + scheme, url = url[:i].lower(), rest if url[:2] == '//': netloc, url = _splitnetloc(url, 2) @@ -1054,9 +1074,9 @@ """splitport('host:port') --> 'host', 'port'.""" global _portprog if _portprog is None: - _portprog = re.compile('(.*):([0-9]*)$', re.DOTALL) + _portprog = re.compile('(.*):([0-9]*)', re.DOTALL) - match = _portprog.match(host) + match = _portprog.fullmatch(host) if match: host, port = match.groups() if port:
diff --git a/Lib/urllib/request.py b/Lib/urllib/request.py index 51c4759..6f6577b 100644 --- a/Lib/urllib/request.py +++ b/Lib/urllib/request.py
@@ -2500,24 +2500,26 @@ try: no_proxy = proxies['no'] except KeyError: - return 0 + return False # '*' is special case for always bypass if no_proxy == '*': - return 1 + return True + host = host.lower() # strip port off host hostonly, port = _splitport(host) # check if the host ends with any of the DNS suffixes - no_proxy_list = [proxy.strip() for proxy in no_proxy.split(',')] - for name in no_proxy_list: + for name in no_proxy.split(','): + name = name.strip() if name: name = name.lstrip('.') # ignore leading dots - name = re.escape(name) - pattern = r'(.+\.)?%s$' % name - if (re.match(pattern, hostonly, re.I) - or re.match(pattern, host, re.I)): - return 1 + name = name.lower() + if hostonly == name or host == name: + return True + name = '.' + name + if hostonly.endswith(name) or host.endswith(name): + return True # otherwise, don't bypass - return 0 + return False # This code tests an OSX specific data structure but is testable on all @@ -2643,7 +2645,7 @@ for p in proxyServer.split(';'): protocol, address = p.split('=', 1) # See if address has a type:// prefix - if not re.match('^([^/:]+)://', address): + if not re.match('(?:[^/:]+)://', address): address = '%s://%s' % (protocol, address) proxies[protocol] = address else:
diff --git a/Lib/warnings.py b/Lib/warnings.py index 00f740c..691ccdd 100644 --- a/Lib/warnings.py +++ b/Lib/warnings.py
@@ -211,7 +211,6 @@ # Helper for _processoptions() def _setoption(arg): - import re parts = arg.split(':') if len(parts) > 5: raise _OptionError("too many fields (max 5): %r" % (arg,)) @@ -220,11 +219,13 @@ action, message, category, module, lineno = [s.strip() for s in parts] action = _getaction(action) - message = re.escape(message) category = _getcategory(category) - module = re.escape(module) + if message or module: + import re + if message: + message = re.escape(message) if module: - module = module + '$' + module = re.escape(module) + r'\Z' if lineno: try: lineno = int(lineno) @@ -248,26 +249,21 @@ # Helper for _setoption() def _getcategory(category): - import re if not category: return Warning - if re.match("^[a-zA-Z0-9_]+$", category): - try: - cat = eval(category) - except NameError: - raise _OptionError("unknown warning category: %r" % (category,)) from None + if '.' not in category: + import builtins as m + klass = category else: - i = category.rfind(".") - module = category[:i] - klass = category[i+1:] + module, _, klass = category.rpartition('.') try: m = __import__(module, None, None, [klass]) except ImportError: raise _OptionError("invalid module name: %r" % (module,)) from None - try: - cat = getattr(m, klass) - except AttributeError: - raise _OptionError("unknown warning category: %r" % (category,)) from None + try: + cat = getattr(m, klass) + except AttributeError: + raise _OptionError("unknown warning category: %r" % (category,)) from None if not issubclass(cat, Warning): raise _OptionError("invalid warning category: %r" % (category,)) return cat
diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 0af36c4..1ef179a 100644 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py
@@ -87,7 +87,7 @@ def _synthesize(browser, *, preferred=False): - """Attempt to synthesize a controller base on existing controllers. + """Attempt to synthesize a controller based on existing controllers. This is useful to create a controller when a user specifies a path to an entry in the BROWSER environment variable -- we can copy a general
diff --git a/Lib/zipfile.py b/Lib/zipfile.py index 8b99c11..5dc6516 100644 --- a/Lib/zipfile.py +++ b/Lib/zipfile.py
@@ -16,6 +16,8 @@ import sys import threading import time +import contextlib +from collections import OrderedDict try: import zlib # We may need its compression method @@ -2182,6 +2184,79 @@ path, tail = posixpath.split(path) +class CompleteDirs(ZipFile): + """ + A ZipFile subclass that ensures that implied directories + are always included in the namelist. + """ + + @staticmethod + def _implied_dirs(names): + parents = itertools.chain.from_iterable(map(_parents, names)) + # Deduplicate entries in original order + implied_dirs = OrderedDict.fromkeys( + p + posixpath.sep for p in parents + # Cast names to a set for O(1) lookups + if p + posixpath.sep not in set(names) + ) + return implied_dirs + + def namelist(self): + names = super(CompleteDirs, self).namelist() + return names + list(self._implied_dirs(names)) + + def _name_set(self): + return set(self.namelist()) + + def resolve_dir(self, name): + """ + If the name represents a directory, return that name + as a directory (with the trailing slash). + """ + names = self._name_set() + dirname = name + '/' + dir_match = name not in names and dirname in names + return dirname if dir_match else name + + @classmethod + def make(cls, source): + """ + Given a source (filename or zipfile), return an + appropriate CompleteDirs subclass. + """ + if isinstance(source, CompleteDirs): + return source + + if not isinstance(source, ZipFile): + return cls(source) + + # Only allow for FastPath when supplied zipfile is read-only + if 'r' not in source.mode: + cls = CompleteDirs + + res = cls.__new__(cls) + vars(res).update(vars(source)) + return res + + +class FastLookup(CompleteDirs): + """ + ZipFile subclass to ensure implicit + dirs exist and are resolved rapidly. + """ + def namelist(self): + with contextlib.suppress(AttributeError): + return self.__names + self.__names = super(FastLookup, self).namelist() + return self.__names + + def _name_set(self): + with contextlib.suppress(AttributeError): + return self.__lookup + self.__lookup = super(FastLookup, self)._name_set() + return self.__lookup + + class Path: """ A pathlib-compatible interface for zip files. @@ -2250,7 +2325,7 @@ __repr = "{self.__class__.__name__}({self.root.filename!r}, {self.at!r})" def __init__(self, root, at=""): - self.root = root if isinstance(root, ZipFile) else ZipFile(root) + self.root = FastLookup.make(root) self.at = at @property @@ -2282,12 +2357,12 @@ return not self.is_dir() def exists(self): - return self.at in self._names() + return self.at in self.root._name_set() def iterdir(self): if not self.is_dir(): raise ValueError("Can't listdir a file") - subs = map(self._next, self._names()) + subs = map(self._next, self.root.namelist()) return filter(self._is_child, subs) def __str__(self): @@ -2298,25 +2373,10 @@ def joinpath(self, add): next = posixpath.join(self.at, add) - next_dir = posixpath.join(self.at, add, "") - names = self._names() - return self._next(next_dir if next not in names and next_dir in names else next) + return self._next(self.root.resolve_dir(next)) __truediv__ = joinpath - @staticmethod - def _implied_dirs(names): - return _unique_everseen( - parent + "/" - for name in names - for parent in _parents(name) - if parent + "/" not in names - ) - - @classmethod - def _add_implied_dirs(cls, names): - return names + list(cls._implied_dirs(names)) - @property def parent(self): parent_at = posixpath.dirname(self.at.rstrip('/')) @@ -2324,9 +2384,6 @@ parent_at += '/' return self._next(parent_at) - def _names(self): - return self._add_implied_dirs(self.root.namelist()) - def main(args=None): import argparse @@ -2388,5 +2445,6 @@ zippath = '' addToZip(zf, path, zippath) + if __name__ == "__main__": main()
diff --git a/Lib/zipimport.py b/Lib/zipimport.py index fd917c1..5ef0a17 100644 --- a/Lib/zipimport.py +++ b/Lib/zipimport.py
@@ -608,7 +608,7 @@ ) try: - _boostrap_external._validate_hash_pyc( + _bootstrap_external._validate_hash_pyc( data, source_hash, fullname, exc_details) except ImportError: return None
diff --git a/include/patchlevel.h b/include/patchlevel.h index 6936a3f..3671bb3 100644 --- a/include/patchlevel.h +++ b/include/patchlevel.h
@@ -18,12 +18,12 @@ /*--start constants--*/ #define PY_MAJOR_VERSION 3 #define PY_MINOR_VERSION 8 -#define PY_MICRO_VERSION 1 +#define PY_MICRO_VERSION 2 #define PY_RELEASE_LEVEL PY_RELEASE_LEVEL_FINAL #define PY_RELEASE_SERIAL 0 /* Version as a string */ -#define PY_VERSION "3.8.1" +#define PY_VERSION "3.8.2" /*--end constants--*/ /* Version as a single 4-byte hex number, e.g. 0x010502B2 == 1.5.2b2.
diff --git a/python.exe b/python.exe index 6e9a4b9..991c338 100644 --- a/python.exe +++ b/python.exe Binary files differ
diff --git a/python38.dll b/python38.dll index c0a6d07..65571b0 100644 --- a/python38.dll +++ b/python38.dll Binary files differ
diff --git a/pythonw.exe b/pythonw.exe index b61dd9a..58b2b84 100644 --- a/pythonw.exe +++ b/pythonw.exe Binary files differ