summaryrefslogtreecommitdiffstats
path: root/contrib/tools/python3/src/Lib/importlib
diff options
context:
space:
mode:
authorshadchin <[email protected]>2022-04-18 12:39:32 +0300
committershadchin <[email protected]>2022-04-18 12:39:32 +0300
commitd4be68e361f4258cf0848fc70018dfe37a2acc24 (patch)
tree153e294cd97ac8b5d7a989612704a0c1f58e8ad4 /contrib/tools/python3/src/Lib/importlib
parent260c02f5ccf242d9d9b8a873afaf6588c00237d6 (diff)
IGNIETFERRO-1816 Update Python 3 from 3.9.12 to 3.10.4
ref:9f96be6d02ee8044fdd6f124b799b270c20ce641
Diffstat (limited to 'contrib/tools/python3/src/Lib/importlib')
-rw-r--r--contrib/tools/python3/src/Lib/importlib/__init__.py14
-rw-r--r--contrib/tools/python3/src/Lib/importlib/_abc.py54
-rw-r--r--contrib/tools/python3/src/Lib/importlib/_adapters.py83
-rw-r--r--contrib/tools/python3/src/Lib/importlib/_bootstrap.py80
-rw-r--r--contrib/tools/python3/src/Lib/importlib/_bootstrap_external.py184
-rw-r--r--contrib/tools/python3/src/Lib/importlib/_common.py85
-rw-r--r--contrib/tools/python3/src/Lib/importlib/abc.py136
-rw-r--r--contrib/tools/python3/src/Lib/importlib/machinery.py2
-rw-r--r--contrib/tools/python3/src/Lib/importlib/metadata.py604
-rw-r--r--contrib/tools/python3/src/Lib/importlib/metadata/__init__.py1045
-rw-r--r--contrib/tools/python3/src/Lib/importlib/metadata/_adapters.py68
-rw-r--r--contrib/tools/python3/src/Lib/importlib/metadata/_collections.py30
-rw-r--r--contrib/tools/python3/src/Lib/importlib/metadata/_functools.py85
-rw-r--r--contrib/tools/python3/src/Lib/importlib/metadata/_itertools.py19
-rw-r--r--contrib/tools/python3/src/Lib/importlib/metadata/_meta.py47
-rw-r--r--contrib/tools/python3/src/Lib/importlib/metadata/_text.py99
-rw-r--r--contrib/tools/python3/src/Lib/importlib/readers.py123
-rw-r--r--contrib/tools/python3/src/Lib/importlib/resources.py216
-rw-r--r--contrib/tools/python3/src/Lib/importlib/util.py14
19 files changed, 2009 insertions, 979 deletions
diff --git a/contrib/tools/python3/src/Lib/importlib/__init__.py b/contrib/tools/python3/src/Lib/importlib/__init__.py
index 0c73c505f98..ce61883288a 100644
--- a/contrib/tools/python3/src/Lib/importlib/__init__.py
+++ b/contrib/tools/python3/src/Lib/importlib/__init__.py
@@ -34,7 +34,7 @@ try:
import _frozen_importlib_external as _bootstrap_external
except ImportError:
from . import _bootstrap_external
- _bootstrap_external._setup(_bootstrap)
+ _bootstrap_external._set_bootstrap_module(_bootstrap)
_bootstrap._bootstrap_external = _bootstrap_external
else:
_bootstrap_external.__name__ = 'importlib._bootstrap_external'
@@ -54,7 +54,6 @@ _unpack_uint32 = _bootstrap_external._unpack_uint32
# Fully bootstrapped at this point, import whatever you like, circular
# dependencies and startup overhead minimisation permitting :)
-import types
import warnings
@@ -79,8 +78,8 @@ def find_loader(name, path=None):
This function is deprecated in favor of importlib.util.find_spec().
"""
- warnings.warn('Deprecated since Python 3.4. '
- 'Use importlib.util.find_spec() instead.',
+ warnings.warn('Deprecated since Python 3.4 and slated for removal in '
+ 'Python 3.12; use importlib.util.find_spec() instead',
DeprecationWarning, stacklevel=2)
try:
loader = sys.modules[name].__loader__
@@ -136,12 +135,13 @@ def reload(module):
The module must have been successfully imported before.
"""
- if not module or not isinstance(module, types.ModuleType):
- raise TypeError("reload() argument must be a module")
try:
name = module.__spec__.name
except AttributeError:
- name = module.__name__
+ try:
+ name = module.__name__
+ except AttributeError:
+ raise TypeError("reload() argument must be a module")
if sys.modules.get(name) is not module:
msg = "module {} not in sys.modules"
diff --git a/contrib/tools/python3/src/Lib/importlib/_abc.py b/contrib/tools/python3/src/Lib/importlib/_abc.py
new file mode 100644
index 00000000000..f80348fc7ff
--- /dev/null
+++ b/contrib/tools/python3/src/Lib/importlib/_abc.py
@@ -0,0 +1,54 @@
+"""Subset of importlib.abc used to reduce importlib.util imports."""
+from . import _bootstrap
+import abc
+import warnings
+
+
+class Loader(metaclass=abc.ABCMeta):
+
+ """Abstract base class for import loaders."""
+
+ def create_module(self, spec):
+ """Return a module to initialize and into which to load.
+
+ This method should raise ImportError if anything prevents it
+ from creating a new module. It may return None to indicate
+ that the spec should create the new module.
+ """
+ # By default, defer to default semantics for the new module.
+ return None
+
+ # We don't define exec_module() here since that would break
+ # hasattr checks we do to support backward compatibility.
+
+ def load_module(self, fullname):
+ """Return the loaded module.
+
+ The module must be added to sys.modules and have import-related
+ attributes set properly. The fullname is a str.
+
+ ImportError is raised on failure.
+
+ This method is deprecated in favor of loader.exec_module(). If
+ exec_module() exists then it is used to provide a backwards-compatible
+ functionality for this method.
+
+ """
+ if not hasattr(self, 'exec_module'):
+ raise ImportError
+ # Warning implemented in _load_module_shim().
+ return _bootstrap._load_module_shim(self, fullname)
+
+ def module_repr(self, module):
+ """Return a module's repr.
+
+ Used by the module type when the method does not raise
+ NotImplementedError.
+
+ This method is deprecated.
+
+ """
+ warnings.warn("importlib.abc.Loader.module_repr() is deprecated and "
+ "slated for removal in Python 3.12", DeprecationWarning)
+ # The exception will cause ModuleType.__repr__ to ignore this method.
+ raise NotImplementedError
diff --git a/contrib/tools/python3/src/Lib/importlib/_adapters.py b/contrib/tools/python3/src/Lib/importlib/_adapters.py
new file mode 100644
index 00000000000..e72edd10705
--- /dev/null
+++ b/contrib/tools/python3/src/Lib/importlib/_adapters.py
@@ -0,0 +1,83 @@
+from contextlib import suppress
+
+from . import abc
+
+
+class SpecLoaderAdapter:
+ """
+ Adapt a package spec to adapt the underlying loader.
+ """
+
+ def __init__(self, spec, adapter=lambda spec: spec.loader):
+ self.spec = spec
+ self.loader = adapter(spec)
+
+ def __getattr__(self, name):
+ return getattr(self.spec, name)
+
+
+class TraversableResourcesLoader:
+ """
+ Adapt a loader to provide TraversableResources.
+ """
+
+ def __init__(self, spec):
+ self.spec = spec
+
+ def get_resource_reader(self, name):
+ return DegenerateFiles(self.spec)._native()
+
+
+class DegenerateFiles:
+ """
+ Adapter for an existing or non-existant resource reader
+ to provide a degenerate .files().
+ """
+
+ class Path(abc.Traversable):
+ def iterdir(self):
+ return iter(())
+
+ def is_dir(self):
+ return False
+
+ is_file = exists = is_dir # type: ignore
+
+ def joinpath(self, other):
+ return DegenerateFiles.Path()
+
+ @property
+ def name(self):
+ return ''
+
+ def open(self, mode='rb', *args, **kwargs):
+ raise ValueError()
+
+ def __init__(self, spec):
+ self.spec = spec
+
+ @property
+ def _reader(self):
+ with suppress(AttributeError):
+ return self.spec.loader.get_resource_reader(self.spec.name)
+
+ def _native(self):
+ """
+ Return the native reader if it supports files().
+ """
+ reader = self._reader
+ return reader if hasattr(reader, 'files') else self
+
+ def __getattr__(self, attr):
+ return getattr(self._reader, attr)
+
+ def files(self):
+ return DegenerateFiles.Path()
+
+
+def wrap_spec(package):
+ """
+ Construct a package spec with traversable compatibility
+ on the spec/loader/reader.
+ """
+ return SpecLoaderAdapter(package.__spec__, TraversableResourcesLoader)
diff --git a/contrib/tools/python3/src/Lib/importlib/_bootstrap.py b/contrib/tools/python3/src/Lib/importlib/_bootstrap.py
index e00b27ece26..527bc9c63c9 100644
--- a/contrib/tools/python3/src/Lib/importlib/_bootstrap.py
+++ b/contrib/tools/python3/src/Lib/importlib/_bootstrap.py
@@ -20,10 +20,23 @@ work. One should use importlib as the public-facing version of this module.
# reference any injected objects! This includes not only global code but also
# anything specified at the class level.
+def _object_name(obj):
+ try:
+ return obj.__qualname__
+ except AttributeError:
+ return type(obj).__qualname__
+
# Bootstrap-related code ######################################################
+# Modules injected manually by _setup()
+_thread = None
+_warnings = None
+_weakref = None
+
+# Import done by _install_external_importers()
_bootstrap_external = None
+
def _wrap(new, old):
"""Simple substitute for functools.update_wrapper."""
for replace in ['__module__', '__name__', '__qualname__', '__doc__']:
@@ -262,9 +275,12 @@ def _requires_frozen(fxn):
def _load_module_shim(self, fullname):
"""Load the specified module into sys.modules and return it.
- This method is deprecated. Use loader.exec_module instead.
+ This method is deprecated. Use loader.exec_module() instead.
"""
+ msg = ("the load_module() method is deprecated and slated for removal in "
+ "Python 3.12; use exec_module() instead")
+ _warnings.warn(msg, DeprecationWarning)
spec = spec_from_loader(fullname, self)
if fullname in sys.modules:
module = sys.modules[fullname]
@@ -276,26 +292,16 @@ def _load_module_shim(self, fullname):
# Module specifications #######################################################
def _module_repr(module):
- # The implementation of ModuleType.__repr__().
+ """The implementation of ModuleType.__repr__()."""
loader = getattr(module, '__loader__', None)
- if hasattr(loader, 'module_repr'):
- # As soon as BuiltinImporter, FrozenImporter, and NamespaceLoader
- # drop their implementations for module_repr. we can add a
- # deprecation warning here.
+ if spec := getattr(module, "__spec__", None):
+ return _module_repr_from_spec(spec)
+ elif hasattr(loader, 'module_repr'):
try:
return loader.module_repr(module)
except Exception:
pass
- try:
- spec = module.__spec__
- except AttributeError:
- pass
- else:
- if spec is not None:
- return _module_repr_from_spec(spec)
-
- # We could use module.__class__.__name__ instead of 'module' in the
- # various repr permutations.
+ # Fall through to a catch-all which always succeeds.
try:
name = module.__name__
except AttributeError:
@@ -605,9 +611,9 @@ def _exec(spec, module):
else:
_init_module_attrs(spec, module, override=True)
if not hasattr(spec.loader, 'exec_module'):
- # (issue19713) Once BuiltinImporter and ExtensionFileLoader
- # have exec_module() implemented, we can add a deprecation
- # warning here.
+ msg = (f"{_object_name(spec.loader)}.exec_module() not found; "
+ "falling back to load_module()")
+ _warnings.warn(msg, ImportWarning)
spec.loader.load_module(name)
else:
spec.loader.exec_module(module)
@@ -620,9 +626,8 @@ def _exec(spec, module):
def _load_backward_compatible(spec):
- # (issue19713) Once BuiltinImporter and ExtensionFileLoader
- # have exec_module() implemented, we can add a deprecation
- # warning here.
+ # It is assumed that all callers have been warned about using load_module()
+ # appropriately before calling this function.
try:
spec.loader.load_module(spec.name)
except:
@@ -661,6 +666,9 @@ def _load_unlocked(spec):
if spec.loader is not None:
# Not a namespace package.
if not hasattr(spec.loader, 'exec_module'):
+ msg = (f"{_object_name(spec.loader)}.exec_module() not found; "
+ "falling back to load_module()")
+ _warnings.warn(msg, ImportWarning)
return _load_backward_compatible(spec)
module = module_from_spec(spec)
@@ -731,6 +739,8 @@ class BuiltinImporter:
The method is deprecated. The import machinery does the job itself.
"""
+ _warnings.warn("BuiltinImporter.module_repr() is deprecated and "
+ "slated for removal in Python 3.12", DeprecationWarning)
return f'<module {module.__name__!r} ({BuiltinImporter._ORIGIN})>'
@classmethod
@@ -751,19 +761,22 @@ class BuiltinImporter:
This method is deprecated. Use find_spec() instead.
"""
+ _warnings.warn("BuiltinImporter.find_module() is deprecated and "
+ "slated for removal in Python 3.12; use find_spec() instead",
+ DeprecationWarning)
spec = cls.find_spec(fullname, path)
return spec.loader if spec is not None else None
- @classmethod
- def create_module(self, spec):
+ @staticmethod
+ def create_module(spec):
"""Create a built-in module"""
if spec.name not in sys.builtin_module_names:
raise ImportError('{!r} is not a built-in module'.format(spec.name),
name=spec.name)
return _call_with_frames_removed(_imp.create_builtin, spec)
- @classmethod
- def exec_module(self, module):
+ @staticmethod
+ def exec_module(module):
"""Exec a built-in module"""
_call_with_frames_removed(_imp.exec_builtin, module)
@@ -806,6 +819,8 @@ class FrozenImporter:
The method is deprecated. The import machinery does the job itself.
"""
+ _warnings.warn("FrozenImporter.module_repr() is deprecated and "
+ "slated for removal in Python 3.12", DeprecationWarning)
return '<module {!r} ({})>'.format(m.__name__, FrozenImporter._ORIGIN)
@classmethod
@@ -822,10 +837,13 @@ class FrozenImporter:
This method is deprecated. Use find_spec() instead.
"""
+ _warnings.warn("FrozenImporter.find_module() is deprecated and "
+ "slated for removal in Python 3.12; use find_spec() instead",
+ DeprecationWarning)
return cls if _imp.is_frozen(fullname) else None
- @classmethod
- def create_module(cls, spec):
+ @staticmethod
+ def create_module(spec):
"""Use default semantics for module creation."""
@staticmethod
@@ -844,6 +862,7 @@ class FrozenImporter:
This method is deprecated. Use exec_module() instead.
"""
+ # Warning about deprecation implemented in _load_module_shim().
return _load_module_shim(cls, fullname)
@classmethod
@@ -890,8 +909,9 @@ def _resolve_name(name, package, level):
def _find_spec_legacy(finder, name, path):
- # This would be a good place for a DeprecationWarning if
- # we ended up going that route.
+ msg = (f"{_object_name(finder)}.find_spec() not found; "
+ "falling back to find_module()")
+ _warnings.warn(msg, ImportWarning)
loader = finder.find_module(name, path)
if loader is None:
return None
diff --git a/contrib/tools/python3/src/Lib/importlib/_bootstrap_external.py b/contrib/tools/python3/src/Lib/importlib/_bootstrap_external.py
index f3828b10e1c..49bcaea78d7 100644
--- a/contrib/tools/python3/src/Lib/importlib/_bootstrap_external.py
+++ b/contrib/tools/python3/src/Lib/importlib/_bootstrap_external.py
@@ -19,6 +19,9 @@ work. One should use importlib as the public-facing version of this module.
# reference any injected objects! This includes not only global code but also
# anything specified at the class level.
+# Module injected manually by _set_bootstrap_module()
+_bootstrap = None
+
# Import builtin modules
import _imp
import _io
@@ -70,6 +73,8 @@ def _make_relax_case():
return False
return _relax_case
+_relax_case = _make_relax_case()
+
def _pack_uint32(x):
"""Convert a 32-bit integer to little-endian."""
@@ -337,6 +342,16 @@ _code_type = type(_write_atomic.__code__)
# Python 3.9a2 3423 (add IS_OP, CONTAINS_OP and JUMP_IF_NOT_EXC_MATCH bytecodes #39156)
# Python 3.9a2 3424 (simplify bytecodes for *value unpacking)
# Python 3.9a2 3425 (simplify bytecodes for **value unpacking)
+# Python 3.10a1 3430 (Make 'annotations' future by default)
+# Python 3.10a1 3431 (New line number table format -- PEP 626)
+# Python 3.10a2 3432 (Function annotation for MAKE_FUNCTION is changed from dict to tuple bpo-42202)
+# Python 3.10a2 3433 (RERAISE restores f_lasti if oparg != 0)
+# Python 3.10a6 3434 (PEP 634: Structural Pattern Matching)
+# Python 3.10a7 3435 Use instruction offsets (as opposed to byte offsets).
+# Python 3.10b1 3436 (Add GEN_START bytecode #43683)
+# Python 3.10b1 3437 (Undo making 'annotations' future by default - We like to dance among core devs!)
+# Python 3.10b1 3438 Safer line number table handling.
+# Python 3.10b1 3439 (Add ROT_N)
#
# MAGIC must change whenever the bytecode emitted by the compiler may no
@@ -346,13 +361,17 @@ _code_type = type(_write_atomic.__code__)
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
# in PC/launcher.c must also be updated.
-MAGIC_NUMBER = (3425).to_bytes(2, 'little') + b'\r\n'
+MAGIC_NUMBER = (3439).to_bytes(2, 'little') + b'\r\n'
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
_PYCACHE = '__pycache__'
_OPT = 'opt-'
-SOURCE_SUFFIXES = ['.py'] # _setup() adds .pyw as needed.
+SOURCE_SUFFIXES = ['.py']
+if _MS_WINDOWS:
+ SOURCE_SUFFIXES.append('.pyw')
+
+EXTENSION_SUFFIXES = _imp.extension_suffixes()
BYTECODE_SUFFIXES = ['.pyc']
# Deprecated.
@@ -527,15 +546,18 @@ def _check_name(method):
raise ImportError('loader for %s cannot handle %s' %
(self.name, name), name=name)
return method(self, name, *args, **kwargs)
- try:
+
+ # FIXME: @_check_name is used to define class methods before the
+ # _bootstrap module is set by _set_bootstrap_module().
+ if _bootstrap is not None:
_wrap = _bootstrap._wrap
- except NameError:
- # XXX yuck
+ else:
def _wrap(new, old):
for replace in ['__module__', '__name__', '__qualname__', '__doc__']:
if hasattr(old, replace):
setattr(new, replace, getattr(old, replace))
new.__dict__.update(old.__dict__)
+
_wrap(_check_name_wrapper, method)
return _check_name_wrapper
@@ -547,6 +569,9 @@ def _find_module_shim(self, fullname):
This method is deprecated in favor of finder.find_spec().
"""
+ _warnings.warn("find_module() is deprecated and "
+ "slated for removal in Python 3.12; use find_spec() instead",
+ DeprecationWarning)
# Call find_loader(). If it returns a string (indicating this
# is a namespace package portion), generate a warning and
# return None.
@@ -718,6 +743,11 @@ def spec_from_file_location(name, location=None, *, loader=None,
pass
else:
location = _os.fspath(location)
+ if not _path_isabs(location):
+ try:
+ location = _path_join(_os.getcwd(), location)
+ except OSError:
+ pass
# If the location is on the filesystem, but doesn't actually exist,
# we could return None here, indicating that the location is not
@@ -771,10 +801,10 @@ class WindowsRegistryFinder:
REGISTRY_KEY_DEBUG = (
'Software\\Python\\PythonCore\\{sys_version}'
'\\Modules\\{fullname}\\Debug')
- DEBUG_BUILD = False # Changed in _setup()
+ DEBUG_BUILD = (_MS_WINDOWS and '_d.pyd' in EXTENSION_SUFFIXES)
- @classmethod
- def _open_registry(cls, key):
+ @staticmethod
+ def _open_registry(key):
try:
return winreg.OpenKey(winreg.HKEY_CURRENT_USER, key)
except OSError:
@@ -815,9 +845,12 @@ class WindowsRegistryFinder:
def find_module(cls, fullname, path=None):
"""Find module named in the registry.
- This method is deprecated. Use exec_module() instead.
+ This method is deprecated. Use find_spec() instead.
"""
+ _warnings.warn("WindowsRegistryFinder.find_module() is deprecated and "
+ "slated for removal in Python 3.12; use find_spec() instead",
+ DeprecationWarning)
spec = cls.find_spec(fullname, path)
if spec is not None:
return spec.loader
@@ -850,7 +883,8 @@ class _LoaderBasics:
_bootstrap._call_with_frames_removed(exec, code, module.__dict__)
def load_module(self, fullname):
- """This module is deprecated."""
+ """This method is deprecated."""
+ # Warning implemented in _load_module_shim().
return _bootstrap._load_module_shim(self, fullname)
@@ -1025,7 +1059,7 @@ class FileLoader:
"""
# The only reason for this method is for the name check.
# Issue #14857: Avoid the zero-argument form of super so the implementation
- # of that form can be updated without breaking the frozen module
+ # of that form can be updated without breaking the frozen module.
return super(FileLoader, self).load_module(fullname)
@_check_name
@@ -1042,32 +1076,10 @@ class FileLoader:
with _io.FileIO(path, 'r') as file:
return file.read()
- # ResourceReader ABC API.
-
@_check_name
def get_resource_reader(self, module):
- if self.is_package(module):
- return self
- return None
-
- def open_resource(self, resource):
- path = _path_join(_path_split(self.path)[0], resource)
- return _io.FileIO(path, 'r')
-
- def resource_path(self, resource):
- if not self.is_resource(resource):
- raise FileNotFoundError
- path = _path_join(_path_split(self.path)[0], resource)
- return path
-
- def is_resource(self, name):
- if path_sep in name:
- return False
- path = _path_join(_path_split(self.path)[0], name)
- return _path_isfile(path)
-
- def contents(self):
- return iter(_os.listdir(_path_split(self.path)[0]))
+ from importlib.readers import FileReader
+ return FileReader(self)
class SourceFileLoader(FileLoader, SourceLoader):
@@ -1140,10 +1152,6 @@ class SourcelessFileLoader(FileLoader, _LoaderBasics):
return None
-# Filled in by _setup().
-EXTENSION_SUFFIXES = []
-
-
class ExtensionFileLoader(FileLoader, _LoaderBasics):
"""Loader for extension modules.
@@ -1154,11 +1162,6 @@ class ExtensionFileLoader(FileLoader, _LoaderBasics):
def __init__(self, name, path):
self.name = name
- if not _path_isabs(path):
- try:
- path = _path_join(_os.getcwd(), path)
- except OSError:
- pass
self.path = path
def __eq__(self, other):
@@ -1275,13 +1278,15 @@ class _NamespaceLoader:
def __init__(self, name, path, path_finder):
self._path = _NamespacePath(name, path, path_finder)
- @classmethod
- def module_repr(cls, module):
+ @staticmethod
+ def module_repr(module):
"""Return repr for the module.
The method is deprecated. The import machinery does the job itself.
"""
+ _warnings.warn("_NamespaceLoader.module_repr() is deprecated and "
+ "slated for removal in Python 3.12", DeprecationWarning)
return '<module {!r} (namespace)>'.format(module.__name__)
def is_package(self, fullname):
@@ -1308,8 +1313,13 @@ class _NamespaceLoader:
# The import system never calls this method.
_bootstrap._verbose_message('namespace module loaded with path {!r}',
self._path)
+ # Warning implemented in _load_module_shim().
return _bootstrap._load_module_shim(self, fullname)
+ def get_resource_reader(self, module):
+ from importlib.readers import NamespaceReader
+ return NamespaceReader(self._path)
+
# Finders #####################################################################
@@ -1317,8 +1327,8 @@ class PathFinder:
"""Meta path finder for sys.path and package __path__ attributes."""
- @classmethod
- def invalidate_caches(cls):
+ @staticmethod
+ def invalidate_caches():
"""Call the invalidate_caches() method on all path entry finders
stored in sys.path_importer_caches (where implemented)."""
for name, finder in list(sys.path_importer_cache.items()):
@@ -1330,8 +1340,8 @@ class PathFinder:
# https://bugs.python.org/issue45703
_NamespacePath._epoch += 1
- @classmethod
- def _path_hooks(cls, path):
+ @staticmethod
+ def _path_hooks(path):
"""Search sys.path_hooks for a finder for 'path'."""
if sys.path_hooks is not None and not sys.path_hooks:
_warnings.warn('sys.path_hooks is empty', ImportWarning)
@@ -1370,8 +1380,14 @@ class PathFinder:
# This would be a good place for a DeprecationWarning if
# we ended up going that route.
if hasattr(finder, 'find_loader'):
+ msg = (f"{_bootstrap._object_name(finder)}.find_spec() not found; "
+ "falling back to find_loader()")
+ _warnings.warn(msg, ImportWarning)
loader, portions = finder.find_loader(fullname)
else:
+ msg = (f"{_bootstrap._object_name(finder)}.find_spec() not found; "
+ "falling back to find_module()")
+ _warnings.warn(msg, ImportWarning)
loader = finder.find_module(fullname)
portions = []
if loader is not None:
@@ -1444,13 +1460,16 @@ class PathFinder:
This method is deprecated. Use find_spec() instead.
"""
+ _warnings.warn("PathFinder.find_module() is deprecated and "
+ "slated for removal in Python 3.12; use find_spec() instead",
+ DeprecationWarning)
spec = cls.find_spec(fullname, path)
if spec is None:
return None
return spec.loader
- @classmethod
- def find_distributions(cls, *args, **kwargs):
+ @staticmethod
+ def find_distributions(*args, **kwargs):
"""
Find distributions.
@@ -1501,6 +1520,9 @@ class FileFinder:
This method is deprecated. Use find_spec() instead.
"""
+ _warnings.warn("FileFinder.find_loader() is deprecated and "
+ "slated for removal in Python 3.12; use find_spec() instead",
+ DeprecationWarning)
spec = self.find_spec(fullname)
if spec is None:
return None, []
@@ -1651,66 +1673,14 @@ def _get_supported_file_loaders():
return [extensions, source, bytecode]
-def _setup(_bootstrap_module):
- """Setup the path-based importers for importlib by importing needed
- built-in modules and injecting them into the global namespace.
-
- Other components are extracted from the core bootstrap module.
-
- """
- global sys, _imp, _bootstrap
+def _set_bootstrap_module(_bootstrap_module):
+ global _bootstrap
_bootstrap = _bootstrap_module
- sys = _bootstrap.sys
- _imp = _bootstrap._imp
-
- self_module = sys.modules[__name__]
-
- # Directly load the os module (needed during bootstrap).
- os_details = ('posix', ['/']), ('nt', ['\\', '/'])
- for builtin_os, path_separators in os_details:
- # Assumption made in _path_join()
- assert all(len(sep) == 1 for sep in path_separators)
- path_sep = path_separators[0]
- if builtin_os in sys.modules:
- os_module = sys.modules[builtin_os]
- break
- else:
- try:
- os_module = _bootstrap._builtin_from_name(builtin_os)
- break
- except ImportError:
- continue
- else:
- raise ImportError('importlib requires posix or nt')
-
- setattr(self_module, '_os', os_module)
- setattr(self_module, 'path_sep', path_sep)
- setattr(self_module, 'path_separators', ''.join(path_separators))
- setattr(self_module, '_pathseps_with_colon', {f':{s}' for s in path_separators})
-
- # Directly load built-in modules needed during bootstrap.
- builtin_names = ['_io', '_warnings', 'marshal']
- if builtin_os == 'nt':
- builtin_names.append('winreg')
- for builtin_name in builtin_names:
- if builtin_name not in sys.modules:
- builtin_module = _bootstrap._builtin_from_name(builtin_name)
- else:
- builtin_module = sys.modules[builtin_name]
- setattr(self_module, builtin_name, builtin_module)
-
- # Constants
- setattr(self_module, '_relax_case', _make_relax_case())
- EXTENSION_SUFFIXES.extend(_imp.extension_suffixes())
- if builtin_os == 'nt':
- SOURCE_SUFFIXES.append('.pyw')
- if '_d.pyd' in EXTENSION_SUFFIXES:
- WindowsRegistryFinder.DEBUG_BUILD = True
def _install(_bootstrap_module):
"""Install the path-based import components."""
- _setup(_bootstrap_module)
+ _set_bootstrap_module(_bootstrap_module)
supported_loaders = _get_supported_file_loaders()
sys.path_hooks.extend([FileFinder.path_hook(*supported_loaders)])
sys.meta_path.append(PathFinder)
diff --git a/contrib/tools/python3/src/Lib/importlib/_common.py b/contrib/tools/python3/src/Lib/importlib/_common.py
index c1204f0b8f9..549fee379a4 100644
--- a/contrib/tools/python3/src/Lib/importlib/_common.py
+++ b/contrib/tools/python3/src/Lib/importlib/_common.py
@@ -1,28 +1,82 @@
import os
import pathlib
-import zipfile
import tempfile
import functools
import contextlib
+import types
+import importlib
+from typing import Union, Any, Optional
+from .abc import ResourceReader, Traversable
-def from_package(package):
+from ._adapters import wrap_spec
+
+Package = Union[types.ModuleType, str]
+
+
+def files(package):
+ # type: (Package) -> Traversable
"""
- Return a Traversable object for the given package.
+ Get a Traversable resource from a package
+ """
+ return from_package(get_package(package))
+
+def normalize_path(path):
+ # type: (Any) -> str
+ """Normalize a path by ensuring it is a string.
+
+ If the resulting string contains path separators, an exception is raised.
"""
- return fallback_resources(package.__spec__)
+ str_path = str(path)
+ parent, file_name = os.path.split(str_path)
+ if parent:
+ raise ValueError(f'{path!r} must be only a file name')
+ return file_name
-def fallback_resources(spec):
- package_directory = pathlib.Path(spec.origin).parent
- try:
- archive_path = spec.loader.archive
- rel_path = package_directory.relative_to(archive_path)
- return zipfile.Path(archive_path, str(rel_path) + '/')
- except Exception:
- pass
- return package_directory
+def get_resource_reader(package):
+ # type: (types.ModuleType) -> Optional[ResourceReader]
+ """
+ Return the package's loader if it's a ResourceReader.
+ """
+ # We can't use
+ # a issubclass() check here because apparently abc.'s __subclasscheck__()
+ # hook wants to create a weak reference to the object, but
+ # zipimport.zipimporter does not support weak references, resulting in a
+ # TypeError. That seems terrible.
+ spec = package.__spec__
+ reader = getattr(spec.loader, 'get_resource_reader', None) # type: ignore
+ if reader is None:
+ return None
+ return reader(spec.name) # type: ignore
+
+
+def resolve(cand):
+ # type: (Package) -> types.ModuleType
+ return cand if isinstance(cand, types.ModuleType) else importlib.import_module(cand)
+
+
+def get_package(package):
+ # type: (Package) -> types.ModuleType
+ """Take a package name or module object and return the module.
+
+ Raise an exception if the resolved module is not a package.
+ """
+ resolved = resolve(package)
+ if wrap_spec(resolved).submodule_search_locations is None:
+ raise TypeError(f'{package!r} is not a package')
+ return resolved
+
+
+def from_package(package):
+ """
+ Return a Traversable object for the given package.
+
+ """
+ spec = wrap_spec(package)
+ reader = spec.loader.get_resource_reader(spec.name)
+ return reader.files()
@contextlib.contextmanager
@@ -34,6 +88,7 @@ def _tempfile(reader, suffix=''):
try:
os.write(fd, reader())
os.close(fd)
+ del reader
yield pathlib.Path(raw_path)
finally:
try:
@@ -43,14 +98,12 @@ def _tempfile(reader, suffix=''):
@functools.singledispatch
def as_file(path):
"""
Given a Traversable object, return that object as a
path on the local file system in a context manager.
"""
- with _tempfile(path.read_bytes, suffix=path.name) as local:
- yield local
+ return _tempfile(path.read_bytes, suffix=path.name)
@as_file.register(pathlib.Path)
diff --git a/contrib/tools/python3/src/Lib/importlib/abc.py b/contrib/tools/python3/src/Lib/importlib/abc.py
index b8a9bb1a21e..0b4a3f80717 100644
--- a/contrib/tools/python3/src/Lib/importlib/abc.py
+++ b/contrib/tools/python3/src/Lib/importlib/abc.py
@@ -1,5 +1,4 @@
"""Abstract base classes related to import."""
-from . import _bootstrap
from . import _bootstrap_external
from . import machinery
try:
@@ -12,8 +11,10 @@ try:
import _frozen_importlib_external
except ImportError:
_frozen_importlib_external = _bootstrap_external
+from ._abc import Loader
import abc
import warnings
+from typing import BinaryIO, Iterable, Text
from typing import Protocol, runtime_checkable
@@ -40,15 +41,27 @@ class Finder(metaclass=abc.ABCMeta):
Deprecated since Python 3.3
"""
+ def __init__(self):
+ warnings.warn("the Finder ABC is deprecated and "
+ "slated for removal in Python 3.12; use MetaPathFinder "
+ "or PathEntryFinder instead",
+ DeprecationWarning)
+
@abc.abstractmethod
def find_module(self, fullname, path=None):
"""An abstract method that should find a module.
The fullname is a str and the optional path is a str or None.
Returns a Loader object or None.
"""
+ warnings.warn("importlib.abc.Finder along with its find_module() "
+ "method are deprecated and "
+ "slated for removal in Python 3.12; use "
+ "MetaPathFinder.find_spec() or "
+ "PathEntryFinder.find_spec() instead",
+ DeprecationWarning)
-class MetaPathFinder(Finder):
+class MetaPathFinder(metaclass=abc.ABCMeta):
"""Abstract base class for import finders on sys.meta_path."""
@@ -67,8 +80,8 @@ class MetaPathFinder(Finder):
"""
warnings.warn("MetaPathFinder.find_module() is deprecated since Python "
- "3.4 in favor of MetaPathFinder.find_spec() "
- "(available since 3.4)",
+ "3.4 in favor of MetaPathFinder.find_spec() and is "
+ "slated for removal in Python 3.12",
DeprecationWarning,
stacklevel=2)
if not hasattr(self, 'find_spec'):
@@ -85,7 +98,7 @@ _register(MetaPathFinder, machinery.BuiltinImporter, machinery.FrozenImporter,
machinery.PathFinder, machinery.WindowsRegistryFinder)
-class PathEntryFinder(Finder):
+class PathEntryFinder(metaclass=abc.ABCMeta):
"""Abstract base class for path entry finders used by PathFinder."""
@@ -134,53 +147,6 @@ class PathEntryFinder(Finder):
_register(PathEntryFinder, machinery.FileFinder)
-class Loader(metaclass=abc.ABCMeta):
-
- """Abstract base class for import loaders."""
-
- def create_module(self, spec):
- """Return a module to initialize and into which to load.
-
- This method should raise ImportError if anything prevents it
- from creating a new module. It may return None to indicate
- that the spec should create the new module.
- """
- # By default, defer to default semantics for the new module.
- return None
-
- # We don't define exec_module() here since that would break
- # hasattr checks we do to support backward compatibility.
-
- def load_module(self, fullname):
- """Return the loaded module.
-
- The module must be added to sys.modules and have import-related
- attributes set properly. The fullname is a str.
-
- ImportError is raised on failure.
-
- This method is deprecated in favor of loader.exec_module(). If
- exec_module() exists then it is used to provide a backwards-compatible
- functionality for this method.
-
- """
- if not hasattr(self, 'exec_module'):
- raise ImportError
- return _bootstrap._load_module_shim(self, fullname)
-
- def module_repr(self, module):
- """Return a module's repr.
-
- Used by the module type when the method does not raise
- NotImplementedError.
-
- This method is deprecated.
-
- """
- # The exception will cause ModuleType.__repr__ to ignore this method.
- raise NotImplementedError
-
-
class ResourceLoader(Loader):
"""Abstract base class for loaders which can return data from their
@@ -344,49 +310,45 @@ _register(SourceLoader, machinery.SourceFileLoader)
class ResourceReader(metaclass=abc.ABCMeta):
-
- """Abstract base class to provide resource-reading support.
-
- Loaders that support resource reading are expected to implement
- the ``get_resource_reader(fullname)`` method and have it either return None
- or an object compatible with this ABC.
- """
+ """Abstract base class for loaders to provide resource reading support."""
@abc.abstractmethod
- def open_resource(self, resource):
+ def open_resource(self, resource: Text) -> BinaryIO:
"""Return an opened, file-like object for binary reading.
- The 'resource' argument is expected to represent only a file name
- and thus not contain any subdirectory components.
-
+ The 'resource' argument is expected to represent only a file name.
If the resource cannot be found, FileNotFoundError is raised.
"""
+ # This deliberately raises FileNotFoundError instead of
+ # NotImplementedError so that if this method is accidentally called,
+ # it'll still do the right thing.
raise FileNotFoundError
@abc.abstractmethod
- def resource_path(self, resource):
+ def resource_path(self, resource: Text) -> Text:
"""Return the file system path to the specified resource.
- The 'resource' argument is expected to represent only a file name
- and thus not contain any subdirectory components.
-
+ The 'resource' argument is expected to represent only a file name.
If the resource does not exist on the file system, raise
FileNotFoundError.
"""
+ # This deliberately raises FileNotFoundError instead of
+ # NotImplementedError so that if this method is accidentally called,
+ # it'll still do the right thing.
raise FileNotFoundError
@abc.abstractmethod
- def is_resource(self, name):
- """Return True if the named 'name' is consider a resource."""
+ def is_resource(self, path: Text) -> bool:
+ """Return True if the named 'path' is a resource.
+
+ Files are resources, directories are not.
+ """
raise FileNotFoundError
@abc.abstractmethod
- def contents(self):
- """Return an iterable of strings over the contents of the package."""
- return []
-
-
-_register(ResourceReader, machinery.SourceFileLoader)
+ def contents(self) -> Iterable[str]:
+ """Return an iterable of entries in `package`."""
+ raise FileNotFoundError
@runtime_checkable
@@ -402,26 +364,28 @@ class Traversable(Protocol):
Yield Traversable objects in self
"""
- @abc.abstractmethod
def read_bytes(self):
"""
Read contents of self as bytes
"""
+ with self.open('rb') as strm:
+ return strm.read()
- @abc.abstractmethod
def read_text(self, encoding=None):
"""
- Read contents of self as bytes
+ Read contents of self as text
"""
+ with self.open(encoding=encoding) as strm:
+ return strm.read()
@abc.abstractmethod
- def is_dir(self):
+ def is_dir(self) -> bool:
"""
Return True if self is a dir
"""
@abc.abstractmethod
- def is_file(self):
+ def is_file(self) -> bool:
"""
Return True if self is a file
"""
@@ -432,11 +396,11 @@ class Traversable(Protocol):
Return Traversable child in self
"""
- @abc.abstractmethod
def __truediv__(self, child):
"""
Return Traversable child in self
"""
+ return self.joinpath(child)
@abc.abstractmethod
def open(self, mode='r', *args, **kwargs):
@@ -449,14 +413,18 @@ class Traversable(Protocol):
"""
@abc.abstractproperty
- def name(self):
- # type: () -> str
+ def name(self) -> str:
"""
The base name of this object without any parent references.
"""
class TraversableResources(ResourceReader):
+ """
+ The required interface for providing traversable
+ resources.
+ """
+
@abc.abstractmethod
def files(self):
"""Return a Traversable object for the loaded package."""
@@ -468,7 +436,7 @@ class TraversableResources(ResourceReader):
raise FileNotFoundError(resource)
def is_resource(self, path):
- return self.files().joinpath(path).isfile()
+ return self.files().joinpath(path).is_file()
def contents(self):
return (item.name for item in self.files().iterdir())
diff --git a/contrib/tools/python3/src/Lib/importlib/machinery.py b/contrib/tools/python3/src/Lib/importlib/machinery.py
index 1b2b5c9b4f3..9a7757fb6e4 100644
--- a/contrib/tools/python3/src/Lib/importlib/machinery.py
+++ b/contrib/tools/python3/src/Lib/importlib/machinery.py
@@ -1,7 +1,5 @@
"""The machinery of importlib: finders, loaders, hooks, etc."""
-import _imp
-
from ._bootstrap import ModuleSpec
from ._bootstrap import BuiltinImporter
from ._bootstrap import FrozenImporter
diff --git a/contrib/tools/python3/src/Lib/importlib/metadata.py b/contrib/tools/python3/src/Lib/importlib/metadata.py
deleted file mode 100644
index 647fd3e4dbf..00000000000
--- a/contrib/tools/python3/src/Lib/importlib/metadata.py
+++ /dev/null
@@ -1,604 +0,0 @@
-import io
-import os
-import re
-import abc
-import csv
-import sys
-import email
-import pathlib
-import zipfile
-import operator
-import functools
-import itertools
-import posixpath
-import collections
-
-from configparser import ConfigParser
-from contextlib import suppress
-from importlib import import_module
-from importlib.abc import MetaPathFinder
-from itertools import starmap
-
-
-__all__ = [
- 'Distribution',
- 'DistributionFinder',
- 'PackageNotFoundError',
- 'distribution',
- 'distributions',
- 'entry_points',
- 'files',
- 'metadata',
- 'requires',
- 'version',
- ]
-
-
-class PackageNotFoundError(ModuleNotFoundError):
- """The package was not found."""
-
-
-class EntryPoint(
- collections.namedtuple('EntryPointBase', 'name value group')):
- """An entry point as defined by Python packaging conventions.
-
- See `the packaging docs on entry points
- <https://packaging.python.org/specifications/entry-points/>`_
- for more information.
-
- >>> ep = EntryPoint(
- ... name=None, group=None, value='package.module:attr [extra1, extra2]')
- >>> ep.module
- 'package.module'
- >>> ep.attr
- 'attr'
- >>> ep.extras
- ['extra1', 'extra2']
- """
-
- pattern = re.compile(
- r'(?P<module>[\w.]+)\s*'
- r'(:\s*(?P<attr>[\w.]+)\s*)?'
- r'((?P<extras>\[.*\])\s*)?$'
- )
- """
- A regular expression describing the syntax for an entry point,
- which might look like:
-
- - module
- - package.module
- - package.module:attribute
- - package.module:object.attribute
- - package.module:attr [extra1, extra2]
-
- Other combinations are possible as well.
-
- The expression is lenient about whitespace around the ':',
- following the attr, and following any extras.
- """
-
- def load(self):
- """Load the entry point from its definition. If only a module
- is indicated by the value, return that module. Otherwise,
- return the named object.
- """
- match = self.pattern.match(self.value)
- module = import_module(match.group('module'))
- attrs = filter(None, (match.group('attr') or '').split('.'))
- return functools.reduce(getattr, attrs, module)
-
- @property
- def module(self):
- match = self.pattern.match(self.value)
- return match.group('module')
-
- @property
- def attr(self):
- match = self.pattern.match(self.value)
- return match.group('attr')
-
- @property
- def extras(self):
- match = self.pattern.match(self.value)
- return re.findall(r'\w+', match.group('extras') or '')
-
- @classmethod
- def _from_config(cls, config):
- return [
- cls(name, value, group)
- for group in config.sections()
- for name, value in config.items(group)
- ]
-
- @classmethod
- def _from_text(cls, text):
- config = ConfigParser(delimiters='=')
- # case sensitive: https://stackoverflow.com/q/1611799/812183
- config.optionxform = str
- try:
- config.read_string(text)
- except AttributeError: # pragma: nocover
- # Python 2 has no read_string
- config.readfp(io.StringIO(text))
- return EntryPoint._from_config(config)
-
- def __iter__(self):
- """
- Supply iter so one may construct dicts of EntryPoints easily.
- """
- return iter((self.name, self))
-
- def __reduce__(self):
- return (
- self.__class__,
- (self.name, self.value, self.group),
- )
-
-
-class PackagePath(pathlib.PurePosixPath):
- """A reference to a path in a package"""
-
- def read_text(self, encoding='utf-8'):
- with self.locate().open(encoding=encoding) as stream:
- return stream.read()
-
- def read_binary(self):
- with self.locate().open('rb') as stream:
- return stream.read()
-
- def locate(self):
- """Return a path-like object for this path"""
- return self.dist.locate_file(self)
-
-
-class FileHash:
- def __init__(self, spec):
- self.mode, _, self.value = spec.partition('=')
-
- def __repr__(self):
- return '<FileHash mode: {} value: {}>'.format(self.mode, self.value)
-
-
-class Distribution:
- """A Python distribution package."""
-
- @abc.abstractmethod
- def read_text(self, filename):
- """Attempt to load metadata file given by the name.
-
- :param filename: The name of the file in the distribution info.
- :return: The text if found, otherwise None.
- """
-
- @abc.abstractmethod
- def locate_file(self, path):
- """
- Given a path to a file in this distribution, return a path
- to it.
- """
-
- @classmethod
- def from_name(cls, name):
- """Return the Distribution for the given package name.
-
- :param name: The name of the distribution package to search for.
- :return: The Distribution instance (or subclass thereof) for the named
- package, if found.
- :raises PackageNotFoundError: When the named package's distribution
- metadata cannot be found.
- """
- for resolver in cls._discover_resolvers():
- dists = resolver(DistributionFinder.Context(name=name))
- dist = next(iter(dists), None)
- if dist is not None:
- return dist
- else:
- raise PackageNotFoundError(name)
-
- @classmethod
- def discover(cls, **kwargs):
- """Return an iterable of Distribution objects for all packages.
-
- Pass a ``context`` or pass keyword arguments for constructing
- a context.
-
- :context: A ``DistributionFinder.Context`` object.
- :return: Iterable of Distribution objects for all packages.
- """
- context = kwargs.pop('context', None)
- if context and kwargs:
- raise ValueError("cannot accept context and kwargs")
- context = context or DistributionFinder.Context(**kwargs)
- return itertools.chain.from_iterable(
- resolver(context)
- for resolver in cls._discover_resolvers()
- )
-
- @staticmethod
- def at(path):
- """Return a Distribution for the indicated metadata path
-
- :param path: a string or path-like object
- :return: a concrete Distribution instance for the path
- """
- return PathDistribution(pathlib.Path(path))
-
- @staticmethod
- def _discover_resolvers():
- """Search the meta_path for resolvers."""
- declared = (
- getattr(finder, 'find_distributions', None)
- for finder in sys.meta_path
- )
- return filter(None, declared)
-
- @classmethod
- def _local(cls, root='.'):
- from pep517 import build, meta
- system = build.compat_system(root)
- builder = functools.partial(
- meta.build,
- source_dir=root,
- system=system,
- )
- return PathDistribution(zipfile.Path(meta.build_as_zip(builder)))
-
- @property
- def metadata(self):
- """Return the parsed metadata for this Distribution.
-
- The returned object will have keys that name the various bits of
- metadata. See PEP 566 for details.
- """
- text = (
- self.read_text('METADATA')
- or self.read_text('PKG-INFO')
- # This last clause is here to support old egg-info files. Its
- # effect is to just end up using the PathDistribution's self._path
- # (which points to the egg-info file) attribute unchanged.
- or self.read_text('')
- )
- return email.message_from_string(text)
-
- @property
- def version(self):
- """Return the 'Version' metadata for the distribution package."""
- return self.metadata['Version']
-
- @property
- def entry_points(self):
- return EntryPoint._from_text(self.read_text('entry_points.txt'))
-
- @property
- def files(self):
- """Files in this distribution.
-
- :return: List of PackagePath for this distribution or None
-
- Result is `None` if the metadata file that enumerates files
- (i.e. RECORD for dist-info or SOURCES.txt for egg-info) is
- missing.
- Result may be empty if the metadata exists but is empty.
- """
- file_lines = self._read_files_distinfo() or self._read_files_egginfo()
-
- def make_file(name, hash=None, size_str=None):
- result = PackagePath(name)
- result.hash = FileHash(hash) if hash else None
- result.size = int(size_str) if size_str else None
- result.dist = self
- return result
-
- return file_lines and list(starmap(make_file, csv.reader(file_lines)))
-
- def _read_files_distinfo(self):
- """
- Read the lines of RECORD
- """
- text = self.read_text('RECORD')
- return text and text.splitlines()
-
- def _read_files_egginfo(self):
- """
- SOURCES.txt might contain literal commas, so wrap each line
- in quotes.
- """
- text = self.read_text('SOURCES.txt')
- return text and map('"{}"'.format, text.splitlines())
-
- @property
- def requires(self):
- """Generated requirements specified for this Distribution"""
- reqs = self._read_dist_info_reqs() or self._read_egg_info_reqs()
- return reqs and list(reqs)
-
- def _read_dist_info_reqs(self):
- return self.metadata.get_all('Requires-Dist')
-
- def _read_egg_info_reqs(self):
- source = self.read_text('requires.txt')
- return None if source is None else self._deps_from_requires_text(source)
-
- @classmethod
- def _deps_from_requires_text(cls, source):
- section_pairs = cls._read_sections(source.splitlines())
- sections = {
- section: list(map(operator.itemgetter('line'), results))
- for section, results in
- itertools.groupby(section_pairs, operator.itemgetter('section'))
- }
- return cls._convert_egg_info_reqs_to_simple_reqs(sections)
-
- @staticmethod
- def _read_sections(lines):
- section = None
- for line in filter(None, lines):
- section_match = re.match(r'\[(.*)\]$', line)
- if section_match:
- section = section_match.group(1)
- continue
- yield locals()
-
- @staticmethod
- def _convert_egg_info_reqs_to_simple_reqs(sections):
- """
- Historically, setuptools would solicit and store 'extra'
- requirements, including those with environment markers,
- in separate sections. More modern tools expect each
- dependency to be defined separately, with any relevant
- extras and environment markers attached directly to that
- requirement. This method converts the former to the
- latter. See _test_deps_from_requires_text for an example.
- """
- def make_condition(name):
- return name and 'extra == "{name}"'.format(name=name)
-
- def quoted_marker(section):
- section = section or ''
- extra, sep, markers = section.partition(':')
- if extra and markers:
- markers = f'({markers})'
- conditions = list(filter(None, [markers, make_condition(extra)]))
- return '; ' + ' and '.join(conditions) if conditions else ''
-
- def url_req_space(req):
- """
- PEP 508 requires a space between the url_spec and the quoted_marker.
- Ref python/importlib_metadata#357.
- """
- # '@' is uniquely indicative of a url_req.
- return ' ' * ('@' in req)
-
- for section, deps in sections.items():
- for dep in deps:
- space = url_req_space(dep)
- yield dep + space + quoted_marker(section)
-
-
-class DistributionFinder(MetaPathFinder):
- """
- A MetaPathFinder capable of discovering installed distributions.
- """
-
- class Context:
- """
- Keyword arguments presented by the caller to
- ``distributions()`` or ``Distribution.discover()``
- to narrow the scope of a search for distributions
- in all DistributionFinders.
-
- Each DistributionFinder may expect any parameters
- and should attempt to honor the canonical
- parameters defined below when appropriate.
- """
-
- name = None
- """
- Specific name for which a distribution finder should match.
- A name of ``None`` matches all distributions.
- """
-
- def __init__(self, **kwargs):
- vars(self).update(kwargs)
-
- @property
- def path(self):
- """
- The path that a distribution finder should search.
-
- Typically refers to Python package paths and defaults
- to ``sys.path``.
- """
- return vars(self).get('path', sys.path)
-
- @abc.abstractmethod
- def find_distributions(self, context=Context()):
- """
- Find distributions.
-
- Return an iterable of all Distribution instances capable of
- loading the metadata for packages matching the ``context``,
- a DistributionFinder.Context instance.
- """
-
-
-class FastPath:
- """
- Micro-optimized class for searching a path for
- children.
- """
-
- def __init__(self, root):
- self.root = root
- self.base = os.path.basename(self.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 dict.fromkeys(
- child.split(posixpath.sep, 1)[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()):
- """
- Find distributions.
-
- Return an iterable of all Distribution instances capable of
- loading the metadata for packages matching ``context.name``
- (or all names if ``None`` indicated) along the paths in the list
- of directories ``context.path``.
- """
- found = cls._search_paths(context.name, context.path)
- return map(PathDistribution, found)
-
- @classmethod
- def _search_paths(cls, name, paths):
- """Find metadata directories in paths heuristically."""
- return itertools.chain.from_iterable(
- path.search(Prepared(name))
- for path in map(FastPath, paths)
- )
-
-
-class PathDistribution(Distribution):
- def __init__(self, path):
- """Construct a distribution from a path to the metadata directory.
-
- :param path: A pathlib.Path or similar object supporting
- .joinpath(), __div__, .parent, and .read_text().
- """
- self._path = path
-
- def read_text(self, filename):
- with suppress(FileNotFoundError, IsADirectoryError, KeyError,
- NotADirectoryError, PermissionError):
- return self._path.joinpath(filename).read_text(encoding='utf-8')
- read_text.__doc__ = Distribution.read_text.__doc__
-
- def locate_file(self, path):
- return self._path.parent / path
-
-
-def distribution(distribution_name):
- """Get the ``Distribution`` instance for the named package.
-
- :param distribution_name: The name of the distribution package as a string.
- :return: A ``Distribution`` instance (or subclass thereof).
- """
- return Distribution.from_name(distribution_name)
-
-
-def distributions(**kwargs):
- """Get all ``Distribution`` instances in the current environment.
-
- :return: An iterable of ``Distribution`` instances.
- """
- return Distribution.discover(**kwargs)
-
-
-def metadata(distribution_name):
- """Get the metadata for the named package.
-
- :param distribution_name: The name of the distribution package to query.
- :return: An email.Message containing the parsed metadata.
- """
- return Distribution.from_name(distribution_name).metadata
-
-
-def version(distribution_name):
- """Get the version string for the named package.
-
- :param distribution_name: The name of the distribution package to query.
- :return: The version string for the package as defined in the package's
- "Version" metadata key.
- """
- return distribution(distribution_name).version
-
-
-def entry_points():
- """Return EntryPoint objects for all installed packages.
-
- :return: EntryPoint objects for all installed packages.
- """
- eps = itertools.chain.from_iterable(
- dist.entry_points for dist in distributions())
- by_group = operator.attrgetter('group')
- ordered = sorted(eps, key=by_group)
- grouped = itertools.groupby(ordered, by_group)
- return {
- group: tuple(eps)
- for group, eps in grouped
- }
-
-
-def files(distribution_name):
- """Return a list of files for the named package.
-
- :param distribution_name: The name of the distribution package to query.
- :return: List of files composing the distribution.
- """
- return distribution(distribution_name).files
-
-
-def requires(distribution_name):
- """
- Return a list of requirements for the named package.
-
- :return: An iterator of requirements, suitable for
- packaging.requirement.Requirement.
- """
- return distribution(distribution_name).requires
diff --git a/contrib/tools/python3/src/Lib/importlib/metadata/__init__.py b/contrib/tools/python3/src/Lib/importlib/metadata/__init__.py
new file mode 100644
index 00000000000..b3063cd9915
--- /dev/null
+++ b/contrib/tools/python3/src/Lib/importlib/metadata/__init__.py
@@ -0,0 +1,1045 @@
+import os
+import re
+import abc
+import csv
+import sys
+import email
+import pathlib
+import zipfile
+import operator
+import textwrap
+import warnings
+import functools
+import itertools
+import posixpath
+import collections
+
+from . import _adapters, _meta
+from ._meta import PackageMetadata
+from ._collections import FreezableDefaultDict, Pair
+from ._functools import method_cache
+from ._itertools import unique_everseen
+from ._meta import PackageMetadata, SimplePath
+
+from contextlib import suppress
+from importlib import import_module
+from importlib.abc import MetaPathFinder
+from itertools import starmap
+from typing import List, Mapping, Optional, Union
+
+
+__all__ = [
+ 'Distribution',
+ 'DistributionFinder',
+ 'PackageMetadata',
+ 'PackageNotFoundError',
+ 'distribution',
+ 'distributions',
+ 'entry_points',
+ 'files',
+ 'metadata',
+ 'packages_distributions',
+ 'requires',
+ 'version',
+]
+
+
+class PackageNotFoundError(ModuleNotFoundError):
+ """The package was not found."""
+
+ def __str__(self):
+ return f"No package metadata was found for {self.name}"
+
+ @property
+ def name(self):
+ (name,) = self.args
+ return name
+
+
+class Sectioned:
+ """
+ A simple entry point config parser for performance
+
+ >>> for item in Sectioned.read(Sectioned._sample):
+ ... print(item)
+ Pair(name='sec1', value='# comments ignored')
+ Pair(name='sec1', value='a = 1')
+ Pair(name='sec1', value='b = 2')
+ Pair(name='sec2', value='a = 2')
+
+ >>> res = Sectioned.section_pairs(Sectioned._sample)
+ >>> item = next(res)
+ >>> item.name
+ 'sec1'
+ >>> item.value
+ Pair(name='a', value='1')
+ >>> item = next(res)
+ >>> item.value
+ Pair(name='b', value='2')
+ >>> item = next(res)
+ >>> item.name
+ 'sec2'
+ >>> item.value
+ Pair(name='a', value='2')
+ >>> list(res)
+ []
+ """
+
+ _sample = textwrap.dedent(
+ """
+ [sec1]
+ # comments ignored
+ a = 1
+ b = 2
+
+ [sec2]
+ a = 2
+ """
+ ).lstrip()
+
+ @classmethod
+ def section_pairs(cls, text):
+ return (
+ section._replace(value=Pair.parse(section.value))
+ for section in cls.read(text, filter_=cls.valid)
+ if section.name is not None
+ )
+
+ @staticmethod
+ def read(text, filter_=None):
+ lines = filter(filter_, map(str.strip, text.splitlines()))
+ name = None
+ for value in lines:
+ section_match = value.startswith('[') and value.endswith(']')
+ if section_match:
+ name = value.strip('[]')
+ continue
+ yield Pair(name, value)
+
+ @staticmethod
+ def valid(line):
+ return line and not line.startswith('#')
+
+
+class EntryPoint(
+ collections.namedtuple('EntryPointBase', 'name value group')):
+ """An entry point as defined by Python packaging conventions.
+
+ See `the packaging docs on entry points
+ <https://packaging.python.org/specifications/entry-points/>`_
+ for more information.
+
+ >>> ep = EntryPoint(
+ ... name=None, group=None, value='package.module:attr [extra1, extra2]')
+ >>> ep.module
+ 'package.module'
+ >>> ep.attr
+ 'attr'
+ >>> ep.extras
+ ['extra1', 'extra2']
+ """
+
+ pattern = re.compile(
+ r'(?P<module>[\w.]+)\s*'
+ r'(:\s*(?P<attr>[\w.]+)\s*)?'
+ r'((?P<extras>\[.*\])\s*)?$'
+ )
+ """
+ A regular expression describing the syntax for an entry point,
+ which might look like:
+
+ - module
+ - package.module
+ - package.module:attribute
+ - package.module:object.attribute
+ - package.module:attr [extra1, extra2]
+
+ Other combinations are possible as well.
+
+ The expression is lenient about whitespace around the ':',
+ following the attr, and following any extras.
+ """
+
+ dist: Optional['Distribution'] = None
+
+ def load(self):
+ """Load the entry point from its definition. If only a module
+ is indicated by the value, return that module. Otherwise,
+ return the named object.
+ """
+ match = self.pattern.match(self.value)
+ module = import_module(match.group('module'))
+ attrs = filter(None, (match.group('attr') or '').split('.'))
+ return functools.reduce(getattr, attrs, module)
+
+ @property
+ def module(self):
+ match = self.pattern.match(self.value)
+ return match.group('module')
+
+ @property
+ def attr(self):
+ match = self.pattern.match(self.value)
+ return match.group('attr')
+
+ @property
+ def extras(self):
+ match = self.pattern.match(self.value)
+ return re.findall(r'\w+', match.group('extras') or '')
+
+ def _for(self, dist):
+ self.dist = dist
+ return self
+
+ def __iter__(self):
+ """
+ Supply iter so one may construct dicts of EntryPoints by name.
+ """
+ msg = (
+ "Construction of dict of EntryPoints is deprecated in "
+ "favor of EntryPoints."
+ )
+ warnings.warn(msg, DeprecationWarning)
+ return iter((self.name, self))
+
+ def __reduce__(self):
+ return (
+ self.__class__,
+ (self.name, self.value, self.group),
+ )
+
+ def matches(self, **params):
+ """
+ EntryPoint matches the given parameters.
+
+ >>> ep = EntryPoint(group='foo', name='bar', value='bing:bong [extra1, extra2]')
+ >>> ep.matches(group='foo')
+ True
+ >>> ep.matches(name='bar', value='bing:bong [extra1, extra2]')
+ True
+ >>> ep.matches(group='foo', name='other')
+ False
+ >>> ep.matches()
+ True
+ >>> ep.matches(extras=['extra1', 'extra2'])
+ True
+ >>> ep.matches(module='bing')
+ True
+ >>> ep.matches(attr='bong')
+ True
+ """
+ attrs = (getattr(self, param) for param in params)
+ return all(map(operator.eq, params.values(), attrs))
+
+
+class DeprecatedList(list):
+ """
+ Allow an otherwise immutable object to implement mutability
+ for compatibility.
+
+ >>> recwarn = getfixture('recwarn')
+ >>> dl = DeprecatedList(range(3))
+ >>> dl[0] = 1
+ >>> dl.append(3)
+ >>> del dl[3]
+ >>> dl.reverse()
+ >>> dl.sort()
+ >>> dl.extend([4])
+ >>> dl.pop(-1)
+ 4
+ >>> dl.remove(1)
+ >>> dl += [5]
+ >>> dl + [6]
+ [1, 2, 5, 6]
+ >>> dl + (6,)
+ [1, 2, 5, 6]
+ >>> dl.insert(0, 0)
+ >>> dl
+ [0, 1, 2, 5]
+ >>> dl == [0, 1, 2, 5]
+ True
+ >>> dl == (0, 1, 2, 5)
+ True
+ >>> len(recwarn)
+ 1
+ """
+
+ __slots__ = ()
+
+ _warn = functools.partial(
+ warnings.warn,
+ "EntryPoints list interface is deprecated. Cast to list if needed.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+
+ def __setitem__(self, *args, **kwargs):
+ self._warn()
+ return super().__setitem__(*args, **kwargs)
+
+ def __delitem__(self, *args, **kwargs):
+ self._warn()
+ return super().__delitem__(*args, **kwargs)
+
+ def append(self, *args, **kwargs):
+ self._warn()
+ return super().append(*args, **kwargs)
+
+ def reverse(self, *args, **kwargs):
+ self._warn()
+ return super().reverse(*args, **kwargs)
+
+ def extend(self, *args, **kwargs):
+ self._warn()
+ return super().extend(*args, **kwargs)
+
+ def pop(self, *args, **kwargs):
+ self._warn()
+ return super().pop(*args, **kwargs)
+
+ def remove(self, *args, **kwargs):
+ self._warn()
+ return super().remove(*args, **kwargs)
+
+ def __iadd__(self, *args, **kwargs):
+ self._warn()
+ return super().__iadd__(*args, **kwargs)
+
+ def __add__(self, other):
+ if not isinstance(other, tuple):
+ self._warn()
+ other = tuple(other)
+ return self.__class__(tuple(self) + other)
+
+ def insert(self, *args, **kwargs):
+ self._warn()
+ return super().insert(*args, **kwargs)
+
+ def sort(self, *args, **kwargs):
+ self._warn()
+ return super().sort(*args, **kwargs)
+
+ def __eq__(self, other):
+ if not isinstance(other, tuple):
+ self._warn()
+ other = tuple(other)
+
+ return tuple(self).__eq__(other)
+
+
+class EntryPoints(DeprecatedList):
+ """
+ An immutable collection of selectable EntryPoint objects.
+ """
+
+ __slots__ = ()
+
+ def __getitem__(self, name): # -> EntryPoint:
+ """
+ Get the EntryPoint in self matching name.
+ """
+ if isinstance(name, int):
+ warnings.warn(
+ "Accessing entry points by index is deprecated. "
+ "Cast to tuple if needed.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return super().__getitem__(name)
+ try:
+ return next(iter(self.select(name=name)))
+ except StopIteration:
+ raise KeyError(name)
+
+ def select(self, **params):
+ """
+ Select entry points from self that match the
+ given parameters (typically group and/or name).
+ """
+ return EntryPoints(ep for ep in self if ep.matches(**params))
+
+ @property
+ def names(self):
+ """
+ Return the set of all names of all entry points.
+ """
+ return set(ep.name for ep in self)
+
+ @property
+ def groups(self):
+ """
+ Return the set of all groups of all entry points.
+
+ For coverage while SelectableGroups is present.
+ >>> EntryPoints().groups
+ set()
+ """
+ return set(ep.group for ep in self)
+
+ @classmethod
+ def _from_text_for(cls, text, dist):
+ return cls(ep._for(dist) for ep in cls._from_text(text))
+
+ @classmethod
+ def _from_text(cls, text):
+ return itertools.starmap(EntryPoint, cls._parse_groups(text or ''))
+
+ @staticmethod
+ def _parse_groups(text):
+ return (
+ (item.value.name, item.value.value, item.name)
+ for item in Sectioned.section_pairs(text)
+ )
+
+
+class Deprecated:
+ """
+ Compatibility add-in for mapping to indicate that
+ mapping behavior is deprecated.
+
+ >>> recwarn = getfixture('recwarn')
+ >>> class DeprecatedDict(Deprecated, dict): pass
+ >>> dd = DeprecatedDict(foo='bar')
+ >>> dd.get('baz', None)
+ >>> dd['foo']
+ 'bar'
+ >>> list(dd)
+ ['foo']
+ >>> list(dd.keys())
+ ['foo']
+ >>> 'foo' in dd
+ True
+ >>> list(dd.values())
+ ['bar']
+ >>> len(recwarn)
+ 1
+ """
+
+ _warn = functools.partial(
+ warnings.warn,
+ "SelectableGroups dict interface is deprecated. Use select.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+
+ def __getitem__(self, name):
+ self._warn()
+ return super().__getitem__(name)
+
+ def get(self, name, default=None):
+ self._warn()
+ return super().get(name, default)
+
+ def __iter__(self):
+ self._warn()
+ return super().__iter__()
+
+ def __contains__(self, *args):
+ self._warn()
+ return super().__contains__(*args)
+
+ def keys(self):
+ self._warn()
+ return super().keys()
+
+ def values(self):
+ self._warn()
+ return super().values()
+
+
+class SelectableGroups(Deprecated, dict):
+ """
+ A backward- and forward-compatible result from
+ entry_points that fully implements the dict interface.
+ """
+
+ @classmethod
+ def load(cls, eps):
+ by_group = operator.attrgetter('group')
+ ordered = sorted(eps, key=by_group)
+ grouped = itertools.groupby(ordered, by_group)
+ return cls((group, EntryPoints(eps)) for group, eps in grouped)
+
+ @property
+ def _all(self):
+ """
+ Reconstruct a list of all entrypoints from the groups.
+ """
+ groups = super(Deprecated, self).values()
+ return EntryPoints(itertools.chain.from_iterable(groups))
+
+ @property
+ def groups(self):
+ return self._all.groups
+
+ @property
+ def names(self):
+ """
+ for coverage:
+ >>> SelectableGroups().names
+ set()
+ """
+ return self._all.names
+
+ def select(self, **params):
+ if not params:
+ return self
+ return self._all.select(**params)
+
+
+class PackagePath(pathlib.PurePosixPath):
+ """A reference to a path in a package"""
+
+ def read_text(self, encoding='utf-8'):
+ with self.locate().open(encoding=encoding) as stream:
+ return stream.read()
+
+ def read_binary(self):
+ with self.locate().open('rb') as stream:
+ return stream.read()
+
+ def locate(self):
+ """Return a path-like object for this path"""
+ return self.dist.locate_file(self)
+
+
+class FileHash:
+ def __init__(self, spec):
+ self.mode, _, self.value = spec.partition('=')
+
+ def __repr__(self):
+ return f'<FileHash mode: {self.mode} value: {self.value}>'
+
+
+class Distribution:
+ """A Python distribution package."""
+
+ @abc.abstractmethod
+ def read_text(self, filename):
+ """Attempt to load metadata file given by the name.
+
+ :param filename: The name of the file in the distribution info.
+ :return: The text if found, otherwise None.
+ """
+
+ @abc.abstractmethod
+ def locate_file(self, path):
+ """
+ Given a path to a file in this distribution, return a path
+ to it.
+ """
+
+ @classmethod
+ def from_name(cls, name):
+ """Return the Distribution for the given package name.
+
+ :param name: The name of the distribution package to search for.
+ :return: The Distribution instance (or subclass thereof) for the named
+ package, if found.
+ :raises PackageNotFoundError: When the named package's distribution
+ metadata cannot be found.
+ """
+ for resolver in cls._discover_resolvers():
+ dists = resolver(DistributionFinder.Context(name=name))
+ dist = next(iter(dists), None)
+ if dist is not None:
+ return dist
+ else:
+ raise PackageNotFoundError(name)
+
+ @classmethod
+ def discover(cls, **kwargs):
+ """Return an iterable of Distribution objects for all packages.
+
+ Pass a ``context`` or pass keyword arguments for constructing
+ a context.
+
+ :context: A ``DistributionFinder.Context`` object.
+ :return: Iterable of Distribution objects for all packages.
+ """
+ context = kwargs.pop('context', None)
+ if context and kwargs:
+ raise ValueError("cannot accept context and kwargs")
+ context = context or DistributionFinder.Context(**kwargs)
+ return itertools.chain.from_iterable(
+ resolver(context) for resolver in cls._discover_resolvers()
+ )
+
+ @staticmethod
+ def at(path):
+ """Return a Distribution for the indicated metadata path
+
+ :param path: a string or path-like object
+ :return: a concrete Distribution instance for the path
+ """
+ return PathDistribution(pathlib.Path(path))
+
+ @staticmethod
+ def _discover_resolvers():
+ """Search the meta_path for resolvers."""
+ declared = (
+ getattr(finder, 'find_distributions', None) for finder in sys.meta_path
+ )
+ return filter(None, declared)
+
+ @classmethod
+ def _local(cls, root='.'):
+ from pep517 import build, meta
+
+ system = build.compat_system(root)
+ builder = functools.partial(
+ meta.build,
+ source_dir=root,
+ system=system,
+ )
+ return PathDistribution(zipfile.Path(meta.build_as_zip(builder)))
+
+ @property
+ def metadata(self) -> _meta.PackageMetadata:
+ """Return the parsed metadata for this Distribution.
+
+ The returned object will have keys that name the various bits of
+ metadata. See PEP 566 for details.
+ """
+ text = (
+ self.read_text('METADATA')
+ or self.read_text('PKG-INFO')
+ # This last clause is here to support old egg-info files. Its
+ # effect is to just end up using the PathDistribution's self._path
+ # (which points to the egg-info file) attribute unchanged.
+ or self.read_text('')
+ )
+ return _adapters.Message(email.message_from_string(text))
+
+ @property
+ def name(self):
+ """Return the 'Name' metadata for the distribution package."""
+ return self.metadata['Name']
+
+ @property
+ def _normalized_name(self):
+ """Return a normalized version of the name."""
+ return Prepared.normalize(self.name)
+
+ @property
+ def version(self):
+ """Return the 'Version' metadata for the distribution package."""
+ return self.metadata['Version']
+
+ @property
+ def entry_points(self):
+ return EntryPoints._from_text_for(self.read_text('entry_points.txt'), self)
+
+ @property
+ def files(self):
+ """Files in this distribution.
+
+ :return: List of PackagePath for this distribution or None
+
+ Result is `None` if the metadata file that enumerates files
+ (i.e. RECORD for dist-info or SOURCES.txt for egg-info) is
+ missing.
+ Result may be empty if the metadata exists but is empty.
+ """
+ file_lines = self._read_files_distinfo() or self._read_files_egginfo()
+
+ def make_file(name, hash=None, size_str=None):
+ result = PackagePath(name)
+ result.hash = FileHash(hash) if hash else None
+ result.size = int(size_str) if size_str else None
+ result.dist = self
+ return result
+
+ return file_lines and list(starmap(make_file, csv.reader(file_lines)))
+
+ def _read_files_distinfo(self):
+ """
+ Read the lines of RECORD
+ """
+ text = self.read_text('RECORD')
+ return text and text.splitlines()
+
+ def _read_files_egginfo(self):
+ """
+ SOURCES.txt might contain literal commas, so wrap each line
+ in quotes.
+ """
+ text = self.read_text('SOURCES.txt')
+ return text and map('"{}"'.format, text.splitlines())
+
+ @property
+ def requires(self):
+ """Generated requirements specified for this Distribution"""
+ reqs = self._read_dist_info_reqs() or self._read_egg_info_reqs()
+ return reqs and list(reqs)
+
+ def _read_dist_info_reqs(self):
+ return self.metadata.get_all('Requires-Dist')
+
+ def _read_egg_info_reqs(self):
+ source = self.read_text('requires.txt')
+ return None if source is None else self._deps_from_requires_text(source)
+
+ @classmethod
+ def _deps_from_requires_text(cls, source):
+ return cls._convert_egg_info_reqs_to_simple_reqs(Sectioned.read(source))
+
+ @staticmethod
+ def _convert_egg_info_reqs_to_simple_reqs(sections):
+ """
+ Historically, setuptools would solicit and store 'extra'
+ requirements, including those with environment markers,
+ in separate sections. More modern tools expect each
+ dependency to be defined separately, with any relevant
+ extras and environment markers attached directly to that
+ requirement. This method converts the former to the
+ latter. See _test_deps_from_requires_text for an example.
+ """
+
+ def make_condition(name):
+ return name and f'extra == "{name}"'
+
+ def quoted_marker(section):
+ section = section or ''
+ extra, sep, markers = section.partition(':')
+ if extra and markers:
+ markers = f'({markers})'
+ conditions = list(filter(None, [markers, make_condition(extra)]))
+ return '; ' + ' and '.join(conditions) if conditions else ''
+
+ def url_req_space(req):
+ """
+ PEP 508 requires a space between the url_spec and the quoted_marker.
+ Ref python/importlib_metadata#357.
+ """
+ # '@' is uniquely indicative of a url_req.
+ return ' ' * ('@' in req)
+
+ for section in sections:
+ space = url_req_space(section.value)
+ yield section.value + space + quoted_marker(section.name)
+
+
+class DistributionFinder(MetaPathFinder):
+ """
+ A MetaPathFinder capable of discovering installed distributions.
+ """
+
+ class Context:
+ """
+ Keyword arguments presented by the caller to
+ ``distributions()`` or ``Distribution.discover()``
+ to narrow the scope of a search for distributions
+ in all DistributionFinders.
+
+ Each DistributionFinder may expect any parameters
+ and should attempt to honor the canonical
+ parameters defined below when appropriate.
+ """
+
+ name = None
+ """
+ Specific name for which a distribution finder should match.
+ A name of ``None`` matches all distributions.
+ """
+
+ def __init__(self, **kwargs):
+ vars(self).update(kwargs)
+
+ @property
+ def path(self):
+ """
+ The sequence of directory path that a distribution finder
+ should search.
+
+ Typically refers to Python installed package paths such as
+ "site-packages" directories and defaults to ``sys.path``.
+ """
+ return vars(self).get('path', sys.path)
+
+ @abc.abstractmethod
+ def find_distributions(self, context=Context()):
+ """
+ Find distributions.
+
+ Return an iterable of all Distribution instances capable of
+ loading the metadata for packages matching the ``context``,
+ a DistributionFinder.Context instance.
+ """
+
+
+class FastPath:
+ """
+ Micro-optimized class for searching a path for
+ children.
+ """
+
+ @functools.lru_cache() # type: ignore
+ def __new__(cls, root):
+ return super().__new__(cls)
+
+ def __init__(self, root):
+ self.root = root
+
+ 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 dict.fromkeys(child.split(posixpath.sep, 1)[0] for child in names)
+
+ def search(self, name):
+ return self.lookup(self.mtime).search(name)
+
+ @property
+ def mtime(self):
+ with suppress(OSError):
+ return os.stat(self.root).st_mtime
+ self.lookup.cache_clear()
+
+ @method_cache
+ def lookup(self, mtime):
+ return Lookup(self)
+
+
+class Lookup:
+ def __init__(self, path: FastPath):
+ base = os.path.basename(path.root).lower()
+ base_is_egg = base.endswith(".egg")
+ self.infos = FreezableDefaultDict(list)
+ self.eggs = FreezableDefaultDict(list)
+
+ for child in path.children():
+ low = child.lower()
+ if low.endswith((".dist-info", ".egg-info")):
+ # rpartition is faster than splitext and suitable for this purpose.
+ name = low.rpartition(".")[0].partition("-")[0]
+ normalized = Prepared.normalize(name)
+ self.infos[normalized].append(path.joinpath(child))
+ elif base_is_egg and low == "egg-info":
+ name = base.rpartition(".")[0].partition("-")[0]
+ legacy_normalized = Prepared.legacy_normalize(name)
+ self.eggs[legacy_normalized].append(path.joinpath(child))
+
+ self.infos.freeze()
+ self.eggs.freeze()
+
+ def search(self, prepared):
+ infos = (
+ self.infos[prepared.normalized]
+ if prepared
+ else itertools.chain.from_iterable(self.infos.values())
+ )
+ eggs = (
+ self.eggs[prepared.legacy_normalized]
+ if prepared
+ else itertools.chain.from_iterable(self.eggs.values())
+ )
+ return itertools.chain(infos, eggs)
+
+
+class Prepared:
+ """
+ A prepared search for metadata on a possibly-named package.
+ """
+
+ normalized = None
+ legacy_normalized = None
+
+ def __init__(self, name):
+ self.name = name
+ if name is None:
+ return
+ self.normalized = self.normalize(name)
+ self.legacy_normalized = self.legacy_normalize(name)
+
+ @staticmethod
+ def normalize(name):
+ """
+ PEP 503 normalization plus dashes as underscores.
+ """
+ return re.sub(r"[-_.]+", "-", name).lower().replace('-', '_')
+
+ @staticmethod
+ def legacy_normalize(name):
+ """
+ Normalize the package name as found in the convention in
+ older packaging tools versions and specs.
+ """
+ return name.lower().replace('-', '_')
+
+ def __bool__(self):
+ return bool(self.name)
+
+
+class MetadataPathFinder(DistributionFinder):
+ @classmethod
+ def find_distributions(cls, context=DistributionFinder.Context()):
+ """
+ Find distributions.
+
+ Return an iterable of all Distribution instances capable of
+ loading the metadata for packages matching ``context.name``
+ (or all names if ``None`` indicated) along the paths in the list
+ of directories ``context.path``.
+ """
+ found = cls._search_paths(context.name, context.path)
+ return map(PathDistribution, found)
+
+ @classmethod
+ def _search_paths(cls, name, paths):
+ """Find metadata directories in paths heuristically."""
+ prepared = Prepared(name)
+ return itertools.chain.from_iterable(
+ path.search(prepared) for path in map(FastPath, paths)
+ )
+
+ def invalidate_caches(cls):
+ FastPath.__new__.cache_clear()
+
+
+class PathDistribution(Distribution):
+ def __init__(self, path: SimplePath):
+ """Construct a distribution.
+
+ :param path: SimplePath indicating the metadata directory.
+ """
+ self._path = path
+
+ def read_text(self, filename):
+ with suppress(
+ FileNotFoundError,
+ IsADirectoryError,
+ KeyError,
+ NotADirectoryError,
+ PermissionError,
+ ):
+ return self._path.joinpath(filename).read_text(encoding='utf-8')
+
+ read_text.__doc__ = Distribution.read_text.__doc__
+
+ def locate_file(self, path):
+ return self._path.parent / path
+
+ @property
+ def _normalized_name(self):
+ """
+ Performance optimization: where possible, resolve the
+ normalized name from the file system path.
+ """
+ stem = os.path.basename(str(self._path))
+ return self._name_from_stem(stem) or super()._normalized_name
+
+ def _name_from_stem(self, stem):
+ name, ext = os.path.splitext(stem)
+ if ext not in ('.dist-info', '.egg-info'):
+ return
+ name, sep, rest = stem.partition('-')
+ return name
+
+
+def distribution(distribution_name):
+ """Get the ``Distribution`` instance for the named package.
+
+ :param distribution_name: The name of the distribution package as a string.
+ :return: A ``Distribution`` instance (or subclass thereof).
+ """
+ return Distribution.from_name(distribution_name)
+
+
+def distributions(**kwargs):
+ """Get all ``Distribution`` instances in the current environment.
+
+ :return: An iterable of ``Distribution`` instances.
+ """
+ return Distribution.discover(**kwargs)
+
+
+def metadata(distribution_name) -> _meta.PackageMetadata:
+ """Get the metadata for the named package.
+
+ :param distribution_name: The name of the distribution package to query.
+ :return: A PackageMetadata containing the parsed metadata.
+ """
+ return Distribution.from_name(distribution_name).metadata
+
+
+def version(distribution_name):
+ """Get the version string for the named package.
+
+ :param distribution_name: The name of the distribution package to query.
+ :return: The version string for the package as defined in the package's
+ "Version" metadata key.
+ """
+ return distribution(distribution_name).version
+
+
+def entry_points(**params) -> Union[EntryPoints, SelectableGroups]:
+ """Return EntryPoint objects for all installed packages.
+
+ Pass selection parameters (group or name) to filter the
+ result to entry points matching those properties (see
+ EntryPoints.select()).
+
+ For compatibility, returns ``SelectableGroups`` object unless
+ selection parameters are supplied. In the future, this function
+ will return ``EntryPoints`` instead of ``SelectableGroups``
+ even when no selection parameters are supplied.
+
+ For maximum future compatibility, pass selection parameters
+ or invoke ``.select`` with parameters on the result.
+
+ :return: EntryPoints or SelectableGroups for all installed packages.
+ """
+ norm_name = operator.attrgetter('_normalized_name')
+ unique = functools.partial(unique_everseen, key=norm_name)
+ eps = itertools.chain.from_iterable(
+ dist.entry_points for dist in unique(distributions())
+ )
+ return SelectableGroups.load(eps).select(**params)
+
+
+def files(distribution_name):
+ """Return a list of files for the named package.
+
+ :param distribution_name: The name of the distribution package to query.
+ :return: List of files composing the distribution.
+ """
+ return distribution(distribution_name).files
+
+
+def requires(distribution_name):
+ """
+ Return a list of requirements for the named package.
+
+ :return: An iterator of requirements, suitable for
+ packaging.requirement.Requirement.
+ """
+ return distribution(distribution_name).requires
+
+
+def packages_distributions() -> Mapping[str, List[str]]:
+ """
+ Return a mapping of top-level packages to their
+ distributions.
+
+ >>> import collections.abc
+ >>> pkgs = packages_distributions()
+ >>> all(isinstance(dist, collections.abc.Sequence) for dist in pkgs.values())
+ True
+ """
+ pkg_to_dist = collections.defaultdict(list)
+ for dist in distributions():
+ for pkg in (dist.read_text('top_level.txt') or '').split():
+ pkg_to_dist[pkg].append(dist.metadata['Name'])
+ return dict(pkg_to_dist)
diff --git a/contrib/tools/python3/src/Lib/importlib/metadata/_adapters.py b/contrib/tools/python3/src/Lib/importlib/metadata/_adapters.py
new file mode 100644
index 00000000000..aa460d3eda5
--- /dev/null
+++ b/contrib/tools/python3/src/Lib/importlib/metadata/_adapters.py
@@ -0,0 +1,68 @@
+import re
+import textwrap
+import email.message
+
+from ._text import FoldedCase
+
+
+class Message(email.message.Message):
+ multiple_use_keys = set(
+ map(
+ FoldedCase,
+ [
+ 'Classifier',
+ 'Obsoletes-Dist',
+ 'Platform',
+ 'Project-URL',
+ 'Provides-Dist',
+ 'Provides-Extra',
+ 'Requires-Dist',
+ 'Requires-External',
+ 'Supported-Platform',
+ 'Dynamic',
+ ],
+ )
+ )
+ """
+ Keys that may be indicated multiple times per PEP 566.
+ """
+
+ def __new__(cls, orig: email.message.Message):
+ res = super().__new__(cls)
+ vars(res).update(vars(orig))
+ return res
+
+ def __init__(self, *args, **kwargs):
+ self._headers = self._repair_headers()
+
+ # suppress spurious error from mypy
+ def __iter__(self):
+ return super().__iter__()
+
+ def _repair_headers(self):
+ def redent(value):
+ "Correct for RFC822 indentation"
+ if not value or '\n' not in value:
+ return value
+ return textwrap.dedent(' ' * 8 + value)
+
+ headers = [(key, redent(value)) for key, value in vars(self)['_headers']]
+ if self._payload:
+ headers.append(('Description', self.get_payload()))
+ return headers
+
+ @property
+ def json(self):
+ """
+ Convert PackageMetadata to a JSON-compatible format
+ per PEP 0566.
+ """
+
+ def transform(key):
+ value = self.get_all(key) if key in self.multiple_use_keys else self[key]
+ if key == 'Keywords':
+ value = re.split(r'\s+', value)
+ tk = key.lower().replace('-', '_')
+ return tk, value
+
+ return dict(map(transform, map(FoldedCase, self)))
diff --git a/contrib/tools/python3/src/Lib/importlib/metadata/_collections.py b/contrib/tools/python3/src/Lib/importlib/metadata/_collections.py
new file mode 100644
index 00000000000..cf0954e1a30
--- /dev/null
+++ b/contrib/tools/python3/src/Lib/importlib/metadata/_collections.py
@@ -0,0 +1,30 @@
+import collections
+
+
+# from jaraco.collections 3.3
+class FreezableDefaultDict(collections.defaultdict):
+ """
+ Often it is desirable to prevent the mutation of
+ a default dict after its initial construction, such
+ as to prevent mutation during iteration.
+
+ >>> dd = FreezableDefaultDict(list)
+ >>> dd[0].append('1')
+ >>> dd.freeze()
+ >>> dd[1]
+ []
+ >>> len(dd)
+ 1
+ """
+
+ def __missing__(self, key):
+ return getattr(self, '_frozen', super().__missing__)(key)
+
+ def freeze(self):
+ self._frozen = lambda key: self.default_factory()
+
+
+class Pair(collections.namedtuple('Pair', 'name value')):
+ @classmethod
+ def parse(cls, text):
+ return cls(*map(str.strip, text.split("=", 1)))
diff --git a/contrib/tools/python3/src/Lib/importlib/metadata/_functools.py b/contrib/tools/python3/src/Lib/importlib/metadata/_functools.py
new file mode 100644
index 00000000000..73f50d00bc0
--- /dev/null
+++ b/contrib/tools/python3/src/Lib/importlib/metadata/_functools.py
@@ -0,0 +1,85 @@
+import types
+import functools
+
+
+# from jaraco.functools 3.3
+def method_cache(method, cache_wrapper=None):
+ """
+ Wrap lru_cache to support storing the cache data in the object instances.
+
+ Abstracts the common paradigm where the method explicitly saves an
+ underscore-prefixed protected property on first call and returns that
+ subsequently.
+
+ >>> class MyClass:
+ ... calls = 0
+ ...
+ ... @method_cache
+ ... def method(self, value):
+ ... self.calls += 1
+ ... return value
+
+ >>> a = MyClass()
+ >>> a.method(3)
+ 3
+ >>> for x in range(75):
+ ... res = a.method(x)
+ >>> a.calls
+ 75
+
+ Note that the apparent behavior will be exactly like that of lru_cache
+ except that the cache is stored on each instance, so values in one
+ instance will not flush values from another, and when an instance is
+ deleted, so are the cached values for that instance.
+
+ >>> b = MyClass()
+ >>> for x in range(35):
+ ... res = b.method(x)
+ >>> b.calls
+ 35
+ >>> a.method(0)
+ 0
+ >>> a.calls
+ 75
+
+ Note that if method had been decorated with ``functools.lru_cache()``,
+ a.calls would have been 76 (due to the cached value of 0 having been
+ flushed by the 'b' instance).
+
+ Clear the cache with ``.cache_clear()``
+
+ >>> a.method.cache_clear()
+
+ Same for a method that hasn't yet been called.
+
+ >>> c = MyClass()
+ >>> c.method.cache_clear()
+
+ Another cache wrapper may be supplied:
+
+ >>> cache = functools.lru_cache(maxsize=2)
+ >>> MyClass.method2 = method_cache(lambda self: 3, cache_wrapper=cache)
+ >>> a = MyClass()
+ >>> a.method2()
+ 3
+
+ Caution - do not subsequently wrap the method with another decorator, such
+ as ``@property``, which changes the semantics of the function.
+
+ See also
+ http://code.activestate.com/recipes/577452-a-memoize-decorator-for-instance-methods/
+ for another implementation and additional justification.
+ """
+ cache_wrapper = cache_wrapper or functools.lru_cache()
+
+ def wrapper(self, *args, **kwargs):
+ # it's the first call, replace the method with a cached, bound method
+ bound_method = types.MethodType(method, self)
+ cached_method = cache_wrapper(bound_method)
+ setattr(self, method.__name__, cached_method)
+ return cached_method(*args, **kwargs)
+
+ # Support cache clear even before cache has been created.
+ wrapper.cache_clear = lambda: None
+
+ return wrapper
diff --git a/contrib/tools/python3/src/Lib/importlib/metadata/_itertools.py b/contrib/tools/python3/src/Lib/importlib/metadata/_itertools.py
new file mode 100644
index 00000000000..dd45f2f0966
--- /dev/null
+++ b/contrib/tools/python3/src/Lib/importlib/metadata/_itertools.py
@@ -0,0 +1,19 @@
+from itertools import filterfalse
+
+
+def unique_everseen(iterable, key=None):
+ "List unique elements, preserving order. Remember all elements ever seen."
+ # unique_everseen('AAAABBBCCDAABBB') --> A B C D
+ # unique_everseen('ABBCcAD', str.lower) --> A B C D
+ seen = set()
+ seen_add = seen.add
+ if key is None:
+ for element in filterfalse(seen.__contains__, iterable):
+ seen_add(element)
+ yield element
+ else:
+ for element in iterable:
+ k = key(element)
+ if k not in seen:
+ seen_add(k)
+ yield element
diff --git a/contrib/tools/python3/src/Lib/importlib/metadata/_meta.py b/contrib/tools/python3/src/Lib/importlib/metadata/_meta.py
new file mode 100644
index 00000000000..1a6edbf957d
--- /dev/null
+++ b/contrib/tools/python3/src/Lib/importlib/metadata/_meta.py
@@ -0,0 +1,47 @@
+from typing import Any, Dict, Iterator, List, Protocol, TypeVar, Union
+
+
+_T = TypeVar("_T")
+
+
+class PackageMetadata(Protocol):
+ def __len__(self) -> int:
+ ... # pragma: no cover
+
+ def __contains__(self, item: str) -> bool:
+ ... # pragma: no cover
+
+ def __getitem__(self, key: str) -> str:
+ ... # pragma: no cover
+
+ def __iter__(self) -> Iterator[str]:
+ ... # pragma: no cover
+
+ def get_all(self, name: str, failobj: _T = ...) -> Union[List[Any], _T]:
+ """
+ Return all values associated with a possibly multi-valued key.
+ """
+
+ @property
+ def json(self) -> Dict[str, Union[str, List[str]]]:
+ """
+ A JSON-compatible form of the metadata.
+ """
+
+
+class SimplePath(Protocol):
+ """
+ A minimal subset of pathlib.Path required by PathDistribution.
+ """
+
+ def joinpath(self) -> 'SimplePath':
+ ... # pragma: no cover
+
+ def __div__(self) -> 'SimplePath':
+ ... # pragma: no cover
+
+ def parent(self) -> 'SimplePath':
+ ... # pragma: no cover
+
+ def read_text(self) -> str:
+ ... # pragma: no cover
diff --git a/contrib/tools/python3/src/Lib/importlib/metadata/_text.py b/contrib/tools/python3/src/Lib/importlib/metadata/_text.py
new file mode 100644
index 00000000000..766979d93c1
--- /dev/null
+++ b/contrib/tools/python3/src/Lib/importlib/metadata/_text.py
@@ -0,0 +1,99 @@
+import re
+
+from ._functools import method_cache
+
+
+# from jaraco.text 3.5
+class FoldedCase(str):
+ """
+ A case insensitive string class; behaves just like str
+ except compares equal when the only variation is case.
+
+ >>> s = FoldedCase('hello world')
+
+ >>> s == 'Hello World'
+ True
+
+ >>> 'Hello World' == s
+ True
+
+ >>> s != 'Hello World'
+ False
+
+ >>> s.index('O')
+ 4
+
+ >>> s.split('O')
+ ['hell', ' w', 'rld']
+
+ >>> sorted(map(FoldedCase, ['GAMMA', 'alpha', 'Beta']))
+ ['alpha', 'Beta', 'GAMMA']
+
+ Sequence membership is straightforward.
+
+ >>> "Hello World" in [s]
+ True
+ >>> s in ["Hello World"]
+ True
+
+ You may test for set inclusion, but candidate and elements
+ must both be folded.
+
+ >>> FoldedCase("Hello World") in {s}
+ True
+ >>> s in {FoldedCase("Hello World")}
+ True
+
+ String inclusion works as long as the FoldedCase object
+ is on the right.
+
+ >>> "hello" in FoldedCase("Hello World")
+ True
+
+ But not if the FoldedCase object is on the left:
+
+ >>> FoldedCase('hello') in 'Hello World'
+ False
+
+ In that case, use in_:
+
+ >>> FoldedCase('hello').in_('Hello World')
+ True
+
+ >>> FoldedCase('hello') > FoldedCase('Hello')
+ False
+ """
+
+ def __lt__(self, other):
+ return self.lower() < other.lower()
+
+ def __gt__(self, other):
+ return self.lower() > other.lower()
+
+ def __eq__(self, other):
+ return self.lower() == other.lower()
+
+ def __ne__(self, other):
+ return self.lower() != other.lower()
+
+ def __hash__(self):
+ return hash(self.lower())
+
+ def __contains__(self, other):
+ return super(FoldedCase, self).lower().__contains__(other.lower())
+
+ def in_(self, other):
+ "Does self appear in other?"
+ return self in FoldedCase(other)
+
+ # cache lower since it's likely to be called frequently.
+ @method_cache
+ def lower(self):
+ return super(FoldedCase, self).lower()
+
+ def index(self, sub):
+ return self.lower().index(sub.lower())
+
+ def split(self, splitter=' ', maxsplit=0):
+ pattern = re.compile(re.escape(splitter), re.I)
+ return pattern.split(self, maxsplit)
diff --git a/contrib/tools/python3/src/Lib/importlib/readers.py b/contrib/tools/python3/src/Lib/importlib/readers.py
new file mode 100644
index 00000000000..41089c071d8
--- /dev/null
+++ b/contrib/tools/python3/src/Lib/importlib/readers.py
@@ -0,0 +1,123 @@
+import collections
+import zipfile
+import pathlib
+from . import abc
+
+
+def remove_duplicates(items):
+ return iter(collections.OrderedDict.fromkeys(items))
+
+
+class FileReader(abc.TraversableResources):
+ def __init__(self, loader):
+ self.path = pathlib.Path(loader.path).parent
+
+ def resource_path(self, resource):
+ """
+ Return the file system path to prevent
+ `resources.path()` from creating a temporary
+ copy.
+ """
+ return str(self.path.joinpath(resource))
+
+ def files(self):
+ return self.path
+
+
+class ZipReader(abc.TraversableResources):
+ def __init__(self, loader, module):
+ _, _, name = module.rpartition('.')
+ self.prefix = loader.prefix.replace('\\', '/') + name + '/'
+ self.archive = loader.archive
+
+ def open_resource(self, resource):
+ try:
+ return super().open_resource(resource)
+ except KeyError as exc:
+ raise FileNotFoundError(exc.args[0])
+
+ def is_resource(self, path):
+ # workaround for `zipfile.Path.is_file` returning true
+ # for non-existent paths.
+ target = self.files().joinpath(path)
+ return target.is_file() and target.exists()
+
+ def files(self):
+ return zipfile.Path(self.archive, self.prefix)
+
+
+class MultiplexedPath(abc.Traversable):
+ """
+ Given a series of Traversable objects, implement a merged
+ version of the interface across all objects. Useful for
+ namespace packages which may be multihomed at a single
+ name.
+ """
+
+ def __init__(self, *paths):
+ self._paths = list(map(pathlib.Path, remove_duplicates(paths)))
+ if not self._paths:
+ message = 'MultiplexedPath must contain at least one path'
+ raise FileNotFoundError(message)
+ if not all(path.is_dir() for path in self._paths):
+ raise NotADirectoryError('MultiplexedPath only supports directories')
+
+ def iterdir(self):
+ visited = []
+ for path in self._paths:
+ for file in path.iterdir():
+ if file.name in visited:
+ continue
+ visited.append(file.name)
+ yield file
+
+ def read_bytes(self):
+ raise FileNotFoundError(f'{self} is not a file')
+
+ def read_text(self, *args, **kwargs):
+ raise FileNotFoundError(f'{self} is not a file')
+
+ def is_dir(self):
+ return True
+
+ def is_file(self):
+ return False
+
+ def joinpath(self, child):
+ # first try to find child in current paths
+ for file in self.iterdir():
+ if file.name == child:
+ return file
+ # if it does not exist, construct it with the first path
+ return self._paths[0] / child
+
+ __truediv__ = joinpath
+
+ def open(self, *args, **kwargs):
+ raise FileNotFoundError(f'{self} is not a file')
+
+ @property
+ def name(self):
+ return self._paths[0].name
+
+ def __repr__(self):
+ paths = ', '.join(f"'{path}'" for path in self._paths)
+ return f'MultiplexedPath({paths})'
+
+
+class NamespaceReader(abc.TraversableResources):
+ def __init__(self, namespace_path):
+ if 'NamespacePath' not in str(namespace_path):
+ raise ValueError('Invalid path')
+ self.path = MultiplexedPath(*list(namespace_path))
+
+ def resource_path(self, resource):
+ """
+ Return the file system path to prevent
+ `resources.path()` from creating a temporary
+ copy.
+ """
+ return str(self.path.joinpath(resource))
+
+ def files(self):
+ return self.path
diff --git a/contrib/tools/python3/src/Lib/importlib/resources.py b/contrib/tools/python3/src/Lib/importlib/resources.py
index b803a01c91d..8a98663ff8e 100644
--- a/contrib/tools/python3/src/Lib/importlib/resources.py
+++ b/contrib/tools/python3/src/Lib/importlib/resources.py
@@ -1,22 +1,26 @@
import os
+import io
-from . import abc as resources_abc
from . import _common
-from ._common import as_file
-from contextlib import contextmanager, suppress
-from importlib import import_module
+from ._common import as_file, files
+from .abc import ResourceReader
+from contextlib import suppress
from importlib.abc import ResourceLoader
+from importlib.machinery import ModuleSpec
from io import BytesIO, TextIOWrapper
from pathlib import Path
from types import ModuleType
-from typing import ContextManager, Iterable, Optional, Union
+from typing import ContextManager, Iterable, Union
from typing import cast
from typing.io import BinaryIO, TextIO
+from collections.abc import Sequence
+from functools import singledispatch
__all__ = [
'Package',
'Resource',
+ 'ResourceReader',
'as_file',
'contents',
'files',
@@ -26,99 +30,57 @@ __all__ = [
'path',
'read_binary',
'read_text',
- ]
+]
Package = Union[str, ModuleType]
Resource = Union[str, os.PathLike]
-def _resolve(name) -> ModuleType:
- """If name is a string, resolve to a module."""
- if hasattr(name, '__spec__'):
- return name
- return import_module(name)
-
-
-def _get_package(package) -> ModuleType:
- """Take a package name or module object and return the module.
-
- If a name, the module is imported. If the resolved module
- object is not a package, raise an exception.
- """
- module = _resolve(package)
- if module.__spec__.submodule_search_locations is None:
- raise TypeError('{!r} is not a package'.format(package))
- return module
-
-
-def _normalize_path(path) -> str:
- """Normalize a path by ensuring it is a string.
-
- If the resulting string contains path separators, an exception is raised.
- """
- parent, file_name = os.path.split(path)
- if parent:
- raise ValueError('{!r} must be only a file name'.format(path))
- return file_name
-
-
-def _get_resource_reader(
- package: ModuleType) -> Optional[resources_abc.ResourceReader]:
- # Return the package's loader if it's a ResourceReader. We can't use
- # a issubclass() check here because apparently abc.'s __subclasscheck__()
- # hook wants to create a weak reference to the object, but
- # zipimport.zipimporter does not support weak references, resulting in a
- # TypeError. That seems terrible.
- spec = package.__spec__
- if hasattr(spec.loader, 'get_resource_reader'):
- return cast(resources_abc.ResourceReader,
- spec.loader.get_resource_reader(spec.name))
- return None
-
-
-def _check_location(package):
- if package.__spec__.origin is None or not package.__spec__.has_location:
- raise FileNotFoundError(f'Package has no location {package!r}')
-
-
def open_binary(package: Package, resource: Resource) -> BinaryIO:
"""Return a file-like object opened for binary reading of the resource."""
- resource = _normalize_path(resource)
- package = _get_package(package)
- reader = _get_resource_reader(package)
+ resource = _common.normalize_path(resource)
+ package = _common.get_package(package)
+ reader = _common.get_resource_reader(package)
if reader is not None:
return reader.open_resource(resource)
- absolute_package_path = os.path.abspath(
- package.__spec__.origin or 'non-existent file')
- package_path = os.path.dirname(absolute_package_path)
- full_path = os.path.join(package_path, resource)
- try:
- return open(full_path, mode='rb')
- except OSError:
- # Just assume the loader is a resource loader; all the relevant
- # importlib.machinery loaders are and an AttributeError for
- # get_data() will make it clear what is needed from the loader.
- loader = cast(ResourceLoader, package.__spec__.loader)
- data = None
- if hasattr(package.__spec__.loader, 'get_data'):
- with suppress(OSError):
- data = loader.get_data(full_path)
- if data is None:
- package_name = package.__spec__.name
- message = '{!r} resource not found in {!r}'.format(
- resource, package_name)
- raise FileNotFoundError(message)
- return BytesIO(data)
+ spec = cast(ModuleSpec, package.__spec__)
+ # Using pathlib doesn't work well here due to the lack of 'strict'
+ # argument for pathlib.Path.resolve() prior to Python 3.6.
+ if spec.submodule_search_locations is not None:
+ paths = spec.submodule_search_locations
+ elif spec.origin is not None:
+ paths = [os.path.dirname(os.path.abspath(spec.origin))]
+
+ for package_path in paths:
+ full_path = os.path.join(package_path, resource)
+ try:
+ return open(full_path, mode='rb')
+ except OSError:
+ # Just assume the loader is a resource loader; all the relevant
+ # importlib.machinery loaders are and an AttributeError for
+ # get_data() will make it clear what is needed from the loader.
+ loader = cast(ResourceLoader, spec.loader)
+ data = None
+ if hasattr(spec.loader, 'get_data'):
+ with suppress(OSError):
+ data = loader.get_data(full_path)
+ if data is not None:
+ return BytesIO(data)
+ raise FileNotFoundError(f'{resource!r} resource not found in {spec.name!r}')
-def open_text(package: Package,
- resource: Resource,
- encoding: str = 'utf-8',
- errors: str = 'strict') -> TextIO:
+
+def open_text(
+ package: Package,
+ resource: Resource,
+ encoding: str = 'utf-8',
+ errors: str = 'strict',
+) -> TextIO:
"""Return a file-like object opened for text reading of the resource."""
return TextIOWrapper(
- open_binary(package, resource), encoding=encoding, errors=errors)
+ open_binary(package, resource), encoding=encoding, errors=errors
+ )
def read_binary(package: Package, resource: Resource) -> bytes:
@@ -127,10 +89,12 @@ def read_binary(package: Package, resource: Resource) -> bytes:
return fp.read()
-def read_text(package: Package,
- resource: Resource,
- encoding: str = 'utf-8',
- errors: str = 'strict') -> str:
+def read_text(
+ package: Package,
+ resource: Resource,
+ encoding: str = 'utf-8',
+ errors: str = 'strict',
+) -> str:
"""Return the decoded string of the resource.
The decoding-related arguments have the same semantics as those of
@@ -140,16 +104,10 @@ def read_text(package: Package,
return fp.read()
-def files(package: Package) -> resources_abc.Traversable:
- """
- Get a Traversable resource from a package
- """
- return _common.from_package(_get_package(package))
-
-
def path(
- package: Package, resource: Resource,
- ) -> 'ContextManager[Path]':
+ package: Package,
+ resource: Resource,
+) -> 'ContextManager[Path]':
"""A context manager providing a file path object to the resource.
If the resource does not already exist on its own on the file system,
@@ -158,23 +116,30 @@ def path(
raised if the file was deleted prior to the context manager
exiting).
"""
- reader = _get_resource_reader(_get_package(package))
+ reader = _common.get_resource_reader(_common.get_package(package))
return (
- _path_from_reader(reader, resource)
- if reader else
- _common.as_file(files(package).joinpath(_normalize_path(resource)))
+ _path_from_reader(reader, _common.normalize_path(resource))
+ if reader
+ else _common.as_file(
+ _common.files(package).joinpath(_common.normalize_path(resource))
)
+ )
-@contextmanager
def _path_from_reader(reader, resource):
- norm_resource = _normalize_path(resource)
+ return _path_from_resource_path(reader, resource) or _path_from_open_resource(
+ reader, resource
+ )
+
+
+def _path_from_resource_path(reader, resource):
with suppress(FileNotFoundError):
- yield Path(reader.resource_path(norm_resource))
- return
- opener_reader = reader.open_resource(norm_resource)
- with _common._tempfile(opener_reader.read, suffix=norm_resource) as res:
- yield res
+ return Path(reader.resource_path(resource))
+
+
+def _path_from_open_resource(reader, resource):
+ saved = io.BytesIO(reader.open_resource(resource).read())
+ return _common._tempfile(saved.read, suffix=resource)
def is_resource(package: Package, name: str) -> bool:
@@ -182,9 +147,9 @@ def is_resource(package: Package, name: str) -> bool:
Directories are *not* resources.
"""
- package = _get_package(package)
- _normalize_path(name)
- reader = _get_resource_reader(package)
+ package = _common.get_package(package)
+ _common.normalize_path(name)
+ reader = _common.get_resource_reader(package)
if reader is not None:
return reader.is_resource(name)
package_contents = set(contents(package))
@@ -200,16 +165,21 @@ def contents(package: Package) -> Iterable[str]:
not considered resources. Use `is_resource()` on each entry returned here
to check if it is a resource or not.
"""
- package = _get_package(package)
- reader = _get_resource_reader(package)
+ package = _common.get_package(package)
+ reader = _common.get_resource_reader(package)
if reader is not None:
- return reader.contents()
- # Is the package a namespace package? By definition, namespace packages
- # cannot have resources.
- namespace = (
- package.__spec__.origin is None or
- package.__spec__.origin == 'namespace'
- )
- if namespace or not package.__spec__.has_location:
- return ()
- return list(item.name for item in _common.from_package(package).iterdir())
+ return _ensure_sequence(reader.contents())
+ transversable = _common.from_package(package)
+ if transversable.is_dir():
+ return list(item.name for item in transversable.iterdir())
+ return []
+
+
+@singledispatch
+def _ensure_sequence(iterable):
+ return list(iterable)
+
+
+@_ensure_sequence.register(Sequence)
+def _(iterable):
+ return iterable
diff --git a/contrib/tools/python3/src/Lib/importlib/util.py b/contrib/tools/python3/src/Lib/importlib/util.py
index 269a6fa930a..8623c89840c 100644
--- a/contrib/tools/python3/src/Lib/importlib/util.py
+++ b/contrib/tools/python3/src/Lib/importlib/util.py
@@ -1,5 +1,5 @@
"""Utility code for constructing importers, etc."""
-from . import abc
+from ._abc import Loader
from ._bootstrap import module_from_spec
from ._bootstrap import _resolve_name
from ._bootstrap import spec_from_loader
@@ -149,7 +149,8 @@ def set_package(fxn):
"""
@functools.wraps(fxn)
def set_package_wrapper(*args, **kwargs):
- warnings.warn('The import system now takes care of this automatically.',
+ warnings.warn('The import system now takes care of this automatically; '
+ 'this decorator is slated for removal in Python 3.12',
DeprecationWarning, stacklevel=2)
module = fxn(*args, **kwargs)
if getattr(module, '__package__', None) is None:
@@ -168,7 +169,8 @@ def set_loader(fxn):
"""
@functools.wraps(fxn)
def set_loader_wrapper(self, *args, **kwargs):
- warnings.warn('The import system now takes care of this automatically.',
+ warnings.warn('The import system now takes care of this automatically; '
+ 'this decorator is slated for removal in Python 3.12',
DeprecationWarning, stacklevel=2)
module = fxn(self, *args, **kwargs)
if getattr(module, '__loader__', None) is None:
@@ -195,7 +197,8 @@ def module_for_loader(fxn):
the second argument.
"""
- warnings.warn('The import system now takes care of this automatically.',
+ warnings.warn('The import system now takes care of this automatically; '
+ 'this decorator is slated for removal in Python 3.12',
DeprecationWarning, stacklevel=2)
@functools.wraps(fxn)
def module_for_loader_wrapper(self, fullname, *args, **kwargs):
@@ -232,7 +235,6 @@ class _LazyModule(types.ModuleType):
# Figure out exactly what attributes were mutated between the creation
# of the module and now.
attrs_then = self.__spec__.loader_state['__dict__']
- original_type = self.__spec__.loader_state['__class__']
attrs_now = self.__dict__
attrs_updated = {}
for key, value in attrs_now.items():
@@ -263,7 +265,7 @@ class _LazyModule(types.ModuleType):
delattr(self, attr)
-class LazyLoader(abc.Loader):
+class LazyLoader(Loader):
"""A loader that creates a module which defers loading until attribute access."""