done
This commit is contained in:
1175
lib/python3.11/site-packages/importlib_metadata/__init__.py
Normal file
1175
lib/python3.11/site-packages/importlib_metadata/__init__.py
Normal file
File diff suppressed because it is too large
Load Diff
135
lib/python3.11/site-packages/importlib_metadata/_adapters.py
Normal file
135
lib/python3.11/site-packages/importlib_metadata/_adapters.py
Normal file
@ -0,0 +1,135 @@
|
||||
import email.message
|
||||
import email.policy
|
||||
import re
|
||||
import textwrap
|
||||
|
||||
from ._text import FoldedCase
|
||||
|
||||
|
||||
class RawPolicy(email.policy.EmailPolicy):
|
||||
def fold(self, name, value):
|
||||
folded = self.linesep.join(
|
||||
textwrap.indent(value, prefix=' ' * 8, predicate=lambda line: True)
|
||||
.lstrip()
|
||||
.splitlines()
|
||||
)
|
||||
return f'{name}: {folded}{self.linesep}'
|
||||
|
||||
|
||||
class Message(email.message.Message):
|
||||
r"""
|
||||
Specialized Message subclass to handle metadata naturally.
|
||||
|
||||
Reads values that may have newlines in them and converts the
|
||||
payload to the Description.
|
||||
|
||||
>>> msg_text = textwrap.dedent('''
|
||||
... Name: Foo
|
||||
... Version: 3.0
|
||||
... License: blah
|
||||
... de-blah
|
||||
... <BLANKLINE>
|
||||
... First line of description.
|
||||
... Second line of description.
|
||||
... <BLANKLINE>
|
||||
... Fourth line!
|
||||
... ''').lstrip().replace('<BLANKLINE>', '')
|
||||
>>> msg = Message(email.message_from_string(msg_text))
|
||||
>>> msg['Description']
|
||||
'First line of description.\nSecond line of description.\n\nFourth line!\n'
|
||||
|
||||
Message should render even if values contain newlines.
|
||||
|
||||
>>> print(msg)
|
||||
Name: Foo
|
||||
Version: 3.0
|
||||
License: blah
|
||||
de-blah
|
||||
Description: First line of description.
|
||||
Second line of description.
|
||||
<BLANKLINE>
|
||||
Fourth line!
|
||||
<BLANKLINE>
|
||||
<BLANKLINE>
|
||||
"""
|
||||
|
||||
multiple_use_keys = set(
|
||||
map(
|
||||
FoldedCase,
|
||||
[
|
||||
'Classifier',
|
||||
'Obsoletes-Dist',
|
||||
'Platform',
|
||||
'Project-URL',
|
||||
'Provides-Dist',
|
||||
'Provides-Extra',
|
||||
'Requires-Dist',
|
||||
'Requires-External',
|
||||
'Supported-Platform',
|
||||
'Dynamic',
|
||||
],
|
||||
)
|
||||
)
|
||||
"""
|
||||
Keys that may be indicated multiple times per PEP 566.
|
||||
"""
|
||||
|
||||
def __new__(cls, orig: email.message.Message):
|
||||
res = super().__new__(cls)
|
||||
vars(res).update(vars(orig))
|
||||
return res
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._headers = self._repair_headers()
|
||||
|
||||
# suppress spurious error from mypy
|
||||
def __iter__(self):
|
||||
return super().__iter__()
|
||||
|
||||
def __getitem__(self, item):
|
||||
"""
|
||||
Override parent behavior to typical dict behavior.
|
||||
|
||||
``email.message.Message`` will emit None values for missing
|
||||
keys. Typical mappings, including this ``Message``, will raise
|
||||
a key error for missing keys.
|
||||
|
||||
Ref python/importlib_metadata#371.
|
||||
"""
|
||||
res = super().__getitem__(item)
|
||||
if res is None:
|
||||
raise KeyError(item)
|
||||
return res
|
||||
|
||||
def _repair_headers(self):
|
||||
def redent(value):
|
||||
"Correct for RFC822 indentation"
|
||||
indent = ' ' * 8
|
||||
if not value or '\n' + indent not in value:
|
||||
return value
|
||||
return textwrap.dedent(indent + value)
|
||||
|
||||
headers = [(key, redent(value)) for key, value in vars(self)['_headers']]
|
||||
if self._payload:
|
||||
headers.append(('Description', self.get_payload()))
|
||||
self.set_payload('')
|
||||
return headers
|
||||
|
||||
def as_string(self):
|
||||
return super().as_string(policy=RawPolicy())
|
||||
|
||||
@property
|
||||
def json(self):
|
||||
"""
|
||||
Convert PackageMetadata to a JSON-compatible format
|
||||
per PEP 0566.
|
||||
"""
|
||||
|
||||
def transform(key):
|
||||
value = self.get_all(key) if key in self.multiple_use_keys else self[key]
|
||||
if key == 'Keywords':
|
||||
value = re.split(r'\s+', value)
|
||||
tk = key.lower().replace('-', '_')
|
||||
return tk, value
|
||||
|
||||
return dict(map(transform, map(FoldedCase, self)))
|
@ -0,0 +1,34 @@
|
||||
import collections
|
||||
import typing
|
||||
|
||||
|
||||
# from jaraco.collections 3.3
|
||||
class FreezableDefaultDict(collections.defaultdict):
|
||||
"""
|
||||
Often it is desirable to prevent the mutation of
|
||||
a default dict after its initial construction, such
|
||||
as to prevent mutation during iteration.
|
||||
|
||||
>>> dd = FreezableDefaultDict(list)
|
||||
>>> dd[0].append('1')
|
||||
>>> dd.freeze()
|
||||
>>> dd[1]
|
||||
[]
|
||||
>>> len(dd)
|
||||
1
|
||||
"""
|
||||
|
||||
def __missing__(self, key):
|
||||
return getattr(self, '_frozen', super().__missing__)(key)
|
||||
|
||||
def freeze(self):
|
||||
self._frozen = lambda key: self.default_factory()
|
||||
|
||||
|
||||
class Pair(typing.NamedTuple):
|
||||
name: str
|
||||
value: str
|
||||
|
||||
@classmethod
|
||||
def parse(cls, text):
|
||||
return cls(*map(str.strip, text.split("=", 1)))
|
56
lib/python3.11/site-packages/importlib_metadata/_compat.py
Normal file
56
lib/python3.11/site-packages/importlib_metadata/_compat.py
Normal file
@ -0,0 +1,56 @@
|
||||
import platform
|
||||
import sys
|
||||
|
||||
__all__ = ['install', 'NullFinder']
|
||||
|
||||
|
||||
def install(cls):
|
||||
"""
|
||||
Class decorator for installation on sys.meta_path.
|
||||
|
||||
Adds the backport DistributionFinder to sys.meta_path and
|
||||
attempts to disable the finder functionality of the stdlib
|
||||
DistributionFinder.
|
||||
"""
|
||||
sys.meta_path.append(cls())
|
||||
disable_stdlib_finder()
|
||||
return cls
|
||||
|
||||
|
||||
def disable_stdlib_finder():
|
||||
"""
|
||||
Give the backport primacy for discovering path-based distributions
|
||||
by monkey-patching the stdlib O_O.
|
||||
|
||||
See #91 for more background for rationale on this sketchy
|
||||
behavior.
|
||||
"""
|
||||
|
||||
def matches(finder):
|
||||
return getattr(
|
||||
finder, '__module__', None
|
||||
) == '_frozen_importlib_external' and hasattr(finder, 'find_distributions')
|
||||
|
||||
for finder in filter(matches, sys.meta_path): # pragma: nocover
|
||||
del finder.find_distributions
|
||||
|
||||
|
||||
class NullFinder:
|
||||
"""
|
||||
A "Finder" (aka "MetaPathFinder") that never finds any modules,
|
||||
but may find distributions.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def find_spec(*args, **kwargs):
|
||||
return None
|
||||
|
||||
|
||||
def pypy_partial(val):
|
||||
"""
|
||||
Adjust for variable stacklevel on partial under PyPy.
|
||||
|
||||
Workaround for #327.
|
||||
"""
|
||||
is_pypy = platform.python_implementation() == 'PyPy'
|
||||
return val + is_pypy
|
104
lib/python3.11/site-packages/importlib_metadata/_functools.py
Normal file
104
lib/python3.11/site-packages/importlib_metadata/_functools.py
Normal file
@ -0,0 +1,104 @@
|
||||
import functools
|
||||
import types
|
||||
|
||||
|
||||
# from jaraco.functools 3.3
|
||||
def method_cache(method, cache_wrapper=None):
|
||||
"""
|
||||
Wrap lru_cache to support storing the cache data in the object instances.
|
||||
|
||||
Abstracts the common paradigm where the method explicitly saves an
|
||||
underscore-prefixed protected property on first call and returns that
|
||||
subsequently.
|
||||
|
||||
>>> class MyClass:
|
||||
... calls = 0
|
||||
...
|
||||
... @method_cache
|
||||
... def method(self, value):
|
||||
... self.calls += 1
|
||||
... return value
|
||||
|
||||
>>> a = MyClass()
|
||||
>>> a.method(3)
|
||||
3
|
||||
>>> for x in range(75):
|
||||
... res = a.method(x)
|
||||
>>> a.calls
|
||||
75
|
||||
|
||||
Note that the apparent behavior will be exactly like that of lru_cache
|
||||
except that the cache is stored on each instance, so values in one
|
||||
instance will not flush values from another, and when an instance is
|
||||
deleted, so are the cached values for that instance.
|
||||
|
||||
>>> b = MyClass()
|
||||
>>> for x in range(35):
|
||||
... res = b.method(x)
|
||||
>>> b.calls
|
||||
35
|
||||
>>> a.method(0)
|
||||
0
|
||||
>>> a.calls
|
||||
75
|
||||
|
||||
Note that if method had been decorated with ``functools.lru_cache()``,
|
||||
a.calls would have been 76 (due to the cached value of 0 having been
|
||||
flushed by the 'b' instance).
|
||||
|
||||
Clear the cache with ``.cache_clear()``
|
||||
|
||||
>>> a.method.cache_clear()
|
||||
|
||||
Same for a method that hasn't yet been called.
|
||||
|
||||
>>> c = MyClass()
|
||||
>>> c.method.cache_clear()
|
||||
|
||||
Another cache wrapper may be supplied:
|
||||
|
||||
>>> cache = functools.lru_cache(maxsize=2)
|
||||
>>> MyClass.method2 = method_cache(lambda self: 3, cache_wrapper=cache)
|
||||
>>> a = MyClass()
|
||||
>>> a.method2()
|
||||
3
|
||||
|
||||
Caution - do not subsequently wrap the method with another decorator, such
|
||||
as ``@property``, which changes the semantics of the function.
|
||||
|
||||
See also
|
||||
http://code.activestate.com/recipes/577452-a-memoize-decorator-for-instance-methods/
|
||||
for another implementation and additional justification.
|
||||
"""
|
||||
cache_wrapper = cache_wrapper or functools.lru_cache()
|
||||
|
||||
def wrapper(self, *args, **kwargs):
|
||||
# it's the first call, replace the method with a cached, bound method
|
||||
bound_method = types.MethodType(method, self)
|
||||
cached_method = cache_wrapper(bound_method)
|
||||
setattr(self, method.__name__, cached_method)
|
||||
return cached_method(*args, **kwargs)
|
||||
|
||||
# Support cache clear even before cache has been created.
|
||||
wrapper.cache_clear = lambda: None
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
# From jaraco.functools 3.3
|
||||
def pass_none(func):
|
||||
"""
|
||||
Wrap func so it's not called if its first param is None
|
||||
|
||||
>>> print_text = pass_none(print)
|
||||
>>> print_text('text')
|
||||
text
|
||||
>>> print_text(None)
|
||||
"""
|
||||
|
||||
@functools.wraps(func)
|
||||
def wrapper(param, *args, **kwargs):
|
||||
if param is not None:
|
||||
return func(param, *args, **kwargs)
|
||||
|
||||
return wrapper
|
171
lib/python3.11/site-packages/importlib_metadata/_itertools.py
Normal file
171
lib/python3.11/site-packages/importlib_metadata/_itertools.py
Normal file
@ -0,0 +1,171 @@
|
||||
from collections import defaultdict, deque
|
||||
from itertools import filterfalse
|
||||
|
||||
|
||||
def unique_everseen(iterable, key=None):
|
||||
"List unique elements, preserving order. Remember all elements ever seen."
|
||||
# unique_everseen('AAAABBBCCDAABBB') --> A B C D
|
||||
# unique_everseen('ABBCcAD', str.lower) --> A B C D
|
||||
seen = set()
|
||||
seen_add = seen.add
|
||||
if key is None:
|
||||
for element in filterfalse(seen.__contains__, iterable):
|
||||
seen_add(element)
|
||||
yield element
|
||||
else:
|
||||
for element in iterable:
|
||||
k = key(element)
|
||||
if k not in seen:
|
||||
seen_add(k)
|
||||
yield element
|
||||
|
||||
|
||||
# copied from more_itertools 8.8
|
||||
def always_iterable(obj, base_type=(str, bytes)):
|
||||
"""If *obj* is iterable, return an iterator over its items::
|
||||
|
||||
>>> obj = (1, 2, 3)
|
||||
>>> list(always_iterable(obj))
|
||||
[1, 2, 3]
|
||||
|
||||
If *obj* is not iterable, return a one-item iterable containing *obj*::
|
||||
|
||||
>>> obj = 1
|
||||
>>> list(always_iterable(obj))
|
||||
[1]
|
||||
|
||||
If *obj* is ``None``, return an empty iterable:
|
||||
|
||||
>>> obj = None
|
||||
>>> list(always_iterable(None))
|
||||
[]
|
||||
|
||||
By default, binary and text strings are not considered iterable::
|
||||
|
||||
>>> obj = 'foo'
|
||||
>>> list(always_iterable(obj))
|
||||
['foo']
|
||||
|
||||
If *base_type* is set, objects for which ``isinstance(obj, base_type)``
|
||||
returns ``True`` won't be considered iterable.
|
||||
|
||||
>>> obj = {'a': 1}
|
||||
>>> list(always_iterable(obj)) # Iterate over the dict's keys
|
||||
['a']
|
||||
>>> list(always_iterable(obj, base_type=dict)) # Treat dicts as a unit
|
||||
[{'a': 1}]
|
||||
|
||||
Set *base_type* to ``None`` to avoid any special handling and treat objects
|
||||
Python considers iterable as iterable:
|
||||
|
||||
>>> obj = 'foo'
|
||||
>>> list(always_iterable(obj, base_type=None))
|
||||
['f', 'o', 'o']
|
||||
"""
|
||||
if obj is None:
|
||||
return iter(())
|
||||
|
||||
if (base_type is not None) and isinstance(obj, base_type):
|
||||
return iter((obj,))
|
||||
|
||||
try:
|
||||
return iter(obj)
|
||||
except TypeError:
|
||||
return iter((obj,))
|
||||
|
||||
|
||||
# Copied from more_itertools 10.3
|
||||
class bucket:
|
||||
"""Wrap *iterable* and return an object that buckets the iterable into
|
||||
child iterables based on a *key* function.
|
||||
|
||||
>>> iterable = ['a1', 'b1', 'c1', 'a2', 'b2', 'c2', 'b3']
|
||||
>>> s = bucket(iterable, key=lambda x: x[0]) # Bucket by 1st character
|
||||
>>> sorted(list(s)) # Get the keys
|
||||
['a', 'b', 'c']
|
||||
>>> a_iterable = s['a']
|
||||
>>> next(a_iterable)
|
||||
'a1'
|
||||
>>> next(a_iterable)
|
||||
'a2'
|
||||
>>> list(s['b'])
|
||||
['b1', 'b2', 'b3']
|
||||
|
||||
The original iterable will be advanced and its items will be cached until
|
||||
they are used by the child iterables. This may require significant storage.
|
||||
|
||||
By default, attempting to select a bucket to which no items belong will
|
||||
exhaust the iterable and cache all values.
|
||||
If you specify a *validator* function, selected buckets will instead be
|
||||
checked against it.
|
||||
|
||||
>>> from itertools import count
|
||||
>>> it = count(1, 2) # Infinite sequence of odd numbers
|
||||
>>> key = lambda x: x % 10 # Bucket by last digit
|
||||
>>> validator = lambda x: x in {1, 3, 5, 7, 9} # Odd digits only
|
||||
>>> s = bucket(it, key=key, validator=validator)
|
||||
>>> 2 in s
|
||||
False
|
||||
>>> list(s[2])
|
||||
[]
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, iterable, key, validator=None):
|
||||
self._it = iter(iterable)
|
||||
self._key = key
|
||||
self._cache = defaultdict(deque)
|
||||
self._validator = validator or (lambda x: True)
|
||||
|
||||
def __contains__(self, value):
|
||||
if not self._validator(value):
|
||||
return False
|
||||
|
||||
try:
|
||||
item = next(self[value])
|
||||
except StopIteration:
|
||||
return False
|
||||
else:
|
||||
self._cache[value].appendleft(item)
|
||||
|
||||
return True
|
||||
|
||||
def _get_values(self, value):
|
||||
"""
|
||||
Helper to yield items from the parent iterator that match *value*.
|
||||
Items that don't match are stored in the local cache as they
|
||||
are encountered.
|
||||
"""
|
||||
while True:
|
||||
# If we've cached some items that match the target value, emit
|
||||
# the first one and evict it from the cache.
|
||||
if self._cache[value]:
|
||||
yield self._cache[value].popleft()
|
||||
# Otherwise we need to advance the parent iterator to search for
|
||||
# a matching item, caching the rest.
|
||||
else:
|
||||
while True:
|
||||
try:
|
||||
item = next(self._it)
|
||||
except StopIteration:
|
||||
return
|
||||
item_value = self._key(item)
|
||||
if item_value == value:
|
||||
yield item
|
||||
break
|
||||
elif self._validator(item_value):
|
||||
self._cache[item_value].append(item)
|
||||
|
||||
def __iter__(self):
|
||||
for item in self._it:
|
||||
item_value = self._key(item)
|
||||
if self._validator(item_value):
|
||||
self._cache[item_value].append(item)
|
||||
|
||||
yield from self._cache.keys()
|
||||
|
||||
def __getitem__(self, value):
|
||||
if not self._validator(value):
|
||||
return iter(())
|
||||
|
||||
return self._get_values(value)
|
71
lib/python3.11/site-packages/importlib_metadata/_meta.py
Normal file
71
lib/python3.11/site-packages/importlib_metadata/_meta.py
Normal file
@ -0,0 +1,71 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from collections.abc import Iterator
|
||||
from typing import (
|
||||
Any,
|
||||
Protocol,
|
||||
TypeVar,
|
||||
overload,
|
||||
)
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
class PackageMetadata(Protocol):
|
||||
def __len__(self) -> int: ... # pragma: no cover
|
||||
|
||||
def __contains__(self, item: str) -> bool: ... # pragma: no cover
|
||||
|
||||
def __getitem__(self, key: str) -> str: ... # pragma: no cover
|
||||
|
||||
def __iter__(self) -> Iterator[str]: ... # pragma: no cover
|
||||
|
||||
@overload
|
||||
def get(
|
||||
self, name: str, failobj: None = None
|
||||
) -> str | None: ... # pragma: no cover
|
||||
|
||||
@overload
|
||||
def get(self, name: str, failobj: _T) -> str | _T: ... # pragma: no cover
|
||||
|
||||
# overload per python/importlib_metadata#435
|
||||
@overload
|
||||
def get_all(
|
||||
self, name: str, failobj: None = None
|
||||
) -> list[Any] | None: ... # pragma: no cover
|
||||
|
||||
@overload
|
||||
def get_all(self, name: str, failobj: _T) -> list[Any] | _T:
|
||||
"""
|
||||
Return all values associated with a possibly multi-valued key.
|
||||
"""
|
||||
|
||||
@property
|
||||
def json(self) -> dict[str, str | list[str]]:
|
||||
"""
|
||||
A JSON-compatible form of the metadata.
|
||||
"""
|
||||
|
||||
|
||||
class SimplePath(Protocol):
|
||||
"""
|
||||
A minimal subset of pathlib.Path required by Distribution.
|
||||
"""
|
||||
|
||||
def joinpath(
|
||||
self, other: str | os.PathLike[str]
|
||||
) -> SimplePath: ... # pragma: no cover
|
||||
|
||||
def __truediv__(
|
||||
self, other: str | os.PathLike[str]
|
||||
) -> SimplePath: ... # pragma: no cover
|
||||
|
||||
@property
|
||||
def parent(self) -> SimplePath: ... # 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
|
99
lib/python3.11/site-packages/importlib_metadata/_text.py
Normal file
99
lib/python3.11/site-packages/importlib_metadata/_text.py
Normal file
@ -0,0 +1,99 @@
|
||||
import re
|
||||
|
||||
from ._functools import method_cache
|
||||
|
||||
|
||||
# from jaraco.text 3.5
|
||||
class FoldedCase(str):
|
||||
"""
|
||||
A case insensitive string class; behaves just like str
|
||||
except compares equal when the only variation is case.
|
||||
|
||||
>>> s = FoldedCase('hello world')
|
||||
|
||||
>>> s == 'Hello World'
|
||||
True
|
||||
|
||||
>>> 'Hello World' == s
|
||||
True
|
||||
|
||||
>>> s != 'Hello World'
|
||||
False
|
||||
|
||||
>>> s.index('O')
|
||||
4
|
||||
|
||||
>>> s.split('O')
|
||||
['hell', ' w', 'rld']
|
||||
|
||||
>>> sorted(map(FoldedCase, ['GAMMA', 'alpha', 'Beta']))
|
||||
['alpha', 'Beta', 'GAMMA']
|
||||
|
||||
Sequence membership is straightforward.
|
||||
|
||||
>>> "Hello World" in [s]
|
||||
True
|
||||
>>> s in ["Hello World"]
|
||||
True
|
||||
|
||||
You may test for set inclusion, but candidate and elements
|
||||
must both be folded.
|
||||
|
||||
>>> FoldedCase("Hello World") in {s}
|
||||
True
|
||||
>>> s in {FoldedCase("Hello World")}
|
||||
True
|
||||
|
||||
String inclusion works as long as the FoldedCase object
|
||||
is on the right.
|
||||
|
||||
>>> "hello" in FoldedCase("Hello World")
|
||||
True
|
||||
|
||||
But not if the FoldedCase object is on the left:
|
||||
|
||||
>>> FoldedCase('hello') in 'Hello World'
|
||||
False
|
||||
|
||||
In that case, use in_:
|
||||
|
||||
>>> FoldedCase('hello').in_('Hello World')
|
||||
True
|
||||
|
||||
>>> FoldedCase('hello') > FoldedCase('Hello')
|
||||
False
|
||||
"""
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.lower() < other.lower()
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.lower() > other.lower()
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.lower() == other.lower()
|
||||
|
||||
def __ne__(self, other):
|
||||
return self.lower() != other.lower()
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.lower())
|
||||
|
||||
def __contains__(self, other):
|
||||
return super().lower().__contains__(other.lower())
|
||||
|
||||
def in_(self, other):
|
||||
"Does self appear in other?"
|
||||
return self in FoldedCase(other)
|
||||
|
||||
# cache lower since it's likely to be called frequently.
|
||||
@method_cache
|
||||
def lower(self):
|
||||
return super().lower()
|
||||
|
||||
def index(self, sub):
|
||||
return self.lower().index(sub.lower())
|
||||
|
||||
def split(self, splitter=' ', maxsplit=0):
|
||||
pattern = re.compile(re.escape(splitter), re.I)
|
||||
return pattern.split(self, maxsplit)
|
15
lib/python3.11/site-packages/importlib_metadata/_typing.py
Normal file
15
lib/python3.11/site-packages/importlib_metadata/_typing.py
Normal file
@ -0,0 +1,15 @@
|
||||
import functools
|
||||
import typing
|
||||
|
||||
from ._meta import PackageMetadata
|
||||
|
||||
md_none = functools.partial(typing.cast, PackageMetadata)
|
||||
"""
|
||||
Suppress type errors for optional metadata.
|
||||
|
||||
Although Distribution.metadata can return None when metadata is corrupt
|
||||
and thus None, allow callers to assume it's not None and crash if
|
||||
that's the case.
|
||||
|
||||
# python/importlib_metadata#493
|
||||
"""
|
@ -0,0 +1,22 @@
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
import types
|
||||
|
||||
|
||||
def wrap(path): # pragma: no cover
|
||||
"""
|
||||
Workaround for https://github.com/python/cpython/issues/84538
|
||||
to add backward compatibility for walk_up=True.
|
||||
An example affected package is dask-labextension, which uses
|
||||
jupyter-packaging to install JupyterLab javascript files outside
|
||||
of site-packages.
|
||||
"""
|
||||
|
||||
def relative_to(root, *, walk_up=False):
|
||||
return pathlib.Path(os.path.relpath(path, root))
|
||||
|
||||
return types.SimpleNamespace(relative_to=relative_to)
|
||||
|
||||
|
||||
relative_fix = wrap if sys.version_info < (3, 12) else lambda x: x
|
@ -0,0 +1,42 @@
|
||||
"""
|
||||
Compatibility layer with Python 3.8/3.9
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
# Prevent circular imports on runtime.
|
||||
from .. import Distribution, EntryPoint
|
||||
else:
|
||||
Distribution = EntryPoint = Any
|
||||
|
||||
from .._typing import md_none
|
||||
|
||||
|
||||
def normalized_name(dist: Distribution) -> str | None:
|
||||
"""
|
||||
Honor name normalization for distributions that don't provide ``_normalized_name``.
|
||||
"""
|
||||
try:
|
||||
return dist._normalized_name
|
||||
except AttributeError:
|
||||
from .. import Prepared # -> delay to prevent circular imports.
|
||||
|
||||
return Prepared.normalize(
|
||||
getattr(dist, "name", None) or md_none(dist.metadata)['Name']
|
||||
)
|
||||
|
||||
|
||||
def ep_matches(ep: EntryPoint, **params) -> bool:
|
||||
"""
|
||||
Workaround for ``EntryPoint`` objects without the ``matches`` method.
|
||||
"""
|
||||
try:
|
||||
return ep.matches(**params)
|
||||
except AttributeError:
|
||||
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/python3.11/site-packages/importlib_metadata/diagnose.py
Normal file
21
lib/python3.11/site-packages/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()
|
Reference in New Issue
Block a user