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