Skip to content

Commit ede3fcc

Browse files
committed
Raise MetadataNotFound when no metadata file was found.
Ref python/cpython#143387
1 parent d8a7576 commit ede3fcc

File tree

6 files changed

+48
-33
lines changed

6 files changed

+48
-33
lines changed

NEWS.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
v8.8.0
2+
======
3+
4+
Features
5+
--------
6+
7+
- Added ``MetadataNotFound`` (subclass of ``FileNotFoundError``) and updated
8+
``Distribution.metadata``/``metadata()`` to raise it when the metadata files
9+
are missing instead of returning ``None`` (python/cpython#143387).
10+
11+
112
v8.7.1
213
======
314

importlib_metadata/__init__.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,14 @@
3838
from ._functools import method_cache, noop, pass_none, passthrough
3939
from ._itertools import always_iterable, bucket, unique_everseen
4040
from ._meta import PackageMetadata, SimplePath
41-
from ._typing import md_none
4241
from .compat import py39, py311
4342

4443
__all__ = [
4544
'Distribution',
4645
'DistributionFinder',
4746
'PackageMetadata',
4847
'PackageNotFoundError',
48+
'MetadataNotFound',
4949
'SimplePath',
5050
'distribution',
5151
'distributions',
@@ -70,6 +70,10 @@ def name(self) -> str: # type: ignore[override] # make readonly
7070
return name
7171

7272

73+
class MetadataNotFound(FileNotFoundError):
74+
"""No metadata file is present in the distribution."""
75+
76+
7377
class Sectioned:
7478
"""
7579
A simple entry point config parser for performance
@@ -491,7 +495,14 @@ def _prefer_valid(dists: Iterable[Distribution]) -> Iterable[Distribution]:
491495
492496
Ref python/importlib_resources#489.
493497
"""
494-
buckets = bucket(dists, lambda dist: bool(dist.metadata))
498+
499+
def has_metadata(dist: Distribution) -> bool:
500+
with suppress(MetadataNotFound):
501+
dist.metadata
502+
return True
503+
return False
504+
505+
buckets = bucket(dists, has_metadata)
495506
return itertools.chain(buckets[True], buckets[False])
496507

497508
@staticmethod
@@ -512,7 +523,7 @@ def _discover_resolvers():
512523
return filter(None, declared)
513524

514525
@property
515-
def metadata(self) -> _meta.PackageMetadata | None:
526+
def metadata(self) -> _meta.PackageMetadata:
516527
"""Return the parsed metadata for this Distribution.
517528
518529
The returned object will have keys that name the various bits of
@@ -521,6 +532,8 @@ def metadata(self) -> _meta.PackageMetadata | None:
521532
522533
Custom providers may provide the METADATA file or override this
523534
property.
535+
536+
:raises MetadataNotFound: If no metadata file is present.
524537
"""
525538

526539
text = (
@@ -531,20 +544,25 @@ def metadata(self) -> _meta.PackageMetadata | None:
531544
# (which points to the egg-info file) attribute unchanged.
532545
or self.read_text('')
533546
)
534-
return self._assemble_message(text)
547+
return self._assemble_message(self._ensure_metadata_present(text))
535548

536549
@staticmethod
537-
@pass_none
538550
def _assemble_message(text: str) -> _meta.PackageMetadata:
539551
# deferred for performance (python/cpython#109829)
540552
from . import _adapters
541553

542554
return _adapters.Message(email.message_from_string(text))
543555

556+
def _ensure_metadata_present(self, text: str | None) -> str:
557+
if text is not None:
558+
return text
559+
560+
raise MetadataNotFound('No package metadata was found.')
561+
544562
@property
545563
def name(self) -> str:
546564
"""Return the 'Name' metadata for the distribution package."""
547-
return md_none(self.metadata)['Name']
565+
return self.metadata['Name']
548566

549567
@property
550568
def _normalized_name(self):
@@ -554,7 +572,7 @@ def _normalized_name(self):
554572
@property
555573
def version(self) -> str:
556574
"""Return the 'Version' metadata for the distribution package."""
557-
return md_none(self.metadata)['Version']
575+
return self.metadata['Version']
558576

559577
@property
560578
def entry_points(self) -> EntryPoints:
@@ -1067,11 +1085,12 @@ def distributions(**kwargs) -> Iterable[Distribution]:
10671085
return Distribution.discover(**kwargs)
10681086

10691087

1070-
def metadata(distribution_name: str) -> _meta.PackageMetadata | None:
1088+
def metadata(distribution_name: str) -> _meta.PackageMetadata:
10711089
"""Get the metadata for the named package.
10721090
10731091
:param distribution_name: The name of the distribution package to query.
10741092
:return: A PackageMetadata containing the parsed metadata.
1093+
:raises MetadataNotFound: If no metadata file is present in the distribution.
10751094
"""
10761095
return Distribution.from_name(distribution_name).metadata
10771096

@@ -1142,7 +1161,7 @@ def packages_distributions() -> Mapping[str, list[str]]:
11421161
pkg_to_dist = collections.defaultdict(list)
11431162
for dist in distributions():
11441163
for pkg in _top_level_declared(dist) or _top_level_inferred(dist):
1145-
pkg_to_dist[pkg].append(md_none(dist.metadata)['Name'])
1164+
pkg_to_dist[pkg].append(dist.metadata['Name'])
11461165
return dict(pkg_to_dist)
11471166

11481167

importlib_metadata/_typing.py

Lines changed: 0 additions & 15 deletions
This file was deleted.

importlib_metadata/compat/py39.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
else:
1313
Distribution = EntryPoint = Any
1414

15-
from .._typing import md_none
16-
1715

1816
def normalized_name(dist: Distribution) -> str | None:
1917
"""
@@ -24,9 +22,7 @@ def normalized_name(dist: Distribution) -> str | None:
2422
except AttributeError:
2523
from .. import Prepared # -> delay to prevent circular imports.
2624

27-
return Prepared.normalize(
28-
getattr(dist, "name", None) or md_none(dist.metadata)['Name']
29-
)
25+
return Prepared.normalize(getattr(dist, "name", None) or dist.metadata['Name'])
3026

3127

3228
def ep_matches(ep: EntryPoint, **params) -> bool:

newsfragments/+.removal.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Removed the internal ``md_none`` typing helper since ``Distribution.metadata`` now always returns ``PackageMetadata`` and raises ``MetadataNotFound`` when absent (python/cpython#143387).

tests/test_main.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from importlib_metadata import (
1010
Distribution,
1111
EntryPoint,
12+
MetadataNotFound,
1213
PackageNotFoundError,
1314
_unique,
1415
distributions,
@@ -157,13 +158,15 @@ def test_valid_dists_preferred(self):
157158

158159
def test_missing_metadata(self):
159160
"""
160-
Dists with a missing metadata file should return None.
161+
Dists with a missing metadata file should raise ``MetadataNotFound``.
161162
162-
Ref python/importlib_metadata#493.
163+
Ref python/importlib_metadata#493 and python/cpython#143387.
163164
"""
164165
fixtures.build_files(self.make_pkg('foo-4.3', files={}), self.site_dir)
165-
assert Distribution.from_name('foo').metadata is None
166-
assert metadata('foo') is None
166+
with self.assertRaises(MetadataNotFound):
167+
Distribution.from_name('foo').metadata
168+
with self.assertRaises(MetadataNotFound):
169+
metadata('foo')
167170

168171

169172
class NonASCIITests(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase):

0 commit comments

Comments
 (0)