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()``
|
||||
function. Note that this returns an iterator::
|
||||
function::
|
||||
|
||||
>>> list(requires('wheel')) # doctest: +SKIP
|
||||
["pytest (>=3.0.0) ; extra == 'test'"]
|
||||
>>> requires('wheel') # doctest: +SKIP
|
||||
["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]
|
||||
|
||||
|
||||
Distributions
|
||||
|
@ -224,23 +224,25 @@ The abstract class :py:class:`importlib.abc.MetaPathFinder` defines the
|
|||
interface expected of finders by Python's import system.
|
||||
``importlib.metadata`` extends this protocol by looking for an optional
|
||||
``find_distributions`` callable on the finders from
|
||||
``sys.meta_path``. If the finder has this method, it must return
|
||||
an iterator over instances of the ``Distribution`` abstract class. This
|
||||
method must have the signature::
|
||||
``sys.meta_path`` and presents this extended interface as the
|
||||
``DistributionFinder`` abstract base class, which defines this abstract
|
||||
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
|
||||
loading the metadata for packages matching the name
|
||||
(or all names if not supplied) along the paths in the list
|
||||
of directories ``path`` (defaults to sys.path).
|
||||
loading the metadata for packages for the indicated ``context``.
|
||||
"""
|
||||
|
||||
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
|
||||
metadata in locations other than the file system, you should derive from
|
||||
``Distribution`` and implement the ``load_metadata()`` method. This takes a
|
||||
single argument which is the name of the package whose metadata is being
|
||||
found. This instance of the ``Distribution`` base abstract class is what your
|
||||
finder's ``find_distributions()`` method should return.
|
||||
``Distribution`` and implement the ``load_metadata()`` method. Then from
|
||||
your finder, return instances of this derived ``Distribution`` in the
|
||||
``find_distributions()`` method.
|
||||
|
||||
|
||||
.. _`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::DeprecationWarning
|
||||
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
|
||||
|
||||
@classmethod
|
||||
def find_distributions(cls, name=None, path=None):
|
||||
def find_distributions(self, context=None):
|
||||
"""
|
||||
Find distributions.
|
||||
|
||||
Return an iterable of all Distribution instances capable of
|
||||
loading the metadata for packages matching the ``name``
|
||||
(or all names if not supplied) along the paths in the list
|
||||
of directories ``path`` (defaults to sys.path).
|
||||
loading the metadata for packages matching ``context.name``
|
||||
(or all names if ``None`` indicated) along the paths in the list
|
||||
of directories ``context.path``.
|
||||
"""
|
||||
import re
|
||||
from importlib.metadata import PathDistribution
|
||||
if path is None:
|
||||
path = sys.path
|
||||
pattern = '.*' if name is None else re.escape(name)
|
||||
found = cls._search_paths(pattern, path)
|
||||
from importlib.metadata import PathDistribution, DistributionFinder
|
||||
if context is None:
|
||||
context = DistributionFinder.Context()
|
||||
found = self._search_paths(context.pattern, context.path)
|
||||
return map(PathDistribution, found)
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -19,6 +19,7 @@ from itertools import starmap
|
|||
|
||||
__all__ = [
|
||||
'Distribution',
|
||||
'DistributionFinder',
|
||||
'PackageNotFoundError',
|
||||
'distribution',
|
||||
'distributions',
|
||||
|
@ -158,7 +159,7 @@ class Distribution:
|
|||
metadata cannot be found.
|
||||
"""
|
||||
for resolver in cls._discover_resolvers():
|
||||
dists = resolver(name)
|
||||
dists = resolver(DistributionFinder.Context(name=name))
|
||||
dist = next(dists, None)
|
||||
if dist is not None:
|
||||
return dist
|
||||
|
@ -166,16 +167,33 @@ class Distribution:
|
|||
raise PackageNotFoundError(name)
|
||||
|
||||
@classmethod
|
||||
def discover(cls):
|
||||
def discover(cls, **kwargs):
|
||||
"""Return an iterable of Distribution objects for all packages.
|
||||
|
||||
Pass a ``context`` or pass keyword arguments for constructing
|
||||
a context.
|
||||
|
||||
:context: A ``DistributionFinder.Context`` object.
|
||||
:return: Iterable of Distribution objects for all packages.
|
||||
"""
|
||||
context = kwargs.pop('context', None)
|
||||
if context and kwargs:
|
||||
raise ValueError("cannot accept context and kwargs")
|
||||
context = context or DistributionFinder.Context(**kwargs)
|
||||
return itertools.chain.from_iterable(
|
||||
resolver()
|
||||
resolver(context)
|
||||
for resolver in cls._discover_resolvers()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def at(path):
|
||||
"""Return a Distribution for the indicated metadata path
|
||||
|
||||
:param path: a string or path-like object
|
||||
:return: a concrete Distribution instance for the path
|
||||
"""
|
||||
return PathDistribution(pathlib.Path(path))
|
||||
|
||||
@staticmethod
|
||||
def _discover_resolvers():
|
||||
"""Search the meta_path for resolvers."""
|
||||
|
@ -215,7 +233,7 @@ class Distribution:
|
|||
def files(self):
|
||||
"""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
|
||||
(i.e. RECORD for dist-info or SOURCES.txt for egg-info) is
|
||||
|
@ -231,7 +249,7 @@ class Distribution:
|
|||
result.dist = self
|
||||
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):
|
||||
"""
|
||||
|
@ -251,7 +269,8 @@ class Distribution:
|
|||
@property
|
||||
def requires(self):
|
||||
"""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):
|
||||
return self.metadata.get_all('Requires-Dist')
|
||||
|
@ -312,15 +331,35 @@ class DistributionFinder(MetaPathFinder):
|
|||
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
|
||||
def find_distributions(self, name=None, path=None):
|
||||
def find_distributions(self, context=Context()):
|
||||
"""
|
||||
Find distributions.
|
||||
|
||||
Return an iterable of all Distribution instances capable of
|
||||
loading the metadata for packages matching the ``name``
|
||||
(or all names if not supplied) along the paths in the list
|
||||
of directories ``path`` (defaults to sys.path).
|
||||
loading the metadata for packages matching the ``context``,
|
||||
a DistributionFinder.Context instance.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -352,12 +391,12 @@ def distribution(package):
|
|||
return Distribution.from_name(package)
|
||||
|
||||
|
||||
def distributions():
|
||||
def distributions(**kwargs):
|
||||
"""Get all ``Distribution`` instances in the current environment.
|
||||
|
||||
:return: An iterable of ``Distribution`` instances.
|
||||
"""
|
||||
return Distribution.discover()
|
||||
return Distribution.discover(**kwargs)
|
||||
|
||||
|
||||
def metadata(package):
|
||||
|
|
|
@ -162,6 +162,10 @@ class DiscoveryTests(fixtures.EggInfoPkg,
|
|||
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):
|
||||
def test_egg_info(self):
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import re
|
||||
import textwrap
|
||||
import unittest
|
||||
import itertools
|
||||
|
||||
from collections.abc import Iterator
|
||||
|
||||
|
@ -61,9 +60,7 @@ class APITests(
|
|||
assert 'Topic :: Software Development :: Libraries' in classifiers
|
||||
|
||||
@staticmethod
|
||||
def _test_files(files_iter):
|
||||
assert isinstance(files_iter, Iterator), files_iter
|
||||
files = list(files_iter)
|
||||
def _test_files(files):
|
||||
root = files[0].root
|
||||
for file in files:
|
||||
assert file.root == root
|
||||
|
@ -99,16 +96,18 @@ class APITests(
|
|||
requirements = requires('egginfo-file')
|
||||
self.assertIsNone(requirements)
|
||||
|
||||
def test_requires(self):
|
||||
def test_requires_egg_info(self):
|
||||
deps = requires('egginfo-pkg')
|
||||
assert len(deps) == 2
|
||||
assert any(
|
||||
dep == 'wheel >= 1.0; python_version >= "2.7"'
|
||||
for dep in deps
|
||||
)
|
||||
|
||||
def test_requires_dist_info(self):
|
||||
deps = list(requires('distinfo-pkg'))
|
||||
assert deps and all(deps)
|
||||
deps = requires('distinfo-pkg')
|
||||
assert len(deps) == 2
|
||||
assert all(deps)
|
||||
assert 'wheel >= 1.0' in deps
|
||||
assert "pytest; extra == 'test'" in deps
|
||||
|
||||
|
@ -143,11 +142,20 @@ class APITests(
|
|||
|
||||
class OffSysPathTests(fixtures.DistInfoPkgOffPath, unittest.TestCase):
|
||||
def test_find_distributions_specified_path(self):
|
||||
dists = itertools.chain.from_iterable(
|
||||
resolver(path=[str(self.site_dir)])
|
||||
for resolver in Distribution._discover_resolvers()
|
||||
)
|
||||
dists = Distribution.discover(path=[str(self.site_dir)])
|
||||
assert any(
|
||||
dist.metadata['Name'] == 'distinfo-pkg'
|
||||
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