Skip to content

Commit a077307

Browse files
Replaceing the metadata parser from packaging.metadata
Replace parse_email() + Metadata.from_raw() with Metadata.from_email() for simpler code and proper validation. Use parse_metadata() helper where appropriate and fix empty metadata error handling. Fixes: #561 Signed-off-by: Lalatendu Mohanty <lmohanty@redhat.com>
1 parent 3ac42dc commit a077307

File tree

3 files changed

+18
-30
lines changed

3 files changed

+18
-30
lines changed

src/fromager/bootstrapper.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import tempfile
1111
import typing
1212
import zipfile
13-
from email.parser import BytesParser
1413
from urllib.parse import urlparse
1514

1615
from packaging.requirements import Requirement
@@ -907,10 +906,8 @@ def _get_version_from_package_metadata(
907906
config_settings=pbi.config_settings,
908907
)
909908
metadata_filename = source_dir.parent / metadata_dir_base / "METADATA"
910-
with open(metadata_filename, "rb") as f:
911-
p = BytesParser()
912-
metadata = p.parse(f, headersonly=True)
913-
return Version(metadata["Version"])
909+
metadata = dependencies.parse_metadata(metadata_filename)
910+
return metadata.version
914911

915912
def _resolve_prebuilt_with_history(
916913
self,

src/fromager/candidate.py

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@
22
import datetime
33
import logging
44
import typing
5-
from email.message import EmailMessage, Message
6-
from email.parser import BytesParser
75
from io import BytesIO
8-
from typing import TYPE_CHECKING
96
from zipfile import ZipFile
107

8+
from packaging.metadata import Metadata
119
from packaging.requirements import Requirement
1210
from packaging.utils import BuildTag, canonicalize_name
1311
from packaging.version import Version
@@ -16,13 +14,6 @@
1614

1715
logger = logging.getLogger(__name__)
1816

19-
# fix for runtime errors caused by inheriting classes that are generic in stubs but not runtime
20-
# https://mypy.readthedocs.io/en/latest/runtime_troubles.html#using-classes-that-are-generic-in-stubs-but-not-at-runtime
21-
if TYPE_CHECKING:
22-
Metadata = Message[str, str]
23-
else:
24-
Metadata = Message
25-
2617

2718
@dataclasses.dataclass(frozen=True, order=True, slots=True, repr=False, kw_only=True)
2819
class Candidate:
@@ -73,11 +64,10 @@ def metadata(self) -> Metadata:
7364
return self._metadata
7465

7566
def _get_dependencies(self) -> typing.Iterable[Requirement]:
76-
deps = self.metadata.get_all("Requires-Dist", [])
67+
deps = self.metadata.requires_dist or []
7768
extras = self.extras if self.extras else [""]
7869

79-
for d in deps:
80-
r = Requirement(d)
70+
for r in deps:
8171
if r.marker is None:
8272
yield r
8373
else:
@@ -95,7 +85,8 @@ def dependencies(self) -> list[Requirement]:
9585

9686
@property
9787
def requires_python(self) -> str | None:
98-
return self.metadata.get("Requires-Python")
88+
spec = self.metadata.requires_python
89+
return str(spec) if spec is not None else None
9990

10091

10192
def get_metadata_for_wheel(url: str, metadata_url: str | None = None) -> Metadata:
@@ -107,7 +98,7 @@ def get_metadata_for_wheel(url: str, metadata_url: str | None = None) -> Metadat
10798
metadata_url: Optional URL of the metadata file (PEP 658)
10899
109100
Returns:
110-
Parsed metadata as a Message object
101+
Parsed metadata as a Metadata object
111102
"""
112103
# Try PEP 658 metadata endpoint first if available
113104
if metadata_url:
@@ -119,8 +110,7 @@ def get_metadata_for_wheel(url: str, metadata_url: str | None = None) -> Metadat
119110
response.raise_for_status()
120111

121112
# Parse metadata directly from the response content
122-
p = BytesParser()
123-
metadata = p.parse(BytesIO(response.content), headersonly=True)
113+
metadata = Metadata.from_email(response.content)
124114
logger.debug(f"Successfully retrieved metadata via PEP 658 for {url}")
125115
return metadata
126116

@@ -136,8 +126,8 @@ def get_metadata_for_wheel(url: str, metadata_url: str | None = None) -> Metadat
136126
with ZipFile(BytesIO(data)) as z:
137127
for n in z.namelist():
138128
if n.endswith(".dist-info/METADATA"):
139-
p = BytesParser()
140-
return p.parse(z.open(n), headersonly=True)
129+
metadata_content = z.read(n)
130+
return Metadata.from_email(metadata_content)
141131

142-
# If we didn't find the metadata, return an empty dict
143-
return EmailMessage()
132+
# If we didn't find the metadata, raise an error
133+
raise ValueError(f"Could not find METADATA file in wheel: {url}")

tests/test_pep658_support.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,11 @@ def test_get_metadata_with_pep658_success(self, mock_session) -> None:
5656
metadata = get_metadata_for_wheel(wheel_url, metadata_url)
5757

5858
# Verify the metadata was parsed correctly
59-
assert metadata["Name"] == "test-package"
60-
assert metadata["Version"] == "1.0.0"
61-
assert metadata["Summary"] == "A test package"
62-
assert "requests >= 2.0.0" in metadata.get_all("Requires-Dist", [])
59+
assert metadata.name == "test-package"
60+
assert str(metadata.version) == "1.0.0"
61+
assert metadata.summary == "A test package"
62+
assert metadata.requires_dist is not None
63+
assert any(str(req) == "requests>=2.0.0" for req in metadata.requires_dist)
6364

6465
# Verify only the metadata URL was called, not the wheel URL
6566
mock_session.get.assert_called_once_with(metadata_url)

0 commit comments

Comments
 (0)