bpo-38086: Sync importlib.metadata with importlib_metadata 0.21. (GH-15840)
https://gitlab.com/python-devs/importlib_metadata/-/tags/0.21
This commit is contained in:
parent
97d7906e30
commit
17499d8270
|
@ -172,10 +172,10 @@ Distribution requirements
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
To get the full set of requirements for a distribution, use the ``requires()``
|
To get the full set of requirements for a distribution, use the ``requires()``
|
||||||
function. Note that this returns an iterator::
|
function::
|
||||||
|
|
||||||
>>> list(requires('wheel')) # doctest: +SKIP
|
>>> requires('wheel') # doctest: +SKIP
|
||||||
["pytest (>=3.0.0) ; extra == 'test'"]
|
["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]
|
||||||
|
|
||||||
|
|
||||||
Distributions
|
Distributions
|
||||||
|
@ -224,23 +224,25 @@ The abstract class :py:class:`importlib.abc.MetaPathFinder` defines the
|
||||||
interface expected of finders by Python's import system.
|
interface expected of finders by Python's import system.
|
||||||
``importlib.metadata`` extends this protocol by looking for an optional
|
``importlib.metadata`` extends this protocol by looking for an optional
|
||||||
``find_distributions`` callable on the finders from
|
``find_distributions`` callable on the finders from
|
||||||
``sys.meta_path``. If the finder has this method, it must return
|
``sys.meta_path`` and presents this extended interface as the
|
||||||
an iterator over instances of the ``Distribution`` abstract class. This
|
``DistributionFinder`` abstract base class, which defines this abstract
|
||||||
method must have the signature::
|
method::
|
||||||
|
|
||||||
def find_distributions(name=None, path=None):
|
@abc.abstractmethod
|
||||||
|
def find_distributions(context=DistributionFinder.Context()):
|
||||||
"""Return an iterable of all Distribution instances capable of
|
"""Return an iterable of all Distribution instances capable of
|
||||||
loading the metadata for packages matching the name
|
loading the metadata for packages for the indicated ``context``.
|
||||||
(or all names if not supplied) along the paths in the list
|
|
||||||
of directories ``path`` (defaults to sys.path).
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
The ``DistributionFinder.Context`` object provides ``.path`` and ``.name``
|
||||||
|
properties indicating the path to search and names to match and may
|
||||||
|
supply other relevant context.
|
||||||
|
|
||||||
What this means in practice is that to support finding distribution package
|
What this means in practice is that to support finding distribution package
|
||||||
metadata in locations other than the file system, you should derive from
|
metadata in locations other than the file system, you should derive from
|
||||||
``Distribution`` and implement the ``load_metadata()`` method. This takes a
|
``Distribution`` and implement the ``load_metadata()`` method. Then from
|
||||||
single argument which is the name of the package whose metadata is being
|
your finder, return instances of this derived ``Distribution`` in the
|
||||||
found. This instance of the ``Distribution`` base abstract class is what your
|
``find_distributions()`` method.
|
||||||
finder's ``find_distributions()`` method should return.
|
|
||||||
|
|
||||||
|
|
||||||
.. _`entry point API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points
|
.. _`entry point API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points
|
||||||
|
|
|
@ -352,4 +352,4 @@ whatsnew/changelog,,::,error::BytesWarning
|
||||||
whatsnew/changelog,,::,default::BytesWarning
|
whatsnew/changelog,,::,default::BytesWarning
|
||||||
whatsnew/changelog,,::,default::DeprecationWarning
|
whatsnew/changelog,,::,default::DeprecationWarning
|
||||||
library/importlib.metadata,,:main,"EntryPoint(name='wheel', value='wheel.cli:main', group='console_scripts')"
|
library/importlib.metadata,,:main,"EntryPoint(name='wheel', value='wheel.cli:main', group='console_scripts')"
|
||||||
library/importlib.metadata,,`,of directories ``path`` (defaults to sys.path).
|
library/importlib.metadata,,`,loading the metadata for packages for the indicated ``context``.
|
||||||
|
|
|
|
@ -1370,21 +1370,19 @@ class PathFinder:
|
||||||
return spec.loader
|
return spec.loader
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def find_distributions(cls, name=None, path=None):
|
def find_distributions(self, context=None):
|
||||||
"""
|
"""
|
||||||
Find distributions.
|
Find distributions.
|
||||||
|
|
||||||
Return an iterable of all Distribution instances capable of
|
Return an iterable of all Distribution instances capable of
|
||||||
loading the metadata for packages matching the ``name``
|
loading the metadata for packages matching ``context.name``
|
||||||
(or all names if not supplied) along the paths in the list
|
(or all names if ``None`` indicated) along the paths in the list
|
||||||
of directories ``path`` (defaults to sys.path).
|
of directories ``context.path``.
|
||||||
"""
|
"""
|
||||||
import re
|
from importlib.metadata import PathDistribution, DistributionFinder
|
||||||
from importlib.metadata import PathDistribution
|
if context is None:
|
||||||
if path is None:
|
context = DistributionFinder.Context()
|
||||||
path = sys.path
|
found = self._search_paths(context.pattern, context.path)
|
||||||
pattern = '.*' if name is None else re.escape(name)
|
|
||||||
found = cls._search_paths(pattern, path)
|
|
||||||
return map(PathDistribution, found)
|
return map(PathDistribution, found)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -19,6 +19,7 @@ from itertools import starmap
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'Distribution',
|
'Distribution',
|
||||||
|
'DistributionFinder',
|
||||||
'PackageNotFoundError',
|
'PackageNotFoundError',
|
||||||
'distribution',
|
'distribution',
|
||||||
'distributions',
|
'distributions',
|
||||||
|
@ -158,7 +159,7 @@ class Distribution:
|
||||||
metadata cannot be found.
|
metadata cannot be found.
|
||||||
"""
|
"""
|
||||||
for resolver in cls._discover_resolvers():
|
for resolver in cls._discover_resolvers():
|
||||||
dists = resolver(name)
|
dists = resolver(DistributionFinder.Context(name=name))
|
||||||
dist = next(dists, None)
|
dist = next(dists, None)
|
||||||
if dist is not None:
|
if dist is not None:
|
||||||
return dist
|
return dist
|
||||||
|
@ -166,16 +167,33 @@ class Distribution:
|
||||||
raise PackageNotFoundError(name)
|
raise PackageNotFoundError(name)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def discover(cls):
|
def discover(cls, **kwargs):
|
||||||
"""Return an iterable of Distribution objects for all packages.
|
"""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 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(
|
return itertools.chain.from_iterable(
|
||||||
resolver()
|
resolver(context)
|
||||||
for resolver in cls._discover_resolvers()
|
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
|
@staticmethod
|
||||||
def _discover_resolvers():
|
def _discover_resolvers():
|
||||||
"""Search the meta_path for resolvers."""
|
"""Search the meta_path for resolvers."""
|
||||||
|
@ -215,7 +233,7 @@ class Distribution:
|
||||||
def files(self):
|
def files(self):
|
||||||
"""Files in this distribution.
|
"""Files in this distribution.
|
||||||
|
|
||||||
:return: Iterable of PackagePath for this distribution or None
|
:return: List of PackagePath for this distribution or None
|
||||||
|
|
||||||
Result is `None` if the metadata file that enumerates files
|
Result is `None` if the metadata file that enumerates files
|
||||||
(i.e. RECORD for dist-info or SOURCES.txt for egg-info) is
|
(i.e. RECORD for dist-info or SOURCES.txt for egg-info) is
|
||||||
|
@ -231,7 +249,7 @@ class Distribution:
|
||||||
result.dist = self
|
result.dist = self
|
||||||
return result
|
return result
|
||||||
|
|
||||||
return file_lines and starmap(make_file, csv.reader(file_lines))
|
return file_lines and list(starmap(make_file, csv.reader(file_lines)))
|
||||||
|
|
||||||
def _read_files_distinfo(self):
|
def _read_files_distinfo(self):
|
||||||
"""
|
"""
|
||||||
|
@ -251,7 +269,8 @@ class Distribution:
|
||||||
@property
|
@property
|
||||||
def requires(self):
|
def requires(self):
|
||||||
"""Generated requirements specified for this Distribution"""
|
"""Generated requirements specified for this Distribution"""
|
||||||
return self._read_dist_info_reqs() or self._read_egg_info_reqs()
|
reqs = self._read_dist_info_reqs() or self._read_egg_info_reqs()
|
||||||
|
return reqs and list(reqs)
|
||||||
|
|
||||||
def _read_dist_info_reqs(self):
|
def _read_dist_info_reqs(self):
|
||||||
return self.metadata.get_all('Requires-Dist')
|
return self.metadata.get_all('Requires-Dist')
|
||||||
|
@ -312,15 +331,35 @@ class DistributionFinder(MetaPathFinder):
|
||||||
A MetaPathFinder capable of discovering installed distributions.
|
A MetaPathFinder capable of discovering installed distributions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class Context:
|
||||||
|
|
||||||
|
name = None
|
||||||
|
"""
|
||||||
|
Specific name for which a distribution finder should match.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
vars(self).update(kwargs)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self):
|
||||||
|
"""
|
||||||
|
The path that a distribution finder should search.
|
||||||
|
"""
|
||||||
|
return vars(self).get('path', sys.path)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pattern(self):
|
||||||
|
return '.*' if self.name is None else re.escape(self.name)
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def find_distributions(self, name=None, path=None):
|
def find_distributions(self, context=Context()):
|
||||||
"""
|
"""
|
||||||
Find distributions.
|
Find distributions.
|
||||||
|
|
||||||
Return an iterable of all Distribution instances capable of
|
Return an iterable of all Distribution instances capable of
|
||||||
loading the metadata for packages matching the ``name``
|
loading the metadata for packages matching the ``context``,
|
||||||
(or all names if not supplied) along the paths in the list
|
a DistributionFinder.Context instance.
|
||||||
of directories ``path`` (defaults to sys.path).
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@ -352,12 +391,12 @@ def distribution(package):
|
||||||
return Distribution.from_name(package)
|
return Distribution.from_name(package)
|
||||||
|
|
||||||
|
|
||||||
def distributions():
|
def distributions(**kwargs):
|
||||||
"""Get all ``Distribution`` instances in the current environment.
|
"""Get all ``Distribution`` instances in the current environment.
|
||||||
|
|
||||||
:return: An iterable of ``Distribution`` instances.
|
:return: An iterable of ``Distribution`` instances.
|
||||||
"""
|
"""
|
||||||
return Distribution.discover()
|
return Distribution.discover(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
def metadata(package):
|
def metadata(package):
|
||||||
|
|
|
@ -162,6 +162,10 @@ class DiscoveryTests(fixtures.EggInfoPkg,
|
||||||
for dist in dists
|
for dist in dists
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_invalid_usage(self):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
list(distributions(context='something', name='else'))
|
||||||
|
|
||||||
|
|
||||||
class DirectoryTest(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase):
|
class DirectoryTest(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase):
|
||||||
def test_egg_info(self):
|
def test_egg_info(self):
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import re
|
import re
|
||||||
import textwrap
|
import textwrap
|
||||||
import unittest
|
import unittest
|
||||||
import itertools
|
|
||||||
|
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
|
|
||||||
|
@ -61,9 +60,7 @@ class APITests(
|
||||||
assert 'Topic :: Software Development :: Libraries' in classifiers
|
assert 'Topic :: Software Development :: Libraries' in classifiers
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _test_files(files_iter):
|
def _test_files(files):
|
||||||
assert isinstance(files_iter, Iterator), files_iter
|
|
||||||
files = list(files_iter)
|
|
||||||
root = files[0].root
|
root = files[0].root
|
||||||
for file in files:
|
for file in files:
|
||||||
assert file.root == root
|
assert file.root == root
|
||||||
|
@ -99,16 +96,18 @@ class APITests(
|
||||||
requirements = requires('egginfo-file')
|
requirements = requires('egginfo-file')
|
||||||
self.assertIsNone(requirements)
|
self.assertIsNone(requirements)
|
||||||
|
|
||||||
def test_requires(self):
|
def test_requires_egg_info(self):
|
||||||
deps = requires('egginfo-pkg')
|
deps = requires('egginfo-pkg')
|
||||||
|
assert len(deps) == 2
|
||||||
assert any(
|
assert any(
|
||||||
dep == 'wheel >= 1.0; python_version >= "2.7"'
|
dep == 'wheel >= 1.0; python_version >= "2.7"'
|
||||||
for dep in deps
|
for dep in deps
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_requires_dist_info(self):
|
def test_requires_dist_info(self):
|
||||||
deps = list(requires('distinfo-pkg'))
|
deps = requires('distinfo-pkg')
|
||||||
assert deps and all(deps)
|
assert len(deps) == 2
|
||||||
|
assert all(deps)
|
||||||
assert 'wheel >= 1.0' in deps
|
assert 'wheel >= 1.0' in deps
|
||||||
assert "pytest; extra == 'test'" in deps
|
assert "pytest; extra == 'test'" in deps
|
||||||
|
|
||||||
|
@ -143,11 +142,20 @@ class APITests(
|
||||||
|
|
||||||
class OffSysPathTests(fixtures.DistInfoPkgOffPath, unittest.TestCase):
|
class OffSysPathTests(fixtures.DistInfoPkgOffPath, unittest.TestCase):
|
||||||
def test_find_distributions_specified_path(self):
|
def test_find_distributions_specified_path(self):
|
||||||
dists = itertools.chain.from_iterable(
|
dists = Distribution.discover(path=[str(self.site_dir)])
|
||||||
resolver(path=[str(self.site_dir)])
|
|
||||||
for resolver in Distribution._discover_resolvers()
|
|
||||||
)
|
|
||||||
assert any(
|
assert any(
|
||||||
dist.metadata['Name'] == 'distinfo-pkg'
|
dist.metadata['Name'] == 'distinfo-pkg'
|
||||||
for dist in dists
|
for dist in dists
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_distribution_at_pathlib(self):
|
||||||
|
"""Demonstrate how to load metadata direct from a directory.
|
||||||
|
"""
|
||||||
|
dist_info_path = self.site_dir / 'distinfo_pkg-1.0.0.dist-info'
|
||||||
|
dist = Distribution.at(dist_info_path)
|
||||||
|
assert dist.version == '1.0.0'
|
||||||
|
|
||||||
|
def test_distribution_at_str(self):
|
||||||
|
dist_info_path = self.site_dir / 'distinfo_pkg-1.0.0.dist-info'
|
||||||
|
dist = Distribution.at(str(dist_info_path))
|
||||||
|
assert dist.version == '1.0.0'
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Update importlib.metadata with changes from `importlib_metadata 0.21 <https://gitlab.com/python-devs/importlib_metadata/blob/0.21/importlib_metadata/docs/changelog.rst>`_.
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue