mirror of
https://github.com/python/cpython.git
synced 2025-11-24 20:30:18 +00:00
Revert "gh-132947: Apply changes from importlib_metadata 8.7 (#137885)" (#137924)
Some checks are pending
Tests / Windows MSI (push) Blocked by required conditions
Tests / Hypothesis tests on Ubuntu (push) Blocked by required conditions
Tests / Address sanitizer (push) Blocked by required conditions
Tests / Sanitizers (push) Blocked by required conditions
Tests / Change detection (push) Waiting to run
Tests / Docs (push) Blocked by required conditions
Tests / Check if Autoconf files are up to date (push) Blocked by required conditions
Tests / Check if generated files are up to date (push) Blocked by required conditions
Tests / (push) Blocked by required conditions
Tests / Ubuntu SSL tests with OpenSSL (push) Blocked by required conditions
Tests / Ubuntu SSL tests with AWS-LC (push) Blocked by required conditions
Tests / Android (aarch64) (push) Blocked by required conditions
Tests / Android (x86_64) (push) Blocked by required conditions
Tests / WASI (push) Blocked by required conditions
Tests / Cross build Linux (push) Blocked by required conditions
Tests / CIFuzz (push) Blocked by required conditions
Tests / All required checks pass (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
Some checks are pending
Tests / Windows MSI (push) Blocked by required conditions
Tests / Hypothesis tests on Ubuntu (push) Blocked by required conditions
Tests / Address sanitizer (push) Blocked by required conditions
Tests / Sanitizers (push) Blocked by required conditions
Tests / Change detection (push) Waiting to run
Tests / Docs (push) Blocked by required conditions
Tests / Check if Autoconf files are up to date (push) Blocked by required conditions
Tests / Check if generated files are up to date (push) Blocked by required conditions
Tests / (push) Blocked by required conditions
Tests / Ubuntu SSL tests with OpenSSL (push) Blocked by required conditions
Tests / Ubuntu SSL tests with AWS-LC (push) Blocked by required conditions
Tests / Android (aarch64) (push) Blocked by required conditions
Tests / Android (x86_64) (push) Blocked by required conditions
Tests / WASI (push) Blocked by required conditions
Tests / Cross build Linux (push) Blocked by required conditions
Tests / CIFuzz (push) Blocked by required conditions
Tests / All required checks pass (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
This reverts commit 5292fc00f2.
This commit is contained in:
parent
8750e5ecfc
commit
3706ef66ef
12 changed files with 120 additions and 292 deletions
|
|
@ -1,40 +1,33 @@
|
||||||
"""
|
|
||||||
APIs exposing metadata from third-party Python packages.
|
|
||||||
|
|
||||||
This codebase is shared between importlib.metadata in the stdlib
|
|
||||||
and importlib_metadata in PyPI. See
|
|
||||||
https://github.com/python/importlib_metadata/wiki/Development-Methodology
|
|
||||||
for more detail.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
import abc
|
import abc
|
||||||
import collections
|
import sys
|
||||||
|
import json
|
||||||
import email
|
import email
|
||||||
|
import types
|
||||||
|
import inspect
|
||||||
|
import pathlib
|
||||||
|
import zipfile
|
||||||
|
import operator
|
||||||
|
import textwrap
|
||||||
import functools
|
import functools
|
||||||
import itertools
|
import itertools
|
||||||
import operator
|
|
||||||
import os
|
|
||||||
import pathlib
|
|
||||||
import posixpath
|
import posixpath
|
||||||
import re
|
import collections
|
||||||
import sys
|
|
||||||
import textwrap
|
|
||||||
import types
|
|
||||||
from collections.abc import Iterable, Mapping
|
|
||||||
from contextlib import suppress
|
|
||||||
from importlib import import_module
|
|
||||||
from importlib.abc import MetaPathFinder
|
|
||||||
from itertools import starmap
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from . import _meta
|
from . import _meta
|
||||||
from ._collections import FreezableDefaultDict, Pair
|
from ._collections import FreezableDefaultDict, Pair
|
||||||
from ._functools import method_cache, pass_none
|
from ._functools import method_cache, pass_none
|
||||||
from ._itertools import always_iterable, bucket, unique_everseen
|
from ._itertools import always_iterable, bucket, unique_everseen
|
||||||
from ._meta import PackageMetadata, SimplePath
|
from ._meta import PackageMetadata, SimplePath
|
||||||
from ._typing import md_none
|
|
||||||
|
from contextlib import suppress
|
||||||
|
from importlib import import_module
|
||||||
|
from importlib.abc import MetaPathFinder
|
||||||
|
from itertools import starmap
|
||||||
|
from typing import Any, Iterable, List, Mapping, Match, Optional, Set, cast
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'Distribution',
|
'Distribution',
|
||||||
|
|
@ -60,7 +53,7 @@ class PackageNotFoundError(ModuleNotFoundError):
|
||||||
return f"No package metadata was found for {self.name}"
|
return f"No package metadata was found for {self.name}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str: # type: ignore[override] # make readonly
|
def name(self) -> str: # type: ignore[override]
|
||||||
(name,) = self.args
|
(name,) = self.args
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
@ -130,12 +123,6 @@ class Sectioned:
|
||||||
return line and not line.startswith('#')
|
return line and not line.startswith('#')
|
||||||
|
|
||||||
|
|
||||||
class _EntryPointMatch(types.SimpleNamespace):
|
|
||||||
module: str
|
|
||||||
attr: str
|
|
||||||
extras: str
|
|
||||||
|
|
||||||
|
|
||||||
class EntryPoint:
|
class EntryPoint:
|
||||||
"""An entry point as defined by Python packaging conventions.
|
"""An entry point as defined by Python packaging conventions.
|
||||||
|
|
||||||
|
|
@ -151,30 +138,6 @@ class EntryPoint:
|
||||||
'attr'
|
'attr'
|
||||||
>>> ep.extras
|
>>> ep.extras
|
||||||
['extra1', 'extra2']
|
['extra1', 'extra2']
|
||||||
|
|
||||||
If the value package or module are not valid identifiers, a
|
|
||||||
ValueError is raised on access.
|
|
||||||
|
|
||||||
>>> EntryPoint(name=None, group=None, value='invalid-name').module
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: ('Invalid object reference...invalid-name...
|
|
||||||
>>> EntryPoint(name=None, group=None, value='invalid-name').attr
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: ('Invalid object reference...invalid-name...
|
|
||||||
>>> EntryPoint(name=None, group=None, value='invalid-name').extras
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: ('Invalid object reference...invalid-name...
|
|
||||||
|
|
||||||
The same thing happens on construction.
|
|
||||||
|
|
||||||
>>> EntryPoint(name=None, group=None, value='invalid-name')
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: ('Invalid object reference...invalid-name...
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pattern = re.compile(
|
pattern = re.compile(
|
||||||
|
|
@ -202,44 +165,38 @@ class EntryPoint:
|
||||||
value: str
|
value: str
|
||||||
group: str
|
group: str
|
||||||
|
|
||||||
dist: Distribution | None = None
|
dist: Optional[Distribution] = None
|
||||||
|
|
||||||
def __init__(self, name: str, value: str, group: str) -> None:
|
def __init__(self, name: str, value: str, group: str) -> None:
|
||||||
vars(self).update(name=name, value=value, group=group)
|
vars(self).update(name=name, value=value, group=group)
|
||||||
self.module
|
|
||||||
|
|
||||||
def load(self) -> Any:
|
def load(self) -> Any:
|
||||||
"""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,
|
||||||
return the named object.
|
return the named object.
|
||||||
"""
|
"""
|
||||||
module = import_module(self.module)
|
match = cast(Match, self.pattern.match(self.value))
|
||||||
attrs = filter(None, (self.attr or '').split('.'))
|
module = import_module(match.group('module'))
|
||||||
|
attrs = filter(None, (match.group('attr') or '').split('.'))
|
||||||
return functools.reduce(getattr, attrs, module)
|
return functools.reduce(getattr, attrs, module)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def module(self) -> str:
|
def module(self) -> str:
|
||||||
return self._match.module
|
match = self.pattern.match(self.value)
|
||||||
|
assert match is not None
|
||||||
|
return match.group('module')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def attr(self) -> str:
|
def attr(self) -> str:
|
||||||
return self._match.attr
|
match = self.pattern.match(self.value)
|
||||||
|
assert match is not None
|
||||||
|
return match.group('attr')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extras(self) -> list[str]:
|
def extras(self) -> List[str]:
|
||||||
return re.findall(r'\w+', self._match.extras or '')
|
|
||||||
|
|
||||||
@functools.cached_property
|
|
||||||
def _match(self) -> _EntryPointMatch:
|
|
||||||
match = self.pattern.match(self.value)
|
match = self.pattern.match(self.value)
|
||||||
if not match:
|
assert match is not None
|
||||||
raise ValueError(
|
return re.findall(r'\w+', match.group('extras') or '')
|
||||||
'Invalid object reference. '
|
|
||||||
'See https://packaging.python.org'
|
|
||||||
'/en/latest/specifications/entry-points/#data-model',
|
|
||||||
self.value,
|
|
||||||
)
|
|
||||||
return _EntryPointMatch(**match.groupdict())
|
|
||||||
|
|
||||||
def _for(self, dist):
|
def _for(self, dist):
|
||||||
vars(self).update(dist=dist)
|
vars(self).update(dist=dist)
|
||||||
|
|
@ -265,26 +222,9 @@ class EntryPoint:
|
||||||
>>> ep.matches(attr='bong')
|
>>> ep.matches(attr='bong')
|
||||||
True
|
True
|
||||||
"""
|
"""
|
||||||
self._disallow_dist(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))
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _disallow_dist(params):
|
|
||||||
"""
|
|
||||||
Querying by dist is not allowed (dist objects are not comparable).
|
|
||||||
>>> EntryPoint(name='fan', value='fav', group='fag').matches(dist='foo')
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: "dist" is not suitable for matching...
|
|
||||||
"""
|
|
||||||
if "dist" in params:
|
|
||||||
raise ValueError(
|
|
||||||
'"dist" is not suitable for matching. '
|
|
||||||
"Instead, use Distribution.entry_points.select() on a "
|
|
||||||
"located distribution."
|
|
||||||
)
|
|
||||||
|
|
||||||
def _key(self):
|
def _key(self):
|
||||||
return self.name, self.value, self.group
|
return self.name, self.value, self.group
|
||||||
|
|
||||||
|
|
@ -314,7 +254,7 @@ class EntryPoints(tuple):
|
||||||
|
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
def __getitem__(self, name: str) -> EntryPoint: # type: ignore[override] # Work with str instead of int
|
def __getitem__(self, name: str) -> EntryPoint: # type: ignore[override]
|
||||||
"""
|
"""
|
||||||
Get the EntryPoint in self matching name.
|
Get the EntryPoint in self matching name.
|
||||||
"""
|
"""
|
||||||
|
|
@ -338,14 +278,14 @@ class EntryPoints(tuple):
|
||||||
return EntryPoints(ep for ep in self if ep.matches(**params))
|
return EntryPoints(ep for ep in self if ep.matches(**params))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def names(self) -> set[str]:
|
def names(self) -> Set[str]:
|
||||||
"""
|
"""
|
||||||
Return the set of all names of all entry points.
|
Return the set of all names of all entry points.
|
||||||
"""
|
"""
|
||||||
return {ep.name for ep in self}
|
return {ep.name for ep in self}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def groups(self) -> set[str]:
|
def groups(self) -> Set[str]:
|
||||||
"""
|
"""
|
||||||
Return the set of all groups of all entry points.
|
Return the set of all groups of all entry points.
|
||||||
"""
|
"""
|
||||||
|
|
@ -366,11 +306,11 @@ class EntryPoints(tuple):
|
||||||
class PackagePath(pathlib.PurePosixPath):
|
class PackagePath(pathlib.PurePosixPath):
|
||||||
"""A reference to a path in a package"""
|
"""A reference to a path in a package"""
|
||||||
|
|
||||||
hash: FileHash | None
|
hash: Optional[FileHash]
|
||||||
size: int
|
size: int
|
||||||
dist: Distribution
|
dist: Distribution
|
||||||
|
|
||||||
def read_text(self, encoding: str = 'utf-8') -> str:
|
def read_text(self, encoding: str = 'utf-8') -> str: # type: ignore[override]
|
||||||
return self.locate().read_text(encoding=encoding)
|
return self.locate().read_text(encoding=encoding)
|
||||||
|
|
||||||
def read_binary(self) -> bytes:
|
def read_binary(self) -> bytes:
|
||||||
|
|
@ -401,7 +341,7 @@ class Distribution(metaclass=abc.ABCMeta):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def read_text(self, filename) -> str | None:
|
def read_text(self, filename) -> Optional[str]:
|
||||||
"""Attempt to load metadata file given by the name.
|
"""Attempt to load metadata file given by the name.
|
||||||
|
|
||||||
Python distribution metadata is organized by blobs of text
|
Python distribution metadata is organized by blobs of text
|
||||||
|
|
@ -428,17 +368,6 @@ class Distribution(metaclass=abc.ABCMeta):
|
||||||
"""
|
"""
|
||||||
Given a path to a file in this distribution, return a SimplePath
|
Given a path to a file in this distribution, return a SimplePath
|
||||||
to it.
|
to it.
|
||||||
|
|
||||||
This method is used by callers of ``Distribution.files()`` to
|
|
||||||
locate files within the distribution. If it's possible for a
|
|
||||||
Distribution to represent files in the distribution as
|
|
||||||
``SimplePath`` objects, it should implement this method
|
|
||||||
to resolve such objects.
|
|
||||||
|
|
||||||
Some Distribution providers may elect not to resolve SimplePath
|
|
||||||
objects within the distribution by raising a
|
|
||||||
NotImplementedError, but consumers of such a Distribution would
|
|
||||||
be unable to invoke ``Distribution.files()``.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -461,7 +390,7 @@ class Distribution(metaclass=abc.ABCMeta):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def discover(
|
def discover(
|
||||||
cls, *, context: DistributionFinder.Context | None = None, **kwargs
|
cls, *, context: Optional[DistributionFinder.Context] = None, **kwargs
|
||||||
) -> Iterable[Distribution]:
|
) -> Iterable[Distribution]:
|
||||||
"""Return an iterable of Distribution objects for all packages.
|
"""Return an iterable of Distribution objects for all packages.
|
||||||
|
|
||||||
|
|
@ -507,7 +436,7 @@ class Distribution(metaclass=abc.ABCMeta):
|
||||||
return filter(None, declared)
|
return filter(None, declared)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def metadata(self) -> _meta.PackageMetadata | None:
|
def metadata(self) -> _meta.PackageMetadata:
|
||||||
"""Return the parsed metadata for this Distribution.
|
"""Return the parsed metadata for this Distribution.
|
||||||
|
|
||||||
The returned object will have keys that name the various bits of
|
The returned object will have keys that name the various bits of
|
||||||
|
|
@ -517,8 +446,10 @@ class Distribution(metaclass=abc.ABCMeta):
|
||||||
Custom providers may provide the METADATA file or override this
|
Custom providers may provide the METADATA file or override this
|
||||||
property.
|
property.
|
||||||
"""
|
"""
|
||||||
|
# deferred for performance (python/cpython#109829)
|
||||||
|
from . import _adapters
|
||||||
|
|
||||||
text = (
|
opt_text = (
|
||||||
self.read_text('METADATA')
|
self.read_text('METADATA')
|
||||||
or self.read_text('PKG-INFO')
|
or self.read_text('PKG-INFO')
|
||||||
# This last clause is here to support old egg-info files. Its
|
# This last clause is here to support old egg-info files. Its
|
||||||
|
|
@ -526,20 +457,13 @@ class Distribution(metaclass=abc.ABCMeta):
|
||||||
# (which points to the egg-info file) attribute unchanged.
|
# (which points to the egg-info file) attribute unchanged.
|
||||||
or self.read_text('')
|
or self.read_text('')
|
||||||
)
|
)
|
||||||
return self._assemble_message(text)
|
text = cast(str, opt_text)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
@pass_none
|
|
||||||
def _assemble_message(text: str) -> _meta.PackageMetadata:
|
|
||||||
# deferred for performance (python/cpython#109829)
|
|
||||||
from . import _adapters
|
|
||||||
|
|
||||||
return _adapters.Message(email.message_from_string(text))
|
return _adapters.Message(email.message_from_string(text))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""Return the 'Name' metadata for the distribution package."""
|
"""Return the 'Name' metadata for the distribution package."""
|
||||||
return md_none(self.metadata)['Name']
|
return self.metadata['Name']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _normalized_name(self):
|
def _normalized_name(self):
|
||||||
|
|
@ -549,7 +473,7 @@ class Distribution(metaclass=abc.ABCMeta):
|
||||||
@property
|
@property
|
||||||
def version(self) -> str:
|
def version(self) -> str:
|
||||||
"""Return the 'Version' metadata for the distribution package."""
|
"""Return the 'Version' metadata for the distribution package."""
|
||||||
return md_none(self.metadata)['Version']
|
return self.metadata['Version']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def entry_points(self) -> EntryPoints:
|
def entry_points(self) -> EntryPoints:
|
||||||
|
|
@ -562,7 +486,7 @@ class Distribution(metaclass=abc.ABCMeta):
|
||||||
return EntryPoints._from_text_for(self.read_text('entry_points.txt'), self)
|
return EntryPoints._from_text_for(self.read_text('entry_points.txt'), self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def files(self) -> list[PackagePath] | None:
|
def files(self) -> Optional[List[PackagePath]]:
|
||||||
"""Files in this distribution.
|
"""Files in this distribution.
|
||||||
|
|
||||||
:return: List of PackagePath for this distribution or None
|
:return: List of PackagePath for this distribution or None
|
||||||
|
|
@ -655,7 +579,7 @@ class Distribution(metaclass=abc.ABCMeta):
|
||||||
return text and map('"{}"'.format, text.splitlines())
|
return text and map('"{}"'.format, text.splitlines())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def requires(self) -> list[str] | None:
|
def requires(self) -> Optional[List[str]]:
|
||||||
"""Generated requirements specified for this Distribution"""
|
"""Generated requirements specified for this Distribution"""
|
||||||
reqs = self._read_dist_info_reqs() or self._read_egg_info_reqs()
|
reqs = self._read_dist_info_reqs() or self._read_egg_info_reqs()
|
||||||
return reqs and list(reqs)
|
return reqs and list(reqs)
|
||||||
|
|
@ -711,9 +635,6 @@ class Distribution(metaclass=abc.ABCMeta):
|
||||||
return self._load_json('direct_url.json')
|
return self._load_json('direct_url.json')
|
||||||
|
|
||||||
def _load_json(self, filename):
|
def _load_json(self, filename):
|
||||||
# Deferred for performance (python/importlib_metadata#503)
|
|
||||||
import json
|
|
||||||
|
|
||||||
return pass_none(json.loads)(
|
return pass_none(json.loads)(
|
||||||
self.read_text(filename),
|
self.read_text(filename),
|
||||||
object_hook=lambda data: types.SimpleNamespace(**data),
|
object_hook=lambda data: types.SimpleNamespace(**data),
|
||||||
|
|
@ -761,7 +682,7 @@ class DistributionFinder(MetaPathFinder):
|
||||||
vars(self).update(kwargs)
|
vars(self).update(kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path(self) -> list[str]:
|
def path(self) -> List[str]:
|
||||||
"""
|
"""
|
||||||
The sequence of directory path that a distribution finder
|
The sequence of directory path that a distribution finder
|
||||||
should search.
|
should search.
|
||||||
|
|
@ -798,7 +719,7 @@ class FastPath:
|
||||||
True
|
True
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@functools.lru_cache() # type: ignore[misc]
|
@functools.lru_cache() # type: ignore
|
||||||
def __new__(cls, root):
|
def __new__(cls, root):
|
||||||
return super().__new__(cls)
|
return super().__new__(cls)
|
||||||
|
|
||||||
|
|
@ -816,9 +737,6 @@ class FastPath:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def zip_children(self):
|
def zip_children(self):
|
||||||
# deferred for performance (python/importlib_metadata#502)
|
|
||||||
import zipfile
|
|
||||||
|
|
||||||
zip_path = zipfile.Path(self.root)
|
zip_path = zipfile.Path(self.root)
|
||||||
names = zip_path.root.namelist()
|
names = zip_path.root.namelist()
|
||||||
self.joinpath = zip_path.joinpath
|
self.joinpath = zip_path.joinpath
|
||||||
|
|
@ -913,7 +831,7 @@ class Prepared:
|
||||||
normalized = None
|
normalized = None
|
||||||
legacy_normalized = None
|
legacy_normalized = None
|
||||||
|
|
||||||
def __init__(self, name: str | None):
|
def __init__(self, name: Optional[str]):
|
||||||
self.name = name
|
self.name = name
|
||||||
if name is None:
|
if name is None:
|
||||||
return
|
return
|
||||||
|
|
@ -976,7 +894,7 @@ class PathDistribution(Distribution):
|
||||||
"""
|
"""
|
||||||
self._path = path
|
self._path = path
|
||||||
|
|
||||||
def read_text(self, filename: str | os.PathLike[str]) -> str | None:
|
def read_text(self, filename: str | os.PathLike[str]) -> Optional[str]:
|
||||||
with suppress(
|
with suppress(
|
||||||
FileNotFoundError,
|
FileNotFoundError,
|
||||||
IsADirectoryError,
|
IsADirectoryError,
|
||||||
|
|
@ -1040,7 +958,7 @@ def distributions(**kwargs) -> Iterable[Distribution]:
|
||||||
return Distribution.discover(**kwargs)
|
return Distribution.discover(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
def metadata(distribution_name: str) -> _meta.PackageMetadata | None:
|
def metadata(distribution_name: str) -> _meta.PackageMetadata:
|
||||||
"""Get the metadata for the named package.
|
"""Get the metadata for the named package.
|
||||||
|
|
||||||
:param distribution_name: The name of the distribution package to query.
|
:param distribution_name: The name of the distribution package to query.
|
||||||
|
|
@ -1083,7 +1001,7 @@ def entry_points(**params) -> EntryPoints:
|
||||||
return EntryPoints(eps).select(**params)
|
return EntryPoints(eps).select(**params)
|
||||||
|
|
||||||
|
|
||||||
def files(distribution_name: str) -> list[PackagePath] | None:
|
def files(distribution_name: str) -> Optional[List[PackagePath]]:
|
||||||
"""Return a list of files for the named package.
|
"""Return a list of files for the named package.
|
||||||
|
|
||||||
:param distribution_name: The name of the distribution package to query.
|
:param distribution_name: The name of the distribution package to query.
|
||||||
|
|
@ -1092,7 +1010,7 @@ def files(distribution_name: str) -> list[PackagePath] | None:
|
||||||
return distribution(distribution_name).files
|
return distribution(distribution_name).files
|
||||||
|
|
||||||
|
|
||||||
def requires(distribution_name: str) -> list[str] | None:
|
def requires(distribution_name: str) -> Optional[List[str]]:
|
||||||
"""
|
"""
|
||||||
Return a list of requirements for the named package.
|
Return a list of requirements for the named package.
|
||||||
|
|
||||||
|
|
@ -1102,7 +1020,7 @@ def requires(distribution_name: str) -> list[str] | None:
|
||||||
return distribution(distribution_name).requires
|
return distribution(distribution_name).requires
|
||||||
|
|
||||||
|
|
||||||
def packages_distributions() -> Mapping[str, list[str]]:
|
def packages_distributions() -> Mapping[str, List[str]]:
|
||||||
"""
|
"""
|
||||||
Return a mapping of top-level packages to their
|
Return a mapping of top-level packages to their
|
||||||
distributions.
|
distributions.
|
||||||
|
|
@ -1115,7 +1033,7 @@ 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 _top_level_declared(dist) or _top_level_inferred(dist):
|
for pkg in _top_level_declared(dist) or _top_level_inferred(dist):
|
||||||
pkg_to_dist[pkg].append(md_none(dist.metadata)['Name'])
|
pkg_to_dist[pkg].append(dist.metadata['Name'])
|
||||||
return dict(pkg_to_dist)
|
return dict(pkg_to_dist)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1123,7 +1041,7 @@ def _top_level_declared(dist):
|
||||||
return (dist.read_text('top_level.txt') or '').split()
|
return (dist.read_text('top_level.txt') or '').split()
|
||||||
|
|
||||||
|
|
||||||
def _topmost(name: PackagePath) -> str | None:
|
def _topmost(name: PackagePath) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Return the top-most parent as long as there is a parent.
|
Return the top-most parent as long as there is a parent.
|
||||||
"""
|
"""
|
||||||
|
|
@ -1149,10 +1067,11 @@ def _get_toplevel_name(name: PackagePath) -> str:
|
||||||
>>> _get_toplevel_name(PackagePath('foo.dist-info'))
|
>>> _get_toplevel_name(PackagePath('foo.dist-info'))
|
||||||
'foo.dist-info'
|
'foo.dist-info'
|
||||||
"""
|
"""
|
||||||
# Defer import of inspect for performance (python/cpython#118761)
|
return _topmost(name) or (
|
||||||
import inspect
|
# python/typeshed#10328
|
||||||
|
inspect.getmodulename(name) # type: ignore
|
||||||
return _topmost(name) or inspect.getmodulename(name) or str(name)
|
or str(name)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _top_level_inferred(dist):
|
def _top_level_inferred(dist):
|
||||||
|
|
|
||||||
|
|
@ -1,58 +1,11 @@
|
||||||
import email.message
|
|
||||||
import email.policy
|
|
||||||
import re
|
import re
|
||||||
import textwrap
|
import textwrap
|
||||||
|
import email.message
|
||||||
|
|
||||||
from ._text import FoldedCase
|
from ._text import FoldedCase
|
||||||
|
|
||||||
|
|
||||||
class RawPolicy(email.policy.EmailPolicy):
|
|
||||||
def fold(self, name, value):
|
|
||||||
folded = self.linesep.join(
|
|
||||||
textwrap.indent(value, prefix=' ' * 8, predicate=lambda line: True)
|
|
||||||
.lstrip()
|
|
||||||
.splitlines()
|
|
||||||
)
|
|
||||||
return f'{name}: {folded}{self.linesep}'
|
|
||||||
|
|
||||||
|
|
||||||
class Message(email.message.Message):
|
class Message(email.message.Message):
|
||||||
r"""
|
|
||||||
Specialized Message subclass to handle metadata naturally.
|
|
||||||
|
|
||||||
Reads values that may have newlines in them and converts the
|
|
||||||
payload to the Description.
|
|
||||||
|
|
||||||
>>> msg_text = textwrap.dedent('''
|
|
||||||
... Name: Foo
|
|
||||||
... Version: 3.0
|
|
||||||
... License: blah
|
|
||||||
... de-blah
|
|
||||||
... <BLANKLINE>
|
|
||||||
... First line of description.
|
|
||||||
... Second line of description.
|
|
||||||
... <BLANKLINE>
|
|
||||||
... Fourth line!
|
|
||||||
... ''').lstrip().replace('<BLANKLINE>', '')
|
|
||||||
>>> msg = Message(email.message_from_string(msg_text))
|
|
||||||
>>> msg['Description']
|
|
||||||
'First line of description.\nSecond line of description.\n\nFourth line!\n'
|
|
||||||
|
|
||||||
Message should render even if values contain newlines.
|
|
||||||
|
|
||||||
>>> print(msg)
|
|
||||||
Name: Foo
|
|
||||||
Version: 3.0
|
|
||||||
License: blah
|
|
||||||
de-blah
|
|
||||||
Description: First line of description.
|
|
||||||
Second line of description.
|
|
||||||
<BLANKLINE>
|
|
||||||
Fourth line!
|
|
||||||
<BLANKLINE>
|
|
||||||
<BLANKLINE>
|
|
||||||
"""
|
|
||||||
|
|
||||||
multiple_use_keys = set(
|
multiple_use_keys = set(
|
||||||
map(
|
map(
|
||||||
FoldedCase,
|
FoldedCase,
|
||||||
|
|
@ -104,20 +57,15 @@ class Message(email.message.Message):
|
||||||
def _repair_headers(self):
|
def _repair_headers(self):
|
||||||
def redent(value):
|
def redent(value):
|
||||||
"Correct for RFC822 indentation"
|
"Correct for RFC822 indentation"
|
||||||
indent = ' ' * 8
|
if not value or '\n' not in value:
|
||||||
if not value or '\n' + indent not in value:
|
|
||||||
return value
|
return value
|
||||||
return textwrap.dedent(indent + value)
|
return textwrap.dedent(' ' * 8 + value)
|
||||||
|
|
||||||
headers = [(key, redent(value)) for key, value in vars(self)['_headers']]
|
headers = [(key, redent(value)) for key, value in vars(self)['_headers']]
|
||||||
if self._payload:
|
if self._payload:
|
||||||
headers.append(('Description', self.get_payload()))
|
headers.append(('Description', self.get_payload()))
|
||||||
self.set_payload('')
|
|
||||||
return headers
|
return headers
|
||||||
|
|
||||||
def as_string(self):
|
|
||||||
return super().as_string(policy=RawPolicy())
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def json(self):
|
def json(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import collections
|
import collections
|
||||||
import typing
|
|
||||||
|
|
||||||
|
|
||||||
# from jaraco.collections 3.3
|
# from jaraco.collections 3.3
|
||||||
|
|
@ -25,10 +24,7 @@ class FreezableDefaultDict(collections.defaultdict):
|
||||||
self._frozen = lambda key: self.default_factory()
|
self._frozen = lambda key: self.default_factory()
|
||||||
|
|
||||||
|
|
||||||
class Pair(typing.NamedTuple):
|
class Pair(collections.namedtuple('Pair', 'name value')):
|
||||||
name: str
|
|
||||||
value: str
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse(cls, text):
|
def parse(cls, text):
|
||||||
return cls(*map(str.strip, text.split("=", 1)))
|
return cls(*map(str.strip, text.split("=", 1)))
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import functools
|
|
||||||
import types
|
import types
|
||||||
|
import functools
|
||||||
|
|
||||||
|
|
||||||
# from jaraco.functools 3.3
|
# from jaraco.functools 3.3
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,9 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from collections.abc import Iterator
|
from typing import Protocol
|
||||||
from typing import (
|
from typing import Any, Dict, Iterator, List, Optional, TypeVar, Union, overload
|
||||||
Any,
|
|
||||||
Protocol,
|
|
||||||
TypeVar,
|
|
||||||
overload,
|
|
||||||
)
|
|
||||||
|
|
||||||
_T = TypeVar("_T")
|
_T = TypeVar("_T")
|
||||||
|
|
||||||
|
|
@ -24,25 +20,25 @@ class PackageMetadata(Protocol):
|
||||||
@overload
|
@overload
|
||||||
def get(
|
def get(
|
||||||
self, name: str, failobj: None = None
|
self, name: str, failobj: None = None
|
||||||
) -> str | None: ... # pragma: no cover
|
) -> Optional[str]: ... # pragma: no cover
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get(self, name: str, failobj: _T) -> str | _T: ... # pragma: no cover
|
def get(self, name: str, failobj: _T) -> Union[str, _T]: ... # pragma: no cover
|
||||||
|
|
||||||
# overload per python/importlib_metadata#435
|
# overload per python/importlib_metadata#435
|
||||||
@overload
|
@overload
|
||||||
def get_all(
|
def get_all(
|
||||||
self, name: str, failobj: None = None
|
self, name: str, failobj: None = None
|
||||||
) -> list[Any] | None: ... # pragma: no cover
|
) -> Optional[List[Any]]: ... # pragma: no cover
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_all(self, name: str, failobj: _T) -> list[Any] | _T:
|
def get_all(self, name: str, failobj: _T) -> Union[List[Any], _T]:
|
||||||
"""
|
"""
|
||||||
Return all values associated with a possibly multi-valued key.
|
Return all values associated with a possibly multi-valued key.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def json(self) -> dict[str, str | list[str]]:
|
def json(self) -> Dict[str, Union[str, List[str]]]:
|
||||||
"""
|
"""
|
||||||
A JSON-compatible form of the metadata.
|
A JSON-compatible form of the metadata.
|
||||||
"""
|
"""
|
||||||
|
|
@ -54,11 +50,11 @@ class SimplePath(Protocol):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def joinpath(
|
def joinpath(
|
||||||
self, other: str | os.PathLike[str]
|
self, other: Union[str, os.PathLike[str]]
|
||||||
) -> SimplePath: ... # pragma: no cover
|
) -> SimplePath: ... # pragma: no cover
|
||||||
|
|
||||||
def __truediv__(
|
def __truediv__(
|
||||||
self, other: str | os.PathLike[str]
|
self, other: Union[str, os.PathLike[str]]
|
||||||
) -> SimplePath: ... # pragma: no cover
|
) -> SimplePath: ... # pragma: no cover
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import functools
|
|
||||||
import typing
|
|
||||||
|
|
||||||
from ._meta import PackageMetadata
|
|
||||||
|
|
||||||
md_none = functools.partial(typing.cast, PackageMetadata)
|
|
||||||
"""
|
|
||||||
Suppress type errors for optional metadata.
|
|
||||||
|
|
||||||
Although Distribution.metadata can return None when metadata is corrupt
|
|
||||||
and thus None, allow callers to assume it's not None and crash if
|
|
||||||
that's the case.
|
|
||||||
|
|
||||||
# python/importlib_metadata#493
|
|
||||||
"""
|
|
||||||
|
|
@ -1,14 +1,9 @@
|
||||||
# from jaraco.path 3.7.2
|
# from jaraco.path 3.7
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
import pathlib
|
import pathlib
|
||||||
from collections.abc import Mapping
|
from typing import Dict, Protocol, Union
|
||||||
from typing import TYPE_CHECKING, Protocol, Union, runtime_checkable
|
from typing import runtime_checkable
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from typing_extensions import Self
|
|
||||||
|
|
||||||
|
|
||||||
class Symlink(str):
|
class Symlink(str):
|
||||||
|
|
@ -17,25 +12,29 @@ class Symlink(str):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
FilesSpec = Mapping[str, Union[str, bytes, Symlink, 'FilesSpec']]
|
FilesSpec = Dict[str, Union[str, bytes, Symlink, 'FilesSpec']] # type: ignore
|
||||||
|
|
||||||
|
|
||||||
@runtime_checkable
|
@runtime_checkable
|
||||||
class TreeMaker(Protocol):
|
class TreeMaker(Protocol):
|
||||||
def __truediv__(self, other, /) -> Self: ...
|
def __truediv__(self, *args, **kwargs): ... # pragma: no cover
|
||||||
def mkdir(self, *, exist_ok) -> object: ...
|
|
||||||
def write_text(self, content, /, *, encoding) -> object: ...
|
def mkdir(self, **kwargs): ... # pragma: no cover
|
||||||
def write_bytes(self, content, /) -> object: ...
|
|
||||||
def symlink_to(self, target, /) -> object: ...
|
def write_text(self, content, **kwargs): ... # pragma: no cover
|
||||||
|
|
||||||
|
def write_bytes(self, content): ... # pragma: no cover
|
||||||
|
|
||||||
|
def symlink_to(self, target): ... # pragma: no cover
|
||||||
|
|
||||||
|
|
||||||
def _ensure_tree_maker(obj: str | TreeMaker) -> TreeMaker:
|
def _ensure_tree_maker(obj: Union[str, TreeMaker]) -> TreeMaker:
|
||||||
return obj if isinstance(obj, TreeMaker) else pathlib.Path(obj)
|
return obj if isinstance(obj, TreeMaker) else pathlib.Path(obj) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def build(
|
def build(
|
||||||
spec: FilesSpec,
|
spec: FilesSpec,
|
||||||
prefix: str | TreeMaker = pathlib.Path(),
|
prefix: Union[str, TreeMaker] = pathlib.Path(), # type: ignore
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Build a set of files/directories, as described by the spec.
|
Build a set of files/directories, as described by the spec.
|
||||||
|
|
@ -67,24 +66,23 @@ def build(
|
||||||
|
|
||||||
|
|
||||||
@functools.singledispatch
|
@functools.singledispatch
|
||||||
def create(content: str | bytes | FilesSpec, path: TreeMaker) -> None:
|
def create(content: Union[str, bytes, FilesSpec], path):
|
||||||
path.mkdir(exist_ok=True)
|
path.mkdir(exist_ok=True)
|
||||||
# Mypy only looks at the signature of the main singledispatch method. So it must contain the complete Union
|
build(content, prefix=path) # type: ignore
|
||||||
build(content, prefix=path) # type: ignore[arg-type] # python/mypy#11727
|
|
||||||
|
|
||||||
|
|
||||||
@create.register
|
@create.register
|
||||||
def _(content: bytes, path: TreeMaker) -> None:
|
def _(content: bytes, path):
|
||||||
path.write_bytes(content)
|
path.write_bytes(content)
|
||||||
|
|
||||||
|
|
||||||
@create.register
|
@create.register
|
||||||
def _(content: str, path: TreeMaker) -> None:
|
def _(content: str, path):
|
||||||
path.write_text(content, encoding='utf-8')
|
path.write_text(content, encoding='utf-8')
|
||||||
|
|
||||||
|
|
||||||
@create.register
|
@create.register
|
||||||
def _(content: Symlink, path: TreeMaker) -> None:
|
def _(content: Symlink, path):
|
||||||
path.symlink_to(content)
|
path.symlink_to(content)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import contextlib
|
|
||||||
import copy
|
|
||||||
import functools
|
|
||||||
import json
|
|
||||||
import pathlib
|
|
||||||
import shutil
|
|
||||||
import sys
|
import sys
|
||||||
|
import copy
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
import pathlib
|
||||||
import textwrap
|
import textwrap
|
||||||
|
import functools
|
||||||
|
import contextlib
|
||||||
|
|
||||||
from test.support import import_helper
|
from test.support import import_helper
|
||||||
from test.support import os_helper
|
from test.support import os_helper
|
||||||
|
|
@ -14,10 +14,14 @@ from test.support import requires_zlib
|
||||||
from . import _path
|
from . import _path
|
||||||
from ._path import FilesSpec
|
from ._path import FilesSpec
|
||||||
|
|
||||||
if sys.version_info >= (3, 9):
|
|
||||||
from importlib import resources
|
try:
|
||||||
else:
|
from importlib import resources # type: ignore
|
||||||
import importlib_resources as resources
|
|
||||||
|
getattr(resources, 'files')
|
||||||
|
getattr(resources, 'as_file')
|
||||||
|
except (ImportError, AttributeError):
|
||||||
|
import importlib_resources as resources # type: ignore
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import importlib
|
|
||||||
import re
|
import re
|
||||||
import textwrap
|
import textwrap
|
||||||
import unittest
|
import unittest
|
||||||
|
import importlib
|
||||||
|
|
||||||
|
from . import fixtures
|
||||||
from importlib.metadata import (
|
from importlib.metadata import (
|
||||||
Distribution,
|
Distribution,
|
||||||
PackageNotFoundError,
|
PackageNotFoundError,
|
||||||
|
|
@ -14,8 +15,6 @@ from importlib.metadata import (
|
||||||
version,
|
version,
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import fixtures
|
|
||||||
|
|
||||||
|
|
||||||
class APITests(
|
class APITests(
|
||||||
fixtures.EggInfoPkg,
|
fixtures.EggInfoPkg,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import importlib
|
|
||||||
import pickle
|
|
||||||
import re
|
import re
|
||||||
|
import pickle
|
||||||
import unittest
|
import unittest
|
||||||
|
import importlib
|
||||||
|
import importlib.metadata
|
||||||
from test.support import os_helper
|
from test.support import os_helper
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -9,6 +10,8 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from .stubs import fake_filesystem_unittest as ffs
|
from .stubs import fake_filesystem_unittest as ffs
|
||||||
|
|
||||||
|
from . import fixtures
|
||||||
|
from ._path import Symlink
|
||||||
from importlib.metadata import (
|
from importlib.metadata import (
|
||||||
Distribution,
|
Distribution,
|
||||||
EntryPoint,
|
EntryPoint,
|
||||||
|
|
@ -21,9 +24,6 @@ from importlib.metadata import (
|
||||||
version,
|
version,
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import fixtures
|
|
||||||
from ._path import Symlink
|
|
||||||
|
|
||||||
|
|
||||||
class BasicTests(fixtures.DistInfoPkg, unittest.TestCase):
|
class BasicTests(fixtures.DistInfoPkg, unittest.TestCase):
|
||||||
version_pattern = r'\d+\.\d+(\.\d)?'
|
version_pattern = r'\d+\.\d+(\.\d)?'
|
||||||
|
|
@ -157,16 +157,6 @@ class InvalidMetadataTests(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCa
|
||||||
dist = Distribution.from_name('foo')
|
dist = Distribution.from_name('foo')
|
||||||
assert dist.version == "1.0"
|
assert dist.version == "1.0"
|
||||||
|
|
||||||
def test_missing_metadata(self):
|
|
||||||
"""
|
|
||||||
Dists with a missing metadata file should return None.
|
|
||||||
|
|
||||||
Ref python/importlib_metadata#493.
|
|
||||||
"""
|
|
||||||
fixtures.build_files(self.make_pkg('foo-4.3', files={}), self.site_dir)
|
|
||||||
assert Distribution.from_name('foo').metadata is None
|
|
||||||
assert metadata('foo') is None
|
|
||||||
|
|
||||||
|
|
||||||
class NonASCIITests(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase):
|
class NonASCIITests(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from . import fixtures
|
||||||
from importlib.metadata import (
|
from importlib.metadata import (
|
||||||
PackageNotFoundError,
|
PackageNotFoundError,
|
||||||
distribution,
|
distribution,
|
||||||
|
|
@ -10,8 +11,6 @@ from importlib.metadata import (
|
||||||
version,
|
version,
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import fixtures
|
|
||||||
|
|
||||||
|
|
||||||
class TestZip(fixtures.ZipFixtures, unittest.TestCase):
|
class TestZip(fixtures.ZipFixtures, unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
Applied changes to ``importlib.metadata`` from `importlib_metadata 8.7
|
|
||||||
<https://importlib-metadata.readthedocs.io/en/latest/history.html#v8-7-0>`_,
|
|
||||||
including ``dist`` now disallowed for ``EntryPoints.select``; deferred
|
|
||||||
imports for faster import times; added support for metadata with newlines
|
|
||||||
(python/cpython#119650); and ``metadata()`` function now returns ``None``
|
|
||||||
when a metadata directory is present but no metadata is present.
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue