]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Update implementation based on importlib_metadata 0.10
authorJason R. Coombs <jaraco@jaraco.com>
Wed, 8 May 2019 14:02:31 +0000 (10:02 -0400)
committerJason R. Coombs <jaraco@jaraco.com>
Wed, 8 May 2019 14:02:31 +0000 (10:02 -0400)
Doc/library/importlib_metadata.rst
Lib/importlib/metadata/_hooks.py
Lib/importlib/metadata/api.py
Lib/importlib/metadata/zipp.py [deleted file]
Lib/test/test_importlib/fixtures.py
Lib/test/test_importlib/test_metadata_api.py

index 93dad3df1f143a9131e8797e480d4b556e197b8c..3ab66cdfb7054f6a1f779aa99b0d8d7fd6c583f2 100644 (file)
@@ -22,6 +22,13 @@ By default, package metadata can live on the file system or in wheels 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
+   <https://www.python.org/dev/peps/pep-0427/#is-it-possible-to-import-python-code-directly-from-a-wheel-file>`_,
+   which states that relying on this format is discouraged, and use is
+   at your own risk.
+
 
 Overview
 ========
@@ -56,40 +63,6 @@ You can also get a :ref:`distribution's version number <version>`, list its
 :ref:`requirements`_.
 
 
-Distributions
-=============
-
-.. CAUTION:: The ``Distribution`` class described here may or may not end up
-             in the final stable public API.  Consider this class `provisional
-             <https://www.python.org/dev/peps/pep-0411/>`_ until the 1.0
-             release.
-
-While the above API is the most common and convenient usage, you can get all
-of that information from the ``Distribution`` class.  A ``Distribution`` is an
-abstract object that represents the metadata for a Python package.  You can
-get the ``Distribution`` instance::
-
-    >>> from importlib_metadata import distribution
-    >>> dist = distribution('wheel')
-
-Thus, an alternative way to get the version number is through the
-``Distribution`` instance::
-
-    >>> dist.version
-    '0.32.3'
-
-There are all kinds of additional metadata available on the ``Distribution``
-instance::
-
-    >>> d.metadata['Requires-Python']
-    '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
-    >>> d.metadata['License']
-    'MIT'
-
-The full set of available metadata is not described here.  See `PEP 566
-<https://www.python.org/dev/peps/pep-0566/>`_ for additional details.
-
-
 Functional API
 ==============
 
@@ -198,6 +171,34 @@ function.  Note that this returns an iterator::
     ["pytest (>=3.0.0) ; extra == 'test'"]
 
 
+Distributions
+=============
+
+While the above API is the most common and convenient usage, you can get all
+of that information from the ``Distribution`` class.  A ``Distribution`` is an
+abstract object that represents the metadata for a Python package.  You can
+get the ``Distribution`` instance::
+
+    >>> from importlib_metadata import distribution
+    >>> dist = distribution('wheel')
+
+Thus, an alternative way to get the version number is through the
+``Distribution`` instance::
+
+    >>> dist.version
+    '0.32.3'
+
+There are all kinds of additional metadata available on the ``Distribution``
+instance::
+
+    >>> d.metadata['Requires-Python']
+    '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
+    >>> d.metadata['License']
+    'MIT'
+
+The full set of available metadata is not described here.  See `PEP 566
+<https://www.python.org/dev/peps/pep-0566/>`_ for additional details.
+
 
 Extending the search algorithm
 ==============================
index 5f9bdb54a2e15c20d010b5d8695a70ec943a5cf7..e624844217dfdfe7892ace61abb0c724b98dca1f 100644 (file)
@@ -1,8 +1,8 @@
 import re
 import sys
+import zipfile
 import itertools
 
-from . import zipp
 from .api import Distribution
 from .abc import DistributionFinder
 from contextlib import suppress
@@ -25,14 +25,12 @@ class NullFinder(DistributionFinder):
         return None
 
 
-@install
-class MetadataPathFinder(NullFinder):
+class MetadataPathBaseFinder(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
@@ -56,21 +54,34 @@ class MetadataPathFinder(NullFinder):
             for path in map(Path, paths)
             )
 
+    @classmethod
+    def _predicate(cls, pattern, root, item):
+        return re.match(pattern, str(item.name), flags=re.IGNORECASE)
+
     @classmethod
     def _search_path(cls, root, pattern):
         if not root.is_dir():
             return ()
         normalized = pattern.replace('-', '_')
+        matcher = cls.search_template.format(pattern=normalized)
+        return (item for item in root.iterdir()
+                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 (
-            item
-            for item in root.iterdir()
-            if item.is_dir()
-            and re.match(
-                cls.search_template.format(pattern=normalized),
-                str(item.name),
-                flags=re.IGNORECASE,
-                )
-            )
+            (root / item).is_file() and
+            re.match(pattern, str(item.name), flags=re.IGNORECASE))
 
 
 class PathDistribution(Distribution):
@@ -79,7 +90,7 @@ class PathDistribution(Distribution):
         self._path = path
 
     def read_text(self, filename):
-        with suppress(FileNotFoundError):
+        with suppress(FileNotFoundError, NotADirectoryError):
             with self._path.joinpath(filename).open(encoding='utf-8') as fp:
                 return fp.read()
         return None
@@ -125,7 +136,7 @@ class WheelMetadataFinder(NullFinder):
 
 class WheelDistribution(Distribution):
     def __init__(self, archive):
-        self._archive = zipp.Path(archive)
+        self._archive = zipfile.Path(archive)
         name, version = archive.name.split('-')[0:2]
         self._dist_info = '{}-{}.dist-info'.format(name, version)
 
index df19aff438bb7a834a0786ffaf59f2b8bdcdc8e4..b95bc454cc6f92fc289db2b21b8729cfcea22f9d 100644 (file)
@@ -179,7 +179,14 @@ class Distribution:
         The returned object will have keys that name the various bits of
         metadata.  See PEP 566 for details.
         """
-        text = self.read_text('METADATA') or self.read_text('PKG-INFO')
+        text = (
+            self.read_text('METADATA')
+            or self.read_text('PKG-INFO')
+            # This last clause is here to support old egg-info files.  Its
+            # effect is to just end up using the PathDistribution's self._path
+            # (which points to the egg-info file) attribute unchanged.
+            or self.read_text('')
+            )
         return email.message_from_string(text)
 
     @property
@@ -230,7 +237,7 @@ class Distribution:
 
     def _read_egg_info_reqs(self):
         source = self.read_text('requires.txt')
-        return self._deps_from_requires_text(source)
+        return source and self._deps_from_requires_text(source)
 
     @classmethod
     def _deps_from_requires_text(cls, source):
diff --git a/Lib/importlib/metadata/zipp.py b/Lib/importlib/metadata/zipp.py
deleted file mode 100644 (file)
index ffd129f..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-"""
->>> root = Path(getfixture('zipfile_abcde'))
->>> a, b = root.iterdir()
->>> a
-Path('abcde.zip', 'a.txt')
->>> b
-Path('abcde.zip', 'b/')
->>> b.name
-'b'
->>> c = b / 'c.txt'
->>> c
-Path('abcde.zip', 'b/c.txt')
->>> c.name
-'c.txt'
->>> c.read_text()
-'content of c'
->>> c.exists()
-True
->>> (b / 'missing.txt').exists()
-False
->>> str(c)
-'abcde.zip/b/c.txt'
-"""
-
-from __future__ import division
-
-import io
-import sys
-import posixpath
-import zipfile
-import operator
-import functools
-
-__metaclass__ = type
-
-
-class Path:
-    __repr = '{self.__class__.__name__}({self.root.filename!r}, {self.at!r})'
-
-    def __init__(self, root, at=''):
-        self.root = root if isinstance(root, zipfile.ZipFile) \
-            else zipfile.ZipFile(self._pathlib_compat(root))
-        self.at = at
-
-    @staticmethod
-    def _pathlib_compat(path):
-        """
-        For path-like objects, convert to a filename for compatibility
-        on Python 3.6.1 and earlier.
-        """
-        try:
-            return path.__fspath__()
-        except AttributeError:
-            return str(path)
-
-    @property
-    def open(self):
-        return functools.partial(self.root.open, self.at)
-
-    @property
-    def name(self):
-        return posixpath.basename(self.at.rstrip('/'))
-
-    def read_text(self, *args, **kwargs):
-        with self.open() as strm:
-            return io.TextIOWrapper(strm, *args, **kwargs).read()
-
-    def read_bytes(self):
-        with self.open() as strm:
-            return strm.read()
-
-    def _is_child(self, path):
-        return posixpath.dirname(path.at.rstrip('/')) == self.at.rstrip('/')
-
-    def _next(self, at):
-        return Path(self.root, at)
-
-    def is_dir(self):
-        return not self.at or self.at.endswith('/')
-
-    def is_file(self):
-        return not self.is_dir()
-
-    def exists(self):
-        return self.at in self.root.namelist()
-
-    def iterdir(self):
-        if not self.is_dir():
-            raise ValueError("Can't listdir a file")
-        names = map(operator.attrgetter('filename'), self.root.infolist())
-        subs = map(self._next, names)
-        return filter(self._is_child, subs)
-
-    def __str__(self):
-        return posixpath.join(self.root.filename, self.at)
-
-    def __repr__(self):
-        return self.__repr.format(self=self)
-
-    def __truediv__(self, add):
-        add = self._pathlib_compat(add)
-        next = posixpath.join(self.at, add)
-        next_dir = posixpath.join(self.at, add, '')
-        names = self.root.namelist()
-        return self._next(
-            next_dir if next not in names and next_dir in names else next
-        )
-
-    if sys.version_info < (3,):
-        __div__ = __truediv__
\ No newline at end of file
index 8a8b4add31cad6b51ff6b42df6ffc74671ad5302..737ea4be7aa39ffa32ec0a7a589cefa7dba8625d 100644 (file)
@@ -127,6 +127,27 @@ class EggInfoPkg(SiteDir):
         build_files(EggInfoPkg.files, prefix=self.site_dir)
 
 
+class EggInfoFile(SiteDir):
+    files = {
+        "egginfo_file.egg-info": """
+            Metadata-Version: 1.0
+            Name: egginfo_file
+            Version: 0.1
+            Summary: An example package
+            Home-page: www.example.com
+            Author: Eric Haffa-Vee
+            Author-email: eric@example.coms
+            License: UNKNOWN
+            Description: UNKNOWN
+            Platform: UNKNOWN
+            """,
+        }
+
+    def setUp(self):
+        super(EggInfoFile, self).setUp()
+        build_files(EggInfoFile.files, prefix=self.site_dir)
+
+
 class LocalPackage:
     def setUp(self):
         self.fixtures = ExitStack()
@@ -136,15 +157,15 @@ class LocalPackage:
 
 
 def build_files(file_defs, prefix=pathlib.Path()):
-    """
-    Build a set of files/directories, as described by the
-    file_defs dictionary.
-    Each key/value pair in the dictionary is interpreted as
-    a filename/contents
-    pair. If the contents value is a dictionary, a directory
-    is created, and the
-    dictionary interpreted as the files within it, recursively.
+    """Build a set of files/directories, as described by the
+
+    file_defs dictionary.  Each key/value pair in the dictionary is
+    interpreted as a filename/contents pair.  If the contents value is a
+    dictionary, a directory is created, and the dictionary interpreted
+    as the files within it, recursively.
+
     For example:
+
     {"README.txt": "A README file",
      "foo": {
         "__init__.py": "",
index a6aa395831d92a7bda810cc7f561529403b87f4b..4aba1af7392b4b4ff0b6625c33d859967433d4e0 100644 (file)
@@ -8,7 +8,12 @@ from collections.abc import Iterator
 from . import fixtures
 
 
-class APITests(fixtures.EggInfoPkg, fixtures.DistInfoPkg, unittest.TestCase):
+class APITests(
+        fixtures.EggInfoPkg,
+        fixtures.DistInfoPkg,
+        fixtures.EggInfoFile,
+        unittest.TestCase):
+
     version_pattern = r'\d+\.\d+(\.\d)?'
 
     def test_retrieves_version_of_self(self):
@@ -85,6 +90,14 @@ class APITests(fixtures.EggInfoPkg, fixtures.DistInfoPkg, unittest.TestCase):
     def test_files_egg_info(self):
         self._test_files(importlib.metadata.files('egginfo-pkg'))
 
+    def test_version_egg_info_file(self):
+        version = importlib_metadata.version('egginfo-file')
+        self.assertEqual(version, '0.1')
+
+    def test_requires_egg_info_file(self):
+        requirements = importlib_metadata.requires('egginfo-file')
+        self.assertIsNone(requirements)
+
     def test_requires(self):
         deps = importlib.metadata.requires('egginfo-pkg')
         assert any(