(root)/
Python-3.12.0/
Lib/
test/
test_importlib/
test_metadata_api.py
       1  import re
       2  import textwrap
       3  import unittest
       4  import warnings
       5  import importlib
       6  import contextlib
       7  
       8  from . import fixtures
       9  from importlib.metadata import (
      10      Distribution,
      11      PackageNotFoundError,
      12      distribution,
      13      entry_points,
      14      files,
      15      metadata,
      16      requires,
      17      version,
      18  )
      19  
      20  
      21  @contextlib.contextmanager
      22  def suppress_known_deprecation():
      23      with warnings.catch_warnings(record=True) as ctx:
      24          warnings.simplefilter('default', category=DeprecationWarning)
      25          yield ctx
      26  
      27  
      28  class ESC[4;38;5;81mAPITests(
      29      ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mEggInfoPkg,
      30      ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mEggInfoPkgPipInstalledNoToplevel,
      31      ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mEggInfoPkgPipInstalledNoModules,
      32      ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mEggInfoPkgSourcesFallback,
      33      ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mDistInfoPkg,
      34      ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mDistInfoPkgWithDot,
      35      ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mEggInfoFile,
      36      ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase,
      37  ):
      38      version_pattern = r'\d+\.\d+(\.\d)?'
      39  
      40      def test_retrieves_version_of_self(self):
      41          pkg_version = version('egginfo-pkg')
      42          assert isinstance(pkg_version, str)
      43          assert re.match(self.version_pattern, pkg_version)
      44  
      45      def test_retrieves_version_of_distinfo_pkg(self):
      46          pkg_version = version('distinfo-pkg')
      47          assert isinstance(pkg_version, str)
      48          assert re.match(self.version_pattern, pkg_version)
      49  
      50      def test_for_name_does_not_exist(self):
      51          with self.assertRaises(PackageNotFoundError):
      52              distribution('does-not-exist')
      53  
      54      def test_name_normalization(self):
      55          names = 'pkg.dot', 'pkg_dot', 'pkg-dot', 'pkg..dot', 'Pkg.Dot'
      56          for name in names:
      57              with self.subTest(name):
      58                  assert distribution(name).metadata['Name'] == 'pkg.dot'
      59  
      60      def test_prefix_not_matched(self):
      61          prefixes = 'p', 'pkg', 'pkg.'
      62          for prefix in prefixes:
      63              with self.subTest(prefix):
      64                  with self.assertRaises(PackageNotFoundError):
      65                      distribution(prefix)
      66  
      67      def test_for_top_level(self):
      68          tests = [
      69              ('egginfo-pkg', 'mod'),
      70              ('egg_with_no_modules-pkg', ''),
      71          ]
      72          for pkg_name, expect_content in tests:
      73              with self.subTest(pkg_name):
      74                  self.assertEqual(
      75                      distribution(pkg_name).read_text('top_level.txt').strip(),
      76                      expect_content,
      77                  )
      78  
      79      def test_read_text(self):
      80          tests = [
      81              ('egginfo-pkg', 'mod\n'),
      82              ('egg_with_no_modules-pkg', '\n'),
      83          ]
      84          for pkg_name, expect_content in tests:
      85              with self.subTest(pkg_name):
      86                  top_level = [
      87                      path for path in files(pkg_name) if path.name == 'top_level.txt'
      88                  ][0]
      89                  self.assertEqual(top_level.read_text(), expect_content)
      90  
      91      def test_entry_points(self):
      92          eps = entry_points()
      93          assert 'entries' in eps.groups
      94          entries = eps.select(group='entries')
      95          assert 'main' in entries.names
      96          ep = entries['main']
      97          self.assertEqual(ep.value, 'mod:main')
      98          self.assertEqual(ep.extras, [])
      99  
     100      def test_entry_points_distribution(self):
     101          entries = entry_points(group='entries')
     102          for entry in ("main", "ns:sub"):
     103              ep = entries[entry]
     104              self.assertIn(ep.dist.name, ('distinfo-pkg', 'egginfo-pkg'))
     105              self.assertEqual(ep.dist.version, "1.0.0")
     106  
     107      def test_entry_points_unique_packages_normalized(self):
     108          """
     109          Entry points should only be exposed for the first package
     110          on sys.path with a given name (even when normalized).
     111          """
     112          alt_site_dir = self.fixtures.enter_context(fixtures.tempdir())
     113          self.fixtures.enter_context(self.add_sys_path(alt_site_dir))
     114          alt_pkg = {
     115              "DistInfo_pkg-1.1.0.dist-info": {
     116                  "METADATA": """
     117                  Name: distinfo-pkg
     118                  Version: 1.1.0
     119                  """,
     120                  "entry_points.txt": """
     121                  [entries]
     122                  main = mod:altmain
     123              """,
     124              },
     125          }
     126          fixtures.build_files(alt_pkg, alt_site_dir)
     127          entries = entry_points(group='entries')
     128          assert not any(
     129              ep.dist.name == 'distinfo-pkg' and ep.dist.version == '1.0.0'
     130              for ep in entries
     131          )
     132          # ns:sub doesn't exist in alt_pkg
     133          assert 'ns:sub' not in entries.names
     134  
     135      def test_entry_points_missing_name(self):
     136          with self.assertRaises(KeyError):
     137              entry_points(group='entries')['missing']
     138  
     139      def test_entry_points_missing_group(self):
     140          assert entry_points(group='missing') == ()
     141  
     142      def test_entry_points_allows_no_attributes(self):
     143          ep = entry_points().select(group='entries', name='main')
     144          with self.assertRaises(AttributeError):
     145              ep.foo = 4
     146  
     147      def test_metadata_for_this_package(self):
     148          md = metadata('egginfo-pkg')
     149          assert md['author'] == 'Steven Ma'
     150          assert md['LICENSE'] == 'Unknown'
     151          assert md['Name'] == 'egginfo-pkg'
     152          classifiers = md.get_all('Classifier')
     153          assert 'Topic :: Software Development :: Libraries' in classifiers
     154  
     155      def test_missing_key_legacy(self):
     156          """
     157          Requesting a missing key will still return None, but warn.
     158          """
     159          md = metadata('distinfo-pkg')
     160          with suppress_known_deprecation():
     161              assert md['does-not-exist'] is None
     162  
     163      def test_get_key(self):
     164          """
     165          Getting a key gets the key.
     166          """
     167          md = metadata('egginfo-pkg')
     168          assert md.get('Name') == 'egginfo-pkg'
     169  
     170      def test_get_missing_key(self):
     171          """
     172          Requesting a missing key will return None.
     173          """
     174          md = metadata('distinfo-pkg')
     175          assert md.get('does-not-exist') is None
     176  
     177      @staticmethod
     178      def _test_files(files):
     179          root = files[0].root
     180          for file in files:
     181              assert file.root == root
     182              assert not file.hash or file.hash.value
     183              assert not file.hash or file.hash.mode == 'sha256'
     184              assert not file.size or file.size >= 0
     185              assert file.locate().exists()
     186              assert isinstance(file.read_binary(), bytes)
     187              if file.name.endswith('.py'):
     188                  file.read_text()
     189  
     190      def test_file_hash_repr(self):
     191          util = [p for p in files('distinfo-pkg') if p.name == 'mod.py'][0]
     192          self.assertRegex(repr(util.hash), '<FileHash mode: sha256 value: .*>')
     193  
     194      def test_files_dist_info(self):
     195          self._test_files(files('distinfo-pkg'))
     196  
     197      def test_files_egg_info(self):
     198          self._test_files(files('egginfo-pkg'))
     199          self._test_files(files('egg_with_module-pkg'))
     200          self._test_files(files('egg_with_no_modules-pkg'))
     201          self._test_files(files('sources_fallback-pkg'))
     202  
     203      def test_version_egg_info_file(self):
     204          self.assertEqual(version('egginfo-file'), '0.1')
     205  
     206      def test_requires_egg_info_file(self):
     207          requirements = requires('egginfo-file')
     208          self.assertIsNone(requirements)
     209  
     210      def test_requires_egg_info(self):
     211          deps = requires('egginfo-pkg')
     212          assert len(deps) == 2
     213          assert any(dep == 'wheel >= 1.0; python_version >= "2.7"' for dep in deps)
     214  
     215      def test_requires_egg_info_empty(self):
     216          fixtures.build_files(
     217              {
     218                  'requires.txt': '',
     219              },
     220              self.site_dir.joinpath('egginfo_pkg.egg-info'),
     221          )
     222          deps = requires('egginfo-pkg')
     223          assert deps == []
     224  
     225      def test_requires_dist_info(self):
     226          deps = requires('distinfo-pkg')
     227          assert len(deps) == 2
     228          assert all(deps)
     229          assert 'wheel >= 1.0' in deps
     230          assert "pytest; extra == 'test'" in deps
     231  
     232      def test_more_complex_deps_requires_text(self):
     233          requires = textwrap.dedent(
     234              """
     235              dep1
     236              dep2
     237  
     238              [:python_version < "3"]
     239              dep3
     240  
     241              [extra1]
     242              dep4
     243              dep6@ git+https://example.com/python/dep.git@v1.0.0
     244  
     245              [extra2:python_version < "3"]
     246              dep5
     247              """
     248          )
     249          deps = sorted(Distribution._deps_from_requires_text(requires))
     250          expected = [
     251              'dep1',
     252              'dep2',
     253              'dep3; python_version < "3"',
     254              'dep4; extra == "extra1"',
     255              'dep5; (python_version < "3") and extra == "extra2"',
     256              'dep6@ git+https://example.com/python/dep.git@v1.0.0 ; extra == "extra1"',
     257          ]
     258          # It's important that the environment marker expression be
     259          # wrapped in parentheses to avoid the following 'and' binding more
     260          # tightly than some other part of the environment expression.
     261  
     262          assert deps == expected
     263  
     264      def test_as_json(self):
     265          md = metadata('distinfo-pkg').json
     266          assert 'name' in md
     267          assert md['keywords'] == ['sample', 'package']
     268          desc = md['description']
     269          assert desc.startswith('Once upon a time\nThere was')
     270          assert len(md['requires_dist']) == 2
     271  
     272      def test_as_json_egg_info(self):
     273          md = metadata('egginfo-pkg').json
     274          assert 'name' in md
     275          assert md['keywords'] == ['sample', 'package']
     276          desc = md['description']
     277          assert desc.startswith('Once upon a time\nThere was')
     278          assert len(md['classifier']) == 2
     279  
     280      def test_as_json_odd_case(self):
     281          self.make_uppercase()
     282          md = metadata('distinfo-pkg').json
     283          assert 'name' in md
     284          assert len(md['requires_dist']) == 2
     285          assert md['keywords'] == ['SAMPLE', 'PACKAGE']
     286  
     287  
     288  class ESC[4;38;5;81mLegacyDots(ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mDistInfoPkgWithDotLegacy, ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     289      def test_name_normalization(self):
     290          names = 'pkg.dot', 'pkg_dot', 'pkg-dot', 'pkg..dot', 'Pkg.Dot'
     291          for name in names:
     292              with self.subTest(name):
     293                  assert distribution(name).metadata['Name'] == 'pkg.dot'
     294  
     295      def test_name_normalization_versionless_egg_info(self):
     296          names = 'pkg.lot', 'pkg_lot', 'pkg-lot', 'pkg..lot', 'Pkg.Lot'
     297          for name in names:
     298              with self.subTest(name):
     299                  assert distribution(name).metadata['Name'] == 'pkg.lot'
     300  
     301  
     302  class ESC[4;38;5;81mOffSysPathTests(ESC[4;38;5;149mfixturesESC[4;38;5;149m.ESC[4;38;5;149mDistInfoPkgOffPath, ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     303      def test_find_distributions_specified_path(self):
     304          dists = Distribution.discover(path=[str(self.site_dir)])
     305          assert any(dist.metadata['Name'] == 'distinfo-pkg' for dist in dists)
     306  
     307      def test_distribution_at_pathlib(self):
     308          """Demonstrate how to load metadata direct from a directory."""
     309          dist_info_path = self.site_dir / 'distinfo_pkg-1.0.0.dist-info'
     310          dist = Distribution.at(dist_info_path)
     311          assert dist.version == '1.0.0'
     312  
     313      def test_distribution_at_str(self):
     314          dist_info_path = self.site_dir / 'distinfo_pkg-1.0.0.dist-info'
     315          dist = Distribution.at(str(dist_info_path))
     316          assert dist.version == '1.0.0'
     317  
     318  
     319  class ESC[4;38;5;81mInvalidateCache(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     320      def test_invalidate_cache(self):
     321          # No externally observable behavior, but ensures test coverage...
     322          importlib.invalidate_caches()