pulp-python 3.25.0__tar.gz → 3.26.0__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.25.0 → pulp_python-3.26.0}/CHANGES.md +23 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/PKG-INFO +1 -1
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/__init__.py +1 -1
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/provenance.py +1 -1
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/serializers.py +1 -1
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/tasks/repair.py +122 -7
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/utils.py +6 -3
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_crud_content_unit.py +3 -1
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_pypi_apis.py +3 -1
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_repair.py +119 -2
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_sync.py +3 -1
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_upload.py +5 -2
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/utils.py +3 -2
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python.egg-info/PKG-INFO +1 -1
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pyproject.toml +4 -2
- {pulp_python-3.25.0 → pulp_python-3.26.0}/COMMITMENT +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/COPYRIGHT +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/LICENSE +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/MANIFEST.in +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/README.md +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/functest_requirements.txt +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/__init__.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/global_access_conditions.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/management/__init__.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/management/commands/__init__.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/management/commands/repair-python-metadata.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/migrations/0001_initial.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/migrations/0001_squashed_0010_update_json_field.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/migrations/0002_pythonpackagecontent_python_version.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/migrations/0003_new_sync_filters.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/migrations/0004_DATA_swap_distribution_model.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/migrations/0005_pythonpackagecontent_sha256.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/migrations/0006_pythonrepository_autopublish.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/migrations/0007_pythonpackagecontent_mv-2-1.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/migrations/0008_pythonpackagecontent_unique_sha256.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/migrations/0009_pythondistribution_allow_uploads.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/migrations/0010_update_json_field.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/migrations/0011_alter_pythondistribution_distribution_ptr_and_more.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/migrations/0012_add_domain.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/migrations/0013_add_rbac_permissions.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/migrations/0014_pythonpackagecontent_dynamic_and_more.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/migrations/0015_alter_pythonpackagecontent_options.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/migrations/0016_pythonpackagecontent_metadata_sha256.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/migrations/0017_pythonpackagecontent_size.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/migrations/0018_packageprovenance.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/migrations/0019_create_missing_metadata_artifacts.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/migrations/__init__.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/modelresource.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/models.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/pypi/__init__.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/pypi/serializers.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/pypi/views.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/replica.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/settings.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/tasks/__init__.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/tasks/publish.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/tasks/sync.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/tasks/upload.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/tasks/vulnerability_report.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/urls.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/viewsets.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/webserver_snippets/__init__.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/webserver_snippets/apache.conf +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/app/webserver_snippets/nginx.conf +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/pytest_plugin.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/__init__.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/__init__.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/__init__.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_attestations.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_auto_publish.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_consume_content.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_crud_publications.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_crud_remotes.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_domains.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_download_content.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_export_import.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_full_mirror.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_pypi_simple_api.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_rbac.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_vulnerability_report.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/assets/shelf-reader-0.1.tar.gz.publish.attestation +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/assets/shelf_reader-0.1-py2-none-any.whl.publish.attestation +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/constants.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/unit/__init__.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/unit/test_models.py +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python.egg-info/SOURCES.txt +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python.egg-info/dependency_links.txt +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python.egg-info/entry_points.txt +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python.egg-info/requires.txt +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python.egg-info/top_level.txt +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/setup.cfg +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/test_requirements.txt +0 -0
- {pulp_python-3.25.0 → pulp_python-3.26.0}/unittest_requirements.txt +0 -0
|
@@ -8,6 +8,29 @@
|
|
|
8
8
|
|
|
9
9
|
[//]: # (towncrier release notes start)
|
|
10
10
|
|
|
11
|
+
## 3.26.0 (2026-02-26) {: #3.26.0 }
|
|
12
|
+
|
|
13
|
+
#### Features {: #3.26.0-feature }
|
|
14
|
+
|
|
15
|
+
- Added support for recreating and fixing metadata files to `repair_metadata` endpoint.
|
|
16
|
+
[#1099](https://github.com/pulp/pulp_python/issues/1099)
|
|
17
|
+
|
|
18
|
+
#### Bugfixes {: #3.26.0-bugfix }
|
|
19
|
+
|
|
20
|
+
- Fixed edge case where metadata file did not match wheel metadata.
|
|
21
|
+
[#1101](https://github.com/pulp/pulp_python/issues/1101)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 3.25.1 (2026-02-16) {: #3.25.1 }
|
|
26
|
+
|
|
27
|
+
#### Bugfixes {: #3.25.1-bugfix }
|
|
28
|
+
|
|
29
|
+
- Fixed edge case where metadata file did not match wheel metadata.
|
|
30
|
+
[#1101](https://github.com/pulp/pulp_python/issues/1101)
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
11
34
|
## 3.25.0 (2026-02-10) {: #3.25.0 }
|
|
12
35
|
|
|
13
36
|
#### Features {: #3.25.0-feature }
|
|
@@ -58,7 +58,7 @@ class Provenance(BaseModel):
|
|
|
58
58
|
attestation_bundles: list[AttestationBundle]
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
def verify_provenance(filename, sha256, provenance, offline=
|
|
61
|
+
def verify_provenance(filename, sha256, provenance, offline=True):
|
|
62
62
|
"""Verify the provenance object is valid for the package."""
|
|
63
63
|
dist = Distribution(name=filename, digest=sha256)
|
|
64
64
|
for bundle in provenance.attestation_bundles:
|
|
@@ -352,7 +352,7 @@ class PythonPackageContentSerializer(core_serializers.SingleArtifactContentUploa
|
|
|
352
352
|
raise serializers.ValidationError(_("Invalid attestations: {}".format(e)))
|
|
353
353
|
return attestations
|
|
354
354
|
|
|
355
|
-
def handle_attestations(self, filename, sha256, attestations, offline=
|
|
355
|
+
def handle_attestations(self, filename, sha256, attestations, offline=True):
|
|
356
356
|
"""Handle converting attestations to a Provenance object."""
|
|
357
357
|
user = get_current_authenticated_user()
|
|
358
358
|
publisher = AnyPublisher(kind="Pulp User", prn=get_prn(user))
|
|
@@ -8,11 +8,12 @@ from django.db.models import Prefetch
|
|
|
8
8
|
from django.db.models.query import QuerySet
|
|
9
9
|
from pulp_python.app.models import PythonPackageContent, PythonRepository
|
|
10
10
|
from pulp_python.app.utils import (
|
|
11
|
+
artifact_to_metadata_artifact,
|
|
11
12
|
artifact_to_python_content_data,
|
|
12
13
|
fetch_json_release_metadata,
|
|
13
14
|
parse_metadata,
|
|
14
15
|
)
|
|
15
|
-
from pulpcore.plugin.models import ContentArtifact, ProgressReport
|
|
16
|
+
from pulpcore.plugin.models import Artifact, ContentArtifact, ProgressReport
|
|
16
17
|
from pulpcore.plugin.util import get_domain
|
|
17
18
|
|
|
18
19
|
log = logging.getLogger(__name__)
|
|
@@ -41,16 +42,25 @@ def repair(repository_pk: UUID) -> None:
|
|
|
41
42
|
content_set = repository.latest_version().content.values_list("pk", flat=True)
|
|
42
43
|
content = PythonPackageContent.objects.filter(pk__in=content_set)
|
|
43
44
|
|
|
44
|
-
num_repaired, pkgs_not_repaired =
|
|
45
|
+
num_repaired, pkgs_not_repaired, num_metadata_repaired, pkgs_metadata_not_repaired = (
|
|
46
|
+
repair_metadata(content)
|
|
47
|
+
)
|
|
48
|
+
# Convert set() to 0
|
|
49
|
+
if not pkgs_not_repaired:
|
|
50
|
+
pkgs_not_repaired = 0
|
|
51
|
+
if not pkgs_metadata_not_repaired:
|
|
52
|
+
pkgs_metadata_not_repaired = 0
|
|
53
|
+
|
|
45
54
|
log.info(
|
|
46
55
|
_(
|
|
47
56
|
"{} packages' metadata repaired. Not repaired packages due to either "
|
|
48
|
-
"inaccessible URL or mismatched sha256: {}."
|
|
49
|
-
|
|
57
|
+
"inaccessible URL or mismatched sha256: {}. "
|
|
58
|
+
"{} metadata files repaired. Packages whose metadata files could not be repaired: {}."
|
|
59
|
+
).format(num_repaired, pkgs_not_repaired, num_metadata_repaired, pkgs_metadata_not_repaired)
|
|
50
60
|
)
|
|
51
61
|
|
|
52
62
|
|
|
53
|
-
def repair_metadata(content: QuerySet[PythonPackageContent]) -> tuple[int, set[str]]:
|
|
63
|
+
def repair_metadata(content: QuerySet[PythonPackageContent]) -> tuple[int, set[str], int, set[str]]:
|
|
54
64
|
"""
|
|
55
65
|
Repairs metadata for a queryset of PythonPackageContent objects
|
|
56
66
|
and updates the progress report.
|
|
@@ -59,9 +69,11 @@ def repair_metadata(content: QuerySet[PythonPackageContent]) -> tuple[int, set[s
|
|
|
59
69
|
content (QuerySet[PythonPackageContent]): The queryset of items to repair.
|
|
60
70
|
|
|
61
71
|
Returns:
|
|
62
|
-
tuple[int, set[str]]: A tuple containing:
|
|
72
|
+
tuple[int, set[str], int, set[str]]: A tuple containing:
|
|
63
73
|
- The number of packages that were repaired.
|
|
64
74
|
- A set of packages' PKs that were not repaired.
|
|
75
|
+
- The number of metadata files that were repaired.
|
|
76
|
+
- A set of packages' PKs without repaired metadata artifacts.
|
|
65
77
|
"""
|
|
66
78
|
immediate_content = (
|
|
67
79
|
content.filter(contentartifact__artifact__isnull=False)
|
|
@@ -87,6 +99,11 @@ def repair_metadata(content: QuerySet[PythonPackageContent]) -> tuple[int, set[s
|
|
|
87
99
|
# Keep track of on-demand packages that were not repaired
|
|
88
100
|
pkgs_not_repaired = set()
|
|
89
101
|
|
|
102
|
+
# Metadata artifacts and content artifacts
|
|
103
|
+
metadata_batch = []
|
|
104
|
+
total_metadata_repaired = 0
|
|
105
|
+
pkgs_metadata_not_repaired = set()
|
|
106
|
+
|
|
90
107
|
progress_report = ProgressReport(
|
|
91
108
|
message="Repairing packages' metadata",
|
|
92
109
|
code="repair.metadata",
|
|
@@ -102,6 +119,13 @@ def repair_metadata(content: QuerySet[PythonPackageContent]) -> tuple[int, set[s
|
|
|
102
119
|
.artifact
|
|
103
120
|
)
|
|
104
121
|
new_data = artifact_to_python_content_data(package.filename, main_artifact, domain)
|
|
122
|
+
total_metadata_repaired += update_metadata_artifact_if_needed(
|
|
123
|
+
package,
|
|
124
|
+
new_data.get("metadata_sha256"),
|
|
125
|
+
main_artifact,
|
|
126
|
+
metadata_batch,
|
|
127
|
+
pkgs_metadata_not_repaired,
|
|
128
|
+
)
|
|
105
129
|
total_repaired += update_package_if_needed(
|
|
106
130
|
package, new_data, batch, set_of_update_fields
|
|
107
131
|
)
|
|
@@ -163,7 +187,12 @@ def repair_metadata(content: QuerySet[PythonPackageContent]) -> tuple[int, set[s
|
|
|
163
187
|
total_repaired += len(batch)
|
|
164
188
|
PythonPackageContent.objects.bulk_update(batch, set_of_update_fields)
|
|
165
189
|
|
|
166
|
-
|
|
190
|
+
if metadata_batch:
|
|
191
|
+
not_repaired = _process_metadata_batch(metadata_batch)
|
|
192
|
+
pkgs_metadata_not_repaired.update(not_repaired)
|
|
193
|
+
total_metadata_repaired += len(metadata_batch) - len(not_repaired)
|
|
194
|
+
|
|
195
|
+
return total_repaired, pkgs_not_repaired, total_metadata_repaired, pkgs_metadata_not_repaired
|
|
167
196
|
|
|
168
197
|
|
|
169
198
|
def update_package_if_needed(
|
|
@@ -202,3 +231,89 @@ def update_package_if_needed(
|
|
|
202
231
|
set_of_update_fields.clear()
|
|
203
232
|
|
|
204
233
|
return total_repaired
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def update_metadata_artifact_if_needed(
|
|
237
|
+
package: PythonPackageContent,
|
|
238
|
+
new_metadata_sha256: str | None,
|
|
239
|
+
main_artifact: Artifact,
|
|
240
|
+
metadata_batch: list[tuple],
|
|
241
|
+
pkgs_metadata_not_repaired: set[str],
|
|
242
|
+
) -> int:
|
|
243
|
+
"""
|
|
244
|
+
Repairs metadata artifacts for wheel packages by creating missing metadata artifacts
|
|
245
|
+
or updating existing ones when the metadata_sha256 differs. Only processes wheel files
|
|
246
|
+
that have a valid new_metadata_sha256. Queues operations for batch processing.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
package: Package to check for metadata changes.
|
|
250
|
+
new_metadata_sha256: The correct metadata_sha256 extracted from the main artifact, or None.
|
|
251
|
+
main_artifact: The main package artifact used to generate metadata.
|
|
252
|
+
metadata_batch: List of tuples for batch processing (updated in-place).
|
|
253
|
+
pkgs_metadata_not_repaired: Set of package PKs that failed repair (updated in-place).
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Number of repaired metadata artifacts (only when batch is flushed at BULK_SIZE).
|
|
257
|
+
"""
|
|
258
|
+
total_metadata_repaired = 0
|
|
259
|
+
|
|
260
|
+
if not package.filename.endswith(".whl") or not new_metadata_sha256:
|
|
261
|
+
return total_metadata_repaired
|
|
262
|
+
|
|
263
|
+
original_metadata_sha256 = package.metadata_sha256
|
|
264
|
+
cas = package.contentartifact_set.filter(relative_path__endswith=".metadata")
|
|
265
|
+
|
|
266
|
+
# Create missing
|
|
267
|
+
if not cas:
|
|
268
|
+
metadata_batch.append((package, main_artifact))
|
|
269
|
+
# Fix existing
|
|
270
|
+
elif new_metadata_sha256 != original_metadata_sha256:
|
|
271
|
+
ca = cas.first()
|
|
272
|
+
metadata_artifact = ca.artifact
|
|
273
|
+
if metadata_artifact is None or (metadata_artifact.sha256 != new_metadata_sha256):
|
|
274
|
+
metadata_batch.append((package, main_artifact))
|
|
275
|
+
|
|
276
|
+
if len(metadata_batch) == BULK_SIZE:
|
|
277
|
+
not_repaired = _process_metadata_batch(metadata_batch)
|
|
278
|
+
pkgs_metadata_not_repaired.update(not_repaired)
|
|
279
|
+
total_metadata_repaired += BULK_SIZE - len(not_repaired)
|
|
280
|
+
metadata_batch.clear()
|
|
281
|
+
|
|
282
|
+
return total_metadata_repaired
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _process_metadata_batch(metadata_batch: list[tuple]) -> set[str]:
|
|
286
|
+
"""
|
|
287
|
+
Processes a batch of metadata repair operations by creating metadata artifacts
|
|
288
|
+
and their corresponding ContentArtifacts.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
metadata_batch: List of (package, main_artifact) tuples.
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
Set of package PKs for which metadata artifacts could not be created.
|
|
295
|
+
"""
|
|
296
|
+
not_repaired = set()
|
|
297
|
+
content_artifacts = []
|
|
298
|
+
|
|
299
|
+
for package, main_artifact in metadata_batch:
|
|
300
|
+
metadata_artifact = artifact_to_metadata_artifact(package.filename, main_artifact)
|
|
301
|
+
if metadata_artifact:
|
|
302
|
+
ca = ContentArtifact(
|
|
303
|
+
artifact=metadata_artifact,
|
|
304
|
+
content=package,
|
|
305
|
+
relative_path=f"{package.filename}.metadata",
|
|
306
|
+
)
|
|
307
|
+
content_artifacts.append(ca)
|
|
308
|
+
else:
|
|
309
|
+
not_repaired.add(package.pk)
|
|
310
|
+
|
|
311
|
+
if content_artifacts:
|
|
312
|
+
ContentArtifact.objects.bulk_create(
|
|
313
|
+
content_artifacts,
|
|
314
|
+
update_conflicts=True,
|
|
315
|
+
update_fields=["artifact"],
|
|
316
|
+
unique_fields=["content", "relative_path"],
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
return not_repaired
|
|
@@ -220,9 +220,12 @@ def extract_wheel_metadata(filename: str) -> bytes | None:
|
|
|
220
220
|
return None
|
|
221
221
|
try:
|
|
222
222
|
with zipfile.ZipFile(filename, "r") as f:
|
|
223
|
-
for
|
|
224
|
-
|
|
225
|
-
|
|
223
|
+
metadata_paths = [p for p in f.namelist() if p.endswith("METADATA")]
|
|
224
|
+
sorted_paths = sorted(metadata_paths, key=lambda s: s.count("/"))
|
|
225
|
+
for metadata_path in sorted_paths:
|
|
226
|
+
file = f.read(metadata_path)
|
|
227
|
+
if b"Metadata-Version" in file:
|
|
228
|
+
return file
|
|
226
229
|
except (zipfile.BadZipFile, KeyError, OSError) as e:
|
|
227
230
|
log.warning(f"Failed to extract metadata file from {filename}: {e}")
|
|
228
231
|
return None
|
{pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_crud_content_unit.py
RENAMED
|
@@ -200,4 +200,6 @@ def test_package_creation_with_metadata(
|
|
|
200
200
|
distro = python_distribution_factory(repository=python_repo)
|
|
201
201
|
|
|
202
202
|
# Test that metadata is accessible
|
|
203
|
-
ensure_metadata(
|
|
203
|
+
ensure_metadata(
|
|
204
|
+
pulp_content_url, distro.base_path, PYTHON_WHEEL_FILENAME, "shelf-reader", "0.1"
|
|
205
|
+
)
|
{pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_pypi_apis.py
RENAMED
|
@@ -165,7 +165,9 @@ def test_package_upload_with_metadata(
|
|
|
165
165
|
assert summary.added["python.python"]["count"] == 1
|
|
166
166
|
|
|
167
167
|
# Test that metadata is accessible
|
|
168
|
-
ensure_metadata(
|
|
168
|
+
ensure_metadata(
|
|
169
|
+
pulp_content_url, distro.base_path, PYTHON_WHEEL_FILENAME, "shelf-reader", "0.1"
|
|
170
|
+
)
|
|
169
171
|
|
|
170
172
|
|
|
171
173
|
@pytest.mark.parallel
|
|
@@ -10,7 +10,7 @@ from pulp_python.tests.functional.constants import (
|
|
|
10
10
|
|
|
11
11
|
@pytest.fixture
|
|
12
12
|
def create_content_direct(python_bindings):
|
|
13
|
-
def _create(artifact_filename, content_data):
|
|
13
|
+
def _create(artifact_filename, content_data, metadata_artifact_filename=None):
|
|
14
14
|
commands = (
|
|
15
15
|
"from pulpcore.plugin.models import Artifact, ContentArtifact; "
|
|
16
16
|
"from pulpcore.plugin.util import get_url; "
|
|
@@ -21,8 +21,16 @@ def create_content_direct(python_bindings):
|
|
|
21
21
|
"c.save(); "
|
|
22
22
|
f"ca = ContentArtifact(artifact=a, content=c, relative_path=c.filename); "
|
|
23
23
|
"ca.save(); "
|
|
24
|
-
"print(get_url(c))"
|
|
25
24
|
)
|
|
25
|
+
if metadata_artifact_filename:
|
|
26
|
+
commands += (
|
|
27
|
+
f"a2 = Artifact.init_and_validate('{metadata_artifact_filename}'); "
|
|
28
|
+
"a2.save(); "
|
|
29
|
+
"ca2_filename = c.filename + '.metadata'; "
|
|
30
|
+
f"ca2 = ContentArtifact(artifact=a2, content=c, relative_path=ca2_filename); "
|
|
31
|
+
"ca2.save(); "
|
|
32
|
+
)
|
|
33
|
+
commands += "print(get_url(c))"
|
|
26
34
|
process = subprocess.run(["pulpcore-manager", "shell", "-c", commands], capture_output=True)
|
|
27
35
|
|
|
28
36
|
assert process.returncode == 0
|
|
@@ -214,3 +222,112 @@ def test_metadata_repair_endpoint(
|
|
|
214
222
|
assert new_content.author == author
|
|
215
223
|
assert new_content.packagetype == packagetype
|
|
216
224
|
assert new_content.requires_python == requires_python
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def test_metadata_artifact_repair_endpoint(
|
|
228
|
+
create_content_direct,
|
|
229
|
+
delete_orphans_pre,
|
|
230
|
+
download_python_file,
|
|
231
|
+
monitor_task,
|
|
232
|
+
move_to_repository,
|
|
233
|
+
pulpcore_bindings,
|
|
234
|
+
python_bindings,
|
|
235
|
+
python_repo_factory,
|
|
236
|
+
):
|
|
237
|
+
"""
|
|
238
|
+
Test repairing of PythonPackageContent's metadata_sha256 and its metadata Artifact
|
|
239
|
+
and ContentArtifact via `Repositories.repair_metadata` endpoint.
|
|
240
|
+
"""
|
|
241
|
+
# 1. Setup tested data
|
|
242
|
+
python_repo = python_repo_factory()
|
|
243
|
+
|
|
244
|
+
# missing metadata_sha256, missing metadata Artifact + ContentArtifact
|
|
245
|
+
filename_1 = "scipy-1.1.0-cp27-none-win_amd64.whl"
|
|
246
|
+
metadata_1 = None
|
|
247
|
+
url_1 = urljoin(urljoin(PYTHON_FIXTURES_URL, "packages/"), filename_1)
|
|
248
|
+
file_1 = download_python_file(filename_1, url_1)
|
|
249
|
+
|
|
250
|
+
# correct metadata_sha256, missing metadata Artifact + ContentArtifact
|
|
251
|
+
filename_2 = "scipy-1.1.0-cp27-cp27m-manylinux1_x86_64.whl"
|
|
252
|
+
metadata_2 = "7f303850d9be88fff27eaeb393c2fd3a6c1a130e21758b8294fc5bb2f38e02f6"
|
|
253
|
+
url_2 = urljoin(urljoin(PYTHON_FIXTURES_URL, "packages/"), filename_2)
|
|
254
|
+
file_2 = download_python_file(filename_2, url_2)
|
|
255
|
+
|
|
256
|
+
# wrong metadata_sha256, missing metadata Artifact + ContentArtifact
|
|
257
|
+
filename_3 = "scipy-1.1.0-cp34-none-win32.whl"
|
|
258
|
+
metadata_3 = "1234"
|
|
259
|
+
url_3 = urljoin(urljoin(PYTHON_FIXTURES_URL, "packages/"), filename_3)
|
|
260
|
+
file_3 = download_python_file(filename_3, url_3)
|
|
261
|
+
|
|
262
|
+
# wrong metadata_sha256, wrong metadata Artifact, correct metadata ContentArtifact
|
|
263
|
+
filename_4 = "scipy-1.1.0-cp35-none-win32.whl"
|
|
264
|
+
metadata_4 = "5678"
|
|
265
|
+
url_4 = urljoin(urljoin(PYTHON_FIXTURES_URL, "packages/"), filename_4)
|
|
266
|
+
file_4 = download_python_file(filename_4, url_4)
|
|
267
|
+
metadata_file_4 = download_python_file(
|
|
268
|
+
f"{filename_1}.metadata",
|
|
269
|
+
urljoin(urljoin(PYTHON_FIXTURES_URL, "packages/"), f"{filename_1}.metadata"),
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
# Build PythonPackageContent data
|
|
273
|
+
filenames = [filename_1, filename_2, filename_3, filename_4]
|
|
274
|
+
metadata_sha256s = [metadata_1, metadata_2, metadata_3, metadata_4]
|
|
275
|
+
data_1, data_2, data_3, data_4 = [
|
|
276
|
+
{"name": "scipy", "version": "1.1.0", "filename": f, "metadata_sha256": m}
|
|
277
|
+
for f, m in zip(filenames, metadata_sha256s)
|
|
278
|
+
]
|
|
279
|
+
|
|
280
|
+
# 2. Create content
|
|
281
|
+
content_1 = create_content_direct(file_1, data_1)
|
|
282
|
+
content_2 = create_content_direct(file_2, data_2)
|
|
283
|
+
content_3 = create_content_direct(file_3, data_3)
|
|
284
|
+
content_4 = create_content_direct(file_4, data_4, metadata_file_4)
|
|
285
|
+
|
|
286
|
+
content_hrefs = {}
|
|
287
|
+
for data, content in [
|
|
288
|
+
(data_1, content_1),
|
|
289
|
+
(data_2, content_2),
|
|
290
|
+
(data_3, content_3),
|
|
291
|
+
(data_4, content_4),
|
|
292
|
+
]:
|
|
293
|
+
for field, test_value in data.items():
|
|
294
|
+
assert getattr(content, field) == test_value
|
|
295
|
+
content_hrefs[data["filename"]] = content.pulp_href
|
|
296
|
+
move_to_repository(python_repo.pulp_href, list(content_hrefs.values()))
|
|
297
|
+
|
|
298
|
+
# 3. Repair metadata and metadata files
|
|
299
|
+
response = python_bindings.RepositoriesPythonApi.repair_metadata(python_repo.pulp_href)
|
|
300
|
+
monitor_task(response.task)
|
|
301
|
+
|
|
302
|
+
# 4. Check new metadata and metadata files
|
|
303
|
+
main_artifact_hrefs = set()
|
|
304
|
+
metadata_artifact_hrefs = set()
|
|
305
|
+
new_data = [
|
|
306
|
+
(filename_1, "15ae132303b2774a0d839d01c618cf99fc92716adfaaa2bc1267142ab2b76b98"),
|
|
307
|
+
(filename_2, "7f303850d9be88fff27eaeb393c2fd3a6c1a130e21758b8294fc5bb2f38e02f6"),
|
|
308
|
+
# filename_3 and filename_4 have the same metadata file
|
|
309
|
+
(filename_3, "747d24e500308067c4e5fd0e20fb2d4fd6595a3fb7b1d2ffa717217fb6a53364"),
|
|
310
|
+
(filename_4, "747d24e500308067c4e5fd0e20fb2d4fd6595a3fb7b1d2ffa717217fb6a53364"),
|
|
311
|
+
]
|
|
312
|
+
for filename, metadata_sha256 in new_data:
|
|
313
|
+
content = pulpcore_bindings.ContentApi.list(pulp_href__in=[content_hrefs[filename]]).results
|
|
314
|
+
assert content
|
|
315
|
+
artifacts = content[0].artifacts
|
|
316
|
+
assert len(artifacts) == 2
|
|
317
|
+
|
|
318
|
+
main_artifact_href = artifacts.get(filename)
|
|
319
|
+
main_artifact_hrefs.add(main_artifact_href)
|
|
320
|
+
main_artifact = pulpcore_bindings.ArtifactsApi.read(main_artifact_href)
|
|
321
|
+
|
|
322
|
+
metadata_artifact_href = artifacts.get(f"{filename}.metadata")
|
|
323
|
+
metadata_artifact_hrefs.add(metadata_artifact_href)
|
|
324
|
+
metadata_artifact = pulpcore_bindings.ArtifactsApi.read(metadata_artifact_href)
|
|
325
|
+
|
|
326
|
+
pkg = python_bindings.ContentPackagesApi.read(content_hrefs[filename])
|
|
327
|
+
assert pkg.metadata_sha256 == metadata_sha256
|
|
328
|
+
assert main_artifact.sha256 == pkg.sha256
|
|
329
|
+
assert metadata_artifact.sha256 == pkg.metadata_sha256
|
|
330
|
+
|
|
331
|
+
# Check deduplication
|
|
332
|
+
assert len(main_artifact_hrefs) == 4
|
|
333
|
+
assert len(metadata_artifact_hrefs) == 3
|
|
@@ -354,4 +354,6 @@ def test_package_sync_with_metadata(
|
|
|
354
354
|
distro = python_distribution_factory(repository=repo)
|
|
355
355
|
|
|
356
356
|
# Test that metadata is accessible
|
|
357
|
-
ensure_metadata(
|
|
357
|
+
ensure_metadata(
|
|
358
|
+
pulp_content_url, distro.base_path, "pytz-2023.2-py2.py3-none-any.whl", "pytz", "2023.2"
|
|
359
|
+
)
|
|
@@ -3,6 +3,7 @@ import requests
|
|
|
3
3
|
from pulp_python.tests.functional.constants import (
|
|
4
4
|
PYTHON_EGG_FILENAME,
|
|
5
5
|
PYTHON_EGG_URL,
|
|
6
|
+
PYTHON_FIXTURES_URL,
|
|
6
7
|
PYTHON_WHEEL_FILENAME,
|
|
7
8
|
PYTHON_WHEEL_URL,
|
|
8
9
|
PYTHON_EGG_SHA256,
|
|
@@ -61,7 +62,9 @@ def test_synchronous_package_upload_with_metadata(
|
|
|
61
62
|
"""
|
|
62
63
|
Test that the synchronous upload of a Python wheel package creates a metadata artifact.
|
|
63
64
|
"""
|
|
64
|
-
|
|
65
|
+
wheel_filename = "setuptools-80.9.0-py3-none-any.whl"
|
|
66
|
+
wheel_url = urljoin(urljoin(PYTHON_FIXTURES_URL, "packages/"), wheel_filename)
|
|
67
|
+
python_file = download_python_file(wheel_filename, wheel_url)
|
|
65
68
|
content_body = {"file": python_file}
|
|
66
69
|
content = python_bindings.ContentPackagesApi.upload(**content_body)
|
|
67
70
|
|
|
@@ -70,7 +73,7 @@ def test_synchronous_package_upload_with_metadata(
|
|
|
70
73
|
distro = python_distribution_factory(repository=python_repo)
|
|
71
74
|
|
|
72
75
|
# Test that metadata is accessible
|
|
73
|
-
ensure_metadata(pulp_content_url, distro.base_path,
|
|
76
|
+
ensure_metadata(pulp_content_url, distro.base_path, wheel_filename, "setuptools", "80.9.0")
|
|
74
77
|
|
|
75
78
|
|
|
76
79
|
@pytest.mark.parallel
|
|
@@ -123,7 +123,7 @@ def ensure_simple(
|
|
|
123
123
|
return len(msgs) == 0, msgs
|
|
124
124
|
|
|
125
125
|
|
|
126
|
-
def ensure_metadata(pulp_content_url, distro_base_path, filename):
|
|
126
|
+
def ensure_metadata(pulp_content_url, distro_base_path, filename, name, version):
|
|
127
127
|
"""
|
|
128
128
|
Tests that metadata is accessible for a given wheel package filename.
|
|
129
129
|
"""
|
|
@@ -132,4 +132,5 @@ def ensure_metadata(pulp_content_url, distro_base_path, filename):
|
|
|
132
132
|
metadata_response = requests.get(metadata_url)
|
|
133
133
|
assert metadata_response.status_code == 200
|
|
134
134
|
assert len(metadata_response.content) > 0
|
|
135
|
-
assert "Name: " in metadata_response.text
|
|
135
|
+
assert f"Name: {name}" in metadata_response.text
|
|
136
|
+
assert f"Version: {version}" in metadata_response.text
|
|
@@ -7,7 +7,7 @@ build-backend = 'setuptools.build_meta'
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "pulp-python"
|
|
10
|
-
version = "3.
|
|
10
|
+
version = "3.26.0"
|
|
11
11
|
description = "pulp-python plugin for the Pulp Project"
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
authors = [
|
|
@@ -61,7 +61,9 @@ underlines = ["", "", ""]
|
|
|
61
61
|
|
|
62
62
|
[tool.check-manifest]
|
|
63
63
|
ignore = [
|
|
64
|
+
"AGENTS.md",
|
|
64
65
|
"CHANGES/**",
|
|
66
|
+
"CLAUDE.md",
|
|
65
67
|
"dev_requirements.txt",
|
|
66
68
|
"doc_requirements.txt",
|
|
67
69
|
"docs/**",
|
|
@@ -77,7 +79,7 @@ ignore = [
|
|
|
77
79
|
[tool.bumpversion]
|
|
78
80
|
# This section is managed by the plugin template. Do not edit manually.
|
|
79
81
|
|
|
80
|
-
current_version = "3.
|
|
82
|
+
current_version = "3.26.0"
|
|
81
83
|
commit = false
|
|
82
84
|
tag = false
|
|
83
85
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<alpha>0a)?(?P<patch>\\d+)(\\.(?P<release>[a-z]+))?"
|
|
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.25.0 → pulp_python-3.26.0}/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.25.0 → pulp_python-3.26.0}/pulp_python/app/migrations/0010_update_json_field.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.25.0 → pulp_python-3.26.0}/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.25.0 → pulp_python-3.26.0}/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
|
{pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_attestations.py
RENAMED
|
File without changes
|
{pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_auto_publish.py
RENAMED
|
File without changes
|
{pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_consume_content.py
RENAMED
|
File without changes
|
{pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_crud_publications.py
RENAMED
|
File without changes
|
{pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_crud_remotes.py
RENAMED
|
File without changes
|
|
File without changes
|
{pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_download_content.py
RENAMED
|
File without changes
|
{pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_export_import.py
RENAMED
|
File without changes
|
{pulp_python-3.25.0 → pulp_python-3.26.0}/pulp_python/tests/functional/api/test_full_mirror.py
RENAMED
|
File without changes
|
{pulp_python-3.25.0 → pulp_python-3.26.0}/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
|