mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-05 20:51: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 re
|
||||
import abc
|
||||
import csv
|
||||
import sys
|
||||
import json
|
||||
import zipp
|
||||
import email
|
||||
import types
|
||||
import inspect
|
||||
import pathlib
|
||||
import operator
|
||||
import textwrap
|
||||
|
@ -14,12 +18,12 @@ import itertools
|
|||
import posixpath
|
||||
import collections
|
||||
|
||||
from . import _adapters, _meta, _py39compat
|
||||
from . import _adapters, _meta
|
||||
from .compat import py39
|
||||
from ._collections import FreezableDefaultDict, Pair
|
||||
from ._compat import (
|
||||
NullFinder,
|
||||
install,
|
||||
pypy_partial,
|
||||
)
|
||||
from ._functools import method_cache, pass_none
|
||||
from ._itertools import always_iterable, unique_everseen
|
||||
|
@ -29,8 +33,7 @@ from contextlib import suppress
|
|||
from importlib import import_module
|
||||
from importlib.abc import MetaPathFinder
|
||||
from itertools import starmap
|
||||
from typing import List, Mapping, Optional
|
||||
|
||||
from typing import Any, Iterable, List, Mapping, Match, Optional, Set, cast
|
||||
|
||||
__all__ = [
|
||||
'Distribution',
|
||||
|
@ -51,11 +54,11 @@ __all__ = [
|
|||
class PackageNotFoundError(ModuleNotFoundError):
|
||||
"""The package was not found."""
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return f"No package metadata was found for {self.name}"
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
def name(self) -> str: # type: ignore[override]
|
||||
(name,) = self.args
|
||||
return name
|
||||
|
||||
|
@ -121,38 +124,11 @@ class Sectioned:
|
|||
yield Pair(name, value)
|
||||
|
||||
@staticmethod
|
||||
def valid(line):
|
||||
def valid(line: str):
|
||||
return line and not line.startswith('#')
|
||||
|
||||
|
||||
class DeprecatedTuple:
|
||||
"""
|
||||
Provide subscript item access for backward compatibility.
|
||||
|
||||
>>> recwarn = getfixture('recwarn')
|
||||
>>> ep = EntryPoint(name='name', value='value', group='group')
|
||||
>>> ep[:]
|
||||
('name', 'value', 'group')
|
||||
>>> ep[0]
|
||||
'name'
|
||||
>>> len(recwarn)
|
||||
1
|
||||
"""
|
||||
|
||||
# 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):
|
||||
class EntryPoint:
|
||||
"""An entry point as defined by Python packaging conventions.
|
||||
|
||||
See `the packaging docs on entry points
|
||||
|
@ -194,34 +170,37 @@ class EntryPoint(DeprecatedTuple):
|
|||
value: 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)
|
||||
|
||||
def load(self):
|
||||
def load(self) -> Any:
|
||||
"""Load the entry point from its definition. If only a module
|
||||
is indicated by the value, return that module. Otherwise,
|
||||
return the named object.
|
||||
"""
|
||||
match = self.pattern.match(self.value)
|
||||
match = cast(Match, self.pattern.match(self.value))
|
||||
module = import_module(match.group('module'))
|
||||
attrs = filter(None, (match.group('attr') or '').split('.'))
|
||||
return functools.reduce(getattr, attrs, module)
|
||||
|
||||
@property
|
||||
def module(self):
|
||||
def module(self) -> str:
|
||||
match = self.pattern.match(self.value)
|
||||
assert match is not None
|
||||
return match.group('module')
|
||||
|
||||
@property
|
||||
def attr(self):
|
||||
def attr(self) -> str:
|
||||
match = self.pattern.match(self.value)
|
||||
assert match is not None
|
||||
return match.group('attr')
|
||||
|
||||
@property
|
||||
def extras(self):
|
||||
def extras(self) -> List[str]:
|
||||
match = self.pattern.match(self.value)
|
||||
assert match is not None
|
||||
return re.findall(r'\w+', match.group('extras') or '')
|
||||
|
||||
def _for(self, dist):
|
||||
|
@ -269,7 +248,7 @@ class EntryPoint(DeprecatedTuple):
|
|||
f'group={self.group!r})'
|
||||
)
|
||||
|
||||
def __hash__(self):
|
||||
def __hash__(self) -> int:
|
||||
return hash(self._key())
|
||||
|
||||
|
||||
|
@ -280,7 +259,7 @@ class EntryPoints(tuple):
|
|||
|
||||
__slots__ = ()
|
||||
|
||||
def __getitem__(self, name): # -> EntryPoint:
|
||||
def __getitem__(self, name: str) -> EntryPoint: # type: ignore[override]
|
||||
"""
|
||||
Get the EntryPoint in self matching name.
|
||||
"""
|
||||
|
@ -289,22 +268,29 @@ class EntryPoints(tuple):
|
|||
except StopIteration:
|
||||
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
|
||||
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
|
||||
def names(self):
|
||||
def names(self) -> Set[str]:
|
||||
"""
|
||||
Return the set of all names of all entry points.
|
||||
"""
|
||||
return {ep.name for ep in self}
|
||||
|
||||
@property
|
||||
def groups(self):
|
||||
def groups(self) -> Set[str]:
|
||||
"""
|
||||
Return the set of all groups of all entry points.
|
||||
"""
|
||||
|
@ -325,47 +311,92 @@ class EntryPoints(tuple):
|
|||
class PackagePath(pathlib.PurePosixPath):
|
||||
"""A reference to a path in a package"""
|
||||
|
||||
def read_text(self, encoding='utf-8'):
|
||||
with self.locate().open(encoding=encoding) as stream:
|
||||
return stream.read()
|
||||
hash: Optional[FileHash]
|
||||
size: int
|
||||
dist: Distribution
|
||||
|
||||
def read_binary(self):
|
||||
with self.locate().open('rb') as stream:
|
||||
return stream.read()
|
||||
def read_text(self, encoding: str = 'utf-8') -> str: # type: ignore[override]
|
||||
return self.locate().read_text(encoding=encoding)
|
||||
|
||||
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 self.dist.locate_file(self)
|
||||
|
||||
|
||||
class FileHash:
|
||||
def __init__(self, spec):
|
||||
def __init__(self, spec: str) -> None:
|
||||
self.mode, _, self.value = spec.partition('=')
|
||||
|
||||
def __repr__(self):
|
||||
def __repr__(self) -> str:
|
||||
return f'<FileHash mode: {self.mode} value: {self.value}>'
|
||||
|
||||
|
||||
class Distribution(metaclass=abc.ABCMeta):
|
||||
"""A Python distribution package."""
|
||||
class DeprecatedNonAbstract:
|
||||
# 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
|
||||
def read_text(self, filename):
|
||||
def read_text(self, filename) -> Optional[str]:
|
||||
"""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.
|
||||
:return: The text if found, otherwise None.
|
||||
"""
|
||||
|
||||
@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.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def from_name(cls, name: str):
|
||||
def from_name(cls, name: str) -> Distribution:
|
||||
"""Return the Distribution for the given package name.
|
||||
|
||||
:param name: The name of the distribution package to search for.
|
||||
|
@ -378,21 +409,23 @@ class Distribution(metaclass=abc.ABCMeta):
|
|||
if not name:
|
||||
raise ValueError("A distribution name is required.")
|
||||
try:
|
||||
return next(cls.discover(name=name))
|
||||
return next(iter(cls.discover(name=name)))
|
||||
except StopIteration:
|
||||
raise PackageNotFoundError(name)
|
||||
|
||||
@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.
|
||||
|
||||
Pass a ``context`` or pass keyword arguments for constructing
|
||||
a context.
|
||||
|
||||
: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:
|
||||
raise ValueError("cannot accept context and kwargs")
|
||||
context = context or DistributionFinder.Context(**kwargs)
|
||||
|
@ -401,8 +434,8 @@ class Distribution(metaclass=abc.ABCMeta):
|
|||
)
|
||||
|
||||
@staticmethod
|
||||
def at(path):
|
||||
"""Return a Distribution for the indicated metadata path
|
||||
def at(path: str | os.PathLike[str]) -> Distribution:
|
||||
"""Return a Distribution for the indicated metadata path.
|
||||
|
||||
:param path: a string or path-like object
|
||||
:return: a concrete Distribution instance for the path
|
||||
|
@ -411,7 +444,7 @@ class Distribution(metaclass=abc.ABCMeta):
|
|||
|
||||
@staticmethod
|
||||
def _discover_resolvers():
|
||||
"""Search the meta_path for resolvers."""
|
||||
"""Search the meta_path for resolvers (MetadataPathFinders)."""
|
||||
declared = (
|
||||
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.
|
||||
|
||||
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')
|
||||
or self.read_text('PKG-INFO')
|
||||
# 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.
|
||||
or self.read_text('')
|
||||
)
|
||||
text = cast(str, opt_text)
|
||||
return _adapters.Message(email.message_from_string(text))
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
def name(self) -> str:
|
||||
"""Return the 'Name' metadata for the distribution package."""
|
||||
return self.metadata['Name']
|
||||
|
||||
|
@ -445,24 +483,34 @@ class Distribution(metaclass=abc.ABCMeta):
|
|||
return Prepared.normalize(self.name)
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
def version(self) -> str:
|
||||
"""Return the 'Version' metadata for the distribution package."""
|
||||
return self.metadata['Version']
|
||||
|
||||
@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)
|
||||
|
||||
@property
|
||||
def files(self):
|
||||
def files(self) -> Optional[List[PackagePath]]:
|
||||
"""Files in this distribution.
|
||||
|
||||
:return: List of PackagePath for this distribution or None
|
||||
|
||||
Result is `None` if the metadata file that enumerates files
|
||||
(i.e. RECORD for dist-info or SOURCES.txt for egg-info) is
|
||||
missing.
|
||||
(i.e. RECORD for dist-info, or installed-files.txt or
|
||||
SOURCES.txt for egg-info) is missing.
|
||||
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):
|
||||
|
@ -474,27 +522,76 @@ class Distribution(metaclass=abc.ABCMeta):
|
|||
|
||||
@pass_none
|
||||
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):
|
||||
"""
|
||||
Read the lines of RECORD
|
||||
Read the lines of RECORD.
|
||||
"""
|
||||
text = self.read_text('RECORD')
|
||||
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
|
||||
in quotes.
|
||||
Read installed-files.txt and return lines in a similar
|
||||
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')
|
||||
return text and map('"{}"'.format, text.splitlines())
|
||||
|
||||
@property
|
||||
def requires(self):
|
||||
def requires(self) -> Optional[List[str]]:
|
||||
"""Generated requirements specified for this Distribution"""
|
||||
reqs = self._read_dist_info_reqs() or self._read_egg_info_reqs()
|
||||
return reqs and list(reqs)
|
||||
|
@ -545,10 +642,23 @@ class Distribution(metaclass=abc.ABCMeta):
|
|||
space = url_req_space(section.value)
|
||||
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):
|
||||
"""
|
||||
A MetaPathFinder capable of discovering installed distributions.
|
||||
|
||||
Custom providers should implement this interface in order to
|
||||
supply metadata.
|
||||
"""
|
||||
|
||||
class Context:
|
||||
|
@ -561,6 +671,17 @@ class DistributionFinder(MetaPathFinder):
|
|||
Each DistributionFinder may expect any parameters
|
||||
and should attempt to honor the canonical
|
||||
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
|
||||
|
@ -573,7 +694,7 @@ class DistributionFinder(MetaPathFinder):
|
|||
vars(self).update(kwargs)
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
def path(self) -> List[str]:
|
||||
"""
|
||||
The sequence of directory path that a distribution finder
|
||||
should search.
|
||||
|
@ -584,7 +705,7 @@ class DistributionFinder(MetaPathFinder):
|
|||
return vars(self).get('path', sys.path)
|
||||
|
||||
@abc.abstractmethod
|
||||
def find_distributions(self, context=Context()):
|
||||
def find_distributions(self, context=Context()) -> Iterable[Distribution]:
|
||||
"""
|
||||
Find distributions.
|
||||
|
||||
|
@ -596,11 +717,18 @@ class DistributionFinder(MetaPathFinder):
|
|||
|
||||
class FastPath:
|
||||
"""
|
||||
Micro-optimized class for searching a path for
|
||||
children.
|
||||
Micro-optimized class for searching a root for 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 objects are cached and recycled for any given root.
|
||||
|
||||
>>> FastPath('foobar') is FastPath('foobar')
|
||||
True
|
||||
"""
|
||||
|
||||
@functools.lru_cache() # type: ignore
|
||||
|
@ -642,7 +770,19 @@ class FastPath:
|
|||
|
||||
|
||||
class Lookup:
|
||||
"""
|
||||
A micro-optimized class for searching a (fast) path for metadata.
|
||||
"""
|
||||
|
||||
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_is_egg = base.endswith(".egg")
|
||||
self.infos = FreezableDefaultDict(list)
|
||||
|
@ -663,7 +803,10 @@ class Lookup:
|
|||
self.infos.freeze()
|
||||
self.eggs.freeze()
|
||||
|
||||
def search(self, prepared):
|
||||
def search(self, prepared: Prepared):
|
||||
"""
|
||||
Yield all infos and eggs matching the Prepared query.
|
||||
"""
|
||||
infos = (
|
||||
self.infos[prepared.normalized]
|
||||
if prepared
|
||||
|
@ -679,13 +822,28 @@ class Lookup:
|
|||
|
||||
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
|
||||
legacy_normalized = None
|
||||
|
||||
def __init__(self, name):
|
||||
def __init__(self, name: Optional[str]):
|
||||
self.name = name
|
||||
if name is None:
|
||||
return
|
||||
|
@ -719,7 +877,10 @@ class MetadataPathFinder(NullFinder, DistributionFinder):
|
|||
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.
|
||||
|
||||
|
@ -728,7 +889,7 @@ class MetadataPathFinder(NullFinder, DistributionFinder):
|
|||
(or all names if ``None`` indicated) along the paths in the list
|
||||
of directories ``context.path``.
|
||||
"""
|
||||
found = self._search_paths(context.name, context.path)
|
||||
found = cls._search_paths(context.name, context.path)
|
||||
return map(PathDistribution, found)
|
||||
|
||||
@classmethod
|
||||
|
@ -739,19 +900,20 @@ class MetadataPathFinder(NullFinder, DistributionFinder):
|
|||
path.search(prepared) for path in map(FastPath, paths)
|
||||
)
|
||||
|
||||
def invalidate_caches(cls):
|
||||
@classmethod
|
||||
def invalidate_caches(cls) -> None:
|
||||
FastPath.__new__.cache_clear()
|
||||
|
||||
|
||||
class PathDistribution(Distribution):
|
||||
def __init__(self, path: SimplePath):
|
||||
def __init__(self, path: SimplePath) -> None:
|
||||
"""Construct a distribution.
|
||||
|
||||
:param path: SimplePath indicating the metadata directory.
|
||||
"""
|
||||
self._path = path
|
||||
|
||||
def read_text(self, filename):
|
||||
def read_text(self, filename: str | os.PathLike[str]) -> Optional[str]:
|
||||
with suppress(
|
||||
FileNotFoundError,
|
||||
IsADirectoryError,
|
||||
|
@ -761,9 +923,11 @@ class PathDistribution(Distribution):
|
|||
):
|
||||
return self._path.joinpath(filename).read_text(encoding='utf-8')
|
||||
|
||||
return None
|
||||
|
||||
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
|
||||
|
||||
@property
|
||||
|
@ -796,7 +960,7 @@ class PathDistribution(Distribution):
|
|||
return name
|
||||
|
||||
|
||||
def distribution(distribution_name):
|
||||
def distribution(distribution_name: str) -> Distribution:
|
||||
"""Get the ``Distribution`` instance for the named package.
|
||||
|
||||
: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)
|
||||
|
||||
|
||||
def distributions(**kwargs):
|
||||
def distributions(**kwargs) -> Iterable[Distribution]:
|
||||
"""Get all ``Distribution`` instances in the current environment.
|
||||
|
||||
:return: An iterable of ``Distribution`` instances.
|
||||
|
@ -813,7 +977,7 @@ def distributions(**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.
|
||||
|
||||
: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
|
||||
|
||||
|
||||
def version(distribution_name):
|
||||
def version(distribution_name: str) -> str:
|
||||
"""Get the version string for the named package.
|
||||
|
||||
:param distribution_name: The name of the distribution package to query.
|
||||
|
@ -834,7 +998,7 @@ def version(distribution_name):
|
|||
|
||||
_unique = functools.partial(
|
||||
unique_everseen,
|
||||
key=_py39compat.normalized_name,
|
||||
key=py39.normalized_name,
|
||||
)
|
||||
"""
|
||||
Wrapper for ``distributions`` to return unique distributions by name.
|
||||
|
@ -856,7 +1020,7 @@ def entry_points(**params) -> EntryPoints:
|
|||
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.
|
||||
|
||||
:param distribution_name: The name of the distribution package to query.
|
||||
|
@ -865,11 +1029,11 @@ def files(distribution_name):
|
|||
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: An iterator of requirements, suitable for
|
||||
:return: An iterable of requirements, suitable for
|
||||
packaging.requirement.Requirement.
|
||||
"""
|
||||
return distribution(distribution_name).requires
|
||||
|
@ -896,9 +1060,43 @@ def _top_level_declared(dist):
|
|||
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):
|
||||
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"
|
||||
}
|
||||
opt_names = set(map(_get_toplevel_name, always_iterable(dist.files)))
|
||||
|
||||
def importable_name(name):
|
||||
return '.' not in name
|
||||
|
||||
return filter(importable_name, opt_names)
|
||||
|
|
|
@ -54,7 +54,7 @@ class Message(email.message.Message):
|
|||
def __getitem__(self, item):
|
||||
"""
|
||||
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)
|
||||
if res is None:
|
||||
|
|
|
@ -2,14 +2,7 @@ import sys
|
|||
import platform
|
||||
|
||||
|
||||
__all__ = ['install', 'NullFinder', 'Protocol']
|
||||
|
||||
|
||||
try:
|
||||
from typing import Protocol
|
||||
except ImportError: # pragma: no cover
|
||||
# Python 3.7 compatibility
|
||||
from typing_extensions import Protocol # type: ignore
|
||||
__all__ = ['install', 'NullFinder']
|
||||
|
||||
|
||||
def install(cls):
|
||||
|
@ -45,7 +38,7 @@ def disable_stdlib_finder():
|
|||
|
||||
class NullFinder:
|
||||
"""
|
||||
A "Finder" (aka "MetaClassFinder") that never finds any modules,
|
||||
A "Finder" (aka "MetaPathFinder") that never finds any modules,
|
||||
but may find distributions.
|
||||
"""
|
||||
|
||||
|
@ -53,14 +46,6 @@ class NullFinder:
|
|||
def find_spec(*args, **kwargs):
|
||||
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):
|
||||
"""
|
||||
|
|
|
@ -1,24 +1,38 @@
|
|||
from ._compat import Protocol
|
||||
from typing import Any, Dict, Iterator, List, TypeVar, Union
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Protocol
|
||||
from typing import Any, Dict, Iterator, List, Optional, TypeVar, Union, overload
|
||||
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
class PackageMetadata(Protocol):
|
||||
def __len__(self) -> int:
|
||||
... # pragma: no cover
|
||||
def __len__(self) -> int: ... # pragma: no cover
|
||||
|
||||
def __contains__(self, item: str) -> bool:
|
||||
... # pragma: no cover
|
||||
def __contains__(self, item: str) -> bool: ... # pragma: no cover
|
||||
|
||||
def __getitem__(self, key: str) -> str:
|
||||
... # pragma: no cover
|
||||
def __getitem__(self, key: str) -> str: ... # pragma: no cover
|
||||
|
||||
def __iter__(self) -> Iterator[str]:
|
||||
... # pragma: no cover
|
||||
def __iter__(self) -> Iterator[str]: ... # 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.
|
||||
"""
|
||||
|
@ -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:
|
||||
... # pragma: no cover
|
||||
def joinpath(
|
||||
self, other: Union[str, os.PathLike[str]]
|
||||
) -> SimplePath: ... # pragma: no cover
|
||||
|
||||
def __truediv__(self, other: Union[str, _T]) -> _T:
|
||||
... # pragma: no cover
|
||||
def __truediv__(
|
||||
self, other: Union[str, os.PathLike[str]]
|
||||
) -> SimplePath: ... # pragma: no cover
|
||||
|
||||
@property
|
||||
def parent(self) -> _T:
|
||||
... # pragma: no cover
|
||||
def parent(self) -> SimplePath: ... # pragma: no cover
|
||||
|
||||
def read_text(self) -> str:
|
||||
... # pragma: no cover
|
||||
def read_text(self, encoding=None) -> str: ... # 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
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
# Prevent circular imports on runtime.
|
||||
from . import Distribution, EntryPoint
|
||||
from .. import Distribution, EntryPoint
|
||||
else:
|
||||
Distribution = EntryPoint = Any
|
||||
|
||||
|
@ -17,7 +18,7 @@ def normalized_name(dist: Distribution) -> Optional[str]:
|
|||
try:
|
||||
return dist._normalized_name
|
||||
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'])
|
||||
|
||||
|
@ -29,7 +30,7 @@ def ep_matches(ep: EntryPoint, **params) -> bool:
|
|||
try:
|
||||
return ep.matches(**params)
|
||||
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.
|
||||
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
|
||||
importlib-metadata==6.8.0
|
||||
importlib-metadata==7.1.0
|
||||
importlib-resources==6.4.0
|
||||
pyinstaller==6.4.0
|
||||
pyopenssl==24.0.0
|
||||
|
|
|
@ -18,7 +18,7 @@ gntp==1.0.3
|
|||
html5lib==1.1
|
||||
httpagentparser==1.9.5
|
||||
idna==3.4
|
||||
importlib-metadata==6.8.0
|
||||
importlib-metadata==7.1.0
|
||||
importlib-resources==6.4.0
|
||||
git+https://github.com/Tautulli/ipwhois.git@master#egg=ipwhois
|
||||
IPy==1.01
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue