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:
dependabot[bot] 2024-03-24 15:29:52 -07:00 committed by GitHub
parent b01b21ae05
commit e248c13c15
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 379 additions and 156 deletions

View file

@ -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)

View file

@ -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:

View file

@ -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):
"""

View file

@ -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

View 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)

View 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()

View file

@ -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

View file

@ -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