Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES/1083.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed pull-through PEP 658 metadata not being served correctly for certain tools.
4 changes: 4 additions & 0 deletions docs/user/guides/publish.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ pulp python distribution update --name foo --remote bar
Functionality may not work or may be incomplete. Also, backwards compatibility when upgrading
is not guaranteed.

!!! warning
Chaining pull-through indices, having a pull-through point to another pull-through, does not
work.

## Use the newly created distribution

The metadata and packages can now be retrieved from the distribution:
Expand Down
16 changes: 15 additions & 1 deletion pulp_python/app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,11 +320,25 @@ def get_remote_artifact_url(self, relative_path=None, request=None):
"""Get url for remote_artifact"""
if request and (url := request.query.get("redirect")):
# This is a special case for pull-through caching
# To handle PEP 658, it states that if the package has metadata available then it
# should be found at the download URL + ".metadata". Thus if the request url ends with
# ".metadata" then we need to add ".metadata" to the redirect url if not present.
if relative_path:
if relative_path.endswith(".metadata") and not url.endswith(".metadata"):
url += ".metadata"
# Handle special case for bug in pip (TODO file issue in pip) where it appends
# ".metadata" to the redirect url instead of the request url
if url.endswith(".metadata") and not relative_path.endswith(".metadata"):
setattr(self, "_real_relative_path", url.rsplit("/", 1)[1])
return url
return super().get_remote_artifact_url(relative_path, request=request)

def get_remote_artifact_content_type(self, relative_path=None):
"""Return PythonPackageContent."""
"""Return PythonPackageContent, except for metadata artifacts."""
if hasattr(self, "_real_relative_path"):
relative_path = getattr(self, "_real_relative_path")
if relative_path and relative_path.endswith(".whl.metadata"):
return None
return PythonPackageContent

class Meta:
Expand Down
38 changes: 37 additions & 1 deletion pulp_python/tests/functional/api/test_full_mirror.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@

from pypi_simple import ProjectPage
from packaging.version import parse
from urllib.parse import urljoin, urlsplit
from urllib.parse import urljoin, urlsplit, urlunsplit
from random import sample
from hashlib import sha256


def test_pull_through_install(
Expand Down Expand Up @@ -182,3 +183,38 @@ def test_pull_through_local_only(
r = requests.get(url)
assert r.status_code == 404
assert r.text == "pulp-python does not exist."


@pytest.mark.parallel
def test_pull_through_metadata(python_remote_factory, python_distribution_factory):
"""
Tests that metadata is correctly served when using pull-through.

So when requesting the metadata url according to PEP 658 you should just need to add .metadata
to the end of the url path. Since pull-through includes a redirect query parameter we need to
test adding .metadata to the end of the url path vs adding it to the end of redirect query.
"""
remote = python_remote_factory(includes=["pytz"])
distro = python_distribution_factory(remote=remote.pulp_href)

url = f"{distro.base_url}simple/pytz/"
project_page = ProjectPage.from_response(requests.get(url), "pytz")
filename1 = "pytz-2023.2-py2.py3-none-any.whl"
filename2 = "pytz-2023.3-py2.py3-none-any.whl"
package1 = next(p for p in project_page.packages if p.filename == filename1)
package2 = next(p for p in project_page.packages if p.filename == filename2)
assert package1.has_metadata
assert package2.has_metadata

# The correct way to get the metadata url: add to path (uv does this)
parts1 = urlsplit(package1.url)
url1 = urlunsplit((parts1[0], parts1[1], parts1[2] + ".metadata", parts1[3], parts1[4]))
r = requests.get(url1)
assert r.status_code == 200
assert sha256(r.content).hexdigest() == package1.metadata_digests["sha256"]

# The incorrect way to get the metadata url: add to end of string (pip does this)
url2 = package2.url + ".metadata"
r = requests.get(url2)
assert r.status_code == 200
assert sha256(r.content).hexdigest() == package2.metadata_digests["sha256"]