(root)/
Python-3.12.0/
Lib/
test/
test_importlib/
test_main.py
       1  import re
       2  import pickle
       3  import unittest
       4  import warnings
       5  import importlib.metadata
       6  import contextlib
       7  import itertools
       8  
       9  try:
      10      import pyfakefs.fake_filesystem_unittest as ffs
      11  except ImportError:
      12      from .stubs import fake_filesystem_unittest as ffs
      13  
      14  from . import fixtures
      15  from ._context import suppress
      16  from importlib.metadata import (
      17      Distribution,
      18      EntryPoint,
      19      PackageNotFoundError,
      20      _unique,
      21      distributions,
      22      entry_points,
      23      metadata,
      24      packages_distributions,
      25      version,
      26  )
      27  
      28  
      29  @contextlib.contextmanager
      30  def suppress_known_deprecation():
      31      with warnings.catch_warnings(record=True) as ctx:
      32          warnings.simplefilter('default', category=DeprecationWarning)
      33          yield ctx
      34  
      35  
      36  class ESC[4;38;5;81mBasicTests(ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mDistInfoPkg, ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
      37      version_pattern = r'\d+\.\d+(\.\d)?'
      38  
      39      def test_retrieves_version_of_self(self):
      40          dist = Distribution.from_name('distinfo-pkg')
      41          assert isinstance(dist.version, str)
      42          assert re.match(self.version_pattern, dist.version)
      43  
      44      def test_for_name_does_not_exist(self):
      45          with self.assertRaises(PackageNotFoundError):
      46              Distribution.from_name('does-not-exist')
      47  
      48      def test_package_not_found_mentions_metadata(self):
      49          """
      50          When a package is not found, that could indicate that the
      51          package is not installed or that it is installed without
      52          metadata. Ensure the exception mentions metadata to help
      53          guide users toward the cause. See #124.
      54          """
      55          with self.assertRaises(PackageNotFoundError) as ctx:
      56              Distribution.from_name('does-not-exist')
      57  
      58          assert "metadata" in str(ctx.exception)
      59  
      60      # expected to fail until ABC is enforced
      61      @suppress(AssertionError)
      62      @suppress_known_deprecation()
      63      def test_abc_enforced(self):
      64          with self.assertRaises(TypeError):
      65              type('DistributionSubclass', (Distribution,), {})()
      66  
      67      @fixtures.parameterize(
      68          dict(name=None),
      69          dict(name=''),
      70      )
      71      def test_invalid_inputs_to_from_name(self, name):
      72          with self.assertRaises(ValueError):
      73              Distribution.from_name(name)
      74  
      75  
      76  class ESC[4;38;5;81mImportTests(ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mDistInfoPkg, ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
      77      def test_import_nonexistent_module(self):
      78          # Ensure that the MetadataPathFinder does not crash an import of a
      79          # non-existent module.
      80          with self.assertRaises(ImportError):
      81              importlib.import_module('does_not_exist')
      82  
      83      def test_resolve(self):
      84          ep = entry_points(group='entries')['main']
      85          self.assertEqual(ep.load().__name__, "main")
      86  
      87      def test_entrypoint_with_colon_in_name(self):
      88          ep = entry_points(group='entries')['ns:sub']
      89          self.assertEqual(ep.value, 'mod:main')
      90  
      91      def test_resolve_without_attr(self):
      92          ep = EntryPoint(
      93              name='ep',
      94              value='importlib.metadata',
      95              group='grp',
      96          )
      97          assert ep.load() is importlib.metadata
      98  
      99  
     100  class ESC[4;38;5;81mNameNormalizationTests(ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mOnSysPath, ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mSiteDir, ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     101      @staticmethod
     102      def make_pkg(name):
     103          """
     104          Create minimal metadata for a dist-info package with
     105          the indicated name on the file system.
     106          """
     107          return {
     108              f'{name}.dist-info': {
     109                  'METADATA': 'VERSION: 1.0\n',
     110              },
     111          }
     112  
     113      def test_dashes_in_dist_name_found_as_underscores(self):
     114          """
     115          For a package with a dash in the name, the dist-info metadata
     116          uses underscores in the name. Ensure the metadata loads.
     117          """
     118          fixtures.build_files(self.make_pkg('my_pkg'), self.site_dir)
     119          assert version('my-pkg') == '1.0'
     120  
     121      def test_dist_name_found_as_any_case(self):
     122          """
     123          Ensure the metadata loads when queried with any case.
     124          """
     125          pkg_name = 'CherryPy'
     126          fixtures.build_files(self.make_pkg(pkg_name), self.site_dir)
     127          assert version(pkg_name) == '1.0'
     128          assert version(pkg_name.lower()) == '1.0'
     129          assert version(pkg_name.upper()) == '1.0'
     130  
     131      def test_unique_distributions(self):
     132          """
     133          Two distributions varying only by non-normalized name on
     134          the file system should resolve as the same.
     135          """
     136          fixtures.build_files(self.make_pkg('abc'), self.site_dir)
     137          before = list(_unique(distributions()))
     138  
     139          alt_site_dir = self.fixtures.enter_context(fixtures.tempdir())
     140          self.fixtures.enter_context(self.add_sys_path(alt_site_dir))
     141          fixtures.build_files(self.make_pkg('ABC'), alt_site_dir)
     142          after = list(_unique(distributions()))
     143  
     144          assert len(after) == len(before)
     145  
     146  
     147  class ESC[4;38;5;81mNonASCIITests(ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mOnSysPath, ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mSiteDir, ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     148      @staticmethod
     149      def pkg_with_non_ascii_description(site_dir):
     150          """
     151          Create minimal metadata for a package with non-ASCII in
     152          the description.
     153          """
     154          contents = {
     155              'portend.dist-info': {
     156                  'METADATA': 'Description: pôrˈtend',
     157              },
     158          }
     159          fixtures.build_files(contents, site_dir)
     160          return 'portend'
     161  
     162      @staticmethod
     163      def pkg_with_non_ascii_description_egg_info(site_dir):
     164          """
     165          Create minimal metadata for an egg-info package with
     166          non-ASCII in the description.
     167          """
     168          contents = {
     169              'portend.dist-info': {
     170                  'METADATA': """
     171                  Name: portend
     172  
     173                  pôrˈtend""",
     174              },
     175          }
     176          fixtures.build_files(contents, site_dir)
     177          return 'portend'
     178  
     179      def test_metadata_loads(self):
     180          pkg_name = self.pkg_with_non_ascii_description(self.site_dir)
     181          meta = metadata(pkg_name)
     182          assert meta['Description'] == 'pôrˈtend'
     183  
     184      def test_metadata_loads_egg_info(self):
     185          pkg_name = self.pkg_with_non_ascii_description_egg_info(self.site_dir)
     186          meta = metadata(pkg_name)
     187          assert meta['Description'] == 'pôrˈtend'
     188  
     189  
     190  class ESC[4;38;5;81mDiscoveryTests(
     191      ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mEggInfoPkg,
     192      ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mEggInfoPkgPipInstalledNoToplevel,
     193      ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mEggInfoPkgPipInstalledNoModules,
     194      ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mEggInfoPkgSourcesFallback,
     195      ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mDistInfoPkg,
     196      ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase,
     197  ):
     198      def test_package_discovery(self):
     199          dists = list(distributions())
     200          assert all(isinstance(dist, Distribution) for dist in dists)
     201          assert any(dist.metadata['Name'] == 'egginfo-pkg' for dist in dists)
     202          assert any(dist.metadata['Name'] == 'egg_with_module-pkg' for dist in dists)
     203          assert any(dist.metadata['Name'] == 'egg_with_no_modules-pkg' for dist in dists)
     204          assert any(dist.metadata['Name'] == 'sources_fallback-pkg' for dist in dists)
     205          assert any(dist.metadata['Name'] == 'distinfo-pkg' for dist in dists)
     206  
     207      def test_invalid_usage(self):
     208          with self.assertRaises(ValueError):
     209              list(distributions(context='something', name='else'))
     210  
     211  
     212  class ESC[4;38;5;81mDirectoryTest(ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mOnSysPath, ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mSiteDir, ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     213      def test_egg_info(self):
     214          # make an `EGG-INFO` directory that's unrelated
     215          self.site_dir.joinpath('EGG-INFO').mkdir()
     216          # used to crash with `IsADirectoryError`
     217          with self.assertRaises(PackageNotFoundError):
     218              version('unknown-package')
     219  
     220      def test_egg(self):
     221          egg = self.site_dir.joinpath('foo-3.6.egg')
     222          egg.mkdir()
     223          with self.add_sys_path(egg):
     224              with self.assertRaises(PackageNotFoundError):
     225                  version('foo')
     226  
     227  
     228  class ESC[4;38;5;81mMissingSysPath(ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mOnSysPath, ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     229      site_dir = '/does-not-exist'
     230  
     231      def test_discovery(self):
     232          """
     233          Discovering distributions should succeed even if
     234          there is an invalid path on sys.path.
     235          """
     236          importlib.metadata.distributions()
     237  
     238  
     239  class ESC[4;38;5;81mInaccessibleSysPath(ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mOnSysPath, ESC[4;38;5;149mffsESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     240      site_dir = '/access-denied'
     241  
     242      def setUp(self):
     243          super().setUp()
     244          self.setUpPyfakefs()
     245          self.fs.create_dir(self.site_dir, perm_bits=000)
     246  
     247      def test_discovery(self):
     248          """
     249          Discovering distributions should succeed even if
     250          there is an invalid path on sys.path.
     251          """
     252          list(importlib.metadata.distributions())
     253  
     254  
     255  class ESC[4;38;5;81mTestEntryPoints(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     256      def __init__(self, *args):
     257          super().__init__(*args)
     258          self.ep = importlib.metadata.EntryPoint(
     259              name='name', value='value', group='group'
     260          )
     261  
     262      def test_entry_point_pickleable(self):
     263          revived = pickle.loads(pickle.dumps(self.ep))
     264          assert revived == self.ep
     265  
     266      def test_positional_args(self):
     267          """
     268          Capture legacy (namedtuple) construction, discouraged.
     269          """
     270          EntryPoint('name', 'value', 'group')
     271  
     272      def test_immutable(self):
     273          """EntryPoints should be immutable"""
     274          with self.assertRaises(AttributeError):
     275              self.ep.name = 'badactor'
     276  
     277      def test_repr(self):
     278          assert 'EntryPoint' in repr(self.ep)
     279          assert 'name=' in repr(self.ep)
     280          assert "'name'" in repr(self.ep)
     281  
     282      def test_hashable(self):
     283          """EntryPoints should be hashable"""
     284          hash(self.ep)
     285  
     286      def test_module(self):
     287          assert self.ep.module == 'value'
     288  
     289      def test_attr(self):
     290          assert self.ep.attr is None
     291  
     292      def test_sortable(self):
     293          """
     294          EntryPoint objects are sortable, but result is undefined.
     295          """
     296          sorted(
     297              [
     298                  EntryPoint(name='b', value='val', group='group'),
     299                  EntryPoint(name='a', value='val', group='group'),
     300              ]
     301          )
     302  
     303  
     304  class ESC[4;38;5;81mFileSystem(
     305      ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mOnSysPath, ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mSiteDir, ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mFileBuilder, ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase
     306  ):
     307      def test_unicode_dir_on_sys_path(self):
     308          """
     309          Ensure a Unicode subdirectory of a directory on sys.path
     310          does not crash.
     311          """
     312          fixtures.build_files(
     313              {self.unicode_filename(): {}},
     314              prefix=self.site_dir,
     315          )
     316          list(distributions())
     317  
     318  
     319  class ESC[4;38;5;81mPackagesDistributionsPrebuiltTest(ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mZipFixtures, ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     320      def test_packages_distributions_example(self):
     321          self._fixture_on_path('example-21.12-py3-none-any.whl')
     322          assert packages_distributions()['example'] == ['example']
     323  
     324      def test_packages_distributions_example2(self):
     325          """
     326          Test packages_distributions on a wheel built
     327          by trampolim.
     328          """
     329          self._fixture_on_path('example2-1.0.0-py3-none-any.whl')
     330          assert packages_distributions()['example2'] == ['example2']
     331  
     332  
     333  class ESC[4;38;5;81mPackagesDistributionsTest(
     334      ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mOnSysPath, ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mSiteDir, ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase
     335  ):
     336      def test_packages_distributions_neither_toplevel_nor_files(self):
     337          """
     338          Test a package built without 'top-level.txt' or a file list.
     339          """
     340          fixtures.build_files(
     341              {
     342                  'trim_example-1.0.0.dist-info': {
     343                      'METADATA': """
     344                  Name: trim_example
     345                  Version: 1.0.0
     346                  """,
     347                  }
     348              },
     349              prefix=self.site_dir,
     350          )
     351          packages_distributions()
     352  
     353      def test_packages_distributions_all_module_types(self):
     354          """
     355          Test top-level modules detected on a package without 'top-level.txt'.
     356          """
     357          suffixes = importlib.machinery.all_suffixes()
     358          metadata = dict(
     359              METADATA="""
     360                  Name: all_distributions
     361                  Version: 1.0.0
     362                  """,
     363          )
     364          files = {
     365              'all_distributions-1.0.0.dist-info': metadata,
     366          }
     367          for i, suffix in enumerate(suffixes):
     368              files.update(
     369                  {
     370                      f'importable-name {i}{suffix}': '',
     371                      f'in_namespace_{i}': {
     372                          f'mod{suffix}': '',
     373                      },
     374                      f'in_package_{i}': {
     375                          '__init__.py': '',
     376                          f'mod{suffix}': '',
     377                      },
     378                  }
     379              )
     380          metadata.update(RECORD=fixtures.build_record(files))
     381          fixtures.build_files(files, prefix=self.site_dir)
     382  
     383          distributions = packages_distributions()
     384  
     385          for i in range(len(suffixes)):
     386              assert distributions[f'importable-name {i}'] == ['all_distributions']
     387              assert distributions[f'in_namespace_{i}'] == ['all_distributions']
     388              assert distributions[f'in_package_{i}'] == ['all_distributions']
     389  
     390          assert not any(name.endswith('.dist-info') for name in distributions)
     391  
     392  
     393  class ESC[4;38;5;81mPackagesDistributionsEggTest(
     394      ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mEggInfoPkg,
     395      ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mEggInfoPkgPipInstalledNoToplevel,
     396      ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mEggInfoPkgPipInstalledNoModules,
     397      ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mEggInfoPkgSourcesFallback,
     398      ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase,
     399  ):
     400      def test_packages_distributions_on_eggs(self):
     401          """
     402          Test old-style egg packages with a variation of 'top_level.txt',
     403          'SOURCES.txt', and 'installed-files.txt', available.
     404          """
     405          distributions = packages_distributions()
     406  
     407          def import_names_from_package(package_name):
     408              return {
     409                  import_name
     410                  for import_name, package_names in distributions.items()
     411                  if package_name in package_names
     412              }
     413  
     414          # egginfo-pkg declares one import ('mod') via top_level.txt
     415          assert import_names_from_package('egginfo-pkg') == {'mod'}
     416  
     417          # egg_with_module-pkg has one import ('egg_with_module') inferred from
     418          # installed-files.txt (top_level.txt is missing)
     419          assert import_names_from_package('egg_with_module-pkg') == {'egg_with_module'}
     420  
     421          # egg_with_no_modules-pkg should not be associated with any import names
     422          # (top_level.txt is empty, and installed-files.txt has no .py files)
     423          assert import_names_from_package('egg_with_no_modules-pkg') == set()
     424  
     425          # sources_fallback-pkg has one import ('sources_fallback') inferred from
     426          # SOURCES.txt (top_level.txt and installed-files.txt is missing)
     427          assert import_names_from_package('sources_fallback-pkg') == {'sources_fallback'}