Apply changes from importlib_metadata 0.11

This commit is contained in:
Jason R. Coombs 2019-05-10 09:11:01 -04:00
parent 071cbd4ea1
commit 21eaaf47ac
8 changed files with 73 additions and 151 deletions

View file

@ -18,17 +18,10 @@ Python's ``site-packages`` directory via tools such as `pip
<https://pypi.org/project/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
<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
========

View file

@ -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',
]

View file

@ -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

View file

@ -1,6 +1,3 @@
from __future__ import absolute_import
import abc
from importlib.abc import MetaPathFinder

View file

@ -1,5 +1,3 @@
from __future__ import absolute_import
import re
import abc
import csv

View file

@ -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(

View file

@ -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(
'<FileHash mode: sha256 value: .*>')
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'

View file

@ -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