|
27 | 27 | from importlib import import_module |
28 | 28 | from importlib.abc import MetaPathFinder |
29 | 29 | from itertools import starmap |
30 | | -from typing import Any |
| 30 | +from typing import Any, TypeVar |
31 | 31 |
|
32 | 32 | from ._collections import FreezableDefaultDict, Pair |
33 | 33 | from ._compat import ( |
34 | 34 | NullFinder, |
35 | 35 | install, |
36 | 36 | localize, |
37 | 37 | ) |
38 | | -from ._context import ExceptionTrap |
39 | 38 | from ._functools import apply, compose, method_cache, noop, pass_none, passthrough |
40 | 39 | from ._itertools import always_iterable, bucket, unique_everseen |
41 | 40 | from ._meta import ( |
|
67 | 66 | ] |
68 | 67 |
|
69 | 68 |
|
| 69 | +_D = TypeVar('_D', bound=IDistribution) |
| 70 | + |
| 71 | + |
70 | 72 | class PackageNotFoundError(ModuleNotFoundError): |
71 | 73 | """The package was not found.""" |
72 | 74 |
|
@@ -520,18 +522,14 @@ def discover( |
520 | 522 | ) |
521 | 523 |
|
522 | 524 | @staticmethod |
523 | | - def _prefer_valid(dists: Iterable[Distribution]) -> Iterable[Distribution]: |
| 525 | + def _prefer_valid(dists: Iterable[_D]) -> Iterable[_D]: |
524 | 526 | """ |
525 | 527 | Prefer (move to the front) distributions that have metadata. |
526 | 528 |
|
527 | 529 | Ref python/importlib_resources#489. |
528 | 530 | """ |
529 | 531 |
|
530 | | - has_metadata = ExceptionTrap(MetadataNotFound).passes( |
531 | | - operator.attrgetter('metadata') |
532 | | - ) |
533 | | - |
534 | | - buckets = bucket(dists, has_metadata) |
| 532 | + buckets = bucket(dists, _has_metadata) |
535 | 533 | return itertools.chain(buckets[True], buckets[False]) |
536 | 534 |
|
537 | 535 | @staticmethod |
@@ -1078,6 +1076,22 @@ def read_text(self, filename: str | os.PathLike[str]) -> str | None: |
1078 | 1076 |
|
1079 | 1077 | read_text.__doc__ = Distribution.read_text.__doc__ |
1080 | 1078 |
|
| 1079 | + @property |
| 1080 | + def _has_metadata(self) -> bool: |
| 1081 | + """ |
| 1082 | + Detect the metadata file without opening it |
| 1083 | + (python/importlib_metadata#534). |
| 1084 | +
|
| 1085 | + An old-style egg-info file, whose metadata is the |
| 1086 | + distribution's own path, is read; there is nothing to detect |
| 1087 | + without reading it. |
| 1088 | + """ |
| 1089 | + return ( |
| 1090 | + self._path.joinpath('METADATA').exists() |
| 1091 | + or self._path.joinpath('PKG-INFO').exists() |
| 1092 | + or bool(self.read_text('')) |
| 1093 | + ) |
| 1094 | + |
1081 | 1095 | def locate_file(self, path: str | os.PathLike[str]) -> SimplePath: |
1082 | 1096 | return self._path.parent / path |
1083 | 1097 |
|
@@ -1148,6 +1162,26 @@ def version(distribution_name: str) -> str: |
1148 | 1162 | return distribution(distribution_name).version |
1149 | 1163 |
|
1150 | 1164 |
|
| 1165 | +def _has_metadata(dist: IDistribution) -> bool: |
| 1166 | + """ |
| 1167 | + Whether ``dist`` supplies metadata. |
| 1168 | +
|
| 1169 | + A ``PathDistribution`` detects the metadata without reading it. For |
| 1170 | + any other distribution, including a provider's own subclass passed |
| 1171 | + through unadapted (#486), the interface offers nothing cheaper than |
| 1172 | + ``read_text``; ``locate_file`` resolves against the package root and |
| 1173 | + not the metadata, and every other member loads the metadata or |
| 1174 | + parses it. Reading it is, notably, still cheaper than asking for |
| 1175 | + ``metadata``, whose ``MetadataNotFound`` is a different class when |
| 1176 | + raised by a distribution deriving from ``importlib.metadata``. |
| 1177 | + """ |
| 1178 | + with suppress(AttributeError): |
| 1179 | + return dist._has_metadata # type: ignore[attr-defined] |
| 1180 | + return bool( |
| 1181 | + dist.read_text('METADATA') or dist.read_text('PKG-INFO') or dist.read_text('') |
| 1182 | + ) |
| 1183 | + |
| 1184 | + |
1151 | 1185 | _unique = functools.partial( |
1152 | 1186 | unique_everseen, |
1153 | 1187 | key=operator.attrgetter('_normalized_name'), |
|
0 commit comments