Skip to content

Commit 440bdef

Browse files
committed
tmp
1 parent 376106b commit 440bdef

File tree

2 files changed

+94
-20
lines changed

2 files changed

+94
-20
lines changed

pulp_python/app/utils.py

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import hashlib
2+
import logging
13
import pkginfo
24
import re
35
import shutil
46
import tempfile
7+
import zipfile
58
import json
69
from collections import defaultdict
710
from django.conf import settings
@@ -11,6 +14,8 @@
1114
from packaging.version import parse, InvalidVersion
1215
from pulpcore.plugin.models import Remote
1316

17+
logger = logging.getLogger(__name__)
18+
1419

1520
PYPI_LAST_SERIAL = "X-PYPI-LAST-SERIAL"
1621
"""TODO This serial constant is temporary until Python repositories implements serials"""
@@ -130,7 +135,7 @@ def parse_project_metadata(project):
130135
# Release metadata
131136
"packagetype": project.get("packagetype") or "",
132137
"python_version": project.get("python_version") or "",
133-
"metadata_sha256": "", # TODO
138+
"metadata_sha256": project.get("metadata_sha256") or "",
134139
}
135140

136141

@@ -158,9 +163,9 @@ def parse_metadata(project, version, distribution):
158163
package["url"] = distribution.get("url") or ""
159164
package["sha256"] = distribution.get("digests", {}).get("sha256") or ""
160165
package["python_version"] = distribution.get("python_version") or package.get("python_version")
161-
package["requires_python"] = distribution.get("requires_python") or package.get(
162-
"requires_python"
163-
) # noqa: E501
166+
package["requires_python"] = distribution.get("requires_python") or "" # package.get(
167+
# "requires_python"
168+
# ) # noqa: E501
164169
package["metadata_sha256"] = distribution.get("data-dist-info-metadata", {}).get(
165170
"sha256"
166171
) or package.get("metadata_sha256")
@@ -181,6 +186,7 @@ def get_project_metadata_from_file(filename):
181186
packagetype = DIST_EXTENSIONS[extensions[pkg_type_index]]
182187

183188
metadata = DIST_TYPES[packagetype](filename)
189+
metadata.metadata_sha256 = compute_metadata_sha256(filename)
184190
metadata.packagetype = packagetype
185191
if packagetype == "sdist":
186192
metadata.python_version = "source"
@@ -193,6 +199,45 @@ def get_project_metadata_from_file(filename):
193199
return metadata
194200

195201

202+
def compute_metadata_sha256(filename: str) -> str:
203+
"""
204+
Compute SHA256 hash of the metadata file from a Python package.
205+
206+
Returns SHA256 hash or empty string if metadata cannot be extracted.
207+
"""
208+
logger.info(f"Computing metadata SHA256 for wheel: {filename}")
209+
210+
if not filename.endswith(".whl"):
211+
logger.debug(f"File {filename} is not a wheel, skipping metadata SHA256 computation")
212+
return ""
213+
214+
try:
215+
with tempfile.NamedTemporaryFile() as temp_file:
216+
with open(filename, "rb") as source:
217+
shutil.copyfileobj(source, temp_file)
218+
temp_file.flush()
219+
220+
logger.debug(f"Copied wheel to temp file: {temp_file.name}")
221+
222+
with zipfile.ZipFile(temp_file.name, "r") as f:
223+
logger.debug(f"Wheel contains files: {f.namelist()}")
224+
225+
for file_path in f.namelist():
226+
if file_path.endswith(".dist-info/METADATA"):
227+
logger.info(f"Found METADATA file: {file_path}")
228+
metadata_content = f.read(file_path)
229+
hash_value = hashlib.sha256(metadata_content).hexdigest()
230+
logger.info(f"Computed metadata SHA256: {hash_value}")
231+
return hash_value
232+
233+
logger.warning(f"No METADATA file found in wheel {filename}")
234+
235+
except Exception as e:
236+
logger.error(f"Error computing metadata SHA256 for {filename}: {e}")
237+
238+
return ""
239+
240+
196241
def artifact_to_python_content_data(filename, artifact, domain=None):
197242
"""
198243
Takes the artifact/filename and returns the metadata needed to create a PythonPackageContent.
@@ -448,7 +493,7 @@ def write_simple_detail_json(project_name, project_packages):
448493
"filename": package["filename"],
449494
"url": package["url"],
450495
"hashes": {"sha256": package["sha256"]},
451-
"requires_python": package["requires_python"] or None,
496+
"requires-python": package["requires_python"] or None,
452497
# data-dist-info-metadata is deprecated alias for core-metadata
453498
"data-dist-info-metadata": (
454499
{"sha256": package["metadata_sha256"]} if package["metadata_sha256"] else False

pulp_python/tests/functional/api/test_pypi_simple_json_api.py

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33
import pytest
44
import requests
55

6-
from pulp_python.tests.functional.constants import PYTHON_SM_PROJECT_SPECIFIER
6+
from pulp_python.tests.functional.constants import (
7+
PYTHON_EGG_FILENAME,
8+
PYTHON_EGG_URL,
9+
PYTHON_SM_PROJECT_SPECIFIER,
10+
PYTHON_WHEEL_FILENAME,
11+
PYTHON_WHEEL_URL,
12+
)
713

814
API_VERSION = "1.0"
915
PYPI_SERIAL_CONSTANT = 1000000000
@@ -38,13 +44,21 @@ def test_simple_json_index_api(
3844

3945
@pytest.mark.parallel
4046
def test_simple_json_detail_api(
41-
python_remote_factory, python_repo_with_sync, python_distribution_factory
47+
monitor_task,
48+
python_bindings,
49+
python_content_factory,
50+
python_distribution_factory,
51+
python_repo_factory,
4252
):
43-
remote = python_remote_factory(includes=PYTHON_SM_PROJECT_SPECIFIER)
44-
repo = python_repo_with_sync(remote)
53+
content_1 = python_content_factory(PYTHON_WHEEL_FILENAME, url=PYTHON_WHEEL_URL)
54+
content_2 = python_content_factory(PYTHON_EGG_FILENAME, url=PYTHON_EGG_URL)
55+
body = {"add_content_units": [content_1.pulp_href, content_2.pulp_href]}
56+
57+
repo = python_repo_factory()
58+
monitor_task(python_bindings.RepositoriesPythonApi.modify(repo.pulp_href, body).task)
4559
distro = python_distribution_factory(repository=repo)
4660

47-
url = f'{urljoin(distro.base_url, "simple/")}aiohttp'
61+
url = f'{urljoin(distro.base_url, "simple/")}shelf-reader'
4862
headers = {"Accept": PYPI_SIMPLE_V1_JSON}
4963

5064
response = requests.get(url, headers=headers)
@@ -53,17 +67,32 @@ def test_simple_json_detail_api(
5367

5468
data = response.json()
5569
assert data["meta"] == {"api-version": API_VERSION, "_last-serial": PYPI_SERIAL_CONSTANT}
56-
assert data["name"] == "aiohttp"
70+
assert data["name"] == "shelf-reader"
5771
assert data["files"]
58-
for file in data["files"]:
59-
for i in [
60-
"filename",
61-
"url",
62-
"hashes",
63-
"data-dist-info-metadata",
64-
"requires_python",
65-
]:
66-
assert i in file
72+
73+
# Check data of a wheel
74+
file_whl = next(
75+
(i for i in data["files"] if i["filename"] == "shelf_reader-0.1-py2-none-any.whl"), None
76+
)
77+
assert file_whl is not None, "wheel file not found"
78+
assert file_whl["url"]
79+
assert file_whl["hashes"] == {
80+
"sha256": "2eceb1643c10c5e4a65970baf63bde43b79cbdac7de81dae853ce47ab05197e9"
81+
}
82+
assert file_whl["requires-python"] is None
83+
assert file_whl["data-dist-info-metadata"] == {
84+
"sha256": "ed333f0db05d77e933a157b7225b403ada9a2f93318d77b41b662eba78bac350"
85+
}
86+
87+
# Check data of a tarball
88+
file_tar = next((i for i in data["files"] if i["filename"] == "shelf-reader-0.1.tar.gz"), None)
89+
assert file_tar is not None, "tar file not found"
90+
assert file_tar["url"]
91+
assert file_tar["hashes"] == {
92+
"sha256": "04cfd8bb4f843e35d51bfdef2035109bdea831b55a57c3e6a154d14be116398c"
93+
}
94+
assert file_tar["requires-python"] is None
95+
assert file_tar["data-dist-info-metadata"] is False
6796

6897

6998
@pytest.mark.parallel

0 commit comments

Comments
 (0)