mirror of
				https://github.com/python/cpython.git
				synced 2025-11-04 07:31:38 +00:00 
			
		
		
		
	* gh-97781: Apply changes from importlib_metadata 5. * Apply changes from upstream * Apply changes from upstream.
		
			
				
	
	
		
			326 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			326 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import re
 | 
						||
import pickle
 | 
						||
import unittest
 | 
						||
import importlib.metadata
 | 
						||
 | 
						||
try:
 | 
						||
    import pyfakefs.fake_filesystem_unittest as ffs
 | 
						||
except ImportError:
 | 
						||
    from .stubs import fake_filesystem_unittest as ffs
 | 
						||
 | 
						||
from . import fixtures
 | 
						||
from importlib.metadata import (
 | 
						||
    Distribution,
 | 
						||
    EntryPoint,
 | 
						||
    PackageNotFoundError,
 | 
						||
    _unique,
 | 
						||
    distributions,
 | 
						||
    entry_points,
 | 
						||
    metadata,
 | 
						||
    packages_distributions,
 | 
						||
    version,
 | 
						||
)
 | 
						||
 | 
						||
 | 
						||
class BasicTests(fixtures.DistInfoPkg, unittest.TestCase):
 | 
						||
    version_pattern = r'\d+\.\d+(\.\d)?'
 | 
						||
 | 
						||
    def test_retrieves_version_of_self(self):
 | 
						||
        dist = Distribution.from_name('distinfo-pkg')
 | 
						||
        assert isinstance(dist.version, str)
 | 
						||
        assert re.match(self.version_pattern, dist.version)
 | 
						||
 | 
						||
    def test_for_name_does_not_exist(self):
 | 
						||
        with self.assertRaises(PackageNotFoundError):
 | 
						||
            Distribution.from_name('does-not-exist')
 | 
						||
 | 
						||
    def test_package_not_found_mentions_metadata(self):
 | 
						||
        """
 | 
						||
        When a package is not found, that could indicate that the
 | 
						||
        packgae is not installed or that it is installed without
 | 
						||
        metadata. Ensure the exception mentions metadata to help
 | 
						||
        guide users toward the cause. See #124.
 | 
						||
        """
 | 
						||
        with self.assertRaises(PackageNotFoundError) as ctx:
 | 
						||
            Distribution.from_name('does-not-exist')
 | 
						||
 | 
						||
        assert "metadata" in str(ctx.exception)
 | 
						||
 | 
						||
    def test_new_style_classes(self):
 | 
						||
        self.assertIsInstance(Distribution, type)
 | 
						||
 | 
						||
    @fixtures.parameterize(
 | 
						||
        dict(name=None),
 | 
						||
        dict(name=''),
 | 
						||
    )
 | 
						||
    def test_invalid_inputs_to_from_name(self, name):
 | 
						||
        with self.assertRaises(Exception):
 | 
						||
            Distribution.from_name(name)
 | 
						||
 | 
						||
 | 
						||
class ImportTests(fixtures.DistInfoPkg, unittest.TestCase):
 | 
						||
    def test_import_nonexistent_module(self):
 | 
						||
        # Ensure that the MetadataPathFinder does not crash an import of a
 | 
						||
        # non-existent module.
 | 
						||
        with self.assertRaises(ImportError):
 | 
						||
            importlib.import_module('does_not_exist')
 | 
						||
 | 
						||
    def test_resolve(self):
 | 
						||
        ep = entry_points(group='entries')['main']
 | 
						||
        self.assertEqual(ep.load().__name__, "main")
 | 
						||
 | 
						||
    def test_entrypoint_with_colon_in_name(self):
 | 
						||
        ep = entry_points(group='entries')['ns:sub']
 | 
						||
        self.assertEqual(ep.value, 'mod:main')
 | 
						||
 | 
						||
    def test_resolve_without_attr(self):
 | 
						||
        ep = EntryPoint(
 | 
						||
            name='ep',
 | 
						||
            value='importlib.metadata',
 | 
						||
            group='grp',
 | 
						||
        )
 | 
						||
        assert ep.load() is importlib.metadata
 | 
						||
 | 
						||
 | 
						||
class NameNormalizationTests(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase):
 | 
						||
    @staticmethod
 | 
						||
    def make_pkg(name):
 | 
						||
        """
 | 
						||
        Create minimal metadata for a dist-info package with
 | 
						||
        the indicated name on the file system.
 | 
						||
        """
 | 
						||
        return {
 | 
						||
            f'{name}.dist-info': {
 | 
						||
                'METADATA': 'VERSION: 1.0\n',
 | 
						||
            },
 | 
						||
        }
 | 
						||
 | 
						||
    def test_dashes_in_dist_name_found_as_underscores(self):
 | 
						||
        """
 | 
						||
        For a package with a dash in the name, the dist-info metadata
 | 
						||
        uses underscores in the name. Ensure the metadata loads.
 | 
						||
        """
 | 
						||
        fixtures.build_files(self.make_pkg('my_pkg'), self.site_dir)
 | 
						||
        assert version('my-pkg') == '1.0'
 | 
						||
 | 
						||
    def test_dist_name_found_as_any_case(self):
 | 
						||
        """
 | 
						||
        Ensure the metadata loads when queried with any case.
 | 
						||
        """
 | 
						||
        pkg_name = 'CherryPy'
 | 
						||
        fixtures.build_files(self.make_pkg(pkg_name), self.site_dir)
 | 
						||
        assert version(pkg_name) == '1.0'
 | 
						||
        assert version(pkg_name.lower()) == '1.0'
 | 
						||
        assert version(pkg_name.upper()) == '1.0'
 | 
						||
 | 
						||
    def test_unique_distributions(self):
 | 
						||
        """
 | 
						||
        Two distributions varying only by non-normalized name on
 | 
						||
        the file system should resolve as the same.
 | 
						||
        """
 | 
						||
        fixtures.build_files(self.make_pkg('abc'), self.site_dir)
 | 
						||
        before = list(_unique(distributions()))
 | 
						||
 | 
						||
        alt_site_dir = self.fixtures.enter_context(fixtures.tempdir())
 | 
						||
        self.fixtures.enter_context(self.add_sys_path(alt_site_dir))
 | 
						||
        fixtures.build_files(self.make_pkg('ABC'), alt_site_dir)
 | 
						||
        after = list(_unique(distributions()))
 | 
						||
 | 
						||
        assert len(after) == len(before)
 | 
						||
 | 
						||
 | 
						||
class NonASCIITests(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase):
 | 
						||
    @staticmethod
 | 
						||
    def pkg_with_non_ascii_description(site_dir):
 | 
						||
        """
 | 
						||
        Create minimal metadata for a package with non-ASCII in
 | 
						||
        the description.
 | 
						||
        """
 | 
						||
        contents = {
 | 
						||
            'portend.dist-info': {
 | 
						||
                'METADATA': 'Description: pôrˈtend',
 | 
						||
            },
 | 
						||
        }
 | 
						||
        fixtures.build_files(contents, site_dir)
 | 
						||
        return 'portend'
 | 
						||
 | 
						||
    @staticmethod
 | 
						||
    def pkg_with_non_ascii_description_egg_info(site_dir):
 | 
						||
        """
 | 
						||
        Create minimal metadata for an egg-info package with
 | 
						||
        non-ASCII in the description.
 | 
						||
        """
 | 
						||
        contents = {
 | 
						||
            'portend.dist-info': {
 | 
						||
                'METADATA': """
 | 
						||
                Name: portend
 | 
						||
 | 
						||
                pôrˈtend""",
 | 
						||
            },
 | 
						||
        }
 | 
						||
        fixtures.build_files(contents, site_dir)
 | 
						||
        return 'portend'
 | 
						||
 | 
						||
    def test_metadata_loads(self):
 | 
						||
        pkg_name = self.pkg_with_non_ascii_description(self.site_dir)
 | 
						||
        meta = metadata(pkg_name)
 | 
						||
        assert meta['Description'] == 'pôrˈtend'
 | 
						||
 | 
						||
    def test_metadata_loads_egg_info(self):
 | 
						||
        pkg_name = self.pkg_with_non_ascii_description_egg_info(self.site_dir)
 | 
						||
        meta = metadata(pkg_name)
 | 
						||
        assert meta['Description'] == 'pôrˈtend'
 | 
						||
 | 
						||
 | 
						||
class DiscoveryTests(fixtures.EggInfoPkg, fixtures.DistInfoPkg, unittest.TestCase):
 | 
						||
    def test_package_discovery(self):
 | 
						||
        dists = list(distributions())
 | 
						||
        assert all(isinstance(dist, Distribution) for dist in dists)
 | 
						||
        assert any(dist.metadata['Name'] == 'egginfo-pkg' for dist in dists)
 | 
						||
        assert any(dist.metadata['Name'] == 'distinfo-pkg' 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):
 | 
						||
        # make an `EGG-INFO` directory that's unrelated
 | 
						||
        self.site_dir.joinpath('EGG-INFO').mkdir()
 | 
						||
        # used to crash with `IsADirectoryError`
 | 
						||
        with self.assertRaises(PackageNotFoundError):
 | 
						||
            version('unknown-package')
 | 
						||
 | 
						||
    def test_egg(self):
 | 
						||
        egg = self.site_dir.joinpath('foo-3.6.egg')
 | 
						||
        egg.mkdir()
 | 
						||
        with self.add_sys_path(egg):
 | 
						||
            with self.assertRaises(PackageNotFoundError):
 | 
						||
                version('foo')
 | 
						||
 | 
						||
 | 
						||
class MissingSysPath(fixtures.OnSysPath, unittest.TestCase):
 | 
						||
    site_dir = '/does-not-exist'
 | 
						||
 | 
						||
    def test_discovery(self):
 | 
						||
        """
 | 
						||
        Discovering distributions should succeed even if
 | 
						||
        there is an invalid path on sys.path.
 | 
						||
        """
 | 
						||
        importlib.metadata.distributions()
 | 
						||
 | 
						||
 | 
						||
class InaccessibleSysPath(fixtures.OnSysPath, ffs.TestCase):
 | 
						||
    site_dir = '/access-denied'
 | 
						||
 | 
						||
    def setUp(self):
 | 
						||
        super().setUp()
 | 
						||
        self.setUpPyfakefs()
 | 
						||
        self.fs.create_dir(self.site_dir, perm_bits=000)
 | 
						||
 | 
						||
    def test_discovery(self):
 | 
						||
        """
 | 
						||
        Discovering distributions should succeed even if
 | 
						||
        there is an invalid path on sys.path.
 | 
						||
        """
 | 
						||
        list(importlib.metadata.distributions())
 | 
						||
 | 
						||
 | 
						||
class TestEntryPoints(unittest.TestCase):
 | 
						||
    def __init__(self, *args):
 | 
						||
        super().__init__(*args)
 | 
						||
        self.ep = importlib.metadata.EntryPoint(
 | 
						||
            name='name', value='value', group='group'
 | 
						||
        )
 | 
						||
 | 
						||
    def test_entry_point_pickleable(self):
 | 
						||
        revived = pickle.loads(pickle.dumps(self.ep))
 | 
						||
        assert revived == self.ep
 | 
						||
 | 
						||
    def test_positional_args(self):
 | 
						||
        """
 | 
						||
        Capture legacy (namedtuple) construction, discouraged.
 | 
						||
        """
 | 
						||
        EntryPoint('name', 'value', 'group')
 | 
						||
 | 
						||
    def test_immutable(self):
 | 
						||
        """EntryPoints should be immutable"""
 | 
						||
        with self.assertRaises(AttributeError):
 | 
						||
            self.ep.name = 'badactor'
 | 
						||
 | 
						||
    def test_repr(self):
 | 
						||
        assert 'EntryPoint' in repr(self.ep)
 | 
						||
        assert 'name=' in repr(self.ep)
 | 
						||
        assert "'name'" in repr(self.ep)
 | 
						||
 | 
						||
    def test_hashable(self):
 | 
						||
        """EntryPoints should be hashable"""
 | 
						||
        hash(self.ep)
 | 
						||
 | 
						||
    def test_module(self):
 | 
						||
        assert self.ep.module == 'value'
 | 
						||
 | 
						||
    def test_attr(self):
 | 
						||
        assert self.ep.attr is None
 | 
						||
 | 
						||
    def test_sortable(self):
 | 
						||
        """
 | 
						||
        EntryPoint objects are sortable, but result is undefined.
 | 
						||
        """
 | 
						||
        sorted(
 | 
						||
            [
 | 
						||
                EntryPoint(name='b', value='val', group='group'),
 | 
						||
                EntryPoint(name='a', value='val', group='group'),
 | 
						||
            ]
 | 
						||
        )
 | 
						||
 | 
						||
 | 
						||
class FileSystem(
 | 
						||
    fixtures.OnSysPath, fixtures.SiteDir, fixtures.FileBuilder, unittest.TestCase
 | 
						||
):
 | 
						||
    def test_unicode_dir_on_sys_path(self):
 | 
						||
        """
 | 
						||
        Ensure a Unicode subdirectory of a directory on sys.path
 | 
						||
        does not crash.
 | 
						||
        """
 | 
						||
        fixtures.build_files(
 | 
						||
            {self.unicode_filename(): {}},
 | 
						||
            prefix=self.site_dir,
 | 
						||
        )
 | 
						||
        list(distributions())
 | 
						||
 | 
						||
 | 
						||
class PackagesDistributionsPrebuiltTest(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']
 | 
						||
 | 
						||
 | 
						||
class PackagesDistributionsTest(
 | 
						||
    fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase
 | 
						||
):
 | 
						||
    def test_packages_distributions_neither_toplevel_nor_files(self):
 | 
						||
        """
 | 
						||
        Test a package built without 'top-level.txt' or a file list.
 | 
						||
        """
 | 
						||
        fixtures.build_files(
 | 
						||
            {
 | 
						||
                'trim_example-1.0.0.dist-info': {
 | 
						||
                    'METADATA': """
 | 
						||
                Name: trim_example
 | 
						||
                Version: 1.0.0
 | 
						||
                """,
 | 
						||
                }
 | 
						||
            },
 | 
						||
            prefix=self.site_dir,
 | 
						||
        )
 | 
						||
        packages_distributions()
 |