summaryrefslogtreecommitdiffhomepage
path: root/libs/importlib_metadata
diff options
context:
space:
mode:
authormorpheus65535 <[email protected]>2024-03-03 12:15:23 -0500
committerGitHub <[email protected]>2024-03-03 12:15:23 -0500
commit03afeb347075381bcb7fd6036295c9fa4a90d2dc (patch)
tree7c5d72c973d2c8e4ade57391a1c9ad5e94903a46 /libs/importlib_metadata
parent9ae684240b5bdd40a870d8122f0e380f8d03a187 (diff)
downloadbazarr-03afeb347075381bcb7fd6036295c9fa4a90d2dc.tar.gz
bazarr-03afeb347075381bcb7fd6036295c9fa4a90d2dc.zip
Updated multiple Python modules (now in libs and custom_libs directories) and React libraries
Diffstat (limited to 'libs/importlib_metadata')
-rw-r--r--libs/importlib_metadata/__init__.py320
-rw-r--r--libs/importlib_metadata/_adapters.py22
-rw-r--r--libs/importlib_metadata/_compat.py19
-rw-r--r--libs/importlib_metadata/_meta.py40
-rw-r--r--libs/importlib_metadata/_py39compat.py21
-rw-r--r--libs/importlib_metadata/diagnose.py21
6 files changed, 304 insertions, 139 deletions
diff --git a/libs/importlib_metadata/__init__.py b/libs/importlib_metadata/__init__.py
index 304b6d484..0ce535a1f 100644
--- a/libs/importlib_metadata/__init__.py
+++ b/libs/importlib_metadata/__init__.py
@@ -1,10 +1,15 @@
+from __future__ import annotations
+
import os
import re
import abc
import csv
import sys
+import json
import zipp
import email
+import types
+import inspect
import pathlib
import operator
import textwrap
@@ -19,7 +24,6 @@ from ._collections import FreezableDefaultDict, Pair
from ._compat import (
NullFinder,
install,
- pypy_partial,
)
from ._functools import method_cache, pass_none
from ._itertools import always_iterable, unique_everseen
@@ -29,8 +33,7 @@ from contextlib import suppress
from importlib import import_module
from importlib.abc import MetaPathFinder
from itertools import starmap
-from typing import List, Mapping, Optional
-
+from typing import Iterable, List, Mapping, Optional, Set, cast
__all__ = [
'Distribution',
@@ -51,11 +54,11 @@ __all__ = [
class PackageNotFoundError(ModuleNotFoundError):
"""The package was not found."""
- def __str__(self):
+ def __str__(self) -> str:
return f"No package metadata was found for {self.name}"
@property
- def name(self):
+ def name(self) -> str: # type: ignore[override]
(name,) = self.args
return name
@@ -121,37 +124,11 @@ class Sectioned:
yield Pair(name, value)
@staticmethod
- def valid(line):
+ def valid(line: str):
return line and not line.startswith('#')
-class DeprecatedTuple:
- """
- Provide subscript item access for backward compatibility.
-
- >>> recwarn = getfixture('recwarn')
- >>> ep = EntryPoint(name='name', value='value', group='group')
- >>> ep[:]
- ('name', 'value', 'group')
- >>> ep[0]
- 'name'
- >>> len(recwarn)
- 1
- """
-
- _warn = functools.partial(
- warnings.warn,
- "EntryPoint tuple interface is deprecated. Access members by name.",
- DeprecationWarning,
- stacklevel=pypy_partial(2),
- )
-
- def __getitem__(self, item):
- self._warn()
- return self._key()[item]
-
-
-class EntryPoint(DeprecatedTuple):
+class EntryPoint:
"""An entry point as defined by Python packaging conventions.
See `the packaging docs on entry points
@@ -195,7 +172,7 @@ class EntryPoint(DeprecatedTuple):
dist: Optional['Distribution'] = None
- def __init__(self, name, value, group):
+ def __init__(self, name: str, value: str, group: str) -> None:
vars(self).update(name=name, value=value, group=group)
def load(self):
@@ -209,18 +186,21 @@ class EntryPoint(DeprecatedTuple):
return functools.reduce(getattr, attrs, module)
@property
- def module(self):
+ def module(self) -> str:
match = self.pattern.match(self.value)
+ assert match is not None
return match.group('module')
@property
- def attr(self):
+ def attr(self) -> str:
match = self.pattern.match(self.value)
+ assert match is not None
return match.group('attr')
@property
- def extras(self):
+ def extras(self) -> List[str]:
match = self.pattern.match(self.value)
+ assert match is not None
return re.findall(r'\w+', match.group('extras') or '')
def _for(self, dist):
@@ -268,7 +248,7 @@ class EntryPoint(DeprecatedTuple):
f'group={self.group!r})'
)
- def __hash__(self):
+ def __hash__(self) -> int:
return hash(self._key())
@@ -279,7 +259,7 @@ class EntryPoints(tuple):
__slots__ = ()
- def __getitem__(self, name): # -> EntryPoint:
+ def __getitem__(self, name: str) -> EntryPoint: # type: ignore[override]
"""
Get the EntryPoint in self matching name.
"""
@@ -288,23 +268,29 @@ class EntryPoints(tuple):
except StopIteration:
raise KeyError(name)
+ def __repr__(self):
+ """
+ Repr with classname and tuple constructor to
+ signal that we deviate from regular tuple behavior.
+ """
+ return '%s(%r)' % (self.__class__.__name__, tuple(self))
+
def select(self, **params):
"""
Select entry points from self that match the
given parameters (typically group and/or name).
"""
- candidates = (_py39compat.ep_matches(ep, **params) for ep in self)
- return EntryPoints(ep for ep, predicate in candidates if predicate)
+ return EntryPoints(ep for ep in self if _py39compat.ep_matches(ep, **params))
@property
- def names(self):
+ def names(self) -> Set[str]:
"""
Return the set of all names of all entry points.
"""
return {ep.name for ep in self}
@property
- def groups(self):
+ def groups(self) -> Set[str]:
"""
Return the set of all groups of all entry points.
"""
@@ -325,47 +311,91 @@ class EntryPoints(tuple):
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()
+ hash: Optional["FileHash"]
+ size: int
+ dist: "Distribution"
+
+ def read_text(self, encoding: str = 'utf-8') -> str: # type: ignore[override]
+ return self.locate().read_text(encoding=encoding)
- def read_binary(self):
- with self.locate().open('rb') as stream:
- return stream.read()
+ def read_binary(self) -> bytes:
+ return self.locate().read_bytes()
- def locate(self):
+ def locate(self) -> SimplePath:
"""Return a path-like object for this path"""
return self.dist.locate_file(self)
class FileHash:
- def __init__(self, spec):
+ def __init__(self, spec: str) -> None:
self.mode, _, self.value = spec.partition('=')
- def __repr__(self):
+ def __repr__(self) -> str:
return f'<FileHash mode: {self.mode} value: {self.value}>'
-class Distribution:
- """A Python distribution package."""
+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):
+ """
+ An abstract Python distribution package.
+
+ Custom providers may derive from this class and define
+ the abstract methods to provide a concrete implementation
+ for their environment.
+ """
@abc.abstractmethod
- def read_text(self, filename):
+ def read_text(self, filename) -> Optional[str]:
"""Attempt to load metadata file given by the name.
+ Python distribution metadata is organized by blobs of text
+ typically represented as "files" in the metadata directory
+ (e.g. package-1.0.dist-info). These files include things
+ like:
+
+ - METADATA: The distribution metadata including fields
+ like Name and Version and Description.
+ - entry_points.txt: A series of entry points defined by
+ the Setuptools spec in an ini format with sections
+ representing the groups.
+ - RECORD: A record of files as installed by a typical
+ installer.
+
+ A package may provide any set of files, including those
+ not listed here or none at all.
+
: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):
+ def locate_file(self, path: str | os.PathLike[str]) -> SimplePath:
"""
- Given a path to a file in this distribution, return a path
+ Given a path to a file in this distribution, return a SimplePath
to it.
"""
@classmethod
- def from_name(cls, name: str):
+ def from_name(cls, name: str) -> "Distribution":
"""Return the Distribution for the given package name.
:param name: The name of the distribution package to search for.
@@ -378,21 +408,23 @@ class Distribution:
if not name:
raise ValueError("A distribution name is required.")
try:
- return next(cls.discover(name=name))
+ return next(iter(cls.discover(name=name)))
except StopIteration:
raise PackageNotFoundError(name)
@classmethod
- def discover(cls, **kwargs):
+ def discover(
+ cls, *, context: Optional['DistributionFinder.Context'] = None, **kwargs
+ ) -> Iterable["Distribution"]:
"""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.
+ :return: Iterable of Distribution objects for packages matching
+ the context.
"""
- context = kwargs.pop('context', None)
if context and kwargs:
raise ValueError("cannot accept context and kwargs")
context = context or DistributionFinder.Context(**kwargs)
@@ -401,8 +433,8 @@ class Distribution:
)
@staticmethod
- def at(path):
- """Return a Distribution for the indicated metadata path
+ def at(path: str | os.PathLike[str]) -> "Distribution":
+ """Return a Distribution for the indicated metadata path.
:param path: a string or path-like object
:return: a concrete Distribution instance for the path
@@ -411,7 +443,7 @@ class Distribution:
@staticmethod
def _discover_resolvers():
- """Search the meta_path for resolvers."""
+ """Search the meta_path for resolvers (MetadataPathFinders)."""
declared = (
getattr(finder, 'find_distributions', None) for finder in sys.meta_path
)
@@ -424,7 +456,7 @@ class Distribution:
The returned object will have keys that name the various bits of
metadata. See PEP 566 for details.
"""
- text = (
+ opt_text = (
self.read_text('METADATA')
or self.read_text('PKG-INFO')
# This last clause is here to support old egg-info files. Its
@@ -432,10 +464,11 @@ class Distribution:
# (which points to the egg-info file) attribute unchanged.
or self.read_text('')
)
+ text = cast(str, opt_text)
return _adapters.Message(email.message_from_string(text))
@property
- def name(self):
+ def name(self) -> str:
"""Return the 'Name' metadata for the distribution package."""
return self.metadata['Name']
@@ -445,23 +478,23 @@ class Distribution:
return Prepared.normalize(self.name)
@property
- def version(self):
+ def version(self) -> str:
"""Return the 'Version' metadata for the distribution package."""
return self.metadata['Version']
@property
- def entry_points(self):
+ def entry_points(self) -> EntryPoints:
return EntryPoints._from_text_for(self.read_text('entry_points.txt'), self)
@property
- def files(self):
+ def files(self) -> Optional[List[PackagePath]]:
"""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.
+ (i.e. RECORD for dist-info, or installed-files.txt or
+ SOURCES.txt for egg-info) is missing.
Result may be empty if the metadata exists but is empty.
"""
@@ -474,9 +507,19 @@ class Distribution:
@pass_none
def make_files(lines):
- return list(starmap(make_file, csv.reader(lines)))
+ return starmap(make_file, csv.reader(lines))
- return make_files(self._read_files_distinfo() or self._read_files_egginfo())
+ @pass_none
+ def skip_missing_files(package_paths):
+ return list(filter(lambda path: path.locate().exists(), package_paths))
+
+ return skip_missing_files(
+ make_files(
+ self._read_files_distinfo()
+ or self._read_files_egginfo_installed()
+ or self._read_files_egginfo_sources()
+ )
+ )
def _read_files_distinfo(self):
"""
@@ -485,16 +528,51 @@ class Distribution:
text = self.read_text('RECORD')
return text and text.splitlines()
- def _read_files_egginfo(self):
+ def _read_files_egginfo_installed(self):
+ """
+ Read installed-files.txt and return lines in a similar
+ CSV-parsable format as RECORD: each file must be placed
+ relative to the site-packages directory and must also be
+ quoted (since file names can contain literal commas).
+
+ This file is written when the package is installed by pip,
+ but it might not be written for other installation methods.
+ Assume the file is accurate if it exists.
+ """
+ text = self.read_text('installed-files.txt')
+ # Prepend the .egg-info/ subdir to the lines in this file.
+ # But this subdir is only available from PathDistribution's
+ # self._path.
+ subdir = getattr(self, '_path', None)
+ if not text or not subdir:
+ return
+
+ paths = (
+ (subdir / name)
+ .resolve()
+ .relative_to(self.locate_file('').resolve())
+ .as_posix()
+ for name in text.splitlines()
+ )
+ return map('"{}"'.format, paths)
+
+ def _read_files_egginfo_sources(self):
"""
- SOURCES.txt might contain literal commas, so wrap each line
- in quotes.
+ Read SOURCES.txt and return lines in a similar CSV-parsable
+ format as RECORD: each file name must be quoted (since it
+ might contain literal commas).
+
+ Note that SOURCES.txt is not a reliable source for what
+ files are installed by a package. This file is generated
+ for a source archive, and the files that are present
+ there (e.g. setup.py) may not correctly reflect the files
+ that are present after the package has been installed.
"""
text = self.read_text('SOURCES.txt')
return text and map('"{}"'.format, text.splitlines())
@property
- def requires(self):
+ def requires(self) -> Optional[List[str]]:
"""Generated requirements specified for this Distribution"""
reqs = self._read_dist_info_reqs() or self._read_egg_info_reqs()
return reqs and list(reqs)
@@ -545,6 +623,16 @@ class Distribution:
space = url_req_space(section.value)
yield section.value + space + quoted_marker(section.name)
+ @property
+ def origin(self):
+ return self._load_json('direct_url.json')
+
+ def _load_json(self, filename):
+ return pass_none(json.loads)(
+ self.read_text(filename),
+ object_hook=lambda data: types.SimpleNamespace(**data),
+ )
+
class DistributionFinder(MetaPathFinder):
"""
@@ -573,7 +661,7 @@ class DistributionFinder(MetaPathFinder):
vars(self).update(kwargs)
@property
- def path(self):
+ def path(self) -> List[str]:
"""
The sequence of directory path that a distribution finder
should search.
@@ -584,7 +672,7 @@ class DistributionFinder(MetaPathFinder):
return vars(self).get('path', sys.path)
@abc.abstractmethod
- def find_distributions(self, context=Context()):
+ def find_distributions(self, context=Context()) -> Iterable[Distribution]:
"""
Find distributions.
@@ -719,7 +807,9 @@ class MetadataPathFinder(NullFinder, DistributionFinder):
of Python that do not have a PathFinder find_distributions().
"""
- def find_distributions(self, context=DistributionFinder.Context()):
+ def find_distributions(
+ self, context=DistributionFinder.Context()
+ ) -> Iterable["PathDistribution"]:
"""
Find distributions.
@@ -739,19 +829,19 @@ class MetadataPathFinder(NullFinder, DistributionFinder):
path.search(prepared) for path in map(FastPath, paths)
)
- def invalidate_caches(cls):
+ def invalidate_caches(cls) -> None:
FastPath.__new__.cache_clear()
class PathDistribution(Distribution):
- def __init__(self, path: SimplePath):
+ def __init__(self, path: SimplePath) -> None:
"""Construct a distribution.
:param path: SimplePath indicating the metadata directory.
"""
self._path = path
- def read_text(self, filename):
+ def read_text(self, filename: str | os.PathLike[str]) -> Optional[str]:
with suppress(
FileNotFoundError,
IsADirectoryError,
@@ -761,9 +851,11 @@ class PathDistribution(Distribution):
):
return self._path.joinpath(filename).read_text(encoding='utf-8')
+ return None
+
read_text.__doc__ = Distribution.read_text.__doc__
- def locate_file(self, path):
+ def locate_file(self, path: str | os.PathLike[str]) -> SimplePath:
return self._path.parent / path
@property
@@ -796,7 +888,7 @@ class PathDistribution(Distribution):
return name
-def distribution(distribution_name):
+def distribution(distribution_name: str) -> Distribution:
"""Get the ``Distribution`` instance for the named package.
:param distribution_name: The name of the distribution package as a string.
@@ -805,7 +897,7 @@ def distribution(distribution_name):
return Distribution.from_name(distribution_name)
-def distributions(**kwargs):
+def distributions(**kwargs) -> Iterable[Distribution]:
"""Get all ``Distribution`` instances in the current environment.
:return: An iterable of ``Distribution`` instances.
@@ -813,7 +905,7 @@ def distributions(**kwargs):
return Distribution.discover(**kwargs)
-def metadata(distribution_name) -> _meta.PackageMetadata:
+def metadata(distribution_name: str) -> _meta.PackageMetadata:
"""Get the metadata for the named package.
:param distribution_name: The name of the distribution package to query.
@@ -822,7 +914,7 @@ def metadata(distribution_name) -> _meta.PackageMetadata:
return Distribution.from_name(distribution_name).metadata
-def version(distribution_name):
+def version(distribution_name: str) -> str:
"""Get the version string for the named package.
:param distribution_name: The name of the distribution package to query.
@@ -856,7 +948,7 @@ def entry_points(**params) -> EntryPoints:
return EntryPoints(eps).select(**params)
-def files(distribution_name):
+def files(distribution_name: str) -> Optional[List[PackagePath]]:
"""Return a list of files for the named package.
:param distribution_name: The name of the distribution package to query.
@@ -865,11 +957,11 @@ def files(distribution_name):
return distribution(distribution_name).files
-def requires(distribution_name):
+def requires(distribution_name: str) -> Optional[List[str]]:
"""
Return a list of requirements for the named package.
- :return: An iterator of requirements, suitable for
+ :return: An iterable of requirements, suitable for
packaging.requirement.Requirement.
"""
return distribution(distribution_name).requires
@@ -896,9 +988,43 @@ def _top_level_declared(dist):
return (dist.read_text('top_level.txt') or '').split()
+def _topmost(name: PackagePath) -> Optional[str]:
+ """
+ Return the top-most parent as long as there is a parent.
+ """
+ top, *rest = name.parts
+ return top if rest else None
+
+
+def _get_toplevel_name(name: PackagePath) -> str:
+ """
+ Infer a possibly importable module name from a name presumed on
+ sys.path.
+
+ >>> _get_toplevel_name(PackagePath('foo.py'))
+ 'foo'
+ >>> _get_toplevel_name(PackagePath('foo'))
+ 'foo'
+ >>> _get_toplevel_name(PackagePath('foo.pyc'))
+ 'foo'
+ >>> _get_toplevel_name(PackagePath('foo/__init__.py'))
+ 'foo'
+ >>> _get_toplevel_name(PackagePath('foo.pth'))
+ 'foo.pth'
+ >>> _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)
+ )
+
+
def _top_level_inferred(dist):
- return {
- f.parts[0] if len(f.parts) > 1 else f.with_suffix('').name
- for f in always_iterable(dist.files)
- if f.suffix == ".py"
- }
+ opt_names = set(map(_get_toplevel_name, always_iterable(dist.files)))
+
+ def importable_name(name):
+ return '.' not in name
+
+ return filter(importable_name, opt_names)
diff --git a/libs/importlib_metadata/_adapters.py b/libs/importlib_metadata/_adapters.py
index aa460d3ed..120e43a04 100644
--- a/libs/importlib_metadata/_adapters.py
+++ b/libs/importlib_metadata/_adapters.py
@@ -1,8 +1,20 @@
+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):
@@ -39,6 +51,16 @@ class Message(email.message.Message):
def __iter__(self):
return super().__iter__()
+ def __getitem__(self, item):
+ """
+ Warn users that a ``KeyError`` can be expected when a
+ missing key is supplied. Ref python/importlib_metadata#371.
+ """
+ res = super().__getitem__(item)
+ if res is None:
+ _warn()
+ return res
+
def _repair_headers(self):
def redent(value):
"Correct for RFC822 indentation"
diff --git a/libs/importlib_metadata/_compat.py b/libs/importlib_metadata/_compat.py
index 3d78566ea..df312b1cb 100644
--- a/libs/importlib_metadata/_compat.py
+++ b/libs/importlib_metadata/_compat.py
@@ -2,14 +2,7 @@ import sys
import platform
-__all__ = ['install', 'NullFinder', 'Protocol']
-
-
-try:
- from typing import Protocol
-except ImportError: # pragma: no cover
- # Python 3.7 compatibility
- from typing_extensions import Protocol # type: ignore
+__all__ = ['install', 'NullFinder']
def install(cls):
@@ -45,7 +38,7 @@ def disable_stdlib_finder():
class NullFinder:
"""
- A "Finder" (aka "MetaClassFinder") that never finds any modules,
+ A "Finder" (aka "MetaPathFinder") that never finds any modules,
but may find distributions.
"""
@@ -53,14 +46,6 @@ class NullFinder:
def find_spec(*args, **kwargs):
return None
- # In Python 2, the import system requires finders
- # to have a find_module() method, but this usage
- # is deprecated in Python 3 in favor of find_spec().
- # For the purposes of this finder (i.e. being present
- # on sys.meta_path but having no other import
- # system functionality), the two methods are identical.
- find_module = find_spec
-
def pypy_partial(val):
"""
diff --git a/libs/importlib_metadata/_meta.py b/libs/importlib_metadata/_meta.py
index 37ee43e6e..1342d839b 100644
--- a/libs/importlib_metadata/_meta.py
+++ b/libs/importlib_metadata/_meta.py
@@ -1,5 +1,8 @@
-from ._compat import Protocol
-from typing import Any, Dict, Iterator, List, TypeVar, Union
+from __future__ import annotations
+
+import os
+from typing import Protocol
+from typing import Any, Dict, Iterator, List, Optional, TypeVar, Union, overload
_T = TypeVar("_T")
@@ -18,7 +21,21 @@ class PackageMetadata(Protocol):
def __iter__(self) -> Iterator[str]:
... # pragma: no cover
- def get_all(self, name: str, failobj: _T = ...) -> Union[List[Any], _T]:
+ @overload
+ def get(self, name: str, failobj: None = None) -> Optional[str]:
+ ... # pragma: no cover
+
+ @overload
+ def get(self, name: str, failobj: _T) -> Union[str, _T]:
+ ... # pragma: no cover
+
+ # overload per python/importlib_metadata#435
+ @overload
+ def get_all(self, name: str, failobj: None = None) -> Optional[List[Any]]:
+ ... # pragma: no cover
+
+ @overload
+ def get_all(self, name: str, failobj: _T) -> Union[List[Any], _T]:
"""
Return all values associated with a possibly multi-valued key.
"""
@@ -32,17 +49,24 @@ class PackageMetadata(Protocol):
class SimplePath(Protocol):
"""
- A minimal subset of pathlib.Path required by PathDistribution.
+ A minimal subset of pathlib.Path required by Distribution.
"""
- def joinpath(self) -> 'SimplePath':
+ def joinpath(self, other: Union[str, os.PathLike[str]]) -> SimplePath:
+ ... # pragma: no cover
+
+ def __truediv__(self, other: Union[str, os.PathLike[str]]) -> SimplePath:
+ ... # pragma: no cover
+
+ @property
+ def parent(self) -> SimplePath:
... # pragma: no cover
- def __truediv__(self) -> 'SimplePath':
+ def read_text(self, encoding=None) -> str:
... # pragma: no cover
- def parent(self) -> 'SimplePath':
+ def read_bytes(self) -> bytes:
... # pragma: no cover
- def read_text(self) -> str:
+ def exists(self) -> bool:
... # pragma: no cover
diff --git a/libs/importlib_metadata/_py39compat.py b/libs/importlib_metadata/_py39compat.py
index cf9cc1242..cde4558fb 100644
--- a/libs/importlib_metadata/_py39compat.py
+++ b/libs/importlib_metadata/_py39compat.py
@@ -1,7 +1,7 @@
"""
Compatibility layer with Python 3.8/3.9
"""
-from typing import TYPE_CHECKING, Any, Optional, Tuple
+from typing import TYPE_CHECKING, Any, Optional
if TYPE_CHECKING: # pragma: no cover
# Prevent circular imports on runtime.
@@ -22,27 +22,14 @@ def normalized_name(dist: Distribution) -> Optional[str]:
return Prepared.normalize(getattr(dist, "name", None) or dist.metadata['Name'])
-def ep_matches(ep: EntryPoint, **params) -> Tuple[EntryPoint, bool]:
+def ep_matches(ep: EntryPoint, **params) -> bool:
"""
Workaround for ``EntryPoint`` objects without the ``matches`` method.
- For the sake of convenience, a tuple is returned containing not only the
- boolean value corresponding to the predicate evalutation, but also a compatible
- ``EntryPoint`` object that can be safely used at a later stage.
-
- For example, the following sequences of expressions should be compatible:
-
- # Sequence 1: using the compatibility layer
- candidates = (_py39compat.ep_matches(ep, **params) for ep in entry_points)
- [ep for ep, predicate in candidates if predicate]
-
- # Sequence 2: using Python 3.9+
- [ep for ep in entry_points if ep.matches(**params)]
"""
try:
- return ep, ep.matches(**params)
+ return ep.matches(**params)
except AttributeError:
from . import EntryPoint # -> delay to prevent circular imports.
# Reconstruct the EntryPoint object to make sure it is compatible.
- _ep = EntryPoint(ep.name, ep.value, ep.group)
- return _ep, _ep.matches(**params)
+ return EntryPoint(ep.name, ep.value, ep.group).matches(**params)
diff --git a/libs/importlib_metadata/diagnose.py b/libs/importlib_metadata/diagnose.py
new file mode 100644
index 000000000..e405471ac
--- /dev/null
+++ b/libs/importlib_metadata/diagnose.py
@@ -0,0 +1,21 @@
+import sys
+
+from . import Distribution
+
+
+def inspect(path):
+ print("Inspecting", path)
+ dists = list(Distribution.discover(path=[path]))
+ if not dists:
+ return
+ print("Found", len(dists), "packages:", end=' ')
+ print(', '.join(dist.name for dist in dists))
+
+
+def run():
+ for path in sys.path:
+ inspect(path)
+
+
+if __name__ == '__main__':
+ run()