Skip to content

Commit 99afbc2

Browse files
jaracoclaude
andcommitted
Detect the presence of metadata without reading it.
`_prefer_valid` decided validity by consulting `metadata`, which runs the whole METADATA file through the email parser only for the result to be discarded. Give `PathDistribution` a `_has_metadata` that detects the metadata file without opening it, and make it the preference's predicate. The interface offers nothing as cheap for a distribution that isn't one of ours: `locate_file` resolves against the package root rather than the metadata, and every other member loads the metadata or parses it. So those fall back to `read_text`, which also closes a gap, as the `MetadataNotFound` of a distribution deriving from `importlib.metadata` is a different class and escaped the preference rather than marking the distribution invalid. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 60bbaca commit 99afbc2

4 files changed

Lines changed: 69 additions & 8 deletions

File tree

‎importlib_metadata/__init__.py‎

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,14 @@
2727
from importlib import import_module
2828
from importlib.abc import MetaPathFinder
2929
from itertools import starmap
30-
from typing import Any
30+
from typing import Any, TypeVar
3131

3232
from ._collections import FreezableDefaultDict, Pair
3333
from ._compat import (
3434
NullFinder,
3535
install,
3636
localize,
3737
)
38-
from ._context import ExceptionTrap
3938
from ._functools import apply, compose, method_cache, noop, pass_none, passthrough
4039
from ._itertools import always_iterable, bucket, unique_everseen
4140
from ._meta import (
@@ -67,6 +66,9 @@
6766
]
6867

6968

69+
_D = TypeVar('_D', bound=IDistribution)
70+
71+
7072
class PackageNotFoundError(ModuleNotFoundError):
7173
"""The package was not found."""
7274

@@ -520,18 +522,14 @@ def discover(
520522
)
521523

522524
@staticmethod
523-
def _prefer_valid(dists: Iterable[Distribution]) -> Iterable[Distribution]:
525+
def _prefer_valid(dists: Iterable[_D]) -> Iterable[_D]:
524526
"""
525527
Prefer (move to the front) distributions that have metadata.
526528
527529
Ref python/importlib_resources#489.
528530
"""
529531

530-
has_metadata = ExceptionTrap(MetadataNotFound).passes(
531-
operator.attrgetter('metadata')
532-
)
533-
534-
buckets = bucket(dists, has_metadata)
532+
buckets = bucket(dists, _has_metadata)
535533
return itertools.chain(buckets[True], buckets[False])
536534

537535
@staticmethod
@@ -1078,6 +1076,22 @@ def read_text(self, filename: str | os.PathLike[str]) -> str | None:
10781076

10791077
read_text.__doc__ = Distribution.read_text.__doc__
10801078

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+
10811095
def locate_file(self, path: str | os.PathLike[str]) -> SimplePath:
10821096
return self._path.parent / path
10831097

@@ -1148,6 +1162,26 @@ def version(distribution_name: str) -> str:
11481162
return distribution(distribution_name).version
11491163

11501164

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+
11511185
_unique = functools.partial(
11521186
unique_everseen,
11531187
key=operator.attrgetter('_normalized_name'),
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
The preference for valid distributions no longer propagates the
2+
``MetadataNotFound`` of a distribution that a provider derived from
3+
``importlib.metadata``, whose exception is a different class from this
4+
package's and so escaped the preference rather than marking the distribution
5+
invalid.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Detecting whether a distribution supplies metadata no longer reads or parses
2+
that metadata where the metadata directory can be inspected instead, so
3+
retrieving a distribution by name, which prefers the valid candidates, is
4+
substantially faster.

‎tests/compat/test_localize.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,24 @@ def test_conforming_provider_is_not_warned_about(self):
119119
assert distribution('distinfo-pkg').version == '1.0.0'
120120
assert not [str(warning.message) for warning in caught]
121121

122+
def test_dist_without_metadata_is_deferred(self):
123+
"""
124+
A provider's own distribution supplies no detection of its own,
125+
so the metadata is sought through ``read_text``. Asking for
126+
``metadata`` instead would raise, as the ``MetadataNotFound``
127+
of a distribution deriving from ``importlib.metadata`` is a
128+
different class.
129+
"""
130+
131+
class Empty(stdlib.Distribution):
132+
def read_text(self, filename):
133+
return None
134+
135+
def locate_file(self, path):
136+
return None
137+
138+
assert not importlib_metadata._has_metadata(Empty())
139+
122140

123141
class ConformanceCheckTests(unittest.TestCase):
124142
@unittest.skipIf(

0 commit comments

Comments
 (0)