Skip to content

Commit 641bf36

Browse files
committed
Fix pull-through metadata serving
fixes: #1083
1 parent 6008980 commit 641bf36

File tree

4 files changed

+57
-2
lines changed

4 files changed

+57
-2
lines changed

CHANGES/1083.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed pull-through PEP 658 metadata not being served correctly for certain tools.

docs/user/guides/publish.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ pulp python distribution update --name foo --remote bar
105105
Functionality may not work or may be incomplete. Also, backwards compatibility when upgrading
106106
is not guaranteed.
107107

108+
!!! warning
109+
Chaining pull-through indices, having a pull-through point to another pull-through, does not
110+
work.
111+
108112
## Use the newly created distribution
109113

110114
The metadata and packages can now be retrieved from the distribution:

pulp_python/app/models.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,11 +320,25 @@ def get_remote_artifact_url(self, relative_path=None, request=None):
320320
"""Get url for remote_artifact"""
321321
if request and (url := request.query.get("redirect")):
322322
# This is a special case for pull-through caching
323+
# To handle PEP 658, it states that if the package has metadata available then it
324+
# should be found at the download URL + ".metadata". Thus if the request url ends with
325+
# ".metadata" then we need to add ".metadata" to the redirect url if not present.
326+
if relative_path:
327+
if relative_path.endswith(".metadata") and not url.endswith(".metadata"):
328+
url += ".metadata"
329+
# Handle special case for bug in pip (TODO file issue in pip) where it appends
330+
# ".metadata" to the redirect url instead of the request url
331+
if url.endswith(".metadata") and not relative_path.endswith(".metadata"):
332+
setattr(self, "_real_relative_path", url.rsplit("/", 1)[1])
323333
return url
324334
return super().get_remote_artifact_url(relative_path, request=request)
325335

326336
def get_remote_artifact_content_type(self, relative_path=None):
327-
"""Return PythonPackageContent."""
337+
"""Return PythonPackageContent, except for metadata artifacts."""
338+
if hasattr(self, "_real_relative_path"):
339+
relative_path = getattr(self, "_real_relative_path")
340+
if relative_path and relative_path.endswith(".whl.metadata"):
341+
return None
328342
return PythonPackageContent
329343

330344
class Meta:

pulp_python/tests/functional/api/test_full_mirror.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111

1212
from pypi_simple import ProjectPage
1313
from packaging.version import parse
14-
from urllib.parse import urljoin, urlsplit
14+
from urllib.parse import urljoin, urlsplit, urlunsplit
1515
from random import sample
16+
from hashlib import sha256
1617

1718

1819
def test_pull_through_install(
@@ -202,3 +203,38 @@ def test_pull_through_filtering_bad_names(python_remote_factory, python_distribu
202203
# Should have no packages with None version (they get filtered out)
203204
assert len(project_page.packages) > 0
204205
assert all(package.version is not None for package in project_page.packages)
206+
207+
208+
@pytest.mark.parallel
209+
def test_pull_through_metadata(python_remote_factory, python_distribution_factory):
210+
"""
211+
Tests that metadata is correctly served when using pull-through.
212+
213+
So when requesting the metadata url according to PEP 658 you should just need to add .metadata
214+
to the end of the url path. Since pull-through includes a redirect query parameter we need to
215+
test adding .metadata to the end of the url path vs adding it to the end of redirect query.
216+
"""
217+
remote = python_remote_factory(includes=["pytz"])
218+
distro = python_distribution_factory(remote=remote.pulp_href)
219+
220+
url = f"{distro.base_url}simple/pytz/"
221+
project_page = ProjectPage.from_response(requests.get(url), "pytz")
222+
filename1 = "pytz-2023.2-py2.py3-none-any.whl"
223+
filename2 = "pytz-2023.3-py2.py3-none-any.whl"
224+
package1 = next(p for p in project_page.packages if p.filename == filename1)
225+
package2 = next(p for p in project_page.packages if p.filename == filename2)
226+
assert package1.has_metadata
227+
assert package2.has_metadata
228+
229+
# The correct way to get the metadata url: add to path (uv does this)
230+
parts1 = urlsplit(package1.url)
231+
url1 = urlunsplit((parts1[0], parts1[1], parts1[2] + ".metadata", parts1[3], parts1[4]))
232+
r = requests.get(url1)
233+
assert r.status_code == 200
234+
assert sha256(r.content).hexdigest() == package1.metadata_digests["sha256"]
235+
236+
# The incorrect way to get the metadata url: add to end of string (pip does this)
237+
url2 = package2.url + ".metadata"
238+
r = requests.get(url2)
239+
assert r.status_code == 200
240+
assert sha256(r.content).hexdigest() == package2.metadata_digests["sha256"]

0 commit comments

Comments
 (0)