mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
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
11 changed files with 267 additions and 106 deletions
|
@ -15,10 +15,9 @@ import posixpath
|
||||||
import collections
|
import collections
|
||||||
|
|
||||||
from . import _adapters, _meta
|
from . import _adapters, _meta
|
||||||
from ._meta import PackageMetadata
|
|
||||||
from ._collections import FreezableDefaultDict, Pair
|
from ._collections import FreezableDefaultDict, Pair
|
||||||
from ._functools import method_cache
|
from ._functools import method_cache, pass_none
|
||||||
from ._itertools import unique_everseen
|
from ._itertools import always_iterable, unique_everseen
|
||||||
from ._meta import PackageMetadata, SimplePath
|
from ._meta import PackageMetadata, SimplePath
|
||||||
|
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
@ -121,8 +120,33 @@ class Sectioned:
|
||||||
return line and not line.startswith('#')
|
return line and not line.startswith('#')
|
||||||
|
|
||||||
|
|
||||||
class EntryPoint(
|
class DeprecatedTuple:
|
||||||
collections.namedtuple('EntryPointBase', 'name value group')):
|
"""
|
||||||
|
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.
|
"""An entry point as defined by Python packaging conventions.
|
||||||
|
|
||||||
See `the packaging docs on entry points
|
See `the packaging docs on entry points
|
||||||
|
@ -153,6 +177,9 @@ class EntryPoint(
|
||||||
|
|
||||||
dist: Optional['Distribution'] = None
|
dist: Optional['Distribution'] = None
|
||||||
|
|
||||||
|
def __init__(self, name, value, group):
|
||||||
|
vars(self).update(name=name, value=value, group=group)
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
"""Load the entry point from its definition. If only a module
|
"""Load the entry point from its definition. If only a module
|
||||||
is indicated by the value, return that module. Otherwise,
|
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 ''))
|
return list(re.finditer(r'\w+', match.group('extras') or ''))
|
||||||
|
|
||||||
def _for(self, dist):
|
def _for(self, dist):
|
||||||
self.dist = dist
|
vars(self).update(dist=dist)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
|
@ -193,16 +220,31 @@ class EntryPoint(
|
||||||
warnings.warn(msg, DeprecationWarning)
|
warnings.warn(msg, DeprecationWarning)
|
||||||
return iter((self.name, self))
|
return iter((self.name, self))
|
||||||
|
|
||||||
def __reduce__(self):
|
|
||||||
return (
|
|
||||||
self.__class__,
|
|
||||||
(self.name, self.value, self.group),
|
|
||||||
)
|
|
||||||
|
|
||||||
def matches(self, **params):
|
def matches(self, **params):
|
||||||
attrs = (getattr(self, param) for param in params)
|
attrs = (getattr(self, param) for param in params)
|
||||||
return all(map(operator.eq, params.values(), attrs))
|
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):
|
class DeprecatedList(list):
|
||||||
"""
|
"""
|
||||||
|
@ -243,37 +285,26 @@ class DeprecatedList(list):
|
||||||
stacklevel=2,
|
stacklevel=2,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __setitem__(self, *args, **kwargs):
|
def _wrap_deprecated_method(method_name: str): # type: ignore
|
||||||
|
def wrapped(self, *args, **kwargs):
|
||||||
self._warn()
|
self._warn()
|
||||||
return super().__setitem__(*args, **kwargs)
|
return getattr(super(), method_name)(*args, **kwargs)
|
||||||
|
|
||||||
def __delitem__(self, *args, **kwargs):
|
return wrapped
|
||||||
self._warn()
|
|
||||||
return super().__delitem__(*args, **kwargs)
|
|
||||||
|
|
||||||
def append(self, *args, **kwargs):
|
for method_name in [
|
||||||
self._warn()
|
'__setitem__',
|
||||||
return super().append(*args, **kwargs)
|
'__delitem__',
|
||||||
|
'append',
|
||||||
def reverse(self, *args, **kwargs):
|
'reverse',
|
||||||
self._warn()
|
'extend',
|
||||||
return super().reverse(*args, **kwargs)
|
'pop',
|
||||||
|
'remove',
|
||||||
def extend(self, *args, **kwargs):
|
'__iadd__',
|
||||||
self._warn()
|
'insert',
|
||||||
return super().extend(*args, **kwargs)
|
'sort',
|
||||||
|
]:
|
||||||
def pop(self, *args, **kwargs):
|
locals()[method_name] = _wrap_deprecated_method(method_name)
|
||||||
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)
|
|
||||||
|
|
||||||
def __add__(self, other):
|
def __add__(self, other):
|
||||||
if not isinstance(other, tuple):
|
if not isinstance(other, tuple):
|
||||||
|
@ -281,14 +312,6 @@ class DeprecatedList(list):
|
||||||
other = tuple(other)
|
other = tuple(other)
|
||||||
return self.__class__(tuple(self) + 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):
|
def __eq__(self, other):
|
||||||
if not isinstance(other, tuple):
|
if not isinstance(other, tuple):
|
||||||
self._warn()
|
self._warn()
|
||||||
|
@ -333,7 +356,7 @@ class EntryPoints(DeprecatedList):
|
||||||
"""
|
"""
|
||||||
Return the set of all names of all entry points.
|
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
|
@property
|
||||||
def groups(self):
|
def groups(self):
|
||||||
|
@ -344,21 +367,17 @@ class EntryPoints(DeprecatedList):
|
||||||
>>> EntryPoints().groups
|
>>> EntryPoints().groups
|
||||||
set()
|
set()
|
||||||
"""
|
"""
|
||||||
return set(ep.group for ep in self)
|
return {ep.group for ep in self}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_text_for(cls, text, dist):
|
def _from_text_for(cls, text, dist):
|
||||||
return cls(ep._for(dist) for ep in cls._from_text(text))
|
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
|
@staticmethod
|
||||||
def _parse_groups(text):
|
def _from_text(text):
|
||||||
return (
|
return (
|
||||||
(item.value.name, item.value.value, item.name)
|
EntryPoint(name=item.value.name, value=item.value.value, group=item.name)
|
||||||
for item in Sectioned.section_pairs(text)
|
for item in Sectioned.section_pairs(text or '')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -611,7 +630,6 @@ class Distribution:
|
||||||
missing.
|
missing.
|
||||||
Result may be empty if the metadata exists but is empty.
|
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):
|
def make_file(name, hash=None, size_str=None):
|
||||||
result = PackagePath(name)
|
result = PackagePath(name)
|
||||||
|
@ -620,7 +638,11 @@ class Distribution:
|
||||||
result.dist = self
|
result.dist = self
|
||||||
return result
|
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):
|
def _read_files_distinfo(self):
|
||||||
"""
|
"""
|
||||||
|
@ -742,6 +764,9 @@ class FastPath:
|
||||||
"""
|
"""
|
||||||
Micro-optimized class for searching a path for
|
Micro-optimized class for searching a path for
|
||||||
children.
|
children.
|
||||||
|
|
||||||
|
>>> FastPath('').children()
|
||||||
|
['...']
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@functools.lru_cache() # type: ignore
|
@functools.lru_cache() # type: ignore
|
||||||
|
@ -1011,6 +1036,18 @@ def packages_distributions() -> Mapping[str, List[str]]:
|
||||||
"""
|
"""
|
||||||
pkg_to_dist = collections.defaultdict(list)
|
pkg_to_dist = collections.defaultdict(list)
|
||||||
for dist in distributions():
|
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'])
|
pkg_to_dist[pkg].append(dist.metadata['Name'])
|
||||||
return dict(pkg_to_dist)
|
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
|
wrapper.cache_clear = lambda: None
|
||||||
|
|
||||||
return wrapper
|
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:
|
if k not in seen:
|
||||||
seen_add(k)
|
seen_add(k)
|
||||||
yield element
|
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':
|
def joinpath(self) -> 'SimplePath':
|
||||||
... # pragma: no cover
|
... # pragma: no cover
|
||||||
|
|
||||||
def __div__(self) -> 'SimplePath':
|
def __truediv__(self) -> 'SimplePath':
|
||||||
... # pragma: no cover
|
... # pragma: no cover
|
||||||
|
|
||||||
def parent(self) -> 'SimplePath':
|
def parent(self) -> 'SimplePath':
|
||||||
|
|
|
@ -80,7 +80,7 @@ class FoldedCase(str):
|
||||||
return hash(self.lower())
|
return hash(self.lower())
|
||||||
|
|
||||||
def __contains__(self, other):
|
def __contains__(self, other):
|
||||||
return super(FoldedCase, self).lower().__contains__(other.lower())
|
return super().lower().__contains__(other.lower())
|
||||||
|
|
||||||
def in_(self, other):
|
def in_(self, other):
|
||||||
"Does self appear in other?"
|
"Does self appear in other?"
|
||||||
|
@ -89,7 +89,7 @@ class FoldedCase(str):
|
||||||
# cache lower since it's likely to be called frequently.
|
# cache lower since it's likely to be called frequently.
|
||||||
@method_cache
|
@method_cache
|
||||||
def lower(self):
|
def lower(self):
|
||||||
return super(FoldedCase, self).lower()
|
return super().lower()
|
||||||
|
|
||||||
def index(self, sub):
|
def index(self, sub):
|
||||||
return self.lower().index(sub.lower())
|
return self.lower().index(sub.lower())
|
||||||
|
|
BIN
Lib/test/test_importlib/data/example2-1.0.0-py3-none-any.whl
Normal file
BIN
Lib/test/test_importlib/data/example2-1.0.0-py3-none-any.whl
Normal file
Binary file not shown.
|
@ -8,8 +8,17 @@ import textwrap
|
||||||
import contextlib
|
import contextlib
|
||||||
|
|
||||||
from test.support.os_helper import FS_NONASCII
|
from test.support.os_helper import FS_NONASCII
|
||||||
|
from test.support import requires_zlib
|
||||||
from typing import Dict, Union
|
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
|
@contextlib.contextmanager
|
||||||
def tempdir():
|
def tempdir():
|
||||||
|
@ -54,7 +63,7 @@ class Fixtures:
|
||||||
|
|
||||||
class SiteDir(Fixtures):
|
class SiteDir(Fixtures):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(SiteDir, self).setUp()
|
super().setUp()
|
||||||
self.site_dir = self.fixtures.enter_context(tempdir())
|
self.site_dir = self.fixtures.enter_context(tempdir())
|
||||||
|
|
||||||
|
|
||||||
|
@ -69,7 +78,7 @@ class OnSysPath(Fixtures):
|
||||||
sys.path.remove(str(dir))
|
sys.path.remove(str(dir))
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(OnSysPath, self).setUp()
|
super().setUp()
|
||||||
self.fixtures.enter_context(self.add_sys_path(self.site_dir))
|
self.fixtures.enter_context(self.add_sys_path(self.site_dir))
|
||||||
|
|
||||||
|
|
||||||
|
@ -106,7 +115,7 @@ class DistInfoPkg(OnSysPath, SiteDir):
|
||||||
}
|
}
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(DistInfoPkg, self).setUp()
|
super().setUp()
|
||||||
build_files(DistInfoPkg.files, self.site_dir)
|
build_files(DistInfoPkg.files, self.site_dir)
|
||||||
|
|
||||||
def make_uppercase(self):
|
def make_uppercase(self):
|
||||||
|
@ -131,7 +140,7 @@ class DistInfoPkgWithDot(OnSysPath, SiteDir):
|
||||||
}
|
}
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(DistInfoPkgWithDot, self).setUp()
|
super().setUp()
|
||||||
build_files(DistInfoPkgWithDot.files, self.site_dir)
|
build_files(DistInfoPkgWithDot.files, self.site_dir)
|
||||||
|
|
||||||
|
|
||||||
|
@ -152,13 +161,13 @@ class DistInfoPkgWithDotLegacy(OnSysPath, SiteDir):
|
||||||
}
|
}
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(DistInfoPkgWithDotLegacy, self).setUp()
|
super().setUp()
|
||||||
build_files(DistInfoPkgWithDotLegacy.files, self.site_dir)
|
build_files(DistInfoPkgWithDotLegacy.files, self.site_dir)
|
||||||
|
|
||||||
|
|
||||||
class DistInfoPkgOffPath(SiteDir):
|
class DistInfoPkgOffPath(SiteDir):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(DistInfoPkgOffPath, self).setUp()
|
super().setUp()
|
||||||
build_files(DistInfoPkg.files, self.site_dir)
|
build_files(DistInfoPkg.files, self.site_dir)
|
||||||
|
|
||||||
|
|
||||||
|
@ -198,7 +207,7 @@ class EggInfoPkg(OnSysPath, SiteDir):
|
||||||
}
|
}
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(EggInfoPkg, self).setUp()
|
super().setUp()
|
||||||
build_files(EggInfoPkg.files, prefix=self.site_dir)
|
build_files(EggInfoPkg.files, prefix=self.site_dir)
|
||||||
|
|
||||||
|
|
||||||
|
@ -219,7 +228,7 @@ class EggInfoFile(OnSysPath, SiteDir):
|
||||||
}
|
}
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(EggInfoFile, self).setUp()
|
super().setUp()
|
||||||
build_files(EggInfoFile.files, prefix=self.site_dir)
|
build_files(EggInfoFile.files, prefix=self.site_dir)
|
||||||
|
|
||||||
|
|
||||||
|
@ -285,3 +294,20 @@ def DALS(str):
|
||||||
class NullFinder:
|
class NullFinder:
|
||||||
def find_module(self, name):
|
def find_module(self, name):
|
||||||
pass
|
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,
|
distributions,
|
||||||
entry_points,
|
entry_points,
|
||||||
metadata,
|
metadata,
|
||||||
|
packages_distributions,
|
||||||
version,
|
version,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -203,7 +204,7 @@ class InaccessibleSysPath(fixtures.OnSysPath, ffs.TestCase):
|
||||||
site_dir = '/access-denied'
|
site_dir = '/access-denied'
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(InaccessibleSysPath, self).setUp()
|
super().setUp()
|
||||||
self.setUpPyfakefs()
|
self.setUpPyfakefs()
|
||||||
self.fs.create_dir(self.site_dir, perm_bits=000)
|
self.fs.create_dir(self.site_dir, perm_bits=000)
|
||||||
|
|
||||||
|
@ -217,13 +218,21 @@ class InaccessibleSysPath(fixtures.OnSysPath, ffs.TestCase):
|
||||||
|
|
||||||
class TestEntryPoints(unittest.TestCase):
|
class TestEntryPoints(unittest.TestCase):
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
super(TestEntryPoints, self).__init__(*args)
|
super().__init__(*args)
|
||||||
self.ep = importlib.metadata.EntryPoint('name', 'value', 'group')
|
self.ep = importlib.metadata.EntryPoint(
|
||||||
|
name='name', value='value', group='group'
|
||||||
|
)
|
||||||
|
|
||||||
def test_entry_point_pickleable(self):
|
def test_entry_point_pickleable(self):
|
||||||
revived = pickle.loads(pickle.dumps(self.ep))
|
revived = pickle.loads(pickle.dumps(self.ep))
|
||||||
assert revived == self.ep
|
assert revived == self.ep
|
||||||
|
|
||||||
|
def test_positional_args(self):
|
||||||
|
"""
|
||||||
|
Capture legacy (namedtuple) construction, discouraged.
|
||||||
|
"""
|
||||||
|
EntryPoint('name', 'value', 'group')
|
||||||
|
|
||||||
def test_immutable(self):
|
def test_immutable(self):
|
||||||
"""EntryPoints should be immutable"""
|
"""EntryPoints should be immutable"""
|
||||||
with self.assertRaises(AttributeError):
|
with self.assertRaises(AttributeError):
|
||||||
|
@ -254,8 +263,8 @@ class TestEntryPoints(unittest.TestCase):
|
||||||
# EntryPoint objects are sortable, but result is undefined.
|
# EntryPoint objects are sortable, but result is undefined.
|
||||||
sorted(
|
sorted(
|
||||||
[
|
[
|
||||||
EntryPoint('b', 'val', 'group'),
|
EntryPoint(name='b', value='val', group='group'),
|
||||||
EntryPoint('a', 'val', 'group'),
|
EntryPoint(name='a', value='val', group='group'),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -271,3 +280,38 @@ class FileSystem(
|
||||||
prefix=self.site_dir,
|
prefix=self.site_dir,
|
||||||
)
|
)
|
||||||
list(distributions())
|
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
|
@contextlib.contextmanager
|
||||||
def suppress_known_deprecation():
|
def suppress_known_deprecation():
|
||||||
with warnings.catch_warnings(record=True) as ctx:
|
with warnings.catch_warnings(record=True) as ctx:
|
||||||
warnings.simplefilter('default')
|
warnings.simplefilter('default', category=DeprecationWarning)
|
||||||
yield ctx
|
yield ctx
|
||||||
|
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ class APITests(
|
||||||
for ep in entries
|
for ep in entries
|
||||||
)
|
)
|
||||||
# ns:sub doesn't exist in alt_pkg
|
# 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):
|
def test_entry_points_missing_name(self):
|
||||||
with self.assertRaises(KeyError):
|
with self.assertRaises(KeyError):
|
||||||
|
@ -194,10 +194,8 @@ class APITests(
|
||||||
file.read_text()
|
file.read_text()
|
||||||
|
|
||||||
def test_file_hash_repr(self):
|
def test_file_hash_repr(self):
|
||||||
assertRegex = self.assertRegex
|
|
||||||
|
|
||||||
util = [p for p in files('distinfo-pkg') if p.name == 'mod.py'][0]
|
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):
|
def test_files_dist_info(self):
|
||||||
self._test_files(files('distinfo-pkg'))
|
self._test_files(files('distinfo-pkg'))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from contextlib import ExitStack
|
from . import fixtures
|
||||||
from importlib.metadata import (
|
from importlib.metadata import (
|
||||||
PackageNotFoundError,
|
PackageNotFoundError,
|
||||||
distribution,
|
distribution,
|
||||||
|
@ -10,27 +10,11 @@ from importlib.metadata import (
|
||||||
files,
|
files,
|
||||||
version,
|
version,
|
||||||
)
|
)
|
||||||
from importlib import resources
|
|
||||||
|
|
||||||
from test.support import requires_zlib
|
|
||||||
|
|
||||||
|
|
||||||
@requires_zlib()
|
class TestZip(fixtures.ZipFixtures, unittest.TestCase):
|
||||||
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)
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# Find the path to the example-*.whl so we can add it to the front of
|
super().setUp()
|
||||||
# sys.path, where we'll then try to find the metadata thereof.
|
|
||||||
self.resources = ExitStack()
|
|
||||||
self.addCleanup(self.resources.close)
|
|
||||||
self._fixture_on_path('example-21.12-py3-none-any.whl')
|
self._fixture_on_path('example-21.12-py3-none-any.whl')
|
||||||
|
|
||||||
def test_zip_version(self):
|
def test_zip_version(self):
|
||||||
|
@ -63,13 +47,9 @@ class TestZip(unittest.TestCase):
|
||||||
assert len(dists) == 1
|
assert len(dists) == 1
|
||||||
|
|
||||||
|
|
||||||
@requires_zlib()
|
|
||||||
class TestEgg(TestZip):
|
class TestEgg(TestZip):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# Find the path to the example-*.egg so we can add it to the front of
|
super().setUp()
|
||||||
# sys.path, where we'll then try to find the metadata thereof.
|
|
||||||
self.resources = ExitStack()
|
|
||||||
self.addCleanup(self.resources.close)
|
|
||||||
self._fixture_on_path('example-21.12-py3.6.egg')
|
self._fixture_on_path('example-21.12-py3.6.egg')
|
||||||
|
|
||||||
def test_files(self):
|
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…
Add table
Add a link
Reference in a new issue