pulp-python 3.24.0__tar.gz → 3.24.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pulp_python-3.24.0 → pulp_python-3.24.2}/CHANGES.md +22 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/PKG-INFO +1 -1
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/__init__.py +1 -1
- pulp_python-3.24.2/pulp_python/app/migrations/0019_create_missing_metadata_artifacts.py +24 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/models.py +20 -2
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/utils.py +1 -1
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_full_mirror.py +104 -1
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python.egg-info/PKG-INFO +1 -1
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pyproject.toml +2 -2
- pulp_python-3.24.0/pulp_python/app/migrations/0019_create_missing_metadata_artifacts.py +0 -204
- {pulp_python-3.24.0 → pulp_python-3.24.2}/COMMITMENT +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/COPYRIGHT +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/LICENSE +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/MANIFEST.in +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/README.md +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/functest_requirements.txt +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/__init__.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/global_access_conditions.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/management/__init__.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/management/commands/__init__.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/management/commands/repair-python-metadata.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/migrations/0001_initial.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/migrations/0001_squashed_0010_update_json_field.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/migrations/0002_pythonpackagecontent_python_version.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/migrations/0003_new_sync_filters.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/migrations/0004_DATA_swap_distribution_model.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/migrations/0005_pythonpackagecontent_sha256.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/migrations/0006_pythonrepository_autopublish.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/migrations/0007_pythonpackagecontent_mv-2-1.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/migrations/0008_pythonpackagecontent_unique_sha256.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/migrations/0009_pythondistribution_allow_uploads.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/migrations/0010_update_json_field.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/migrations/0011_alter_pythondistribution_distribution_ptr_and_more.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/migrations/0012_add_domain.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/migrations/0013_add_rbac_permissions.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/migrations/0014_pythonpackagecontent_dynamic_and_more.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/migrations/0015_alter_pythonpackagecontent_options.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/migrations/0016_pythonpackagecontent_metadata_sha256.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/migrations/0017_pythonpackagecontent_size.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/migrations/0018_packageprovenance.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/migrations/__init__.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/modelresource.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/provenance.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/pypi/__init__.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/pypi/serializers.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/pypi/views.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/replica.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/serializers.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/settings.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/tasks/__init__.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/tasks/publish.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/tasks/repair.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/tasks/sync.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/tasks/upload.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/tasks/vulnerability_report.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/urls.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/viewsets.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/webserver_snippets/__init__.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/webserver_snippets/apache.conf +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/webserver_snippets/nginx.conf +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/pytest_plugin.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/__init__.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/__init__.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/__init__.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_attestations.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_auto_publish.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_consume_content.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_crud_content_unit.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_crud_publications.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_crud_remotes.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_domains.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_download_content.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_export_import.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_pypi_apis.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_pypi_simple_api.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_rbac.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_repair.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_sync.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_upload.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_vulnerability_report.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/assets/shelf-reader-0.1.tar.gz.publish.attestation +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/assets/shelf_reader-0.1-py2-none-any.whl.publish.attestation +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/constants.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/utils.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/unit/__init__.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/unit/test_models.py +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python.egg-info/SOURCES.txt +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python.egg-info/dependency_links.txt +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python.egg-info/entry_points.txt +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python.egg-info/requires.txt +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python.egg-info/top_level.txt +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/setup.cfg +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/test_requirements.txt +0 -0
- {pulp_python-3.24.0 → pulp_python-3.24.2}/unittest_requirements.txt +0 -0
|
@@ -8,6 +8,28 @@
|
|
|
8
8
|
|
|
9
9
|
[//]: # (towncrier release notes start)
|
|
10
10
|
|
|
11
|
+
## 3.24.2 (2026-02-06) {: #3.24.2 }
|
|
12
|
+
|
|
13
|
+
#### Bugfixes {: #3.24.2-bugfix }
|
|
14
|
+
|
|
15
|
+
- Fixed pull-through caching failing for packages with bad names.
|
|
16
|
+
[#1040](https://github.com/pulp/pulp_python/issues/1040)
|
|
17
|
+
- Fixed pull-through PEP 658 metadata not being served correctly for certain tools.
|
|
18
|
+
[#1083](https://github.com/pulp/pulp_python/issues/1083)
|
|
19
|
+
- Fixed pull-through PEP 658 metadata not being saved correctly with the package.
|
|
20
|
+
[#1087](https://github.com/pulp/pulp_python/issues/1087)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 3.24.1 (2026-01-22) {: #3.24.1 }
|
|
25
|
+
|
|
26
|
+
#### Bugfixes {: #3.24.1-bugfix }
|
|
27
|
+
|
|
28
|
+
- Changed migration 19 to reset package's metadata_sha256 to null. This field will be fixed in a later release.
|
|
29
|
+
[#1071](https://github.com/pulp/pulp_python/issues/1071)
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
11
33
|
## 3.24.0 (2026-01-20) {: #3.24.0 }
|
|
12
34
|
|
|
13
35
|
#### Features {: #3.24.0-feature }
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Generated manually on 2025-12-15 14:00 for creating missing metadata artifacts
|
|
2
|
+
from django.db import migrations
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def set_metadata_sha256_null(apps, schema_editor):
|
|
6
|
+
# We can't easily create the metadata artifacts in this migration, so just set the metadata_sha256
|
|
7
|
+
# to null and we will introduce a new command later to create them.
|
|
8
|
+
PythonPackageContent = apps.get_model("python", "PythonPackageContent")
|
|
9
|
+
PythonPackageContent.objects.filter(metadata_sha256__isnull=False).update(metadata_sha256=None)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Migration(migrations.Migration):
|
|
13
|
+
|
|
14
|
+
dependencies = [
|
|
15
|
+
("python", "0018_packageprovenance"),
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
operations = [
|
|
19
|
+
migrations.RunPython(
|
|
20
|
+
set_metadata_sha256_null,
|
|
21
|
+
reverse_code=migrations.RunPython.noop,
|
|
22
|
+
elidable=True,
|
|
23
|
+
),
|
|
24
|
+
]
|
|
@@ -25,6 +25,7 @@ from pathlib import PurePath
|
|
|
25
25
|
from .provenance import Provenance
|
|
26
26
|
from .utils import (
|
|
27
27
|
artifact_to_python_content_data,
|
|
28
|
+
artifact_to_metadata_artifact,
|
|
28
29
|
canonicalize_name,
|
|
29
30
|
python_content_to_json,
|
|
30
31
|
PYPI_LAST_SERIAL,
|
|
@@ -215,7 +216,10 @@ class PythonPackageContent(Content):
|
|
|
215
216
|
"""Used when downloading package from pull-through cache."""
|
|
216
217
|
path = PurePath(relative_path)
|
|
217
218
|
data = artifact_to_python_content_data(path.name, artifact, domain=get_domain())
|
|
218
|
-
|
|
219
|
+
artifacts = {path.name: artifact}
|
|
220
|
+
if metadata_artifact := artifact_to_metadata_artifact(path.name, artifact):
|
|
221
|
+
artifacts[f"{path.name}.metadata"] = metadata_artifact
|
|
222
|
+
return PythonPackageContent(**data), artifacts
|
|
219
223
|
|
|
220
224
|
def __str__(self):
|
|
221
225
|
"""
|
|
@@ -320,11 +324,25 @@ class PythonRemote(Remote, AutoAddObjPermsMixin):
|
|
|
320
324
|
"""Get url for remote_artifact"""
|
|
321
325
|
if request and (url := request.query.get("redirect")):
|
|
322
326
|
# This is a special case for pull-through caching
|
|
327
|
+
# To handle PEP 658, it states that if the package has metadata available then it
|
|
328
|
+
# should be found at the download URL + ".metadata". Thus if the request url ends with
|
|
329
|
+
# ".metadata" then we need to add ".metadata" to the redirect url if not present.
|
|
330
|
+
if relative_path:
|
|
331
|
+
if relative_path.endswith(".metadata") and not url.endswith(".metadata"):
|
|
332
|
+
url += ".metadata"
|
|
333
|
+
# Handle special case for bug in pip (TODO file issue in pip) where it appends
|
|
334
|
+
# ".metadata" to the redirect url instead of the request url
|
|
335
|
+
if url.endswith(".metadata") and not relative_path.endswith(".metadata"):
|
|
336
|
+
setattr(self, "_real_relative_path", url.rsplit("/", 1)[1])
|
|
323
337
|
return url
|
|
324
338
|
return super().get_remote_artifact_url(relative_path, request=request)
|
|
325
339
|
|
|
326
340
|
def get_remote_artifact_content_type(self, relative_path=None):
|
|
327
|
-
"""Return PythonPackageContent."""
|
|
341
|
+
"""Return PythonPackageContent, except for metadata artifacts."""
|
|
342
|
+
if hasattr(self, "_real_relative_path"):
|
|
343
|
+
relative_path = getattr(self, "_real_relative_path")
|
|
344
|
+
if relative_path and relative_path.endswith(".whl.metadata"):
|
|
345
|
+
return None
|
|
328
346
|
return PythonPackageContent
|
|
329
347
|
|
|
330
348
|
class Meta:
|
{pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_full_mirror.py
RENAMED
|
@@ -11,8 +11,9 @@ from pulp_python.tests.functional.constants import (
|
|
|
11
11
|
|
|
12
12
|
from pypi_simple import ProjectPage
|
|
13
13
|
from packaging.version import parse
|
|
14
|
-
from urllib.parse import urljoin, urlsplit
|
|
14
|
+
from urllib.parse import urljoin, urlsplit, urlunsplit
|
|
15
15
|
from random import sample
|
|
16
|
+
from hashlib import sha256
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
def test_pull_through_install(
|
|
@@ -182,3 +183,105 @@ def test_pull_through_local_only(
|
|
|
182
183
|
r = requests.get(url)
|
|
183
184
|
assert r.status_code == 404
|
|
184
185
|
assert r.text == "pulp-python does not exist."
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@pytest.mark.parallel
|
|
189
|
+
def test_pull_through_filtering_bad_names(python_remote_factory, python_distribution_factory):
|
|
190
|
+
"""Tests that pull-through handles packages with invalid names gracefully."""
|
|
191
|
+
# ipython version 0.13.X has improper named bdist_wininst, e.g ipython-0.13.py2-win-amd64.exe
|
|
192
|
+
# pypi-simple expects the platform to go before the pyversion (for .exe), so when parsed the
|
|
193
|
+
# version and package type will be None.
|
|
194
|
+
remote = python_remote_factory(url=PYPI_URL, includes=["ipython"])
|
|
195
|
+
distro = python_distribution_factory(remote=remote.pulp_href)
|
|
196
|
+
|
|
197
|
+
url = f"{distro.base_url}simple/ipython/"
|
|
198
|
+
response = requests.get(url)
|
|
199
|
+
|
|
200
|
+
assert response.status_code == 200
|
|
201
|
+
|
|
202
|
+
project_page = ProjectPage.from_response(response, "ipython")
|
|
203
|
+
# Should have no packages with None version (they get filtered out)
|
|
204
|
+
assert len(project_page.packages) > 0
|
|
205
|
+
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"]
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@pytest.mark.parallel
|
|
244
|
+
def test_pull_through_metadata_with_repo(
|
|
245
|
+
python_repo_factory,
|
|
246
|
+
python_remote_factory,
|
|
247
|
+
python_distribution_factory,
|
|
248
|
+
pulpcore_bindings,
|
|
249
|
+
):
|
|
250
|
+
"""Tests that metadata is correctly saved when using pull-through with a repository."""
|
|
251
|
+
remote = python_remote_factory(url=PYPI_URL, includes=["pip"])
|
|
252
|
+
repo = python_repo_factory()
|
|
253
|
+
distro = python_distribution_factory(repository=repo.pulp_href, remote=remote.pulp_href)
|
|
254
|
+
|
|
255
|
+
pip_url = f"{distro.base_url}simple/pip/"
|
|
256
|
+
project_page = ProjectPage.from_response(requests.get(pip_url), "pip")
|
|
257
|
+
filename = "pip-26.0.1-py3-none-any.whl"
|
|
258
|
+
package = next(p for p in project_page.packages if p.filename == filename)
|
|
259
|
+
assert package.has_metadata
|
|
260
|
+
assert "?redirect=" in package.url
|
|
261
|
+
|
|
262
|
+
# Retrieve the metadata and assert the content was not saved to the repository
|
|
263
|
+
parts = urlsplit(package.url)
|
|
264
|
+
url = urlunsplit((parts[0], parts[1], parts[2] + ".metadata", parts[3], parts[4]))
|
|
265
|
+
r = requests.get(url)
|
|
266
|
+
assert r.status_code == 200
|
|
267
|
+
assert sha256(r.content).hexdigest() == package.metadata_digests["sha256"]
|
|
268
|
+
project_page = ProjectPage.from_response(requests.get(pip_url), "pip")
|
|
269
|
+
package = next(p for p in project_page.packages if p.filename == filename)
|
|
270
|
+
assert package.has_metadata
|
|
271
|
+
assert "?redirect=" in package.url
|
|
272
|
+
|
|
273
|
+
# Now retrieve the package and assert the content was saved with metadata
|
|
274
|
+
r = requests.get(package.url)
|
|
275
|
+
assert r.status_code == 200
|
|
276
|
+
pa = pulpcore_bindings.ArtifactsApi.list(sha256=package.digests["sha256"])
|
|
277
|
+
assert pa.count == 1
|
|
278
|
+
ma = pulpcore_bindings.ArtifactsApi.list(sha256=package.metadata_digests["sha256"])
|
|
279
|
+
assert ma.count == 1
|
|
280
|
+
|
|
281
|
+
# Check the simple page is updated to point to the local repository
|
|
282
|
+
project_page = ProjectPage.from_response(requests.get(pip_url), "pip")
|
|
283
|
+
package = next(p for p in project_page.packages if p.filename == filename)
|
|
284
|
+
assert "?redirect=" not in package.url
|
|
285
|
+
r = requests.get(package.metadata_url)
|
|
286
|
+
assert r.status_code == 200
|
|
287
|
+
assert sha256(r.content).hexdigest() == package.metadata_digests["sha256"]
|
|
@@ -7,7 +7,7 @@ build-backend = 'setuptools.build_meta'
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "pulp-python"
|
|
10
|
-
version = "3.24.
|
|
10
|
+
version = "3.24.2"
|
|
11
11
|
description = "pulp-python plugin for the Pulp Project"
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
authors = [
|
|
@@ -77,7 +77,7 @@ ignore = [
|
|
|
77
77
|
[tool.bumpversion]
|
|
78
78
|
# This section is managed by the plugin template. Do not edit manually.
|
|
79
79
|
|
|
80
|
-
current_version = "3.24.
|
|
80
|
+
current_version = "3.24.2"
|
|
81
81
|
commit = false
|
|
82
82
|
tag = false
|
|
83
83
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<alpha>0a)?(?P<patch>\\d+)(\\.(?P<release>[a-z]+))?"
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
# Generated manually on 2025-12-15 14:00 for creating missing metadata artifacts
|
|
2
|
-
|
|
3
|
-
from django.db import migrations
|
|
4
|
-
|
|
5
|
-
BATCH_SIZE = 1000
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def pulp_hashlib_new(name, *args, **kwargs):
|
|
9
|
-
"""
|
|
10
|
-
Copied and updated (to comply with migrations) from pulpcore.
|
|
11
|
-
"""
|
|
12
|
-
import hashlib as the_real_hashlib
|
|
13
|
-
from django.conf import settings
|
|
14
|
-
|
|
15
|
-
if name not in settings.ALLOWED_CONTENT_CHECKSUMS:
|
|
16
|
-
return None
|
|
17
|
-
|
|
18
|
-
return the_real_hashlib.new(name, *args, **kwargs)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def init_and_validate(file, artifact_model, expected_digests):
|
|
22
|
-
"""
|
|
23
|
-
Copied and updated (to comply with migrations) from pulpcore.
|
|
24
|
-
"""
|
|
25
|
-
from django.conf import settings
|
|
26
|
-
|
|
27
|
-
digest_fields = []
|
|
28
|
-
for alg in ("sha512", "sha384", "sha256", "sha224", "sha1", "md5"):
|
|
29
|
-
if alg in settings.ALLOWED_CONTENT_CHECKSUMS:
|
|
30
|
-
digest_fields.append(alg)
|
|
31
|
-
|
|
32
|
-
if isinstance(file, str):
|
|
33
|
-
with open(file, "rb") as f:
|
|
34
|
-
hashers = {
|
|
35
|
-
n: hasher for n in digest_fields if (hasher := pulp_hashlib_new(n)) is not None
|
|
36
|
-
}
|
|
37
|
-
if not hashers:
|
|
38
|
-
return None
|
|
39
|
-
|
|
40
|
-
size = 0
|
|
41
|
-
while True:
|
|
42
|
-
chunk = f.read(1048576) # 1 megabyte
|
|
43
|
-
if not chunk:
|
|
44
|
-
break
|
|
45
|
-
for algorithm in hashers.values():
|
|
46
|
-
algorithm.update(chunk)
|
|
47
|
-
size = size + len(chunk)
|
|
48
|
-
else:
|
|
49
|
-
size = file.size
|
|
50
|
-
hashers = file.hashers
|
|
51
|
-
|
|
52
|
-
mismatched_sha256 = None
|
|
53
|
-
for algorithm, expected_digest in expected_digests.items():
|
|
54
|
-
if algorithm not in hashers:
|
|
55
|
-
return None
|
|
56
|
-
actual_digest = hashers[algorithm].hexdigest()
|
|
57
|
-
if expected_digest != actual_digest:
|
|
58
|
-
# Store the actual value for later fixing if it differs from the package value
|
|
59
|
-
mismatched_sha256 = actual_digest
|
|
60
|
-
|
|
61
|
-
attributes = {"size": size, "file": file}
|
|
62
|
-
for algorithm in digest_fields:
|
|
63
|
-
attributes[algorithm] = hashers[algorithm].hexdigest()
|
|
64
|
-
|
|
65
|
-
return artifact_model(**attributes), mismatched_sha256
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def extract_wheel_metadata(filename):
|
|
69
|
-
"""
|
|
70
|
-
Extract the metadata file content from a wheel file.
|
|
71
|
-
Return the raw metadata content as bytes or None if metadata cannot be extracted.
|
|
72
|
-
"""
|
|
73
|
-
import zipfile
|
|
74
|
-
|
|
75
|
-
try:
|
|
76
|
-
with zipfile.ZipFile(filename, "r") as f:
|
|
77
|
-
for file_path in f.namelist():
|
|
78
|
-
if file_path.endswith(".dist-info/METADATA"):
|
|
79
|
-
return f.read(file_path)
|
|
80
|
-
except (zipfile.BadZipFile, KeyError, OSError):
|
|
81
|
-
pass
|
|
82
|
-
return None
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def artifact_to_metadata_artifact(filename, artifact, md_digests, tmp_dir, artifact_model):
|
|
86
|
-
"""
|
|
87
|
-
Create artifact for metadata from the provided wheel artifact.
|
|
88
|
-
Return (artifact, mismatched_sha256) on success, None on any failure.
|
|
89
|
-
"""
|
|
90
|
-
import shutil
|
|
91
|
-
import tempfile
|
|
92
|
-
|
|
93
|
-
with tempfile.NamedTemporaryFile("wb", dir=tmp_dir, suffix=filename, delete=False) as temp_file:
|
|
94
|
-
temp_wheel_path = temp_file.name
|
|
95
|
-
artifact.file.seek(0)
|
|
96
|
-
shutil.copyfileobj(artifact.file, temp_file)
|
|
97
|
-
temp_file.flush()
|
|
98
|
-
|
|
99
|
-
metadata_content = extract_wheel_metadata(temp_wheel_path)
|
|
100
|
-
if not metadata_content:
|
|
101
|
-
return None
|
|
102
|
-
|
|
103
|
-
with tempfile.NamedTemporaryFile(
|
|
104
|
-
"wb", dir=tmp_dir, suffix=".metadata", delete=False
|
|
105
|
-
) as temp_md:
|
|
106
|
-
temp_metadata_path = temp_md.name
|
|
107
|
-
temp_md.write(metadata_content)
|
|
108
|
-
temp_md.flush()
|
|
109
|
-
|
|
110
|
-
return init_and_validate(temp_metadata_path, artifact_model, md_digests)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def create_missing_metadata_artifacts(apps, schema_editor):
|
|
114
|
-
"""
|
|
115
|
-
Create metadata artifacts for PythonPackageContent instances that have metadata_sha256
|
|
116
|
-
but are missing the corresponding metadata artifact.
|
|
117
|
-
"""
|
|
118
|
-
import tempfile
|
|
119
|
-
from django.conf import settings
|
|
120
|
-
from django.db import models
|
|
121
|
-
|
|
122
|
-
PythonPackageContent = apps.get_model("python", "PythonPackageContent")
|
|
123
|
-
ContentArtifact = apps.get_model("core", "ContentArtifact")
|
|
124
|
-
Artifact = apps.get_model("core", "Artifact")
|
|
125
|
-
|
|
126
|
-
packages = (
|
|
127
|
-
PythonPackageContent.objects.filter(
|
|
128
|
-
metadata_sha256__isnull=False,
|
|
129
|
-
filename__endswith=".whl",
|
|
130
|
-
contentartifact__artifact__isnull=False,
|
|
131
|
-
contentartifact__relative_path=models.F("filename"),
|
|
132
|
-
)
|
|
133
|
-
.exclude(metadata_sha256="")
|
|
134
|
-
.prefetch_related("_artifacts")
|
|
135
|
-
.only("filename", "metadata_sha256")
|
|
136
|
-
)
|
|
137
|
-
artifact_batch = []
|
|
138
|
-
contentartifact_batch = []
|
|
139
|
-
packages_batch = []
|
|
140
|
-
|
|
141
|
-
with tempfile.TemporaryDirectory(dir=settings.WORKING_DIRECTORY) as temp_dir:
|
|
142
|
-
for package in packages:
|
|
143
|
-
# Get the main artifact for package
|
|
144
|
-
main_artifact = package._artifacts.get()
|
|
145
|
-
|
|
146
|
-
filename = package.filename
|
|
147
|
-
metadata_digests = {"sha256": package.metadata_sha256}
|
|
148
|
-
result = artifact_to_metadata_artifact(
|
|
149
|
-
filename, main_artifact, metadata_digests, temp_dir, Artifact
|
|
150
|
-
)
|
|
151
|
-
if result is None:
|
|
152
|
-
# Unset metadata_sha256 when extraction or validation fails
|
|
153
|
-
package.metadata_sha256 = None
|
|
154
|
-
packages_batch.append(package)
|
|
155
|
-
continue
|
|
156
|
-
metadata_artifact, mismatched_sha256 = result
|
|
157
|
-
if mismatched_sha256:
|
|
158
|
-
# Fix the package if its metadata_sha256 differs from the actual value
|
|
159
|
-
package.metadata_sha256 = mismatched_sha256
|
|
160
|
-
packages_batch.append(package)
|
|
161
|
-
|
|
162
|
-
# Set the domain on the metadata artifact to match the package's domain
|
|
163
|
-
metadata_artifact.pulp_domain = package._pulp_domain
|
|
164
|
-
|
|
165
|
-
contentartifact = ContentArtifact(
|
|
166
|
-
artifact=metadata_artifact,
|
|
167
|
-
content=package,
|
|
168
|
-
relative_path=f"{filename}.metadata",
|
|
169
|
-
)
|
|
170
|
-
artifact_batch.append(metadata_artifact)
|
|
171
|
-
contentartifact_batch.append(contentartifact)
|
|
172
|
-
|
|
173
|
-
if len(artifact_batch) == BATCH_SIZE:
|
|
174
|
-
Artifact.objects.bulk_create(artifact_batch, batch_size=BATCH_SIZE)
|
|
175
|
-
ContentArtifact.objects.bulk_create(contentartifact_batch, batch_size=BATCH_SIZE)
|
|
176
|
-
artifact_batch.clear()
|
|
177
|
-
contentartifact_batch.clear()
|
|
178
|
-
if len(packages_batch) == BATCH_SIZE:
|
|
179
|
-
PythonPackageContent.objects.bulk_update(
|
|
180
|
-
packages_batch, ["metadata_sha256"], batch_size=BATCH_SIZE
|
|
181
|
-
)
|
|
182
|
-
packages_batch.clear()
|
|
183
|
-
|
|
184
|
-
if artifact_batch:
|
|
185
|
-
Artifact.objects.bulk_create(artifact_batch, batch_size=BATCH_SIZE)
|
|
186
|
-
ContentArtifact.objects.bulk_create(contentartifact_batch, batch_size=BATCH_SIZE)
|
|
187
|
-
if packages_batch:
|
|
188
|
-
PythonPackageContent.objects.bulk_update(
|
|
189
|
-
packages_batch, ["metadata_sha256"], batch_size=BATCH_SIZE
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
class Migration(migrations.Migration):
|
|
194
|
-
|
|
195
|
-
dependencies = [
|
|
196
|
-
("python", "0018_packageprovenance"),
|
|
197
|
-
]
|
|
198
|
-
|
|
199
|
-
operations = [
|
|
200
|
-
migrations.RunPython(
|
|
201
|
-
create_missing_metadata_artifacts,
|
|
202
|
-
reverse_code=migrations.RunPython.noop,
|
|
203
|
-
),
|
|
204
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/migrations/0003_new_sync_filters.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/migrations/0010_update_json_field.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/migrations/0013_add_rbac_permissions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/app/migrations/0018_packageprovenance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_attestations.py
RENAMED
|
File without changes
|
{pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_auto_publish.py
RENAMED
|
File without changes
|
{pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_consume_content.py
RENAMED
|
File without changes
|
{pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_crud_content_unit.py
RENAMED
|
File without changes
|
{pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_crud_publications.py
RENAMED
|
File without changes
|
{pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_crud_remotes.py
RENAMED
|
File without changes
|
|
File without changes
|
{pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_download_content.py
RENAMED
|
File without changes
|
{pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_export_import.py
RENAMED
|
File without changes
|
{pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_pypi_apis.py
RENAMED
|
File without changes
|
{pulp_python-3.24.0 → pulp_python-3.24.2}/pulp_python/tests/functional/api/test_pypi_simple_api.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|