mirror of https://github.com/python/cpython
bpo-44893: Implement EntryPoint as simple class with attributes. (GH-30150)
* bpo-44893: Implement EntryPoint as simple class and deprecate tuple access in favor of attribute access. Syncs with importlib_metadata 4.8.1. * Apply refactorings found in importlib_metadata 4.8.2.
This commit is contained in:
parent
109d966021
commit
04deaee4c8
|
@ -15,10 +15,9 @@ import posixpath
|
|||
import collections
|
||||
|
||||
from . import _adapters, _meta
|
||||
from ._meta import PackageMetadata
|
||||
from ._collections import FreezableDefaultDict, Pair
|
||||
from ._functools import method_cache
|
||||
from ._itertools import unique_everseen
|
||||
from ._functools import method_cache, pass_none
|
||||
from ._itertools import always_iterable, unique_everseen
|
||||
from ._meta import PackageMetadata, SimplePath
|
||||
|
||||
from contextlib import suppress
|
||||
|
@ -121,8 +120,33 @@ class Sectioned:
|
|||
return line and not line.startswith('#')
|
||||
|
||||
|
||||
class EntryPoint(
|
||||
collections.namedtuple('EntryPointBase', 'name value group')):
|
||||
class DeprecatedTuple:
|
||||
"""
|
||||
Provide subscript item access for backward compatibility.
|
||||
|
||||
>>> recwarn = getfixture('recwarn')
|
||||
>>> ep = EntryPoint(name='name', value='value', group='group')
|
||||
>>> ep[:]
|
||||
('name', 'value', 'group')
|
||||
>>> ep[0]
|
||||
'name'
|
||||
>>> len(recwarn)
|
||||
1
|
||||
"""
|
||||
|
||||
_warn = functools.partial(
|
||||
warnings.warn,
|
||||
"EntryPoint tuple interface is deprecated. Access members by name.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
def __getitem__(self, item):
|
||||
self._warn()
|
||||
return self._key()[item]
|
||||
|
||||
|
||||
class EntryPoint(DeprecatedTuple):
|
||||
"""An entry point as defined by Python packaging conventions.
|
||||
|
||||
See `the packaging docs on entry points
|
||||
|
@ -153,6 +177,9 @@ class EntryPoint(
|
|||
|
||||
dist: Optional['Distribution'] = None
|
||||
|
||||
def __init__(self, name, value, group):
|
||||
vars(self).update(name=name, value=value, group=group)
|
||||
|
||||
def load(self):
|
||||
"""Load the entry point from its definition. If only a module
|
||||
is indicated by the value, return that module. Otherwise,
|
||||
|
@ -179,7 +206,7 @@ class EntryPoint(
|
|||
return list(re.finditer(r'\w+', match.group('extras') or ''))
|
||||
|
||||
def _for(self, dist):
|
||||
self.dist = dist
|
||||
vars(self).update(dist=dist)
|
||||
return self
|
||||
|
||||
def __iter__(self):
|
||||
|
@ -193,16 +220,31 @@ class EntryPoint(
|
|||
warnings.warn(msg, DeprecationWarning)
|
||||
return iter((self.name, self))
|
||||
|
||||
def __reduce__(self):
|
||||
return (
|
||||
self.__class__,
|
||||
(self.name, self.value, self.group),
|
||||
)
|
||||
|
||||
def matches(self, **params):
|
||||
attrs = (getattr(self, param) for param in params)
|
||||
return all(map(operator.eq, params.values(), attrs))
|
||||
|
||||
def _key(self):
|
||||
return self.name, self.value, self.group
|
||||
|
||||
def __lt__(self, other):
|
||||
return self._key() < other._key()
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._key() == other._key()
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
raise AttributeError("EntryPoint objects are immutable.")
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
f'EntryPoint(name={self.name!r}, value={self.value!r}, '
|
||||
f'group={self.group!r})'
|
||||
)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._key())
|
||||
|
||||
|
||||
class DeprecatedList(list):
|
||||
"""
|
||||
|
@ -243,37 +285,26 @@ class DeprecatedList(list):
|
|||
stacklevel=2,
|
||||
)
|
||||
|
||||
def __setitem__(self, *args, **kwargs):
|
||||
self._warn()
|
||||
return super().__setitem__(*args, **kwargs)
|
||||
def _wrap_deprecated_method(method_name: str): # type: ignore
|
||||
def wrapped(self, *args, **kwargs):
|
||||
self._warn()
|
||||
return getattr(super(), method_name)(*args, **kwargs)
|
||||
|
||||
def __delitem__(self, *args, **kwargs):
|
||||
self._warn()
|
||||
return super().__delitem__(*args, **kwargs)
|
||||
return wrapped
|
||||
|
||||
def append(self, *args, **kwargs):
|
||||
self._warn()
|
||||
return super().append(*args, **kwargs)
|
||||
|
||||
def reverse(self, *args, **kwargs):
|
||||
self._warn()
|
||||
return super().reverse(*args, **kwargs)
|
||||
|
||||
def extend(self, *args, **kwargs):
|
||||
self._warn()
|
||||
return super().extend(*args, **kwargs)
|
||||
|
||||
def pop(self, *args, **kwargs):
|
||||
self._warn()
|
||||
return super().pop(*args, **kwargs)
|
||||
|
||||
def remove(self, *args, **kwargs):
|
||||
self._warn()
|
||||
return super().remove(*args, **kwargs)
|
||||
|
||||
def __iadd__(self, *args, **kwargs):
|
||||
self._warn()
|
||||
return super().__iadd__(*args, **kwargs)
|
||||
for method_name in [
|
||||
'__setitem__',
|
||||
'__delitem__',
|
||||
'append',
|
||||
'reverse',
|
||||
'extend',
|
||||
'pop',
|
||||
'remove',
|
||||
'__iadd__',
|
||||
'insert',
|
||||
'sort',
|
||||
]:
|
||||
locals()[method_name] = _wrap_deprecated_method(method_name)
|
||||
|
||||
def __add__(self, other):
|
||||
if not isinstance(other, tuple):
|
||||
|
@ -281,14 +312,6 @@ class DeprecatedList(list):
|
|||
other = tuple(other)
|
||||
return self.__class__(tuple(self) + other)
|
||||
|
||||
def insert(self, *args, **kwargs):
|
||||
self._warn()
|
||||
return super().insert(*args, **kwargs)
|
||||
|
||||
def sort(self, *args, **kwargs):
|
||||
self._warn()
|
||||
return super().sort(*args, **kwargs)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, tuple):
|
||||
self._warn()
|
||||
|
@ -333,7 +356,7 @@ class EntryPoints(DeprecatedList):
|
|||
"""
|
||||
Return the set of all names of all entry points.
|
||||
"""
|
||||
return set(ep.name for ep in self)
|
||||
return {ep.name for ep in self}
|
||||
|
||||
@property
|
||||
def groups(self):
|
||||
|
@ -344,21 +367,17 @@ class EntryPoints(DeprecatedList):
|
|||
>>> EntryPoints().groups
|
||||
set()
|
||||
"""
|
||||
return set(ep.group for ep in self)
|
||||
return {ep.group for ep in self}
|
||||
|
||||
@classmethod
|
||||
def _from_text_for(cls, text, dist):
|
||||
return cls(ep._for(dist) for ep in cls._from_text(text))
|
||||
|
||||
@classmethod
|
||||
def _from_text(cls, text):
|
||||
return itertools.starmap(EntryPoint, cls._parse_groups(text or ''))
|
||||
|
||||
@staticmethod
|
||||
def _parse_groups(text):
|
||||
def _from_text(text):
|
||||
return (
|
||||
(item.value.name, item.value.value, item.name)
|
||||
for item in Sectioned.section_pairs(text)
|
||||
EntryPoint(name=item.value.name, value=item.value.value, group=item.name)
|
||||
for item in Sectioned.section_pairs(text or '')
|
||||
)
|
||||
|
||||
|
||||
|
@ -611,7 +630,6 @@ class Distribution:
|
|||
missing.
|
||||
Result may be empty if the metadata exists but is empty.
|
||||
"""
|
||||
file_lines = self._read_files_distinfo() or self._read_files_egginfo()
|
||||
|
||||
def make_file(name, hash=None, size_str=None):
|
||||
result = PackagePath(name)
|
||||
|
@ -620,7 +638,11 @@ class Distribution:
|
|||
result.dist = self
|
||||
return result
|
||||
|
||||
return file_lines and list(starmap(make_file, csv.reader(file_lines)))
|
||||
@pass_none
|
||||
def make_files(lines):
|
||||
return list(starmap(make_file, csv.reader(lines)))
|
||||
|
||||
return make_files(self._read_files_distinfo() or self._read_files_egginfo())
|
||||
|
||||
def _read_files_distinfo(self):
|
||||
"""
|
||||
|
@ -742,6 +764,9 @@ class FastPath:
|
|||
"""
|
||||
Micro-optimized class for searching a path for
|
||||
children.
|
||||
|
||||
>>> FastPath('').children()
|
||||
['...']
|
||||
"""
|
||||
|
||||
@functools.lru_cache() # type: ignore
|
||||
|
@ -1011,6 +1036,18 @@ def packages_distributions() -> Mapping[str, List[str]]:
|
|||
"""
|
||||
pkg_to_dist = collections.defaultdict(list)
|
||||
for dist in distributions():
|
||||
for pkg in (dist.read_text('top_level.txt') or '').split():
|
||||
for pkg in _top_level_declared(dist) or _top_level_inferred(dist):
|
||||
pkg_to_dist[pkg].append(dist.metadata['Name'])
|
||||
return dict(pkg_to_dist)
|
||||
|
||||
|
||||
def _top_level_declared(dist):
|
||||
return (dist.read_text('top_level.txt') or '').split()
|
||||
|
||||
|
||||
def _top_level_inferred(dist):
|
||||
return {
|
||||
f.parts[0] if len(f.parts) > 1 else f.with_suffix('').name
|
||||
for f in always_iterable(dist.files)
|
||||
if f.suffix == ".py"
|
||||
}
|
||||
|
|
|
@ -83,3 +83,22 @@ def method_cache(method, cache_wrapper=None):
|
|||
wrapper.cache_clear = lambda: None
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
# From jaraco.functools 3.3
|
||||
def pass_none(func):
|
||||
"""
|
||||
Wrap func so it's not called if its first param is None
|
||||
|
||||
>>> print_text = pass_none(print)
|
||||
>>> print_text('text')
|
||||
text
|
||||
>>> print_text(None)
|
||||
"""
|
||||
|
||||
@functools.wraps(func)
|
||||
def wrapper(param, *args, **kwargs):
|
||||
if param is not None:
|
||||
return func(param, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
|
|
@ -17,3 +17,57 @@ def unique_everseen(iterable, key=None):
|
|||
if k not in seen:
|
||||
seen_add(k)
|
||||
yield element
|
||||
|
||||
|
||||
# copied from more_itertools 8.8
|
||||
def always_iterable(obj, base_type=(str, bytes)):
|
||||
"""If *obj* is iterable, return an iterator over its items::
|
||||
|
||||
>>> obj = (1, 2, 3)
|
||||
>>> list(always_iterable(obj))
|
||||
[1, 2, 3]
|
||||
|
||||
If *obj* is not iterable, return a one-item iterable containing *obj*::
|
||||
|
||||
>>> obj = 1
|
||||
>>> list(always_iterable(obj))
|
||||
[1]
|
||||
|
||||
If *obj* is ``None``, return an empty iterable:
|
||||
|
||||
>>> obj = None
|
||||
>>> list(always_iterable(None))
|
||||
[]
|
||||
|
||||
By default, binary and text strings are not considered iterable::
|
||||
|
||||
>>> obj = 'foo'
|
||||
>>> list(always_iterable(obj))
|
||||
['foo']
|
||||
|
||||
If *base_type* is set, objects for which ``isinstance(obj, base_type)``
|
||||
returns ``True`` won't be considered iterable.
|
||||
|
||||
>>> obj = {'a': 1}
|
||||
>>> list(always_iterable(obj)) # Iterate over the dict's keys
|
||||
['a']
|
||||
>>> list(always_iterable(obj, base_type=dict)) # Treat dicts as a unit
|
||||
[{'a': 1}]
|
||||
|
||||
Set *base_type* to ``None`` to avoid any special handling and treat objects
|
||||
Python considers iterable as iterable:
|
||||
|
||||
>>> obj = 'foo'
|
||||
>>> list(always_iterable(obj, base_type=None))
|
||||
['f', 'o', 'o']
|
||||
"""
|
||||
if obj is None:
|
||||
return iter(())
|
||||
|
||||
if (base_type is not None) and isinstance(obj, base_type):
|
||||
return iter((obj,))
|
||||
|
||||
try:
|
||||
return iter(obj)
|
||||
except TypeError:
|
||||
return iter((obj,))
|
||||
|
|
|
@ -37,7 +37,7 @@ class SimplePath(Protocol):
|
|||
def joinpath(self) -> 'SimplePath':
|
||||
... # pragma: no cover
|
||||
|
||||
def __div__(self) -> 'SimplePath':
|
||||
def __truediv__(self) -> 'SimplePath':
|
||||
... # pragma: no cover
|
||||
|
||||
def parent(self) -> 'SimplePath':
|
||||
|
|
|
@ -80,7 +80,7 @@ class FoldedCase(str):
|
|||
return hash(self.lower())
|
||||
|
||||
def __contains__(self, other):
|
||||
return super(FoldedCase, self).lower().__contains__(other.lower())
|
||||
return super().lower().__contains__(other.lower())
|
||||
|
||||
def in_(self, other):
|
||||
"Does self appear in other?"
|
||||
|
@ -89,7 +89,7 @@ class FoldedCase(str):
|
|||
# cache lower since it's likely to be called frequently.
|
||||
@method_cache
|
||||
def lower(self):
|
||||
return super(FoldedCase, self).lower()
|
||||
return super().lower()
|
||||
|
||||
def index(self, sub):
|
||||
return self.lower().index(sub.lower())
|
||||
|
|
Binary file not shown.
|
@ -8,8 +8,17 @@ import textwrap
|
|||
import contextlib
|
||||
|
||||
from test.support.os_helper import FS_NONASCII
|
||||
from test.support import requires_zlib
|
||||
from typing import Dict, Union
|
||||
|
||||
try:
|
||||
from importlib import resources
|
||||
|
||||
getattr(resources, 'files')
|
||||
getattr(resources, 'as_file')
|
||||
except (ImportError, AttributeError):
|
||||
import importlib_resources as resources # type: ignore
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def tempdir():
|
||||
|
@ -54,7 +63,7 @@ class Fixtures:
|
|||
|
||||
class SiteDir(Fixtures):
|
||||
def setUp(self):
|
||||
super(SiteDir, self).setUp()
|
||||
super().setUp()
|
||||
self.site_dir = self.fixtures.enter_context(tempdir())
|
||||
|
||||
|
||||
|
@ -69,7 +78,7 @@ class OnSysPath(Fixtures):
|
|||
sys.path.remove(str(dir))
|
||||
|
||||
def setUp(self):
|
||||
super(OnSysPath, self).setUp()
|
||||
super().setUp()
|
||||
self.fixtures.enter_context(self.add_sys_path(self.site_dir))
|
||||
|
||||
|
||||
|
@ -106,7 +115,7 @@ class DistInfoPkg(OnSysPath, SiteDir):
|
|||
}
|
||||
|
||||
def setUp(self):
|
||||
super(DistInfoPkg, self).setUp()
|
||||
super().setUp()
|
||||
build_files(DistInfoPkg.files, self.site_dir)
|
||||
|
||||
def make_uppercase(self):
|
||||
|
@ -131,7 +140,7 @@ class DistInfoPkgWithDot(OnSysPath, SiteDir):
|
|||
}
|
||||
|
||||
def setUp(self):
|
||||
super(DistInfoPkgWithDot, self).setUp()
|
||||
super().setUp()
|
||||
build_files(DistInfoPkgWithDot.files, self.site_dir)
|
||||
|
||||
|
||||
|
@ -152,13 +161,13 @@ class DistInfoPkgWithDotLegacy(OnSysPath, SiteDir):
|
|||
}
|
||||
|
||||
def setUp(self):
|
||||
super(DistInfoPkgWithDotLegacy, self).setUp()
|
||||
super().setUp()
|
||||
build_files(DistInfoPkgWithDotLegacy.files, self.site_dir)
|
||||
|
||||
|
||||
class DistInfoPkgOffPath(SiteDir):
|
||||
def setUp(self):
|
||||
super(DistInfoPkgOffPath, self).setUp()
|
||||
super().setUp()
|
||||
build_files(DistInfoPkg.files, self.site_dir)
|
||||
|
||||
|
||||
|
@ -198,7 +207,7 @@ class EggInfoPkg(OnSysPath, SiteDir):
|
|||
}
|
||||
|
||||
def setUp(self):
|
||||
super(EggInfoPkg, self).setUp()
|
||||
super().setUp()
|
||||
build_files(EggInfoPkg.files, prefix=self.site_dir)
|
||||
|
||||
|
||||
|
@ -219,7 +228,7 @@ class EggInfoFile(OnSysPath, SiteDir):
|
|||
}
|
||||
|
||||
def setUp(self):
|
||||
super(EggInfoFile, self).setUp()
|
||||
super().setUp()
|
||||
build_files(EggInfoFile.files, prefix=self.site_dir)
|
||||
|
||||
|
||||
|
@ -285,3 +294,20 @@ def DALS(str):
|
|||
class NullFinder:
|
||||
def find_module(self, name):
|
||||
pass
|
||||
|
||||
|
||||
@requires_zlib()
|
||||
class ZipFixtures:
|
||||
root = 'test.test_importlib.data'
|
||||
|
||||
def _fixture_on_path(self, filename):
|
||||
pkg_file = resources.files(self.root).joinpath(filename)
|
||||
file = self.resources.enter_context(resources.as_file(pkg_file))
|
||||
assert file.name.startswith('example'), file.name
|
||||
sys.path.insert(0, str(file))
|
||||
self.resources.callback(sys.path.pop, 0)
|
||||
|
||||
def setUp(self):
|
||||
# Add self.zip_name to the front of sys.path.
|
||||
self.resources = contextlib.ExitStack()
|
||||
self.addCleanup(self.resources.close)
|
||||
|
|
|
@ -19,6 +19,7 @@ from importlib.metadata import (
|
|||
distributions,
|
||||
entry_points,
|
||||
metadata,
|
||||
packages_distributions,
|
||||
version,
|
||||
)
|
||||
|
||||
|
@ -203,7 +204,7 @@ class InaccessibleSysPath(fixtures.OnSysPath, ffs.TestCase):
|
|||
site_dir = '/access-denied'
|
||||
|
||||
def setUp(self):
|
||||
super(InaccessibleSysPath, self).setUp()
|
||||
super().setUp()
|
||||
self.setUpPyfakefs()
|
||||
self.fs.create_dir(self.site_dir, perm_bits=000)
|
||||
|
||||
|
@ -217,13 +218,21 @@ class InaccessibleSysPath(fixtures.OnSysPath, ffs.TestCase):
|
|||
|
||||
class TestEntryPoints(unittest.TestCase):
|
||||
def __init__(self, *args):
|
||||
super(TestEntryPoints, self).__init__(*args)
|
||||
self.ep = importlib.metadata.EntryPoint('name', 'value', 'group')
|
||||
super().__init__(*args)
|
||||
self.ep = importlib.metadata.EntryPoint(
|
||||
name='name', value='value', group='group'
|
||||
)
|
||||
|
||||
def test_entry_point_pickleable(self):
|
||||
revived = pickle.loads(pickle.dumps(self.ep))
|
||||
assert revived == self.ep
|
||||
|
||||
def test_positional_args(self):
|
||||
"""
|
||||
Capture legacy (namedtuple) construction, discouraged.
|
||||
"""
|
||||
EntryPoint('name', 'value', 'group')
|
||||
|
||||
def test_immutable(self):
|
||||
"""EntryPoints should be immutable"""
|
||||
with self.assertRaises(AttributeError):
|
||||
|
@ -254,8 +263,8 @@ class TestEntryPoints(unittest.TestCase):
|
|||
# EntryPoint objects are sortable, but result is undefined.
|
||||
sorted(
|
||||
[
|
||||
EntryPoint('b', 'val', 'group'),
|
||||
EntryPoint('a', 'val', 'group'),
|
||||
EntryPoint(name='b', value='val', group='group'),
|
||||
EntryPoint(name='a', value='val', group='group'),
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -271,3 +280,38 @@ class FileSystem(
|
|||
prefix=self.site_dir,
|
||||
)
|
||||
list(distributions())
|
||||
|
||||
|
||||
class PackagesDistributionsPrebuiltTest(fixtures.ZipFixtures, unittest.TestCase):
|
||||
def test_packages_distributions_example(self):
|
||||
self._fixture_on_path('example-21.12-py3-none-any.whl')
|
||||
assert packages_distributions()['example'] == ['example']
|
||||
|
||||
def test_packages_distributions_example2(self):
|
||||
"""
|
||||
Test packages_distributions on a wheel built
|
||||
by trampolim.
|
||||
"""
|
||||
self._fixture_on_path('example2-1.0.0-py3-none-any.whl')
|
||||
assert packages_distributions()['example2'] == ['example2']
|
||||
|
||||
|
||||
class PackagesDistributionsTest(
|
||||
fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase
|
||||
):
|
||||
def test_packages_distributions_neither_toplevel_nor_files(self):
|
||||
"""
|
||||
Test a package built without 'top-level.txt' or a file list.
|
||||
"""
|
||||
fixtures.build_files(
|
||||
{
|
||||
'trim_example-1.0.0.dist-info': {
|
||||
'METADATA': """
|
||||
Name: trim_example
|
||||
Version: 1.0.0
|
||||
""",
|
||||
}
|
||||
},
|
||||
prefix=self.site_dir,
|
||||
)
|
||||
packages_distributions()
|
||||
|
|
|
@ -21,7 +21,7 @@ from importlib.metadata import (
|
|||
@contextlib.contextmanager
|
||||
def suppress_known_deprecation():
|
||||
with warnings.catch_warnings(record=True) as ctx:
|
||||
warnings.simplefilter('default')
|
||||
warnings.simplefilter('default', category=DeprecationWarning)
|
||||
yield ctx
|
||||
|
||||
|
||||
|
@ -113,7 +113,7 @@ class APITests(
|
|||
for ep in entries
|
||||
)
|
||||
# ns:sub doesn't exist in alt_pkg
|
||||
assert 'ns:sub' not in entries
|
||||
assert 'ns:sub' not in entries.names
|
||||
|
||||
def test_entry_points_missing_name(self):
|
||||
with self.assertRaises(KeyError):
|
||||
|
@ -194,10 +194,8 @@ class APITests(
|
|||
file.read_text()
|
||||
|
||||
def test_file_hash_repr(self):
|
||||
assertRegex = self.assertRegex
|
||||
|
||||
util = [p for p in files('distinfo-pkg') if p.name == 'mod.py'][0]
|
||||
assertRegex(repr(util.hash), '<FileHash mode: sha256 value: .*>')
|
||||
self.assertRegex(repr(util.hash), '<FileHash mode: sha256 value: .*>')
|
||||
|
||||
def test_files_dist_info(self):
|
||||
self._test_files(files('distinfo-pkg'))
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import sys
|
||||
import unittest
|
||||
|
||||
from contextlib import ExitStack
|
||||
from . import fixtures
|
||||
from importlib.metadata import (
|
||||
PackageNotFoundError,
|
||||
distribution,
|
||||
|
@ -10,27 +10,11 @@ from importlib.metadata import (
|
|||
files,
|
||||
version,
|
||||
)
|
||||
from importlib import resources
|
||||
|
||||
from test.support import requires_zlib
|
||||
|
||||
|
||||
@requires_zlib()
|
||||
class TestZip(unittest.TestCase):
|
||||
root = 'test.test_importlib.data'
|
||||
|
||||
def _fixture_on_path(self, filename):
|
||||
pkg_file = resources.files(self.root).joinpath(filename)
|
||||
file = self.resources.enter_context(resources.as_file(pkg_file))
|
||||
assert file.name.startswith('example-'), file.name
|
||||
sys.path.insert(0, str(file))
|
||||
self.resources.callback(sys.path.pop, 0)
|
||||
|
||||
class TestZip(fixtures.ZipFixtures, unittest.TestCase):
|
||||
def setUp(self):
|
||||
# Find the path to the example-*.whl so we can add it to the front of
|
||||
# sys.path, where we'll then try to find the metadata thereof.
|
||||
self.resources = ExitStack()
|
||||
self.addCleanup(self.resources.close)
|
||||
super().setUp()
|
||||
self._fixture_on_path('example-21.12-py3-none-any.whl')
|
||||
|
||||
def test_zip_version(self):
|
||||
|
@ -63,13 +47,9 @@ class TestZip(unittest.TestCase):
|
|||
assert len(dists) == 1
|
||||
|
||||
|
||||
@requires_zlib()
|
||||
class TestEgg(TestZip):
|
||||
def setUp(self):
|
||||
# Find the path to the example-*.egg so we can add it to the front of
|
||||
# sys.path, where we'll then try to find the metadata thereof.
|
||||
self.resources = ExitStack()
|
||||
self.addCleanup(self.resources.close)
|
||||
super().setUp()
|
||||
self._fixture_on_path('example-21.12-py3.6.egg')
|
||||
|
||||
def test_files(self):
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
EntryPoint objects are no longer tuples. Recommended means to access is by
|
||||
attribute ('.name', '.group') or accessor ('.load()'). Access by index is
|
||||
deprecated and will raise deprecation warning.
|
Loading…
Reference in New Issue