diff --git a/CHANGES.rst b/CHANGES.rst index 0faf0a47..2f20b890 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,10 @@ +v4.7.0 +====== + +* #330: In ``packages_distributions``, now infer top-level + names from ``.files()`` when a ``top-level.txt`` + (Setuptools-specific metadata) is not present. + v4.6.4 ====== diff --git a/docs/conf.py b/docs/conf.py index f619facc..bcd9f6c8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- extensions = ['sphinx.ext.autodoc', 'jaraco.packaging.sphinx', 'rst.linker'] diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index 9f5bf347..9bb06412 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -339,7 +339,7 @@ def names(self): """ Return the set of all names of all entry points. """ - return set(ep.name for ep in self) + return {ep.name for ep in self} @property def groups(self): @@ -350,7 +350,7 @@ def groups(self): >>> EntryPoints().groups set() """ - return set(ep.group for ep in self) + return {ep.group for ep in self} @classmethod def _from_text_for(cls, text, dist): @@ -1013,6 +1013,18 @@ def packages_distributions() -> Mapping[str, List[str]]: """ pkg_to_dist = collections.defaultdict(list) for dist in distributions(): - for pkg in (dist.read_text('top_level.txt') or '').split(): + for pkg in _top_level_declared(dist) or _top_level_inferred(dist): pkg_to_dist[pkg].append(dist.metadata['Name']) return dict(pkg_to_dist) + + +def _top_level_declared(dist): + return (dist.read_text('top_level.txt') or '').split() + + +def _top_level_inferred(dist): + return { + f.parts[0] if len(f.parts) > 1 else f.with_suffix('').name + for f in dist.files + if f.suffix == ".py" + } diff --git a/importlib_metadata/_text.py b/importlib_metadata/_text.py index 766979d9..c88cfbb2 100644 --- a/importlib_metadata/_text.py +++ b/importlib_metadata/_text.py @@ -80,7 +80,7 @@ def __hash__(self): return hash(self.lower()) def __contains__(self, other): - return super(FoldedCase, self).lower().__contains__(other.lower()) + return super().lower().__contains__(other.lower()) def in_(self, other): "Does self appear in other?" @@ -89,7 +89,7 @@ def in_(self, other): # cache lower since it's likely to be called frequently. @method_cache def lower(self): - return super(FoldedCase, self).lower() + return super().lower() def index(self, sub): return self.lower().index(sub.lower()) diff --git a/prepare/example2/example2/__init__.py b/prepare/example2/example2/__init__.py new file mode 100644 index 00000000..de645c2e --- /dev/null +++ b/prepare/example2/example2/__init__.py @@ -0,0 +1,2 @@ +def main(): + return "example" diff --git a/prepare/example2/pyproject.toml b/prepare/example2/pyproject.toml new file mode 100644 index 00000000..011f4751 --- /dev/null +++ b/prepare/example2/pyproject.toml @@ -0,0 +1,10 @@ +[build-system] +build-backend = 'trampolim' +requires = ['trampolim'] + +[project] +name = 'example2' +version = '1.0.0' + +[project.scripts] +example = 'example2:main' diff --git a/tests/data/example2-1.0.0-py3-none-any.whl b/tests/data/example2-1.0.0-py3-none-any.whl new file mode 100644 index 00000000..5ca93657 Binary files /dev/null and b/tests/data/example2-1.0.0-py3-none-any.whl differ diff --git a/tests/fixtures.py b/tests/fixtures.py index 8ef54f84..c6e645f5 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -10,6 +10,14 @@ from .py39compat import FS_NONASCII from typing import Dict, Union +try: + from importlib import resources + + getattr(resources, 'files') + getattr(resources, 'as_file') +except (ImportError, AttributeError): + import importlib_resources as resources # type: ignore + @contextlib.contextmanager def tempdir(): @@ -54,7 +62,7 @@ def setUp(self): class SiteDir(Fixtures): def setUp(self): - super(SiteDir, self).setUp() + super().setUp() self.site_dir = self.fixtures.enter_context(tempdir()) @@ -69,7 +77,7 @@ def add_sys_path(dir): sys.path.remove(str(dir)) def setUp(self): - super(OnSysPath, self).setUp() + super().setUp() self.fixtures.enter_context(self.add_sys_path(self.site_dir)) @@ -106,7 +114,7 @@ def main(): } def setUp(self): - super(DistInfoPkg, self).setUp() + super().setUp() build_files(DistInfoPkg.files, self.site_dir) def make_uppercase(self): @@ -131,7 +139,7 @@ class DistInfoPkgWithDot(OnSysPath, SiteDir): } def setUp(self): - super(DistInfoPkgWithDot, self).setUp() + super().setUp() build_files(DistInfoPkgWithDot.files, self.site_dir) @@ -152,13 +160,13 @@ class DistInfoPkgWithDotLegacy(OnSysPath, SiteDir): } def setUp(self): - super(DistInfoPkgWithDotLegacy, self).setUp() + super().setUp() build_files(DistInfoPkgWithDotLegacy.files, self.site_dir) class DistInfoPkgOffPath(SiteDir): def setUp(self): - super(DistInfoPkgOffPath, self).setUp() + super().setUp() build_files(DistInfoPkg.files, self.site_dir) @@ -198,7 +206,7 @@ def main(): } def setUp(self): - super(EggInfoPkg, self).setUp() + super().setUp() build_files(EggInfoPkg.files, prefix=self.site_dir) @@ -219,7 +227,7 @@ class EggInfoFile(OnSysPath, SiteDir): } def setUp(self): - super(EggInfoFile, self).setUp() + super().setUp() build_files(EggInfoFile.files, prefix=self.site_dir) @@ -285,3 +293,19 @@ def DALS(str): class NullFinder: def find_module(self, name): pass + + +class ZipFixtures: + root = 'tests.data' + + def _fixture_on_path(self, filename): + pkg_file = resources.files(self.root).joinpath(filename) + file = self.resources.enter_context(resources.as_file(pkg_file)) + assert file.name.startswith('example'), file.name + sys.path.insert(0, str(file)) + self.resources.callback(sys.path.pop, 0) + + def setUp(self): + # Add self.zip_name to the front of sys.path. + self.resources = contextlib.ExitStack() + self.addCleanup(self.resources.close) diff --git a/tests/test_api.py b/tests/test_api.py index 6bf25b3c..75d4184d 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -115,7 +115,7 @@ def test_entry_points_unique_packages(self): for ep in entries ) # ns:sub doesn't exist in alt_pkg - assert 'ns:sub' not in entries + assert 'ns:sub' not in entries.names def test_entry_points_missing_name(self): with self.assertRaises(KeyError): @@ -206,14 +206,8 @@ def _test_files(files): file.read_text() def test_file_hash_repr(self): - try: - assertRegex = self.assertRegex - except AttributeError: - # Python 2 - assertRegex = self.assertRegexpMatches - util = [p for p in files('distinfo-pkg') if p.name == 'mod.py'][0] - assertRegex(repr(util.hash), '') + self.assertRegex(repr(util.hash), '') def test_files_dist_info(self): self._test_files(files('distinfo-pkg')) diff --git a/tests/test_main.py b/tests/test_main.py index f7c9c518..081e9fa1 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -17,6 +17,7 @@ distributions, entry_points, metadata, + packages_distributions, version, ) @@ -208,7 +209,7 @@ class InaccessibleSysPath(fixtures.OnSysPath, ffs.TestCase): site_dir = '/access-denied' def setUp(self): - super(InaccessibleSysPath, self).setUp() + super().setUp() self.setUpPyfakefs() self.fs.create_dir(self.site_dir, perm_bits=000) @@ -222,7 +223,7 @@ def test_discovery(self): class TestEntryPoints(unittest.TestCase): def __init__(self, *args): - super(TestEntryPoints, self).__init__(*args) + super().__init__(*args) self.ep = importlib_metadata.EntryPoint('name', 'value', 'group') def test_entry_point_pickleable(self): @@ -282,3 +283,17 @@ def test_unicode_dir_on_sys_path(self): prefix=self.site_dir, ) list(distributions()) + + +class PackagesDistributionsTest(fixtures.ZipFixtures, unittest.TestCase): + def test_packages_distributions_example(self): + self._fixture_on_path('example-21.12-py3-none-any.whl') + assert packages_distributions()['example'] == ['example'] + + def test_packages_distributions_example2(self): + """ + Test packages_distributions on a wheel built + by trampolim. + """ + self._fixture_on_path('example2-1.0.0-py3-none-any.whl') + assert packages_distributions()['example2'] == ['example2'] diff --git a/tests/test_zip.py b/tests/test_zip.py index 8a4c9458..01aba6df 100644 --- a/tests/test_zip.py +++ b/tests/test_zip.py @@ -1,7 +1,7 @@ import sys import unittest -from contextlib import ExitStack +from . import fixtures from importlib_metadata import ( PackageNotFoundError, distribution, @@ -11,30 +11,10 @@ version, ) -try: - from importlib import resources - - getattr(resources, 'files') - getattr(resources, 'as_file') -except (ImportError, AttributeError): - import importlib_resources as resources # type: ignore - - -class TestZip(unittest.TestCase): - root = 'tests.data' - - def _fixture_on_path(self, filename): - pkg_file = resources.files(self.root).joinpath(filename) - file = self.resources.enter_context(resources.as_file(pkg_file)) - assert file.name.startswith('example-'), file.name - sys.path.insert(0, str(file)) - self.resources.callback(sys.path.pop, 0) +class TestZip(fixtures.ZipFixtures, unittest.TestCase): def setUp(self): - # Find the path to the example-*.whl so we can add it to the front of - # sys.path, where we'll then try to find the metadata thereof. - self.resources = ExitStack() - self.addCleanup(self.resources.close) + super().setUp() self._fixture_on_path('example-21.12-py3-none-any.whl') def test_zip_version(self): @@ -69,10 +49,7 @@ def test_one_distribution(self): class TestEgg(TestZip): def setUp(self): - # Find the path to the example-*.egg so we can add it to the front of - # sys.path, where we'll then try to find the metadata thereof. - self.resources = ExitStack() - self.addCleanup(self.resources.close) + super().setUp() self._fixture_on_path('example-21.12-py3.6.egg') def test_files(self):