diff options
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/importlib/_bootstrap_external.py | 18 | ||||
-rw-r--r-- | Lib/importlib/metadata.py | 63 | ||||
-rw-r--r-- | Lib/test/test_importlib/test_main.py | 4 | ||||
-rw-r--r-- | Lib/test/test_importlib/test_metadata_api.py | 30 |
4 files changed, 82 insertions, 33 deletions
diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py index 671b043..ec4bbec 100644 --- a/Lib/importlib/_bootstrap_external.py +++ b/Lib/importlib/_bootstrap_external.py @@ -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 diff --git a/Lib/importlib/metadata.py b/Lib/importlib/metadata.py index 3b46142..e230766 100644 --- a/Lib/importlib/metadata.py +++ b/Lib/importlib/metadata.py @@ -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,17 +167,34 @@ 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.""" declared = ( @@ -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): diff --git a/Lib/test/test_importlib/test_main.py b/Lib/test/test_importlib/test_main.py index 3d7da81..4d5b127 100644 --- a/Lib/test/test_importlib/test_main.py +++ b/Lib/test/test_importlib/test_main.py @@ -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): diff --git a/Lib/test/test_importlib/test_metadata_api.py b/Lib/test/test_importlib/test_metadata_api.py index af3bab3..1d7b29a 100644 --- a/Lib/test/test_importlib/test_metadata_api.py +++ b/Lib/test/test_importlib/test_metadata_api.py @@ -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' |