mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-06 21:21:15 -07:00
Bump importlib-metadata from 6.8.0 to 7.1.0 (#2286)
* Bump importlib-metadata from 6.8.0 to 7.1.0 Bumps [importlib-metadata](https://github.com/python/importlib_metadata) from 6.8.0 to 7.1.0. - [Release notes](https://github.com/python/importlib_metadata/releases) - [Changelog](https://github.com/python/importlib_metadata/blob/main/NEWS.rst) - [Commits](https://github.com/python/importlib_metadata/compare/v6.8.0...v7.1.0) --- updated-dependencies: - dependency-name: importlib-metadata dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * Update importlib-metadata==7.1.0 --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> [skip ci]
This commit is contained in:
parent
b01b21ae05
commit
e248c13c15
9 changed files with 379 additions and 156 deletions
|
@ -1,10 +1,14 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import abc
|
import abc
|
||||||
import csv
|
|
||||||
import sys
|
import sys
|
||||||
|
import json
|
||||||
import zipp
|
import zipp
|
||||||
import email
|
import email
|
||||||
|
import types
|
||||||
|
import inspect
|
||||||
import pathlib
|
import pathlib
|
||||||
import operator
|
import operator
|
||||||
import textwrap
|
import textwrap
|
||||||
|
@ -14,12 +18,12 @@ import itertools
|
||||||
import posixpath
|
import posixpath
|
||||||
import collections
|
import collections
|
||||||
|
|
||||||
from . import _adapters, _meta, _py39compat
|
from . import _adapters, _meta
|
||||||
|
from .compat import py39
|
||||||
from ._collections import FreezableDefaultDict, Pair
|
from ._collections import FreezableDefaultDict, Pair
|
||||||
from ._compat import (
|
from ._compat import (
|
||||||
NullFinder,
|
NullFinder,
|
||||||
install,
|
install,
|
||||||
pypy_partial,
|
|
||||||
)
|
)
|
||||||
from ._functools import method_cache, pass_none
|
from ._functools import method_cache, pass_none
|
||||||
from ._itertools import always_iterable, unique_everseen
|
from ._itertools import always_iterable, unique_everseen
|
||||||
|
@ -29,8 +33,7 @@ from contextlib import suppress
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from importlib.abc import MetaPathFinder
|
from importlib.abc import MetaPathFinder
|
||||||
from itertools import starmap
|
from itertools import starmap
|
||||||
from typing import List, Mapping, Optional
|
from typing import Any, Iterable, List, Mapping, Match, Optional, Set, cast
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'Distribution',
|
'Distribution',
|
||||||
|
@ -51,11 +54,11 @@ __all__ = [
|
||||||
class PackageNotFoundError(ModuleNotFoundError):
|
class PackageNotFoundError(ModuleNotFoundError):
|
||||||
"""The package was not found."""
|
"""The package was not found."""
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
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):
|
def name(self) -> str: # type: ignore[override]
|
||||||
(name,) = self.args
|
(name,) = self.args
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
@ -121,38 +124,11 @@ class Sectioned:
|
||||||
yield Pair(name, value)
|
yield Pair(name, value)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def valid(line):
|
def valid(line: str):
|
||||||
return line and not line.startswith('#')
|
return line and not line.startswith('#')
|
||||||
|
|
||||||
|
|
||||||
class DeprecatedTuple:
|
class EntryPoint:
|
||||||
"""
|
|
||||||
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
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Do not remove prior to 2023-05-01 or Python 3.13
|
|
||||||
_warn = functools.partial(
|
|
||||||
warnings.warn,
|
|
||||||
"EntryPoint tuple interface is deprecated. Access members by name.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=pypy_partial(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
|
||||||
|
@ -194,34 +170,37 @@ class EntryPoint(DeprecatedTuple):
|
||||||
value: str
|
value: str
|
||||||
group: str
|
group: str
|
||||||
|
|
||||||
dist: Optional['Distribution'] = None
|
dist: Optional[Distribution] = None
|
||||||
|
|
||||||
def __init__(self, name, value, group):
|
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)
|
||||||
|
|
||||||
def load(self):
|
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.
|
||||||
"""
|
"""
|
||||||
match = self.pattern.match(self.value)
|
match = cast(Match, self.pattern.match(self.value))
|
||||||
module = import_module(match.group('module'))
|
module = import_module(match.group('module'))
|
||||||
attrs = filter(None, (match.group('attr') or '').split('.'))
|
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):
|
def module(self) -> str:
|
||||||
match = self.pattern.match(self.value)
|
match = self.pattern.match(self.value)
|
||||||
|
assert match is not None
|
||||||
return match.group('module')
|
return match.group('module')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def attr(self):
|
def attr(self) -> str:
|
||||||
match = self.pattern.match(self.value)
|
match = self.pattern.match(self.value)
|
||||||
|
assert match is not None
|
||||||
return match.group('attr')
|
return match.group('attr')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extras(self):
|
def extras(self) -> List[str]:
|
||||||
match = self.pattern.match(self.value)
|
match = self.pattern.match(self.value)
|
||||||
|
assert match is not None
|
||||||
return re.findall(r'\w+', match.group('extras') or '')
|
return re.findall(r'\w+', match.group('extras') or '')
|
||||||
|
|
||||||
def _for(self, dist):
|
def _for(self, dist):
|
||||||
|
@ -269,7 +248,7 @@ class EntryPoint(DeprecatedTuple):
|
||||||
f'group={self.group!r})'
|
f'group={self.group!r})'
|
||||||
)
|
)
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self) -> int:
|
||||||
return hash(self._key())
|
return hash(self._key())
|
||||||
|
|
||||||
|
|
||||||
|
@ -280,7 +259,7 @@ class EntryPoints(tuple):
|
||||||
|
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
def __getitem__(self, name): # -> EntryPoint:
|
def __getitem__(self, name: str) -> EntryPoint: # type: ignore[override]
|
||||||
"""
|
"""
|
||||||
Get the EntryPoint in self matching name.
|
Get the EntryPoint in self matching name.
|
||||||
"""
|
"""
|
||||||
|
@ -289,22 +268,29 @@ class EntryPoints(tuple):
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
raise KeyError(name)
|
raise KeyError(name)
|
||||||
|
|
||||||
def select(self, **params):
|
def __repr__(self):
|
||||||
|
"""
|
||||||
|
Repr with classname and tuple constructor to
|
||||||
|
signal that we deviate from regular tuple behavior.
|
||||||
|
"""
|
||||||
|
return '%s(%r)' % (self.__class__.__name__, tuple(self))
|
||||||
|
|
||||||
|
def select(self, **params) -> EntryPoints:
|
||||||
"""
|
"""
|
||||||
Select entry points from self that match the
|
Select entry points from self that match the
|
||||||
given parameters (typically group and/or name).
|
given parameters (typically group and/or name).
|
||||||
"""
|
"""
|
||||||
return EntryPoints(ep for ep in self if _py39compat.ep_matches(ep, **params))
|
return EntryPoints(ep for ep in self if py39.ep_matches(ep, **params))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def names(self):
|
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):
|
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.
|
||||||
"""
|
"""
|
||||||
|
@ -325,47 +311,92 @@ 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"""
|
||||||
|
|
||||||
def read_text(self, encoding='utf-8'):
|
hash: Optional[FileHash]
|
||||||
with self.locate().open(encoding=encoding) as stream:
|
size: int
|
||||||
return stream.read()
|
dist: Distribution
|
||||||
|
|
||||||
def read_binary(self):
|
def read_text(self, encoding: str = 'utf-8') -> str: # type: ignore[override]
|
||||||
with self.locate().open('rb') as stream:
|
return self.locate().read_text(encoding=encoding)
|
||||||
return stream.read()
|
|
||||||
|
|
||||||
def locate(self):
|
def read_binary(self) -> bytes:
|
||||||
|
return self.locate().read_bytes()
|
||||||
|
|
||||||
|
def locate(self) -> SimplePath:
|
||||||
"""Return a path-like object for this path"""
|
"""Return a path-like object for this path"""
|
||||||
return self.dist.locate_file(self)
|
return self.dist.locate_file(self)
|
||||||
|
|
||||||
|
|
||||||
class FileHash:
|
class FileHash:
|
||||||
def __init__(self, spec):
|
def __init__(self, spec: str) -> None:
|
||||||
self.mode, _, self.value = spec.partition('=')
|
self.mode, _, self.value = spec.partition('=')
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return f'<FileHash mode: {self.mode} value: {self.value}>'
|
return f'<FileHash mode: {self.mode} value: {self.value}>'
|
||||||
|
|
||||||
|
|
||||||
class Distribution(metaclass=abc.ABCMeta):
|
class DeprecatedNonAbstract:
|
||||||
"""A Python distribution package."""
|
# Required until Python 3.14
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
all_names = {
|
||||||
|
name for subclass in inspect.getmro(cls) for name in vars(subclass)
|
||||||
|
}
|
||||||
|
abstract = {
|
||||||
|
name
|
||||||
|
for name in all_names
|
||||||
|
if getattr(getattr(cls, name), '__isabstractmethod__', False)
|
||||||
|
}
|
||||||
|
if abstract:
|
||||||
|
warnings.warn(
|
||||||
|
f"Unimplemented abstract methods {abstract}",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return super().__new__(cls)
|
||||||
|
|
||||||
|
|
||||||
|
class Distribution(DeprecatedNonAbstract):
|
||||||
|
"""
|
||||||
|
An abstract Python distribution package.
|
||||||
|
|
||||||
|
Custom providers may derive from this class and define
|
||||||
|
the abstract methods to provide a concrete implementation
|
||||||
|
for their environment. Some providers may opt to override
|
||||||
|
the default implementation of some properties to bypass
|
||||||
|
the file-reading mechanism.
|
||||||
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def read_text(self, filename):
|
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
|
||||||
|
typically represented as "files" in the metadata directory
|
||||||
|
(e.g. package-1.0.dist-info). These files include things
|
||||||
|
like:
|
||||||
|
|
||||||
|
- METADATA: The distribution metadata including fields
|
||||||
|
like Name and Version and Description.
|
||||||
|
- entry_points.txt: A series of entry points as defined in
|
||||||
|
`the entry points spec <https://packaging.python.org/en/latest/specifications/entry-points/#file-format>`_.
|
||||||
|
- RECORD: A record of files according to
|
||||||
|
`this recording spec <https://packaging.python.org/en/latest/specifications/recording-installed-packages/#the-record-file>`_.
|
||||||
|
|
||||||
|
A package may provide any set of files, including those
|
||||||
|
not listed here or none at all.
|
||||||
|
|
||||||
:param filename: The name of the file in the distribution info.
|
:param filename: The name of the file in the distribution info.
|
||||||
:return: The text if found, otherwise None.
|
:return: The text if found, otherwise None.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def locate_file(self, path):
|
def locate_file(self, path: str | os.PathLike[str]) -> SimplePath:
|
||||||
"""
|
"""
|
||||||
Given a path to a file in this distribution, return a path
|
Given a path to a file in this distribution, return a SimplePath
|
||||||
to it.
|
to it.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_name(cls, name: str):
|
def from_name(cls, name: str) -> Distribution:
|
||||||
"""Return the Distribution for the given package name.
|
"""Return the Distribution for the given package name.
|
||||||
|
|
||||||
:param name: The name of the distribution package to search for.
|
:param name: The name of the distribution package to search for.
|
||||||
|
@ -378,21 +409,23 @@ class Distribution(metaclass=abc.ABCMeta):
|
||||||
if not name:
|
if not name:
|
||||||
raise ValueError("A distribution name is required.")
|
raise ValueError("A distribution name is required.")
|
||||||
try:
|
try:
|
||||||
return next(cls.discover(name=name))
|
return next(iter(cls.discover(name=name)))
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
raise PackageNotFoundError(name)
|
raise PackageNotFoundError(name)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def discover(cls, **kwargs):
|
def discover(
|
||||||
|
cls, *, context: Optional[DistributionFinder.Context] = None, **kwargs
|
||||||
|
) -> Iterable[Distribution]:
|
||||||
"""Return an iterable of Distribution objects for all packages.
|
"""Return an iterable of Distribution objects for all packages.
|
||||||
|
|
||||||
Pass a ``context`` or pass keyword arguments for constructing
|
Pass a ``context`` or pass keyword arguments for constructing
|
||||||
a context.
|
a context.
|
||||||
|
|
||||||
:context: A ``DistributionFinder.Context`` object.
|
:context: A ``DistributionFinder.Context`` object.
|
||||||
:return: Iterable of Distribution objects for all packages.
|
:return: Iterable of Distribution objects for packages matching
|
||||||
|
the context.
|
||||||
"""
|
"""
|
||||||
context = kwargs.pop('context', None)
|
|
||||||
if context and kwargs:
|
if context and kwargs:
|
||||||
raise ValueError("cannot accept context and kwargs")
|
raise ValueError("cannot accept context and kwargs")
|
||||||
context = context or DistributionFinder.Context(**kwargs)
|
context = context or DistributionFinder.Context(**kwargs)
|
||||||
|
@ -401,8 +434,8 @@ class Distribution(metaclass=abc.ABCMeta):
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def at(path):
|
def at(path: str | os.PathLike[str]) -> Distribution:
|
||||||
"""Return a Distribution for the indicated metadata path
|
"""Return a Distribution for the indicated metadata path.
|
||||||
|
|
||||||
:param path: a string or path-like object
|
:param path: a string or path-like object
|
||||||
:return: a concrete Distribution instance for the path
|
:return: a concrete Distribution instance for the path
|
||||||
|
@ -411,7 +444,7 @@ class Distribution(metaclass=abc.ABCMeta):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _discover_resolvers():
|
def _discover_resolvers():
|
||||||
"""Search the meta_path for resolvers."""
|
"""Search the meta_path for resolvers (MetadataPathFinders)."""
|
||||||
declared = (
|
declared = (
|
||||||
getattr(finder, 'find_distributions', None) for finder in sys.meta_path
|
getattr(finder, 'find_distributions', None) for finder in sys.meta_path
|
||||||
)
|
)
|
||||||
|
@ -422,9 +455,13 @@ class Distribution(metaclass=abc.ABCMeta):
|
||||||
"""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
|
||||||
metadata. See PEP 566 for details.
|
metadata per the
|
||||||
|
`Core metadata specifications <https://packaging.python.org/en/latest/specifications/core-metadata/#core-metadata>`_.
|
||||||
|
|
||||||
|
Custom providers may provide the METADATA file or override this
|
||||||
|
property.
|
||||||
"""
|
"""
|
||||||
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
|
||||||
|
@ -432,10 +469,11 @@ 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('')
|
||||||
)
|
)
|
||||||
|
text = cast(str, opt_text)
|
||||||
return _adapters.Message(email.message_from_string(text))
|
return _adapters.Message(email.message_from_string(text))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self) -> str:
|
||||||
"""Return the 'Name' metadata for the distribution package."""
|
"""Return the 'Name' metadata for the distribution package."""
|
||||||
return self.metadata['Name']
|
return self.metadata['Name']
|
||||||
|
|
||||||
|
@ -445,24 +483,34 @@ class Distribution(metaclass=abc.ABCMeta):
|
||||||
return Prepared.normalize(self.name)
|
return Prepared.normalize(self.name)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def version(self):
|
def version(self) -> str:
|
||||||
"""Return the 'Version' metadata for the distribution package."""
|
"""Return the 'Version' metadata for the distribution package."""
|
||||||
return self.metadata['Version']
|
return self.metadata['Version']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def entry_points(self):
|
def entry_points(self) -> EntryPoints:
|
||||||
|
"""
|
||||||
|
Return EntryPoints for this distribution.
|
||||||
|
|
||||||
|
Custom providers may provide the ``entry_points.txt`` file
|
||||||
|
or override this property.
|
||||||
|
"""
|
||||||
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):
|
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
|
||||||
|
|
||||||
Result is `None` if the metadata file that enumerates files
|
Result is `None` if the metadata file that enumerates files
|
||||||
(i.e. RECORD for dist-info or SOURCES.txt for egg-info) is
|
(i.e. RECORD for dist-info, or installed-files.txt or
|
||||||
missing.
|
SOURCES.txt for egg-info) is missing.
|
||||||
Result may be empty if the metadata exists but is empty.
|
Result may be empty if the metadata exists but is empty.
|
||||||
|
|
||||||
|
Custom providers are recommended to provide a "RECORD" file (in
|
||||||
|
``read_text``) or override this property to allow for callers to be
|
||||||
|
able to resolve filenames provided by the package.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def make_file(name, hash=None, size_str=None):
|
def make_file(name, hash=None, size_str=None):
|
||||||
|
@ -474,27 +522,76 @@ class Distribution(metaclass=abc.ABCMeta):
|
||||||
|
|
||||||
@pass_none
|
@pass_none
|
||||||
def make_files(lines):
|
def make_files(lines):
|
||||||
return list(starmap(make_file, csv.reader(lines)))
|
# Delay csv import, since Distribution.files is not as widely used
|
||||||
|
# as other parts of importlib.metadata
|
||||||
|
import csv
|
||||||
|
|
||||||
return make_files(self._read_files_distinfo() or self._read_files_egginfo())
|
return starmap(make_file, csv.reader(lines))
|
||||||
|
|
||||||
|
@pass_none
|
||||||
|
def skip_missing_files(package_paths):
|
||||||
|
return list(filter(lambda path: path.locate().exists(), package_paths))
|
||||||
|
|
||||||
|
return skip_missing_files(
|
||||||
|
make_files(
|
||||||
|
self._read_files_distinfo()
|
||||||
|
or self._read_files_egginfo_installed()
|
||||||
|
or self._read_files_egginfo_sources()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def _read_files_distinfo(self):
|
def _read_files_distinfo(self):
|
||||||
"""
|
"""
|
||||||
Read the lines of RECORD
|
Read the lines of RECORD.
|
||||||
"""
|
"""
|
||||||
text = self.read_text('RECORD')
|
text = self.read_text('RECORD')
|
||||||
return text and text.splitlines()
|
return text and text.splitlines()
|
||||||
|
|
||||||
def _read_files_egginfo(self):
|
def _read_files_egginfo_installed(self):
|
||||||
"""
|
"""
|
||||||
SOURCES.txt might contain literal commas, so wrap each line
|
Read installed-files.txt and return lines in a similar
|
||||||
in quotes.
|
CSV-parsable format as RECORD: each file must be placed
|
||||||
|
relative to the site-packages directory and must also be
|
||||||
|
quoted (since file names can contain literal commas).
|
||||||
|
|
||||||
|
This file is written when the package is installed by pip,
|
||||||
|
but it might not be written for other installation methods.
|
||||||
|
Assume the file is accurate if it exists.
|
||||||
|
"""
|
||||||
|
text = self.read_text('installed-files.txt')
|
||||||
|
# Prepend the .egg-info/ subdir to the lines in this file.
|
||||||
|
# But this subdir is only available from PathDistribution's
|
||||||
|
# self._path.
|
||||||
|
subdir = getattr(self, '_path', None)
|
||||||
|
if not text or not subdir:
|
||||||
|
return
|
||||||
|
|
||||||
|
paths = (
|
||||||
|
(subdir / name)
|
||||||
|
.resolve()
|
||||||
|
.relative_to(self.locate_file('').resolve())
|
||||||
|
.as_posix()
|
||||||
|
for name in text.splitlines()
|
||||||
|
)
|
||||||
|
return map('"{}"'.format, paths)
|
||||||
|
|
||||||
|
def _read_files_egginfo_sources(self):
|
||||||
|
"""
|
||||||
|
Read SOURCES.txt and return lines in a similar CSV-parsable
|
||||||
|
format as RECORD: each file name must be quoted (since it
|
||||||
|
might contain literal commas).
|
||||||
|
|
||||||
|
Note that SOURCES.txt is not a reliable source for what
|
||||||
|
files are installed by a package. This file is generated
|
||||||
|
for a source archive, and the files that are present
|
||||||
|
there (e.g. setup.py) may not correctly reflect the files
|
||||||
|
that are present after the package has been installed.
|
||||||
"""
|
"""
|
||||||
text = self.read_text('SOURCES.txt')
|
text = self.read_text('SOURCES.txt')
|
||||||
return text and map('"{}"'.format, text.splitlines())
|
return text and map('"{}"'.format, text.splitlines())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def requires(self):
|
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)
|
||||||
|
@ -545,10 +642,23 @@ class Distribution(metaclass=abc.ABCMeta):
|
||||||
space = url_req_space(section.value)
|
space = url_req_space(section.value)
|
||||||
yield section.value + space + quoted_marker(section.name)
|
yield section.value + space + quoted_marker(section.name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def origin(self):
|
||||||
|
return self._load_json('direct_url.json')
|
||||||
|
|
||||||
|
def _load_json(self, filename):
|
||||||
|
return pass_none(json.loads)(
|
||||||
|
self.read_text(filename),
|
||||||
|
object_hook=lambda data: types.SimpleNamespace(**data),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DistributionFinder(MetaPathFinder):
|
class DistributionFinder(MetaPathFinder):
|
||||||
"""
|
"""
|
||||||
A MetaPathFinder capable of discovering installed distributions.
|
A MetaPathFinder capable of discovering installed distributions.
|
||||||
|
|
||||||
|
Custom providers should implement this interface in order to
|
||||||
|
supply metadata.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Context:
|
class Context:
|
||||||
|
@ -561,6 +671,17 @@ class DistributionFinder(MetaPathFinder):
|
||||||
Each DistributionFinder may expect any parameters
|
Each DistributionFinder may expect any parameters
|
||||||
and should attempt to honor the canonical
|
and should attempt to honor the canonical
|
||||||
parameters defined below when appropriate.
|
parameters defined below when appropriate.
|
||||||
|
|
||||||
|
This mechanism gives a custom provider a means to
|
||||||
|
solicit additional details from the caller beyond
|
||||||
|
"name" and "path" when searching distributions.
|
||||||
|
For example, imagine a provider that exposes suites
|
||||||
|
of packages in either a "public" or "private" ``realm``.
|
||||||
|
A caller may wish to query only for distributions in
|
||||||
|
a particular realm and could call
|
||||||
|
``distributions(realm="private")`` to signal to the
|
||||||
|
custom provider to only include distributions from that
|
||||||
|
realm.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = None
|
name = None
|
||||||
|
@ -573,7 +694,7 @@ class DistributionFinder(MetaPathFinder):
|
||||||
vars(self).update(kwargs)
|
vars(self).update(kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path(self):
|
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.
|
||||||
|
@ -584,7 +705,7 @@ class DistributionFinder(MetaPathFinder):
|
||||||
return vars(self).get('path', sys.path)
|
return vars(self).get('path', sys.path)
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def find_distributions(self, context=Context()):
|
def find_distributions(self, context=Context()) -> Iterable[Distribution]:
|
||||||
"""
|
"""
|
||||||
Find distributions.
|
Find distributions.
|
||||||
|
|
||||||
|
@ -596,11 +717,18 @@ class DistributionFinder(MetaPathFinder):
|
||||||
|
|
||||||
class FastPath:
|
class FastPath:
|
||||||
"""
|
"""
|
||||||
Micro-optimized class for searching a path for
|
Micro-optimized class for searching a root for children.
|
||||||
children.
|
|
||||||
|
Root is a path on the file system that may contain metadata
|
||||||
|
directories either as natural directories or within a zip file.
|
||||||
|
|
||||||
>>> FastPath('').children()
|
>>> FastPath('').children()
|
||||||
['...']
|
['...']
|
||||||
|
|
||||||
|
FastPath objects are cached and recycled for any given root.
|
||||||
|
|
||||||
|
>>> FastPath('foobar') is FastPath('foobar')
|
||||||
|
True
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@functools.lru_cache() # type: ignore
|
@functools.lru_cache() # type: ignore
|
||||||
|
@ -642,7 +770,19 @@ class FastPath:
|
||||||
|
|
||||||
|
|
||||||
class Lookup:
|
class Lookup:
|
||||||
|
"""
|
||||||
|
A micro-optimized class for searching a (fast) path for metadata.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, path: FastPath):
|
def __init__(self, path: FastPath):
|
||||||
|
"""
|
||||||
|
Calculate all of the children representing metadata.
|
||||||
|
|
||||||
|
From the children in the path, calculate early all of the
|
||||||
|
children that appear to represent metadata (infos) or legacy
|
||||||
|
metadata (eggs).
|
||||||
|
"""
|
||||||
|
|
||||||
base = os.path.basename(path.root).lower()
|
base = os.path.basename(path.root).lower()
|
||||||
base_is_egg = base.endswith(".egg")
|
base_is_egg = base.endswith(".egg")
|
||||||
self.infos = FreezableDefaultDict(list)
|
self.infos = FreezableDefaultDict(list)
|
||||||
|
@ -663,7 +803,10 @@ class Lookup:
|
||||||
self.infos.freeze()
|
self.infos.freeze()
|
||||||
self.eggs.freeze()
|
self.eggs.freeze()
|
||||||
|
|
||||||
def search(self, prepared):
|
def search(self, prepared: Prepared):
|
||||||
|
"""
|
||||||
|
Yield all infos and eggs matching the Prepared query.
|
||||||
|
"""
|
||||||
infos = (
|
infos = (
|
||||||
self.infos[prepared.normalized]
|
self.infos[prepared.normalized]
|
||||||
if prepared
|
if prepared
|
||||||
|
@ -679,13 +822,28 @@ class Lookup:
|
||||||
|
|
||||||
class Prepared:
|
class Prepared:
|
||||||
"""
|
"""
|
||||||
A prepared search for metadata on a possibly-named package.
|
A prepared search query for metadata on a possibly-named package.
|
||||||
|
|
||||||
|
Pre-calculates the normalization to prevent repeated operations.
|
||||||
|
|
||||||
|
>>> none = Prepared(None)
|
||||||
|
>>> none.normalized
|
||||||
|
>>> none.legacy_normalized
|
||||||
|
>>> bool(none)
|
||||||
|
False
|
||||||
|
>>> sample = Prepared('Sample__Pkg-name.foo')
|
||||||
|
>>> sample.normalized
|
||||||
|
'sample_pkg_name_foo'
|
||||||
|
>>> sample.legacy_normalized
|
||||||
|
'sample__pkg_name.foo'
|
||||||
|
>>> bool(sample)
|
||||||
|
True
|
||||||
"""
|
"""
|
||||||
|
|
||||||
normalized = None
|
normalized = None
|
||||||
legacy_normalized = None
|
legacy_normalized = None
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name: Optional[str]):
|
||||||
self.name = name
|
self.name = name
|
||||||
if name is None:
|
if name is None:
|
||||||
return
|
return
|
||||||
|
@ -719,7 +877,10 @@ class MetadataPathFinder(NullFinder, DistributionFinder):
|
||||||
of Python that do not have a PathFinder find_distributions().
|
of Python that do not have a PathFinder find_distributions().
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def find_distributions(self, context=DistributionFinder.Context()):
|
@classmethod
|
||||||
|
def find_distributions(
|
||||||
|
cls, context=DistributionFinder.Context()
|
||||||
|
) -> Iterable[PathDistribution]:
|
||||||
"""
|
"""
|
||||||
Find distributions.
|
Find distributions.
|
||||||
|
|
||||||
|
@ -728,7 +889,7 @@ class MetadataPathFinder(NullFinder, DistributionFinder):
|
||||||
(or all names if ``None`` indicated) along the paths in the list
|
(or all names if ``None`` indicated) along the paths in the list
|
||||||
of directories ``context.path``.
|
of directories ``context.path``.
|
||||||
"""
|
"""
|
||||||
found = self._search_paths(context.name, context.path)
|
found = cls._search_paths(context.name, context.path)
|
||||||
return map(PathDistribution, found)
|
return map(PathDistribution, found)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -739,19 +900,20 @@ class MetadataPathFinder(NullFinder, DistributionFinder):
|
||||||
path.search(prepared) for path in map(FastPath, paths)
|
path.search(prepared) for path in map(FastPath, paths)
|
||||||
)
|
)
|
||||||
|
|
||||||
def invalidate_caches(cls):
|
@classmethod
|
||||||
|
def invalidate_caches(cls) -> None:
|
||||||
FastPath.__new__.cache_clear()
|
FastPath.__new__.cache_clear()
|
||||||
|
|
||||||
|
|
||||||
class PathDistribution(Distribution):
|
class PathDistribution(Distribution):
|
||||||
def __init__(self, path: SimplePath):
|
def __init__(self, path: SimplePath) -> None:
|
||||||
"""Construct a distribution.
|
"""Construct a distribution.
|
||||||
|
|
||||||
:param path: SimplePath indicating the metadata directory.
|
:param path: SimplePath indicating the metadata directory.
|
||||||
"""
|
"""
|
||||||
self._path = path
|
self._path = path
|
||||||
|
|
||||||
def read_text(self, filename):
|
def read_text(self, filename: str | os.PathLike[str]) -> Optional[str]:
|
||||||
with suppress(
|
with suppress(
|
||||||
FileNotFoundError,
|
FileNotFoundError,
|
||||||
IsADirectoryError,
|
IsADirectoryError,
|
||||||
|
@ -761,9 +923,11 @@ class PathDistribution(Distribution):
|
||||||
):
|
):
|
||||||
return self._path.joinpath(filename).read_text(encoding='utf-8')
|
return self._path.joinpath(filename).read_text(encoding='utf-8')
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
read_text.__doc__ = Distribution.read_text.__doc__
|
read_text.__doc__ = Distribution.read_text.__doc__
|
||||||
|
|
||||||
def locate_file(self, path):
|
def locate_file(self, path: str | os.PathLike[str]) -> SimplePath:
|
||||||
return self._path.parent / path
|
return self._path.parent / path
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -796,7 +960,7 @@ class PathDistribution(Distribution):
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
||||||
def distribution(distribution_name):
|
def distribution(distribution_name: str) -> Distribution:
|
||||||
"""Get the ``Distribution`` instance for the named package.
|
"""Get the ``Distribution`` instance for the named package.
|
||||||
|
|
||||||
:param distribution_name: The name of the distribution package as a string.
|
:param distribution_name: The name of the distribution package as a string.
|
||||||
|
@ -805,7 +969,7 @@ def distribution(distribution_name):
|
||||||
return Distribution.from_name(distribution_name)
|
return Distribution.from_name(distribution_name)
|
||||||
|
|
||||||
|
|
||||||
def distributions(**kwargs):
|
def distributions(**kwargs) -> Iterable[Distribution]:
|
||||||
"""Get all ``Distribution`` instances in the current environment.
|
"""Get all ``Distribution`` instances in the current environment.
|
||||||
|
|
||||||
:return: An iterable of ``Distribution`` instances.
|
:return: An iterable of ``Distribution`` instances.
|
||||||
|
@ -813,7 +977,7 @@ def distributions(**kwargs):
|
||||||
return Distribution.discover(**kwargs)
|
return Distribution.discover(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
def metadata(distribution_name) -> _meta.PackageMetadata:
|
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.
|
||||||
|
@ -822,7 +986,7 @@ def metadata(distribution_name) -> _meta.PackageMetadata:
|
||||||
return Distribution.from_name(distribution_name).metadata
|
return Distribution.from_name(distribution_name).metadata
|
||||||
|
|
||||||
|
|
||||||
def version(distribution_name):
|
def version(distribution_name: str) -> str:
|
||||||
"""Get the version string for the named package.
|
"""Get the version string 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.
|
||||||
|
@ -834,7 +998,7 @@ def version(distribution_name):
|
||||||
|
|
||||||
_unique = functools.partial(
|
_unique = functools.partial(
|
||||||
unique_everseen,
|
unique_everseen,
|
||||||
key=_py39compat.normalized_name,
|
key=py39.normalized_name,
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
Wrapper for ``distributions`` to return unique distributions by name.
|
Wrapper for ``distributions`` to return unique distributions by name.
|
||||||
|
@ -856,7 +1020,7 @@ def entry_points(**params) -> EntryPoints:
|
||||||
return EntryPoints(eps).select(**params)
|
return EntryPoints(eps).select(**params)
|
||||||
|
|
||||||
|
|
||||||
def files(distribution_name):
|
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.
|
||||||
|
@ -865,11 +1029,11 @@ def files(distribution_name):
|
||||||
return distribution(distribution_name).files
|
return distribution(distribution_name).files
|
||||||
|
|
||||||
|
|
||||||
def requires(distribution_name):
|
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.
|
||||||
|
|
||||||
:return: An iterator of requirements, suitable for
|
:return: An iterable of requirements, suitable for
|
||||||
packaging.requirement.Requirement.
|
packaging.requirement.Requirement.
|
||||||
"""
|
"""
|
||||||
return distribution(distribution_name).requires
|
return distribution(distribution_name).requires
|
||||||
|
@ -896,9 +1060,43 @@ 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) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
Return the top-most parent as long as there is a parent.
|
||||||
|
"""
|
||||||
|
top, *rest = name.parts
|
||||||
|
return top if rest else None
|
||||||
|
|
||||||
|
|
||||||
|
def _get_toplevel_name(name: PackagePath) -> str:
|
||||||
|
"""
|
||||||
|
Infer a possibly importable module name from a name presumed on
|
||||||
|
sys.path.
|
||||||
|
|
||||||
|
>>> _get_toplevel_name(PackagePath('foo.py'))
|
||||||
|
'foo'
|
||||||
|
>>> _get_toplevel_name(PackagePath('foo'))
|
||||||
|
'foo'
|
||||||
|
>>> _get_toplevel_name(PackagePath('foo.pyc'))
|
||||||
|
'foo'
|
||||||
|
>>> _get_toplevel_name(PackagePath('foo/__init__.py'))
|
||||||
|
'foo'
|
||||||
|
>>> _get_toplevel_name(PackagePath('foo.pth'))
|
||||||
|
'foo.pth'
|
||||||
|
>>> _get_toplevel_name(PackagePath('foo.dist-info'))
|
||||||
|
'foo.dist-info'
|
||||||
|
"""
|
||||||
|
return _topmost(name) or (
|
||||||
|
# python/typeshed#10328
|
||||||
|
inspect.getmodulename(name) # type: ignore
|
||||||
|
or str(name)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _top_level_inferred(dist):
|
def _top_level_inferred(dist):
|
||||||
return {
|
opt_names = set(map(_get_toplevel_name, always_iterable(dist.files)))
|
||||||
f.parts[0] if len(f.parts) > 1 else f.with_suffix('').name
|
|
||||||
for f in always_iterable(dist.files)
|
def importable_name(name):
|
||||||
if f.suffix == ".py"
|
return '.' not in name
|
||||||
}
|
|
||||||
|
return filter(importable_name, opt_names)
|
||||||
|
|
|
@ -54,7 +54,7 @@ class Message(email.message.Message):
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, item):
|
||||||
"""
|
"""
|
||||||
Warn users that a ``KeyError`` can be expected when a
|
Warn users that a ``KeyError`` can be expected when a
|
||||||
mising key is supplied. Ref python/importlib_metadata#371.
|
missing key is supplied. Ref python/importlib_metadata#371.
|
||||||
"""
|
"""
|
||||||
res = super().__getitem__(item)
|
res = super().__getitem__(item)
|
||||||
if res is None:
|
if res is None:
|
||||||
|
|
|
@ -2,14 +2,7 @@ import sys
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['install', 'NullFinder', 'Protocol']
|
__all__ = ['install', 'NullFinder']
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
from typing import Protocol
|
|
||||||
except ImportError: # pragma: no cover
|
|
||||||
# Python 3.7 compatibility
|
|
||||||
from typing_extensions import Protocol # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
def install(cls):
|
def install(cls):
|
||||||
|
@ -45,7 +38,7 @@ def disable_stdlib_finder():
|
||||||
|
|
||||||
class NullFinder:
|
class NullFinder:
|
||||||
"""
|
"""
|
||||||
A "Finder" (aka "MetaClassFinder") that never finds any modules,
|
A "Finder" (aka "MetaPathFinder") that never finds any modules,
|
||||||
but may find distributions.
|
but may find distributions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -53,14 +46,6 @@ class NullFinder:
|
||||||
def find_spec(*args, **kwargs):
|
def find_spec(*args, **kwargs):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# In Python 2, the import system requires finders
|
|
||||||
# to have a find_module() method, but this usage
|
|
||||||
# is deprecated in Python 3 in favor of find_spec().
|
|
||||||
# For the purposes of this finder (i.e. being present
|
|
||||||
# on sys.meta_path but having no other import
|
|
||||||
# system functionality), the two methods are identical.
|
|
||||||
find_module = find_spec
|
|
||||||
|
|
||||||
|
|
||||||
def pypy_partial(val):
|
def pypy_partial(val):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,24 +1,38 @@
|
||||||
from ._compat import Protocol
|
from __future__ import annotations
|
||||||
from typing import Any, Dict, Iterator, List, TypeVar, Union
|
|
||||||
|
import os
|
||||||
|
from typing import Protocol
|
||||||
|
from typing import Any, Dict, Iterator, List, Optional, TypeVar, Union, overload
|
||||||
|
|
||||||
|
|
||||||
_T = TypeVar("_T")
|
_T = TypeVar("_T")
|
||||||
|
|
||||||
|
|
||||||
class PackageMetadata(Protocol):
|
class PackageMetadata(Protocol):
|
||||||
def __len__(self) -> int:
|
def __len__(self) -> int: ... # pragma: no cover
|
||||||
... # pragma: no cover
|
|
||||||
|
|
||||||
def __contains__(self, item: str) -> bool:
|
def __contains__(self, item: str) -> bool: ... # pragma: no cover
|
||||||
... # pragma: no cover
|
|
||||||
|
|
||||||
def __getitem__(self, key: str) -> str:
|
def __getitem__(self, key: str) -> str: ... # pragma: no cover
|
||||||
... # pragma: no cover
|
|
||||||
|
|
||||||
def __iter__(self) -> Iterator[str]:
|
def __iter__(self) -> Iterator[str]: ... # pragma: no cover
|
||||||
... # pragma: no cover
|
|
||||||
|
|
||||||
def get_all(self, name: str, failobj: _T = ...) -> Union[List[Any], _T]:
|
@overload
|
||||||
|
def get(
|
||||||
|
self, name: str, failobj: None = None
|
||||||
|
) -> Optional[str]: ... # pragma: no cover
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def get(self, name: str, failobj: _T) -> Union[str, _T]: ... # pragma: no cover
|
||||||
|
|
||||||
|
# overload per python/importlib_metadata#435
|
||||||
|
@overload
|
||||||
|
def get_all(
|
||||||
|
self, name: str, failobj: None = None
|
||||||
|
) -> Optional[List[Any]]: ... # pragma: no cover
|
||||||
|
|
||||||
|
@overload
|
||||||
|
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.
|
||||||
"""
|
"""
|
||||||
|
@ -30,20 +44,24 @@ class PackageMetadata(Protocol):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class SimplePath(Protocol[_T]):
|
class SimplePath(Protocol):
|
||||||
"""
|
"""
|
||||||
A minimal subset of pathlib.Path required by PathDistribution.
|
A minimal subset of pathlib.Path required by Distribution.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def joinpath(self) -> _T:
|
def joinpath(
|
||||||
... # pragma: no cover
|
self, other: Union[str, os.PathLike[str]]
|
||||||
|
) -> SimplePath: ... # pragma: no cover
|
||||||
|
|
||||||
def __truediv__(self, other: Union[str, _T]) -> _T:
|
def __truediv__(
|
||||||
... # pragma: no cover
|
self, other: Union[str, os.PathLike[str]]
|
||||||
|
) -> SimplePath: ... # pragma: no cover
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parent(self) -> _T:
|
def parent(self) -> SimplePath: ... # pragma: no cover
|
||||||
... # pragma: no cover
|
|
||||||
|
|
||||||
def read_text(self) -> str:
|
def read_text(self, encoding=None) -> str: ... # pragma: no cover
|
||||||
... # pragma: no cover
|
|
||||||
|
def read_bytes(self) -> bytes: ... # pragma: no cover
|
||||||
|
|
||||||
|
def exists(self) -> bool: ... # pragma: no cover
|
||||||
|
|
0
lib/importlib_metadata/compat/__init__.py
Normal file
0
lib/importlib_metadata/compat/__init__.py
Normal file
|
@ -1,11 +1,12 @@
|
||||||
"""
|
"""
|
||||||
Compatibility layer with Python 3.8/3.9
|
Compatibility layer with Python 3.8/3.9
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Any, Optional
|
from typing import TYPE_CHECKING, Any, Optional
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
# Prevent circular imports on runtime.
|
# Prevent circular imports on runtime.
|
||||||
from . import Distribution, EntryPoint
|
from .. import Distribution, EntryPoint
|
||||||
else:
|
else:
|
||||||
Distribution = EntryPoint = Any
|
Distribution = EntryPoint = Any
|
||||||
|
|
||||||
|
@ -17,7 +18,7 @@ def normalized_name(dist: Distribution) -> Optional[str]:
|
||||||
try:
|
try:
|
||||||
return dist._normalized_name
|
return dist._normalized_name
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
from . import Prepared # -> delay to prevent circular imports.
|
from .. import Prepared # -> delay to prevent circular imports.
|
||||||
|
|
||||||
return Prepared.normalize(getattr(dist, "name", None) or dist.metadata['Name'])
|
return Prepared.normalize(getattr(dist, "name", None) or dist.metadata['Name'])
|
||||||
|
|
||||||
|
@ -29,7 +30,7 @@ def ep_matches(ep: EntryPoint, **params) -> bool:
|
||||||
try:
|
try:
|
||||||
return ep.matches(**params)
|
return ep.matches(**params)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
from . import EntryPoint # -> delay to prevent circular imports.
|
from .. import EntryPoint # -> delay to prevent circular imports.
|
||||||
|
|
||||||
# Reconstruct the EntryPoint object to make sure it is compatible.
|
# Reconstruct the EntryPoint object to make sure it is compatible.
|
||||||
return EntryPoint(ep.name, ep.value, ep.group).matches(**params)
|
return EntryPoint(ep.name, ep.value, ep.group).matches(**params)
|
21
lib/importlib_metadata/diagnose.py
Normal file
21
lib/importlib_metadata/diagnose.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from . import Distribution
|
||||||
|
|
||||||
|
|
||||||
|
def inspect(path):
|
||||||
|
print("Inspecting", path)
|
||||||
|
dists = list(Distribution.discover(path=[path]))
|
||||||
|
if not dists:
|
||||||
|
return
|
||||||
|
print("Found", len(dists), "packages:", end=' ')
|
||||||
|
print(', '.join(dist.name for dist in dists))
|
||||||
|
|
||||||
|
|
||||||
|
def run():
|
||||||
|
for path in sys.path:
|
||||||
|
inspect(path)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
run()
|
|
@ -1,5 +1,5 @@
|
||||||
apscheduler==3.10.1
|
apscheduler==3.10.1
|
||||||
importlib-metadata==6.8.0
|
importlib-metadata==7.1.0
|
||||||
importlib-resources==6.4.0
|
importlib-resources==6.4.0
|
||||||
pyinstaller==6.4.0
|
pyinstaller==6.4.0
|
||||||
pyopenssl==24.0.0
|
pyopenssl==24.0.0
|
||||||
|
|
|
@ -18,7 +18,7 @@ gntp==1.0.3
|
||||||
html5lib==1.1
|
html5lib==1.1
|
||||||
httpagentparser==1.9.5
|
httpagentparser==1.9.5
|
||||||
idna==3.4
|
idna==3.4
|
||||||
importlib-metadata==6.8.0
|
importlib-metadata==7.1.0
|
||||||
importlib-resources==6.4.0
|
importlib-resources==6.4.0
|
||||||
git+https://github.com/Tautulli/ipwhois.git@master#egg=ipwhois
|
git+https://github.com/Tautulli/ipwhois.git@master#egg=ipwhois
|
||||||
IPy==1.01
|
IPy==1.01
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue