diff --git a/Doc/library/importlib.metadata.rst b/Doc/library/importlib.metadata.rst
index ad17d939d71..8a5b66d04e1 100644
--- a/Doc/library/importlib.metadata.rst
+++ b/Doc/library/importlib.metadata.rst
@@ -18,17 +18,10 @@ Python's ``site-packages`` directory via tools such as `pip
`_. Specifically,
it means a package with either a discoverable ``dist-info`` or ``egg-info``
directory, and metadata defined by `PEP 566`_ or its older specifications.
-By default, package metadata can live on the file system or in wheels on
+By default, package metadata can live on the file system or in zip archives on
``sys.path``. Through an extension mechanism, the metadata can live almost
anywhere.
-.. note:: Although this package supports loading metadata from wheels
- on ``sys.path``, that support is provisional and does not serve to
- contravene the `PEP 427 directive
- `_,
- which states that relying on this format is discouraged, and use is
- at your own risk.
-
Overview
========
diff --git a/Lib/importlib/metadata/__init__.py b/Lib/importlib/metadata/__init__.py
index 314ece65afe..8d4b0a344cd 100644
--- a/Lib/importlib/metadata/__init__.py
+++ b/Lib/importlib/metadata/__init__.py
@@ -1,17 +1,19 @@
-from .api import distribution, Distribution, PackageNotFoundError # noqa: F401
from .api import (
- metadata, entry_points, version, files, requires, distributions,
- )
+ Distribution, PackageNotFoundError, distribution, distributions,
+ entry_points, files, metadata, requires, version)
# Import for installation side-effects.
from . import _hooks # noqa: F401
__all__ = [
+ 'Distribution',
+ 'PackageNotFoundError',
+ 'distribution',
+ 'distributions',
'entry_points',
'files',
'metadata',
'requires',
'version',
- 'distributions',
]
diff --git a/Lib/importlib/metadata/_hooks.py b/Lib/importlib/metadata/_hooks.py
index e624844217d..f6bed1a67fd 100644
--- a/Lib/importlib/metadata/_hooks.py
+++ b/Lib/importlib/metadata/_hooks.py
@@ -25,12 +25,14 @@ class NullFinder(DistributionFinder):
return None
-class MetadataPathBaseFinder(NullFinder):
+@install
+class MetadataPathFinder(NullFinder):
"""A degenerate finder for distribution packages on the file system.
This finder supplies only a find_distributions() method for versions
of Python that do not have a PathFinder find_distributions().
"""
+ search_template = r'{pattern}(-.*)?\.(dist|egg)-info'
def find_distributions(self, name=None, path=None):
"""Return an iterable of all Distribution instances capable of
@@ -51,9 +53,15 @@ class MetadataPathBaseFinder(NullFinder):
"""
return itertools.chain.from_iterable(
cls._search_path(path, pattern)
- for path in map(Path, paths)
+ for path in map(cls._switch_path, paths)
)
+ @staticmethod
+ def _switch_path(path):
+ with suppress(Exception):
+ return zipfile.Path(path)
+ return Path(path)
+
@classmethod
def _predicate(cls, pattern, root, item):
return re.match(pattern, str(item.name), flags=re.IGNORECASE)
@@ -68,82 +76,15 @@ class MetadataPathBaseFinder(NullFinder):
if cls._predicate(matcher, root, item))
-@install
-class MetadataPathFinder(MetadataPathBaseFinder):
- search_template = r'{pattern}(-.*)?\.(dist|egg)-info'
-
-
-@install
-class MetadataPathEggInfoFileFinder(MetadataPathBaseFinder):
- search_template = r'{pattern}(-.*)?\.egg-info'
-
- @classmethod
- def _predicate(cls, pattern, root, item):
- return (
- (root / item).is_file() and
- re.match(pattern, str(item.name), flags=re.IGNORECASE))
-
-
class PathDistribution(Distribution):
def __init__(self, path):
"""Construct a distribution from a path to the metadata directory."""
self._path = path
def read_text(self, filename):
- with suppress(FileNotFoundError, NotADirectoryError):
- with self._path.joinpath(filename).open(encoding='utf-8') as fp:
- return fp.read()
- return None
+ with suppress(FileNotFoundError, NotADirectoryError, KeyError):
+ return self._path.joinpath(filename).read_text(encoding='utf-8')
read_text.__doc__ = Distribution.read_text.__doc__
def locate_file(self, path):
return self._path.parent / path
-
-
-@install
-class WheelMetadataFinder(NullFinder):
- """A degenerate finder for distribution packages in wheels.
-
- This finder supplies only a find_distributions() method for versions
- of Python that do not have a PathFinder find_distributions().
- """
- search_template = r'{pattern}(-.*)?\.whl'
-
- def find_distributions(self, name=None, path=None):
- """Return an iterable of all Distribution instances capable of
- loading the metadata for packages matching the name
- (or all names if not supplied) along the paths in the list
- of directories ``path`` (defaults to sys.path).
- """
- if path is None:
- path = sys.path
- pattern = '.*' if name is None else re.escape(name)
- found = self._search_paths(pattern, path)
- return map(WheelDistribution, found)
-
- @classmethod
- def _search_paths(cls, pattern, paths):
- return (
- path
- for path in map(Path, paths)
- if re.match(
- cls.search_template.format(pattern=pattern),
- str(path.name),
- flags=re.IGNORECASE,
- )
- )
-
-
-class WheelDistribution(Distribution):
- def __init__(self, archive):
- self._archive = zipfile.Path(archive)
- name, version = archive.name.split('-')[0:2]
- self._dist_info = '{}-{}.dist-info'.format(name, version)
-
- def read_text(self, filename):
- target = self._archive / self._dist_info / filename
- return target.read_text() if target.exists() else None
- read_text.__doc__ = Distribution.read_text.__doc__
-
- def locate_file(self, path):
- return self._archive / path
diff --git a/Lib/importlib/metadata/abc.py b/Lib/importlib/metadata/abc.py
index 1785cf3c1c2..845e41afff7 100644
--- a/Lib/importlib/metadata/abc.py
+++ b/Lib/importlib/metadata/abc.py
@@ -1,6 +1,3 @@
-from __future__ import absolute_import
-
-
import abc
from importlib.abc import MetaPathFinder
diff --git a/Lib/importlib/metadata/api.py b/Lib/importlib/metadata/api.py
index b95bc454cc6..ba5c17120b9 100644
--- a/Lib/importlib/metadata/api.py
+++ b/Lib/importlib/metadata/api.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
import re
import abc
import csv
diff --git a/Lib/test/test_importlib/test_main.py b/Lib/test/test_importlib/test_main.py
index 64270850a9f..926064ed486 100644
--- a/Lib/test/test_importlib/test_main.py
+++ b/Lib/test/test_importlib/test_main.py
@@ -1,33 +1,31 @@
# coding: utf-8
-from __future__ import unicode_literals
import re
import textwrap
import unittest
import importlib
-import importlib.metadata
from . import fixtures
-from importlib.metadata import _hooks
+from importlib.metadata import (
+ Distribution, PackageNotFoundError, _hooks, api, distributions,
+ entry_points, metadata, version)
class BasicTests(fixtures.DistInfoPkg, unittest.TestCase):
version_pattern = r'\d+\.\d+(\.\d)?'
def test_retrieves_version_of_self(self):
- dist = importlib.metadata.Distribution.from_name('distinfo-pkg')
+ 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(importlib.metadata.PackageNotFoundError):
- importlib.metadata.Distribution.from_name('does-not-exist')
+ with self.assertRaises(PackageNotFoundError):
+ Distribution.from_name('does-not-exist')
def test_new_style_classes(self):
- self.assertIsInstance(importlib.metadata.Distribution, type)
+ self.assertIsInstance(Distribution, type)
self.assertIsInstance(_hooks.MetadataPathFinder, type)
- self.assertIsInstance(_hooks.WheelMetadataFinder, type)
- self.assertIsInstance(_hooks.WheelDistribution, type)
class ImportTests(fixtures.DistInfoPkg, unittest.TestCase):
@@ -38,17 +36,17 @@ class ImportTests(fixtures.DistInfoPkg, unittest.TestCase):
importlib.import_module('does_not_exist')
def test_resolve(self):
- entries = dict(importlib.metadata.entry_points()['entries'])
+ entries = dict(entry_points()['entries'])
ep = entries['main']
self.assertEqual(ep.load().__name__, "main")
def test_resolve_without_attr(self):
- ep = importlib.metadata.api.EntryPoint(
+ ep = api.EntryPoint(
name='ep',
value='importlib.metadata.api',
group='grp',
)
- assert ep.load() is importlib.metadata.api
+ assert ep.load() is api
class NameNormalizationTests(fixtures.SiteDir, unittest.TestCase):
@@ -71,7 +69,7 @@ class NameNormalizationTests(fixtures.SiteDir, unittest.TestCase):
uses underscores in the name. Ensure the metadata loads.
"""
pkg_name = self.pkg_with_dashes(self.site_dir)
- assert importlib.metadata.version(pkg_name) == '1.0'
+ assert version(pkg_name) == '1.0'
@staticmethod
def pkg_with_mixed_case(site_dir):
@@ -91,9 +89,9 @@ class NameNormalizationTests(fixtures.SiteDir, unittest.TestCase):
Ensure the metadata loads when queried with any case.
"""
pkg_name = self.pkg_with_mixed_case(self.site_dir)
- assert importlib.metadata.version(pkg_name) == '1.0'
- assert importlib.metadata.version(pkg_name.lower()) == '1.0'
- assert importlib.metadata.version(pkg_name.upper()) == '1.0'
+ assert version(pkg_name) == '1.0'
+ assert version(pkg_name.lower()) == '1.0'
+ assert version(pkg_name.upper()) == '1.0'
class NonASCIITests(fixtures.SiteDir, unittest.TestCase):
@@ -129,22 +127,23 @@ class NonASCIITests(fixtures.SiteDir, unittest.TestCase):
def test_metadata_loads(self):
pkg_name = self.pkg_with_non_ascii_description(self.site_dir)
- meta = importlib.metadata.metadata(pkg_name)
+ 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 = importlib.metadata.metadata(pkg_name)
+ meta = metadata(pkg_name)
assert meta.get_payload() == 'pôrˈtend\n'
-class DiscoveryTests(fixtures.EggInfoPkg, fixtures.DistInfoPkg,
+class DiscoveryTests(fixtures.EggInfoPkg,
+ fixtures.DistInfoPkg,
unittest.TestCase):
def test_package_discovery(self):
- dists = list(importlib.metadata.api.distributions())
+ dists = list(distributions())
assert all(
- isinstance(dist, importlib.metadata.Distribution)
+ isinstance(dist, Distribution)
for dist in dists
)
assert any(
diff --git a/Lib/test/test_importlib/test_metadata_api.py b/Lib/test/test_importlib/test_metadata_api.py
index 8e4c95cc672..f837a6343eb 100644
--- a/Lib/test/test_importlib/test_metadata_api.py
+++ b/Lib/test/test_importlib/test_metadata_api.py
@@ -1,11 +1,15 @@
import re
import textwrap
import unittest
-import importlib.metadata
from collections.abc import Iterator
from . import fixtures
+from importlib.metadata import (
+ Distribution, PackageNotFoundError, distribution,
+ entry_points, files, metadata, requires, version,
+ )
+from importlib.metadata.api import local_distribution
class APITests(
@@ -17,41 +21,39 @@ class APITests(
version_pattern = r'\d+\.\d+(\.\d)?'
def test_retrieves_version_of_self(self):
- version = importlib.metadata.version('egginfo-pkg')
- assert isinstance(version, str)
- assert re.match(self.version_pattern, version)
+ pkg_version = version('egginfo-pkg')
+ assert isinstance(pkg_version, str)
+ assert re.match(self.version_pattern, pkg_version)
- def test_retrieves_version_of_pip(self):
- version = importlib.metadata.version('distinfo-pkg')
- assert isinstance(version, str)
- assert re.match(self.version_pattern, version)
+ def test_retrieves_version_of_distinfo_pkg(self):
+ pkg_version = version('distinfo-pkg')
+ assert isinstance(pkg_version, str)
+ assert re.match(self.version_pattern, pkg_version)
def test_for_name_does_not_exist(self):
- with self.assertRaises(importlib.metadata.PackageNotFoundError):
- importlib.metadata.distribution('does-not-exist')
+ with self.assertRaises(PackageNotFoundError):
+ distribution('does-not-exist')
def test_for_top_level(self):
- distribution = importlib.metadata.distribution('egginfo-pkg')
self.assertEqual(
- distribution.read_text('top_level.txt').strip(),
+ distribution('egginfo-pkg').read_text('top_level.txt').strip(),
'mod')
def test_read_text(self):
top_level = [
- path for path in importlib.metadata.files('egginfo-pkg')
+ path for path in files('egginfo-pkg')
if path.name == 'top_level.txt'
][0]
self.assertEqual(top_level.read_text(), 'mod\n')
def test_entry_points(self):
- entires = importlib.metadata.entry_points()['entries']
- entries = dict(entires)
+ entries = dict(entry_points()['entries'])
ep = entries['main']
self.assertEqual(ep.value, 'mod:main')
self.assertEqual(ep.extras, [])
def test_metadata_for_this_package(self):
- md = importlib.metadata.metadata('egginfo-pkg')
+ md = metadata('egginfo-pkg')
assert md['author'] == 'Steven Ma'
assert md['LICENSE'] == 'Unknown'
assert md['Name'] == 'egginfo-pkg'
@@ -77,7 +79,7 @@ class APITests(
assertRegex = self.assertRegex
util = [
- p for p in importlib.metadata.files('distinfo-pkg')
+ p for p in files('distinfo-pkg')
if p.name == 'mod.py'
][0]
assertRegex(
@@ -85,28 +87,27 @@ class APITests(
'')
def test_files_dist_info(self):
- self._test_files(importlib.metadata.files('distinfo-pkg'))
+ self._test_files(files('distinfo-pkg'))
def test_files_egg_info(self):
- self._test_files(importlib.metadata.files('egginfo-pkg'))
+ self._test_files(files('egginfo-pkg'))
def test_version_egg_info_file(self):
- version = importlib.metadata.version('egginfo-file')
- self.assertEqual(version, '0.1')
+ self.assertEqual(version('egginfo-file'), '0.1')
def test_requires_egg_info_file(self):
- requirements = importlib.metadata.requires('egginfo-file')
+ requirements = requires('egginfo-file')
self.assertIsNone(requirements)
def test_requires(self):
- deps = importlib.metadata.requires('egginfo-pkg')
+ deps = requires('egginfo-pkg')
assert any(
dep == 'wheel >= 1.0; python_version >= "2.7"'
for dep in deps
)
def test_requires_dist_info(self):
- deps = list(importlib.metadata.requires('distinfo-pkg'))
+ deps = list(requires('distinfo-pkg'))
assert deps and all(deps)
def test_more_complex_deps_requires_text(self):
@@ -123,10 +124,7 @@ class APITests(
[extra2:python_version < "3"]
dep5
""")
- deps = sorted(
- importlib.metadata.api.Distribution._deps_from_requires_text(
- requires)
- )
+ deps = sorted(Distribution._deps_from_requires_text(requires))
expected = [
'dep1',
'dep2',
@@ -143,5 +141,5 @@ class APITests(
class LocalProjectTests(fixtures.LocalPackage, unittest.TestCase):
def test_find_local(self):
- dist = importlib.metadata.api.local_distribution()
+ dist = local_distribution()
assert dist.metadata['Name'] == 'egginfo-pkg'
diff --git a/Lib/test/test_importlib/test_zip.py b/Lib/test/test_importlib/test_zip.py
index 4af027a12be..c7c8c0b1843 100644
--- a/Lib/test/test_importlib/test_zip.py
+++ b/Lib/test/test_importlib/test_zip.py
@@ -1,14 +1,10 @@
import sys
import unittest
-import importlib.metadata
+from contextlib import ExitStack
+from importlib.metadata import distribution, entry_points, files, version
from importlib.resources import path
-try:
- from contextlib import ExitStack
-except ImportError:
- from contextlib2 import ExitStack
-
class BespokeLoader:
archive = 'bespoke'
@@ -27,22 +23,20 @@ class TestZip(unittest.TestCase):
self.resources.callback(sys.path.pop, 0)
def test_zip_version(self):
- self.assertEqual(importlib.metadata.version('example'), '21.12')
+ self.assertEqual(version('example'), '21.12')
def test_zip_entry_points(self):
- scripts = dict(importlib.metadata.entry_points()['console_scripts'])
+ scripts = dict(entry_points()['console_scripts'])
entry_point = scripts['example']
self.assertEqual(entry_point.value, 'example:main')
def test_missing_metadata(self):
- distribution = importlib.metadata.distribution('example')
- self.assertIsNone(distribution.read_text('does not exist'))
+ self.assertIsNone(distribution('example').read_text('does not exist'))
def test_case_insensitive(self):
- self.assertEqual(importlib.metadata.version('Example'), '21.12')
+ self.assertEqual(version('Example'), '21.12')
def test_files(self):
- files = importlib.metadata.files('example')
- for file in files:
+ for file in files('example'):
path = str(file.dist.locate_file(file))
assert '.whl/' in path, path