Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vendor: bump importlib-metadata to 8.4.0 #6235

Merged
merged 3 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/6235.vendor.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Update importlib-metadata to 8.4.0
88 changes: 53 additions & 35 deletions pipenv/vendor/importlib_metadata/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,23 @@
import pipenv.vendor.zipp as zipp
import email
import types
import inspect
import pathlib
import operator
import textwrap
import warnings
import functools
import itertools
import posixpath
import collections

from . import _adapters, _meta
from .compat import py39
from . import _meta
from .compat import py39, py311
from ._collections import FreezableDefaultDict, Pair
from ._compat import (
NullFinder,
install,
)
from ._functools import method_cache, pass_none
from ._itertools import always_iterable, unique_everseen
from ._itertools import always_iterable, bucket, unique_everseen
from ._meta import PackageMetadata, SimplePath

from contextlib import suppress
Expand All @@ -40,6 +38,7 @@
'DistributionFinder',
'PackageMetadata',
'PackageNotFoundError',
'SimplePath',
'distribution',
'distributions',
'entry_points',
Expand Down Expand Up @@ -227,9 +226,26 @@ def matches(self, **params):
>>> ep.matches(attr='bong')
True
"""
self._disallow_dist(params)
attrs = (getattr(self, param) for param in params)
return all(map(operator.eq, params.values(), attrs))

@staticmethod
def _disallow_dist(params):
"""
Querying by dist is not allowed (dist objects are not comparable).
>>> EntryPoint(name='fan', value='fav', group='fag').matches(dist='foo')
Traceback (most recent call last):
...
ValueError: "dist" is not suitable for matching...
"""
if "dist" in params:
raise ValueError(
'"dist" is not suitable for matching. '
"Instead, use Distribution.entry_points.select() on a "
"located distribution."
)

def _key(self):
return self.name, self.value, self.group

Expand Down Expand Up @@ -334,27 +350,7 @@ def __repr__(self) -> str:
return f'<FileHash mode: {self.mode} value: {self.value}>'


class DeprecatedNonAbstract:
# Required until Python 3.14
def __new__(cls, *args, **kwargs):
all_names = {
name for subclass in inspect.getmro(cls) for name in vars(subclass)
}
abstract = {
name
for name in all_names
if getattr(getattr(cls, name), '__isabstractmethod__', False)
}
if abstract:
warnings.warn(
f"Unimplemented abstract methods {abstract}",
DeprecationWarning,
stacklevel=2,
)
return super().__new__(cls)


class Distribution(DeprecatedNonAbstract):
class Distribution(metaclass=abc.ABCMeta):
"""
An abstract Python distribution package.

Expand Down Expand Up @@ -393,6 +389,17 @@ def locate_file(self, path: str | os.PathLike[str]) -> SimplePath:
"""
Given a path to a file in this distribution, return a SimplePath
to it.

This method is used by callers of ``Distribution.files()`` to
locate files within the distribution. If it's possible for a
Distribution to represent files in the distribution as
``SimplePath`` objects, it should implement this method
to resolve such objects.

Some Distribution providers may elect not to resolve SimplePath
objects within the distribution by raising a
NotImplementedError, but consumers of such a Distribution would
be unable to invoke ``Distribution.files()``.
"""

@classmethod
Expand All @@ -409,7 +416,7 @@ def from_name(cls, name: str) -> Distribution:
if not name:
raise ValueError("A distribution name is required.")
try:
return next(iter(cls.discover(name=name)))
return next(iter(cls._prefer_valid(cls.discover(name=name))))
except StopIteration:
raise PackageNotFoundError(name)

Expand All @@ -433,6 +440,16 @@ def discover(
resolver(context) for resolver in cls._discover_resolvers()
)

@staticmethod
def _prefer_valid(dists: Iterable[Distribution]) -> Iterable[Distribution]:
"""
Prefer (move to the front) distributions that have metadata.

Ref python/importlib_resources#489.
"""
buckets = bucket(dists, lambda dist: bool(dist.metadata))
return itertools.chain(buckets[True], buckets[False])

@staticmethod
def at(path: str | os.PathLike[str]) -> Distribution:
"""Return a Distribution for the indicated metadata path.
Expand Down Expand Up @@ -461,6 +478,9 @@ def metadata(self) -> _meta.PackageMetadata:
Custom providers may provide the METADATA file or override this
property.
"""
# deferred for performance (python/cpython#109829)
from . import _adapters

opt_text = (
self.read_text('METADATA')
or self.read_text('PKG-INFO')
Expand Down Expand Up @@ -567,9 +587,8 @@ def _read_files_egginfo_installed(self):
return

paths = (
(subdir / name)
.resolve()
.relative_to(self.locate_file('').resolve())
py311.relative_fix((subdir / name).resolve())
.relative_to(self.locate_file('').resolve(), walk_up=True)
.as_posix()
for name in text.splitlines()
)
Expand Down Expand Up @@ -1086,11 +1105,10 @@ def _get_toplevel_name(name: PackagePath) -> str:
>>> _get_toplevel_name(PackagePath('foo.dist-info'))
'foo.dist-info'
"""
return _topmost(name) or (
# python/typeshed#10328
inspect.getmodulename(name) # type: ignore
or str(name)
)
# Defer import of inspect for performance (python/cpython#118761)
import inspect

return _topmost(name) or (inspect.getmodulename(name) or str(name))


def _top_level_inferred(dist):
Expand Down
23 changes: 8 additions & 15 deletions pipenv/vendor/importlib_metadata/_adapters.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,8 @@
import functools
import warnings
import re
import textwrap
import email.message

from ._text import FoldedCase
from ._compat import pypy_partial


# Do not remove prior to 2024-01-01 or Python 3.14
_warn = functools.partial(
warnings.warn,
"Implicit None on return values is deprecated and will raise KeyErrors.",
DeprecationWarning,
stacklevel=pypy_partial(2),
)


class Message(email.message.Message):
Expand Down Expand Up @@ -53,12 +41,17 @@ def __iter__(self):

def __getitem__(self, item):
"""
Warn users that a ``KeyError`` can be expected when a
missing key is supplied. Ref python/importlib_metadata#371.
Override parent behavior to typical dict behavior.

``email.message.Message`` will emit None values for missing
keys. Typical mappings, including this ``Message``, will raise
a key error for missing keys.

Ref python/importlib_metadata#371.
"""
res = super().__getitem__(item)
if res is None:
_warn()
raise KeyError(item)
return res

def _repair_headers(self):
Expand Down
98 changes: 98 additions & 0 deletions pipenv/vendor/importlib_metadata/_itertools.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from collections import defaultdict, deque
from itertools import filterfalse


Expand Down Expand Up @@ -71,3 +72,100 @@ def always_iterable(obj, base_type=(str, bytes)):
return iter(obj)
except TypeError:
return iter((obj,))


# Copied from more_itertools 10.3
class bucket:
"""Wrap *iterable* and return an object that buckets the iterable into
child iterables based on a *key* function.

>>> iterable = ['a1', 'b1', 'c1', 'a2', 'b2', 'c2', 'b3']
>>> s = bucket(iterable, key=lambda x: x[0]) # Bucket by 1st character
>>> sorted(list(s)) # Get the keys
['a', 'b', 'c']
>>> a_iterable = s['a']
>>> next(a_iterable)
'a1'
>>> next(a_iterable)
'a2'
>>> list(s['b'])
['b1', 'b2', 'b3']

The original iterable will be advanced and its items will be cached until
they are used by the child iterables. This may require significant storage.

By default, attempting to select a bucket to which no items belong will
exhaust the iterable and cache all values.
If you specify a *validator* function, selected buckets will instead be
checked against it.

>>> from itertools import count
>>> it = count(1, 2) # Infinite sequence of odd numbers
>>> key = lambda x: x % 10 # Bucket by last digit
>>> validator = lambda x: x in {1, 3, 5, 7, 9} # Odd digits only
>>> s = bucket(it, key=key, validator=validator)
>>> 2 in s
False
>>> list(s[2])
[]

"""

def __init__(self, iterable, key, validator=None):
self._it = iter(iterable)
self._key = key
self._cache = defaultdict(deque)
self._validator = validator or (lambda x: True)

def __contains__(self, value):
if not self._validator(value):
return False

try:
item = next(self[value])
except StopIteration:
return False
else:
self._cache[value].appendleft(item)

return True

def _get_values(self, value):
"""
Helper to yield items from the parent iterator that match *value*.
Items that don't match are stored in the local cache as they
are encountered.
"""
while True:
# If we've cached some items that match the target value, emit
# the first one and evict it from the cache.
if self._cache[value]:
yield self._cache[value].popleft()
# Otherwise we need to advance the parent iterator to search for
# a matching item, caching the rest.
else:
while True:
try:
item = next(self._it)
except StopIteration:
return
item_value = self._key(item)
if item_value == value:
yield item
break
elif self._validator(item_value):
self._cache[item_value].append(item)

def __iter__(self):
for item in self._it:
item_value = self._key(item)
if self._validator(item_value):
self._cache[item_value].append(item)

yield from self._cache.keys()

def __getitem__(self, value):
if not self._validator(value):
return iter(())

return self._get_values(value)
22 changes: 22 additions & 0 deletions pipenv/vendor/importlib_metadata/compat/py311.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import os
import pathlib
import sys
import types


def wrap(path): # pragma: no cover
"""
Workaround for https://github.com/python/cpython/issues/84538
to add backward compatibility for walk_up=True.
An example affected package is dask-labextension, which uses
jupyter-packaging to install JupyterLab javascript files outside
of site-packages.
"""

def relative_to(root, *, walk_up=False):
return pathlib.Path(os.path.relpath(path, root))

return types.SimpleNamespace(relative_to=relative_to)


relative_fix = wrap if sys.version_info < (3, 12) else lambda x: x
4 changes: 2 additions & 2 deletions pipenv/vendor/vendor.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ click-didyoumean==0.3.1
click==8.1.7
colorama==0.4.6
dparse==0.6.3
importlib-metadata==7.1.0
importlib-metadata==8.4.0
zipp==3.18.1
packaging==24.0
pexpect==4.9.0
pipdeptree==2.18.1
zipp==3.18.1
plette==2.1.0
ptyprocess==0.7.0
python-dotenv==1.0.1
Expand Down
Loading