Skip to content

Commit e113dee

Browse files
committed
tmp
1 parent 376106b commit e113dee

File tree

4 files changed

+125
-30
lines changed

4 files changed

+125
-30
lines changed

pulp_python/app/pypi/views.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -361,16 +361,22 @@ def retrieve(self, request, path, package):
361361
else:
362362
packages = chain([present], packages)
363363
name = present[2]
364-
releases = (
365-
{
366-
"filename": f,
367-
"url": urljoin(self.base_content_url, f"{path}/{f}"),
368-
"sha256": s,
369-
"metadata_sha256": ms,
370-
"requires_python": rp,
371-
}
372-
for f, s, _, ms, rp in packages
373-
)
364+
releases = []
365+
for f, s, name, ms, rp in packages:
366+
import logging
367+
368+
logger = logging.getLogger(__name__)
369+
logger.info(f"Database content: filename={f}, metadata_sha256={ms}")
370+
371+
releases.append(
372+
{
373+
"filename": f,
374+
"url": urljoin(self.base_content_url, f"{path}/{f}"),
375+
"sha256": s,
376+
"metadata_sha256": ms,
377+
"requires_python": rp,
378+
}
379+
)
374380
media_type = request.accepted_renderer.media_type
375381
headers = {"X-PyPI-Last-Serial": str(PYPI_SERIAL_CONSTANT)}
376382

pulp_python/app/serializers.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,12 @@ def deferred_validate(self, data):
322322

323323
data.update(_data)
324324

325+
# Log the final metadata_sha256 value
326+
import logging
327+
328+
logger = logging.getLogger(__name__)
329+
logger.info(f"deferred_validate final data metadata_sha256: {data.get('metadata_sha256')}")
330+
325331
return data
326332

327333
def retrieve(self, validated_data):

pulp_python/app/utils.py

Lines changed: 59 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"""
@@ -91,6 +96,9 @@ def parse_project_metadata(project):
9196
dictionary: of python project metadata
9297
9398
"""
99+
metadata_sha256_value = project.get("metadata_sha256") or ""
100+
logger.info(f"parse_project_metadata: metadata_sha256 = {metadata_sha256_value}")
101+
94102
return {
95103
# Core metadata
96104
# Version 1.0
@@ -130,7 +138,7 @@ def parse_project_metadata(project):
130138
# Release metadata
131139
"packagetype": project.get("packagetype") or "",
132140
"python_version": project.get("python_version") or "",
133-
"metadata_sha256": "", # TODO
141+
"metadata_sha256": metadata_sha256_value,
134142
}
135143

136144

@@ -158,9 +166,9 @@ def parse_metadata(project, version, distribution):
158166
package["url"] = distribution.get("url") or ""
159167
package["sha256"] = distribution.get("digests", {}).get("sha256") or ""
160168
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
169+
package["requires_python"] = distribution.get("requires_python") or "" # package.get(
170+
# "requires_python"
171+
# ) # noqa: E501
164172
package["metadata_sha256"] = distribution.get("data-dist-info-metadata", {}).get(
165173
"sha256"
166174
) or package.get("metadata_sha256")
@@ -181,6 +189,9 @@ def get_project_metadata_from_file(filename):
181189
packagetype = DIST_EXTENSIONS[extensions[pkg_type_index]]
182190

183191
metadata = DIST_TYPES[packagetype](filename)
192+
computed_hash = compute_metadata_sha256(filename)
193+
metadata.metadata_sha256 = computed_hash
194+
logger.info(f"Set metadata.metadata_sha256 to: {computed_hash} for {filename}")
184195
metadata.packagetype = packagetype
185196
if packagetype == "sdist":
186197
metadata.python_version = "source"
@@ -193,6 +204,45 @@ def get_project_metadata_from_file(filename):
193204
return metadata
194205

195206

207+
def compute_metadata_sha256(filename: str) -> str:
208+
"""
209+
Compute SHA256 hash of the metadata file from a Python package.
210+
211+
Returns SHA256 hash or empty string if metadata cannot be extracted.
212+
"""
213+
logger.info(f"Computing metadata SHA256 for wheel: {filename}")
214+
215+
if not filename.endswith(".whl"):
216+
logger.debug(f"File {filename} is not a wheel, skipping metadata SHA256 computation")
217+
return ""
218+
219+
try:
220+
with tempfile.NamedTemporaryFile() as temp_file:
221+
with open(filename, "rb") as source:
222+
shutil.copyfileobj(source, temp_file)
223+
temp_file.flush()
224+
225+
logger.debug(f"Copied wheel to temp file: {temp_file.name}")
226+
227+
with zipfile.ZipFile(temp_file.name, "r") as f:
228+
logger.debug(f"Wheel contains files: {f.namelist()}")
229+
230+
for file_path in f.namelist():
231+
if file_path.endswith(".dist-info/METADATA"):
232+
logger.info(f"Found METADATA file: {file_path}")
233+
metadata_content = f.read(file_path)
234+
hash_value = hashlib.sha256(metadata_content).hexdigest()
235+
logger.info(f"Computed metadata SHA256: {hash_value}")
236+
return hash_value
237+
238+
logger.warning(f"No METADATA file found in wheel {filename}")
239+
240+
except Exception as e:
241+
logger.error(f"Error computing metadata SHA256 for {filename}: {e}")
242+
243+
return ""
244+
245+
196246
def artifact_to_python_content_data(filename, artifact, domain=None):
197247
"""
198248
Takes the artifact/filename and returns the metadata needed to create a PythonPackageContent.
@@ -209,6 +259,10 @@ def artifact_to_python_content_data(filename, artifact, domain=None):
209259
data["filename"] = filename
210260
data["pulp_domain"] = domain or artifact.pulp_domain
211261
data["_pulp_domain"] = data["pulp_domain"]
262+
263+
logger.info(
264+
f"artifact_to_python_content_data returning metadata_sha256: {data.get('metadata_sha256')}"
265+
)
212266
return data
213267

214268

@@ -448,7 +502,7 @@ def write_simple_detail_json(project_name, project_packages):
448502
"filename": package["filename"],
449503
"url": package["url"],
450504
"hashes": {"sha256": package["sha256"]},
451-
"requires_python": package["requires_python"] or None,
505+
"requires-python": package["requires_python"] or None,
452506
# data-dist-info-metadata is deprecated alias for core-metadata
453507
"data-dist-info-metadata": (
454508
{"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)