Commit deb95b32 authored by Jason R. Coombs's avatar Jason R. Coombs

Merge branch 'master' into feature/distribution-finder-context

parents 2d63cd7b d2357c79
Pipeline #81298616 passed with stages
in 4 minutes and 29 seconds
......@@ -27,6 +27,7 @@ from ._compat import (
ModuleNotFoundError,
MetaPathFinder,
email_message_from_string,
ensure_is_path,
)
from importlib import import_module
from itertools import starmap
......@@ -37,6 +38,7 @@ __metaclass__ = type
__all__ = [
'Distribution',
'DistributionFinder',
'PackageNotFoundError',
'distribution',
'distributions',
......@@ -202,6 +204,15 @@ class Distribution:
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(ensure_is_path(path))
@staticmethod
def _discover_resolvers():
"""Search the meta_path for resolvers."""
......@@ -241,7 +252,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
......@@ -257,7 +268,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):
"""
......@@ -277,7 +288,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')
......
......@@ -89,3 +89,12 @@ email_message_from_string = (
# https://bitbucket.org/pypy/pypy/issues/3021/ioopen-directory-leaks-a-file-descriptor
PYPY_OPEN_BUG = getattr(sys, 'pypy_version_info', (9, 9, 9))[:3] <= (7, 1, 1)
def ensure_is_path(ob):
"""Construct a Path from ob even if it's already one.
Specialized for Python 3.4.
"""
if (3,) < sys.version_info < (3, 5):
ob = str(ob) # pragma: nocover
return pathlib.Path(ob)
......@@ -4,6 +4,11 @@
0.21
====
* ``importlib.metadata`` now exposes the ``DistributionFinder``
metaclass and references it in the docs for extending the
search algorithm.
* Add ``Distribution.at`` for constructing a Distribution object
from a known metadata directory on the file system. Closes #80.
* Distribution finders now receive a context object that
supplies ``.path`` and ``.name`` properties. This change
introduces a fundamental backward incompatibility for
......
......@@ -165,10 +165,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'))
["pytest (>=3.0.0) ; extra == 'test'"]
>>> requires('wheel')
["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]
Distributions
......@@ -217,10 +217,11 @@ 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::
@abc.abstractmethod
def find_distributions(context=DistributionFinder.Context()):
"""Return an iterable of all Distribution instances capable of
loading the metadata for packages for the indicated ``context``.
......
......@@ -71,9 +71,7 @@ class APITests(
assert re.match(self.version_pattern, __version__)
@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
......@@ -113,16 +111,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
......@@ -162,3 +162,15 @@ class OffSysPathTests(fixtures.DistInfoPkgOffPath, unittest.TestCase):
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'
import unittest
import packaging.requirements
import packaging.version
from . import fixtures
from .. import version
class IntegrationTests(fixtures.DistInfoPkg, unittest.TestCase):
def test_package_spec_installed(self):
"""
Illustrate the recommended procedure to determine if
a specified version of a package is installed.
"""
def is_installed(package_spec):
req = packaging.requirements.Requirement(package_spec)
return version(req.name) in req.specifier
assert is_installed('distinfo-pkg==1.0')
assert is_installed('distinfo-pkg>=1.0,<2.0')
assert not is_installed('distinfo-pkg<1.0')
......@@ -54,6 +54,7 @@ universal=1
[options.extras_require]
testing =
importlib_resources; python_version < "3.7"
packaging
docs =
sphinx
rst.linker
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment