Update vendored `pkg_resources`

This commit is contained in:
Pradyun Gedam 2023-02-04 21:18:10 +00:00
parent e3e7bc34eb
commit eb7b4ed62e
No known key found for this signature in database
GPG Key ID: FF99710C4332258E
9 changed files with 303 additions and 200 deletions

View File

@ -0,0 +1 @@
Patch pkg_resources to remove dependency on ``jaraco.text``.

View File

@ -0,0 +1 @@
Update pkg_resources (via setuptools) to 65.6.3

View File

@ -50,6 +50,8 @@ drop = [
"easy_install.py", "easy_install.py",
"setuptools", "setuptools",
"pkg_resources/_vendor/", "pkg_resources/_vendor/",
"_distutils_hack",
"distutils-precedence.pth",
"pkg_resources/extern/", "pkg_resources/extern/",
# trim vendored pygments styles and lexers # trim vendored pygments styles and lexers
"pygments/styles/[!_]*.py", "pygments/styles/[!_]*.py",

View File

@ -0,0 +1,109 @@
"""Functions brought over from jaraco.text.
These functions are not supposed to be used within `pip._internal`. These are
helper functions brought over from `jaraco.text` to enable vendoring newer
copies of `pkg_resources` without having to vendor `jaraco.text` and its entire
dependency cone; something that our vendoring setup is not currently capable of
handling.
License reproduced from original source below:
Copyright Jason R. Coombs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
"""
import functools
import itertools
def _nonblank(str):
return str and not str.startswith("#")
@functools.singledispatch
def yield_lines(iterable):
r"""
Yield valid lines of a string or iterable.
>>> list(yield_lines(''))
[]
>>> list(yield_lines(['foo', 'bar']))
['foo', 'bar']
>>> list(yield_lines('foo\nbar'))
['foo', 'bar']
>>> list(yield_lines('\nfoo\n#bar\nbaz #comment'))
['foo', 'baz #comment']
>>> list(yield_lines(['foo\nbar', 'baz', 'bing\n\n\n']))
['foo', 'bar', 'baz', 'bing']
"""
return itertools.chain.from_iterable(map(yield_lines, iterable))
@yield_lines.register(str)
def _(text):
return filter(_nonblank, map(str.strip, text.splitlines()))
def drop_comment(line):
"""
Drop comments.
>>> drop_comment('foo # bar')
'foo'
A hash without a space may be in a URL.
>>> drop_comment('http://example.com/foo#bar')
'http://example.com/foo#bar'
"""
return line.partition(" #")[0]
def join_continuation(lines):
r"""
Join lines continued by a trailing backslash.
>>> list(join_continuation(['foo \\', 'bar', 'baz']))
['foobar', 'baz']
>>> list(join_continuation(['foo \\', 'bar', 'baz']))
['foobar', 'baz']
>>> list(join_continuation(['foo \\', 'bar \\', 'baz']))
['foobarbaz']
Not sure why, but...
The character preceeding the backslash is also elided.
>>> list(join_continuation(['goo\\', 'dly']))
['godly']
A terrible idea, but...
If no line is available to continue, suppress the lines.
>>> list(join_continuation(['foo', 'bar\\', 'baz\\']))
['foo']
"""
lines = iter(lines)
for item in lines:
while item.endswith("\\"):
try:
item = item[:-2].strip() + next(lines)
except StopIteration:
return
yield item

View File

@ -1,19 +1,19 @@
Copyright (C) 2016 Jason R Coombs <jaraco@jaraco.com> Copyright Jason R. Coombs
Permission is hereby granted, free of charge, to any person obtaining a copy of Permission is hereby granted, free of charge, to any person obtaining a copy
this software and associated documentation files (the "Software"), to deal in of this software and associated documentation files (the "Software"), to
the Software without restriction, including without limitation the rights to deal in the Software without restriction, including without limitation the
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
of the Software, and to permit persons to whom the Software is furnished to do sell copies of the Software, and to permit persons to whom the Software is
so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in
copies or substantial portions of the Software. all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
SOFTWARE. IN THE SOFTWARE.

View File

@ -1,4 +1,3 @@
# coding: utf-8
""" """
Package resource API Package resource API
-------------------- --------------------
@ -15,8 +14,6 @@ The package resource API is designed to work with normal filesystem packages,
method. method.
""" """
from __future__ import absolute_import
import sys import sys
import os import os
import io import io
@ -41,6 +38,7 @@ import itertools
import inspect import inspect
import ntpath import ntpath
import posixpath import posixpath
import importlib
from pkgutil import get_importer from pkgutil import get_importer
try: try:
@ -54,9 +52,6 @@ try:
except NameError: except NameError:
FileExistsError = OSError FileExistsError = OSError
from pip._vendor import six
from pip._vendor.six.moves import urllib, map, filter
# capture these to bypass sandboxing # capture these to bypass sandboxing
from os import utime from os import utime
try: try:
@ -76,26 +71,23 @@ try:
except ImportError: except ImportError:
importlib_machinery = None importlib_machinery = None
from . import py31compat from pip._internal.utils._jaraco_text import (
yield_lines,
drop_comment,
join_continuation,
)
from pip._vendor import platformdirs from pip._vendor import platformdirs
from pip._vendor import packaging from pip._vendor import packaging
__import__('pip._vendor.packaging.version') __import__('pip._vendor.packaging.version')
__import__('pip._vendor.packaging.specifiers') __import__('pip._vendor.packaging.specifiers')
__import__('pip._vendor.packaging.requirements') __import__('pip._vendor.packaging.requirements')
__import__('pip._vendor.packaging.markers') __import__('pip._vendor.packaging.markers')
__import__('pip._vendor.packaging.utils')
if sys.version_info < (3, 5):
__metaclass__ = type
if (3, 0) < sys.version_info < (3, 5):
raise RuntimeError("Python 3.5 or later is required") raise RuntimeError("Python 3.5 or later is required")
if six.PY2:
# Those builtin exceptions are only defined in Python 3
PermissionError = None
NotADirectoryError = None
# declare some globals that will be defined later to # declare some globals that will be defined later to
# satisfy the linters. # satisfy the linters.
require = None require = None
@ -128,6 +120,11 @@ def parse_version(v):
try: try:
return packaging.version.Version(v) return packaging.version.Version(v)
except packaging.version.InvalidVersion: except packaging.version.InvalidVersion:
warnings.warn(
f"{v} is an invalid version and will not be supported in "
"a future release",
PkgResourcesDeprecationWarning,
)
return packaging.version.LegacyVersion(v) return packaging.version.LegacyVersion(v)
@ -178,10 +175,10 @@ def get_supported_platform():
"""Return this platform's maximum compatible version. """Return this platform's maximum compatible version.
distutils.util.get_platform() normally reports the minimum version distutils.util.get_platform() normally reports the minimum version
of Mac OS X that would be required to *use* extensions produced by of macOS that would be required to *use* extensions produced by
distutils. But what we want when checking compatibility is to know the distutils. But what we want when checking compatibility is to know the
version of Mac OS X that we are *running*. To allow usage of packages that version of macOS that we are *running*. To allow usage of packages that
explicitly require a newer version of Mac OS X, we must also know the explicitly require a newer version of macOS, we must also know the
current version of the OS. current version of the OS.
If this condition occurs for any other platform with a version in its If this condition occurs for any other platform with a version in its
@ -191,9 +188,9 @@ def get_supported_platform():
m = macosVersionString.match(plat) m = macosVersionString.match(plat)
if m is not None and sys.platform == "darwin": if m is not None and sys.platform == "darwin":
try: try:
plat = 'macosx-%s-%s' % ('.'.join(_macosx_vers()[:2]), m.group(3)) plat = 'macosx-%s-%s' % ('.'.join(_macos_vers()[:2]), m.group(3))
except ValueError: except ValueError:
# not Mac OS X # not macOS
pass pass
return plat return plat
@ -364,7 +361,7 @@ def get_provider(moduleOrReq):
return _find_adapter(_provider_factories, loader)(module) return _find_adapter(_provider_factories, loader)(module)
def _macosx_vers(_cache=[]): def _macos_vers(_cache=[]):
if not _cache: if not _cache:
version = platform.mac_ver()[0] version = platform.mac_ver()[0]
# fallback for MacPorts # fallback for MacPorts
@ -380,7 +377,7 @@ def _macosx_vers(_cache=[]):
return _cache[0] return _cache[0]
def _macosx_arch(machine): def _macos_arch(machine):
return {'PowerPC': 'ppc', 'Power_Macintosh': 'ppc'}.get(machine, machine) return {'PowerPC': 'ppc', 'Power_Macintosh': 'ppc'}.get(machine, machine)
@ -388,18 +385,18 @@ def get_build_platform():
"""Return this platform's string for platform-specific distributions """Return this platform's string for platform-specific distributions
XXX Currently this is the same as ``distutils.util.get_platform()``, but it XXX Currently this is the same as ``distutils.util.get_platform()``, but it
needs some hacks for Linux and Mac OS X. needs some hacks for Linux and macOS.
""" """
from sysconfig import get_platform from sysconfig import get_platform
plat = get_platform() plat = get_platform()
if sys.platform == "darwin" and not plat.startswith('macosx-'): if sys.platform == "darwin" and not plat.startswith('macosx-'):
try: try:
version = _macosx_vers() version = _macos_vers()
machine = os.uname()[4].replace(" ", "_") machine = os.uname()[4].replace(" ", "_")
return "macosx-%d.%d-%s" % ( return "macosx-%d.%d-%s" % (
int(version[0]), int(version[1]), int(version[0]), int(version[1]),
_macosx_arch(machine), _macos_arch(machine),
) )
except ValueError: except ValueError:
# if someone is running a non-Mac darwin system, this will fall # if someone is running a non-Mac darwin system, this will fall
@ -425,7 +422,7 @@ def compatible_platforms(provided, required):
# easy case # easy case
return True return True
# Mac OS X special cases # macOS special cases
reqMac = macosVersionString.match(required) reqMac = macosVersionString.match(required)
if reqMac: if reqMac:
provMac = macosVersionString.match(provided) provMac = macosVersionString.match(provided)
@ -434,7 +431,7 @@ def compatible_platforms(provided, required):
if not provMac: if not provMac:
# this is backwards compatibility for packages built before # this is backwards compatibility for packages built before
# setuptools 0.6. All packages built after this point will # setuptools 0.6. All packages built after this point will
# use the new macosx designation. # use the new macOS designation.
provDarwin = darwinVersionString.match(provided) provDarwin = darwinVersionString.match(provided)
if provDarwin: if provDarwin:
dversion = int(provDarwin.group(1)) dversion = int(provDarwin.group(1))
@ -442,7 +439,7 @@ def compatible_platforms(provided, required):
if dversion == 7 and macosversion >= "10.3" or \ if dversion == 7 and macosversion >= "10.3" or \
dversion == 8 and macosversion >= "10.4": dversion == 8 and macosversion >= "10.4":
return True return True
# egg isn't macosx or legacy darwin # egg isn't macOS or legacy darwin
return False return False
# are they the same major version and machine type? # are they the same major version and machine type?
@ -475,7 +472,7 @@ run_main = run_script
def get_distribution(dist): def get_distribution(dist):
"""Return a current distribution object for a Requirement or string""" """Return a current distribution object for a Requirement or string"""
if isinstance(dist, six.string_types): if isinstance(dist, str):
dist = Requirement.parse(dist) dist = Requirement.parse(dist)
if isinstance(dist, Requirement): if isinstance(dist, Requirement):
dist = get_provider(dist) dist = get_provider(dist)
@ -558,6 +555,7 @@ class WorkingSet:
self.entries = [] self.entries = []
self.entry_keys = {} self.entry_keys = {}
self.by_key = {} self.by_key = {}
self.normalized_to_canonical_keys = {}
self.callbacks = [] self.callbacks = []
if entries is None: if entries is None:
@ -638,6 +636,14 @@ class WorkingSet:
is returned. is returned.
""" """
dist = self.by_key.get(req.key) dist = self.by_key.get(req.key)
if dist is None:
canonical_key = self.normalized_to_canonical_keys.get(req.key)
if canonical_key is not None:
req.key = canonical_key
dist = self.by_key.get(canonical_key)
if dist is not None and dist not in req: if dist is not None and dist not in req:
# XXX add more info # XXX add more info
raise VersionConflict(dist, req) raise VersionConflict(dist, req)
@ -706,13 +712,16 @@ class WorkingSet:
return return
self.by_key[dist.key] = dist self.by_key[dist.key] = dist
normalized_name = packaging.utils.canonicalize_name(dist.key)
self.normalized_to_canonical_keys[normalized_name] = dist.key
if dist.key not in keys: if dist.key not in keys:
keys.append(dist.key) keys.append(dist.key)
if dist.key not in keys2: if dist.key not in keys2:
keys2.append(dist.key) keys2.append(dist.key)
self._added_new(dist) self._added_new(dist)
def resolve(self, requirements, env=None, installer=None, # FIXME: 'WorkingSet.resolve' is too complex (11)
def resolve(self, requirements, env=None, installer=None, # noqa: C901
replace_conflicting=False, extras=None): replace_conflicting=False, extras=None):
"""List all distributions needed to (recursively) meet `requirements` """List all distributions needed to (recursively) meet `requirements`
@ -925,14 +934,15 @@ class WorkingSet:
def __getstate__(self): def __getstate__(self):
return ( return (
self.entries[:], self.entry_keys.copy(), self.by_key.copy(), self.entries[:], self.entry_keys.copy(), self.by_key.copy(),
self.callbacks[:] self.normalized_to_canonical_keys.copy(), self.callbacks[:]
) )
def __setstate__(self, e_k_b_c): def __setstate__(self, e_k_b_n_c):
entries, keys, by_key, callbacks = e_k_b_c entries, keys, by_key, normalized_to_canonical_keys, callbacks = e_k_b_n_c
self.entries = entries[:] self.entries = entries[:]
self.entry_keys = keys.copy() self.entry_keys = keys.copy()
self.by_key = by_key.copy() self.by_key = by_key.copy()
self.normalized_to_canonical_keys = normalized_to_canonical_keys.copy()
self.callbacks = callbacks[:] self.callbacks = callbacks[:]
@ -1234,12 +1244,13 @@ class ResourceManager:
mode = os.stat(path).st_mode mode = os.stat(path).st_mode
if mode & stat.S_IWOTH or mode & stat.S_IWGRP: if mode & stat.S_IWOTH or mode & stat.S_IWGRP:
msg = ( msg = (
"%s is writable by group/others and vulnerable to attack " "Extraction path is writable by group/others "
"when " "and vulnerable to attack when "
"used with get_resource_filename. Consider a more secure " "used with get_resource_filename ({path}). "
"Consider a more secure "
"location (set with .set_extraction_path or the " "location (set with .set_extraction_path or the "
"PYTHON_EGG_CACHE environment variable)." % path "PYTHON_EGG_CACHE environment variable)."
) ).format(**locals())
warnings.warn(msg, UserWarning) warnings.warn(msg, UserWarning)
def postprocess(self, tempname, filename): def postprocess(self, tempname, filename):
@ -1377,7 +1388,7 @@ def evaluate_marker(text, extra=None):
marker = packaging.markers.Marker(text) marker = packaging.markers.Marker(text)
return marker.evaluate() return marker.evaluate()
except packaging.markers.InvalidMarker as e: except packaging.markers.InvalidMarker as e:
raise SyntaxError(e) raise SyntaxError(e) from e
class NullProvider: class NullProvider:
@ -1418,8 +1429,6 @@ class NullProvider:
return "" return ""
path = self._get_metadata_path(name) path = self._get_metadata_path(name)
value = self._get(path) value = self._get(path)
if six.PY2:
return value
try: try:
return value.decode('utf-8') return value.decode('utf-8')
except UnicodeDecodeError as exc: except UnicodeDecodeError as exc:
@ -1457,7 +1466,8 @@ class NullProvider:
script_filename = self._fn(self.egg_info, script) script_filename = self._fn(self.egg_info, script)
namespace['__file__'] = script_filename namespace['__file__'] = script_filename
if os.path.exists(script_filename): if os.path.exists(script_filename):
source = open(script_filename).read() with open(script_filename) as fid:
source = fid.read()
code = compile(source, script_filename, 'exec') code = compile(source, script_filename, 'exec')
exec(code, namespace, namespace) exec(code, namespace, namespace)
else: else:
@ -1493,7 +1503,7 @@ class NullProvider:
def _validate_resource_path(path): def _validate_resource_path(path):
""" """
Validate the resource paths according to the docs. Validate the resource paths according to the docs.
https://setuptools.readthedocs.io/en/latest/pkg_resources.html#basic-resource-access https://setuptools.pypa.io/en/latest/pkg_resources.html#basic-resource-access
>>> warned = getfixture('recwarn') >>> warned = getfixture('recwarn')
>>> warnings.simplefilter('always') >>> warnings.simplefilter('always')
@ -1575,26 +1585,35 @@ is not allowed.
register_loader_type(object, NullProvider) register_loader_type(object, NullProvider)
def _parents(path):
"""
yield all parents of path including path
"""
last = None
while path != last:
yield path
last = path
path, _ = os.path.split(path)
class EggProvider(NullProvider): class EggProvider(NullProvider):
"""Provider based on a virtual filesystem""" """Provider based on a virtual filesystem"""
def __init__(self, module): def __init__(self, module):
NullProvider.__init__(self, module) super().__init__(module)
self._setup_prefix() self._setup_prefix()
def _setup_prefix(self): def _setup_prefix(self):
# we assume here that our metadata may be nested inside a "basket" # Assume that metadata may be nested inside a "basket"
# of multiple eggs; that's why we use module_path instead of .archive # of multiple eggs and use module_path instead of .archive.
path = self.module_path eggs = filter(_is_egg_path, _parents(self.module_path))
old = None egg = next(eggs, None)
while path != old: egg and self._set_egg(egg)
if _is_egg_path(path):
self.egg_name = os.path.basename(path) def _set_egg(self, path):
self.egg_info = os.path.join(path, 'EGG-INFO') self.egg_name = os.path.basename(path)
self.egg_root = path self.egg_info = os.path.join(path, 'EGG-INFO')
break self.egg_root = path
old = path
path, base = os.path.split(path)
class DefaultProvider(EggProvider): class DefaultProvider(EggProvider):
@ -1701,7 +1720,7 @@ class ZipProvider(EggProvider):
_zip_manifests = MemoizedZipManifests() _zip_manifests = MemoizedZipManifests()
def __init__(self, module): def __init__(self, module):
EggProvider.__init__(self, module) super().__init__(module)
self.zip_pre = self.loader.archive + os.sep self.zip_pre = self.loader.archive + os.sep
def _zipinfo_name(self, fspath): def _zipinfo_name(self, fspath):
@ -1752,7 +1771,8 @@ class ZipProvider(EggProvider):
timestamp = time.mktime(date_time) timestamp = time.mktime(date_time)
return timestamp, size return timestamp, size
def _extract_resource(self, manager, zip_path): # FIXME: 'ZipProvider._extract_resource' is too complex (12)
def _extract_resource(self, manager, zip_path): # noqa: C901
if zip_path in self._index(): if zip_path in self._index():
for name in self._index()[zip_path]: for name in self._index()[zip_path]:
@ -1900,8 +1920,7 @@ class FileMetadata(EmptyProvider):
return metadata return metadata
def _warn_on_replacement(self, metadata): def _warn_on_replacement(self, metadata):
# Python 2.7 compat for: replacement_char = '<27>' replacement_char = '<EFBFBD>'
replacement_char = b'\xef\xbf\xbd'.decode('utf-8')
if replacement_char in metadata: if replacement_char in metadata:
tmpl = "{self.path} could not be properly decoded in UTF-8" tmpl = "{self.path} could not be properly decoded in UTF-8"
msg = tmpl.format(**locals()) msg = tmpl.format(**locals())
@ -1991,7 +2010,7 @@ def find_eggs_in_zip(importer, path_item, only=False):
dists = find_eggs_in_zip(zipimport.zipimporter(subpath), subpath) dists = find_eggs_in_zip(zipimport.zipimporter(subpath), subpath)
for dist in dists: for dist in dists:
yield dist yield dist
elif subitem.lower().endswith('.dist-info'): elif subitem.lower().endswith(('.dist-info', '.egg-info')):
subpath = os.path.join(path_item, subitem) subpath = os.path.join(path_item, subitem)
submeta = EggMetadata(zipimport.zipimporter(subpath)) submeta = EggMetadata(zipimport.zipimporter(subpath))
submeta.egg_info = subpath submeta.egg_info = subpath
@ -2015,7 +2034,7 @@ def _by_version_descending(names):
>>> names = 'bar', 'foo', 'Python-2.7.10.egg', 'Python-2.7.2.egg' >>> names = 'bar', 'foo', 'Python-2.7.10.egg', 'Python-2.7.2.egg'
>>> _by_version_descending(names) >>> _by_version_descending(names)
['Python-2.7.10.egg', 'Python-2.7.2.egg', 'foo', 'bar'] ['Python-2.7.10.egg', 'Python-2.7.2.egg', 'bar', 'foo']
>>> names = 'Setuptools-1.2.3b1.egg', 'Setuptools-1.2.3.egg' >>> names = 'Setuptools-1.2.3b1.egg', 'Setuptools-1.2.3.egg'
>>> _by_version_descending(names) >>> _by_version_descending(names)
['Setuptools-1.2.3.egg', 'Setuptools-1.2.3b1.egg'] ['Setuptools-1.2.3.egg', 'Setuptools-1.2.3b1.egg']
@ -2023,13 +2042,22 @@ def _by_version_descending(names):
>>> _by_version_descending(names) >>> _by_version_descending(names)
['Setuptools-1.2.3.post1.egg', 'Setuptools-1.2.3b1.egg'] ['Setuptools-1.2.3.post1.egg', 'Setuptools-1.2.3b1.egg']
""" """
def try_parse(name):
"""
Attempt to parse as a version or return a null version.
"""
try:
return packaging.version.Version(name)
except Exception:
return packaging.version.Version('0')
def _by_version(name): def _by_version(name):
""" """
Parse each component of the filename Parse each component of the filename
""" """
name, ext = os.path.splitext(name) name, ext = os.path.splitext(name)
parts = itertools.chain(name.split('-'), [ext]) parts = itertools.chain(name.split('-'), [ext])
return [packaging.version.parse(part) for part in parts] return [try_parse(part) for part in parts]
return sorted(names, key=_by_version, reverse=True) return sorted(names, key=_by_version, reverse=True)
@ -2046,7 +2074,10 @@ def find_on_path(importer, path_item, only=False):
) )
return return
entries = safe_listdir(path_item) entries = (
os.path.join(path_item, child)
for child in safe_listdir(path_item)
)
# for performance, before sorting by version, # for performance, before sorting by version,
# screen entries for only those that will yield # screen entries for only those that will yield
@ -2067,11 +2098,14 @@ def find_on_path(importer, path_item, only=False):
def dist_factory(path_item, entry, only): def dist_factory(path_item, entry, only):
""" """Return a dist_factory for the given entry."""
Return a dist_factory for a path_item and entry
"""
lower = entry.lower() lower = entry.lower()
is_meta = any(map(lower.endswith, ('.egg-info', '.dist-info'))) is_egg_info = lower.endswith('.egg-info')
is_dist_info = (
lower.endswith('.dist-info') and
os.path.isdir(os.path.join(path_item, entry))
)
is_meta = is_egg_info or is_dist_info
return ( return (
distributions_from_metadata distributions_from_metadata
if is_meta else if is_meta else
@ -2093,8 +2127,6 @@ class NoDists:
""" """
def __bool__(self): def __bool__(self):
return False return False
if six.PY2:
__nonzero__ = __bool__
def __call__(self, fullpath): def __call__(self, fullpath):
return iter(()) return iter(())
@ -2111,12 +2143,7 @@ def safe_listdir(path):
except OSError as e: except OSError as e:
# Ignore the directory if does not exist, not a directory or # Ignore the directory if does not exist, not a directory or
# permission denied # permission denied
ignorable = ( if e.errno not in (errno.ENOTDIR, errno.EACCES, errno.ENOENT):
e.errno in (errno.ENOTDIR, errno.EACCES, errno.ENOENT)
# Python 2 on Windows needs to be handled this way :(
or getattr(e, "winerror", None) == 267
)
if not ignorable:
raise raise
return () return ()
@ -2195,10 +2222,16 @@ def _handle_ns(packageName, path_item):
if importer is None: if importer is None:
return None return None
# capture warnings due to #1111 # use find_spec (PEP 451) and fall-back to find_module (PEP 302)
with warnings.catch_warnings(): try:
warnings.simplefilter("ignore") spec = importer.find_spec(packageName)
loader = importer.find_module(packageName) except AttributeError:
# capture warnings due to #1111
with warnings.catch_warnings():
warnings.simplefilter("ignore")
loader = importer.find_module(packageName)
else:
loader = spec.loader if spec else None
if loader is None: if loader is None:
return None return None
@ -2214,7 +2247,7 @@ def _handle_ns(packageName, path_item):
if subpath is not None: if subpath is not None:
path = module.__path__ path = module.__path__
path.append(subpath) path.append(subpath)
loader.load_module(packageName) importlib.import_module(packageName)
_rebuild_mod_path(path, packageName, module) _rebuild_mod_path(path, packageName, module)
return subpath return subpath
@ -2270,8 +2303,8 @@ def declare_namespace(packageName):
__import__(parent) __import__(parent)
try: try:
path = sys.modules[parent].__path__ path = sys.modules[parent].__path__
except AttributeError: except AttributeError as e:
raise TypeError("Not a package:", parent) raise TypeError("Not a package:", parent) from e
# Track what packages are namespaces, so when new path items are added, # Track what packages are namespaces, so when new path items are added,
# they can be updated # they can be updated
@ -2328,7 +2361,8 @@ register_namespace_handler(object, null_ns_handler)
def normalize_path(filename): def normalize_path(filename):
"""Normalize a file/dir name for comparison purposes""" """Normalize a file/dir name for comparison purposes"""
return os.path.normcase(os.path.realpath(os.path.normpath(_cygwin_patch(filename)))) return os.path.normcase(os.path.realpath(os.path.normpath(
_cygwin_patch(filename))))
def _cygwin_patch(filename): # pragma: nocover def _cygwin_patch(filename): # pragma: nocover
@ -2354,7 +2388,15 @@ def _is_egg_path(path):
""" """
Determine if given path appears to be an egg. Determine if given path appears to be an egg.
""" """
return path.lower().endswith('.egg') return _is_zip_egg(path) or _is_unpacked_egg(path)
def _is_zip_egg(path):
return (
path.lower().endswith('.egg') and
os.path.isfile(path) and
zipfile.is_zipfile(path)
)
def _is_unpacked_egg(path): def _is_unpacked_egg(path):
@ -2362,7 +2404,7 @@ def _is_unpacked_egg(path):
Determine if given path appears to be an unpacked egg. Determine if given path appears to be an unpacked egg.
""" """
return ( return (
_is_egg_path(path) and path.lower().endswith('.egg') and
os.path.isfile(os.path.join(path, 'EGG-INFO', 'PKG-INFO')) os.path.isfile(os.path.join(path, 'EGG-INFO', 'PKG-INFO'))
) )
@ -2375,20 +2417,6 @@ def _set_parent_ns(packageName):
setattr(sys.modules[parent], name, sys.modules[packageName]) setattr(sys.modules[parent], name, sys.modules[packageName])
def yield_lines(strs):
"""Yield non-empty/non-comment lines of a string or sequence"""
if isinstance(strs, six.string_types):
for s in strs.splitlines():
s = s.strip()
# skip blank lines/comments
if s and not s.startswith('#'):
yield s
else:
for ss in strs:
for s in yield_lines(ss):
yield s
MODULE = re.compile(r"\w+(\.\w+)*$").match MODULE = re.compile(r"\w+(\.\w+)*$").match
EGG_NAME = re.compile( EGG_NAME = re.compile(
r""" r"""
@ -2450,7 +2478,7 @@ class EntryPoint:
try: try:
return functools.reduce(getattr, self.attrs, module) return functools.reduce(getattr, self.attrs, module)
except AttributeError as exc: except AttributeError as exc:
raise ImportError(str(exc)) raise ImportError(str(exc)) from exc
def require(self, env=None, installer=None): def require(self, env=None, installer=None):
if self.extras and not self.dist: if self.extras and not self.dist:
@ -2536,15 +2564,6 @@ class EntryPoint:
return maps return maps
def _remove_md5_fragment(location):
if not location:
return ''
parsed = urllib.parse.urlparse(location)
if parsed[-1].startswith('md5='):
return urllib.parse.urlunparse(parsed[:-1] + ('',))
return location
def _version_from_file(lines): def _version_from_file(lines):
""" """
Given an iterable of lines from a Metadata file, return Given an iterable of lines from a Metadata file, return
@ -2601,7 +2620,7 @@ class Distribution:
self.parsed_version, self.parsed_version,
self.precedence, self.precedence,
self.key, self.key,
_remove_md5_fragment(self.location), self.location,
self.py_version or '', self.py_version or '',
self.platform or '', self.platform or '',
) )
@ -2679,14 +2698,14 @@ class Distribution:
def version(self): def version(self):
try: try:
return self._version return self._version
except AttributeError: except AttributeError as e:
version = self._get_version() version = self._get_version()
if version is None: if version is None:
path = self._get_metadata_path_for_display(self.PKG_INFO) path = self._get_metadata_path_for_display(self.PKG_INFO)
msg = ( msg = (
"Missing 'Version:' header and/or {} file at path: {}" "Missing 'Version:' header and/or {} file at path: {}"
).format(self.PKG_INFO, path) ).format(self.PKG_INFO, path)
raise ValueError(msg, self) raise ValueError(msg, self) from e
return version return version
@ -2739,10 +2758,10 @@ class Distribution:
for ext in extras: for ext in extras:
try: try:
deps.extend(dm[safe_extra(ext)]) deps.extend(dm[safe_extra(ext)])
except KeyError: except KeyError as e:
raise UnknownExtra( raise UnknownExtra(
"%s has no such extra feature %r" % (self, ext) "%s has no such extra feature %r" % (self, ext)
) ) from e
return deps return deps
def _get_metadata_path_for_display(self, name): def _get_metadata_path_for_display(self, name):
@ -2824,10 +2843,6 @@ class Distribution:
) )
) )
if not hasattr(object, '__dir__'):
# python 2.7 not supported
del __dir__
@classmethod @classmethod
def from_filename(cls, filename, metadata=None, **kw): def from_filename(cls, filename, metadata=None, **kw):
return cls.from_location( return cls.from_location(
@ -2867,7 +2882,8 @@ class Distribution:
"""Return the EntryPoint object for `group`+`name`, or ``None``""" """Return the EntryPoint object for `group`+`name`, or ``None``"""
return self.get_entry_map(group).get(name) return self.get_entry_map(group).get(name)
def insert_on(self, path, loc=None, replace=False): # FIXME: 'Distribution.insert_on' is too complex (13)
def insert_on(self, path, loc=None, replace=False): # noqa: C901
"""Ensure self.location is on path """Ensure self.location is on path
If replace=False (default): If replace=False (default):
@ -3037,12 +3053,12 @@ class DistInfoDistribution(Distribution):
if not req.marker or req.marker.evaluate({'extra': extra}): if not req.marker or req.marker.evaluate({'extra': extra}):
yield req yield req
common = frozenset(reqs_for_extra(None)) common = types.MappingProxyType(dict.fromkeys(reqs_for_extra(None)))
dm[None].extend(common) dm[None].extend(common)
for extra in self._parsed_pkg_info.get_all('Provides-Extra') or []: for extra in self._parsed_pkg_info.get_all('Provides-Extra') or []:
s_extra = safe_extra(extra.strip()) s_extra = safe_extra(extra.strip())
dm[s_extra] = list(frozenset(reqs_for_extra(extra)) - common) dm[s_extra] = [r for r in reqs_for_extra(extra) if r not in common]
return dm return dm
@ -3067,40 +3083,23 @@ def issue_warning(*args, **kw):
warnings.warn(stacklevel=level + 1, *args, **kw) warnings.warn(stacklevel=level + 1, *args, **kw)
class RequirementParseError(ValueError):
def __str__(self):
return ' '.join(self.args)
def parse_requirements(strs): def parse_requirements(strs):
"""Yield ``Requirement`` objects for each specification in `strs` """
Yield ``Requirement`` objects for each specification in `strs`.
`strs` must be a string, or a (possibly-nested) iterable thereof. `strs` must be a string, or a (possibly-nested) iterable thereof.
""" """
# create a steppable iterator, so we can handle \-continuations return map(Requirement, join_continuation(map(drop_comment, yield_lines(strs))))
lines = iter(yield_lines(strs))
for line in lines:
# Drop comments -- a hash without a space may be in a URL. class RequirementParseError(packaging.requirements.InvalidRequirement):
if ' #' in line: "Compatibility wrapper for InvalidRequirement"
line = line[:line.find(' #')]
# If there is a line continuation, drop it, and append the next line.
if line.endswith('\\'):
line = line[:-2].strip()
try:
line += next(lines)
except StopIteration:
return
yield Requirement(line)
class Requirement(packaging.requirements.Requirement): class Requirement(packaging.requirements.Requirement):
def __init__(self, requirement_string): def __init__(self, requirement_string):
"""DO NOT CALL THIS UNDOCUMENTED METHOD; use Requirement.parse()!""" """DO NOT CALL THIS UNDOCUMENTED METHOD; use Requirement.parse()!"""
try: super(Requirement, self).__init__(requirement_string)
super(Requirement, self).__init__(requirement_string)
except packaging.requirements.InvalidRequirement as e:
raise RequirementParseError(str(e))
self.unsafe_name = self.name self.unsafe_name = self.name
project_name = safe_name(self.name) project_name = safe_name(self.name)
self.project_name, self.key = project_name, project_name.lower() self.project_name, self.key = project_name, project_name.lower()
@ -3170,7 +3169,7 @@ def _find_adapter(registry, ob):
def ensure_directory(path): def ensure_directory(path):
"""Ensure that the parent directory of `path` exists""" """Ensure that the parent directory of `path` exists"""
dirname = os.path.dirname(path) dirname = os.path.dirname(path)
py31compat.makedirs(dirname, exist_ok=True) os.makedirs(dirname, exist_ok=True)
def _bypass_ensure_directory(path): def _bypass_ensure_directory(path):
@ -3248,6 +3247,15 @@ def _initialize(g=globals()):
) )
class PkgResourcesDeprecationWarning(Warning):
"""
Base class for warning about deprecations in ``pkg_resources``
This class is not derived from ``DeprecationWarning``, and as such is
visible by default.
"""
@_call_aside @_call_aside
def _initialize_master_working_set(): def _initialize_master_working_set():
""" """
@ -3286,11 +3294,3 @@ def _initialize_master_working_set():
# match order # match order
list(map(working_set.add_entry, sys.path)) list(map(working_set.add_entry, sys.path))
globals().update(locals()) globals().update(locals())
class PkgResourcesDeprecationWarning(Warning):
"""
Base class for warning about deprecations in ``pkg_resources``
This class is not derived from ``DeprecationWarning``, and as such is
visible by default.
"""

View File

@ -1,23 +0,0 @@
import os
import errno
import sys
from pip._vendor import six
def _makedirs_31(path, exist_ok=False):
try:
os.makedirs(path)
except OSError as exc:
if not exist_ok or exc.errno != errno.EEXIST:
raise
# rely on compatibility behavior until mode considerations
# and exists_ok considerations are disentangled.
# See https://github.com/pypa/setuptools/pull/1083#issuecomment-315168663
needs_makedirs = (
six.PY2 or
(3, 4) <= sys.version_info < (3, 4, 1)
)
makedirs = _makedirs_31 if needs_makedirs else os.makedirs

View File

@ -16,7 +16,7 @@ rich==12.6.0
pygments==2.13.0 pygments==2.13.0
typing_extensions==4.4.0 typing_extensions==4.4.0
resolvelib==0.8.1 resolvelib==0.8.1
setuptools==44.0.0 setuptools==65.6.3
six==1.16.0 six==1.16.0
tenacity==8.1.0 tenacity==8.1.0
tomli==2.0.1 tomli==2.0.1

View File

@ -1,22 +1,35 @@
diff --git a/src/pip/_vendor/pkg_resources/__init__.py b/src/pip/_vendor/pkg_resources/__init__.py diff --git a/src/pip/_vendor/pkg_resources/__init__.py b/src/pip/_vendor/pkg_resources/__init__.py
index a457ff27e..4cd562cf9 100644 index d59226af9..3b9565893 100644
--- a/src/pip/_vendor/pkg_resources/__init__.py --- a/src/pip/_vendor/pkg_resources/__init__.py
+++ b/src/pip/_vendor/pkg_resources/__init__.py +++ b/src/pip/_vendor/pkg_resources/__init__.py
@@ -77,7 +77,7 @@ except ImportError: @@ -77,7 +77,7 @@
importlib_machinery = None join_continuation,
)
from . import py31compat
-from pkg_resources.extern import appdirs -from pkg_resources.extern import appdirs
+from pkg_resources.extern import platformdirs +from pkg_resources.extern import platformdirs
from pkg_resources.extern import packaging from pkg_resources.extern import packaging
__import__('pkg_resources.extern.packaging.version') __import__('pkg_resources.extern.packaging.version')
__import__('pkg_resources.extern.packaging.specifiers') __import__('pkg_resources.extern.packaging.specifiers')
@@ -1310,7 +1310,7 @@ def get_default_cache(): @@ -1321,7 +1321,7 @@ def get_default_cache():
""" """
return ( return (
os.environ.get('PYTHON_EGG_CACHE') os.environ.get('PYTHON_EGG_CACHE')
- or appdirs.user_cache_dir(appname='Python-Eggs') - or appdirs.user_cache_dir(appname='Python-Eggs')
+ or platformdirs.user_cache_dir(appname='Python-Eggs') + or platformdirs.user_cache_dir(appname='Python-Eggs')
) )
diff --git a/src/pip/_vendor/pkg_resources/__init__.py b/src/pip/_vendor/pkg_resources/__init__.py
index 3f2476a0c..8d5727d35 100644
--- a/src/pip/_vendor/pkg_resources/__init__.py
+++ b/src/pip/_vendor/pkg_resources/__init__.py
@@ -71,7 +71,7 @@
except ImportError:
importlib_machinery = None
-from pkg_resources.extern.jaraco.text import (
+from pip._internal.utils._jaraco_text import (
yield_lines,
drop_comment,
join_continuation,