pulp-python 3.13.5__tar.gz → 3.15.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.13.5 → pulp_python-3.15.0}/CHANGES.md +58 -5
- {pulp_python-3.13.5 → pulp_python-3.15.0}/PKG-INFO +1 -1
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/__init__.py +1 -1
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/models.py +1 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/pypi/views.py +36 -24
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/tasks/__init__.py +1 -0
- pulp_python-3.15.0/pulp_python/app/tasks/repair.py +89 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/utils.py +79 -1
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/viewsets.py +20 -1
- pulp_python-3.15.0/pulp_python/tests/functional/api/test_full_mirror.py +140 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/functional/api/test_repair.py +49 -1
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python.egg-info/PKG-INFO +1 -1
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python.egg-info/SOURCES.txt +1 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pyproject.toml +2 -2
- pulp_python-3.13.5/pulp_python/tests/functional/api/test_full_mirror.py +0 -72
- {pulp_python-3.13.5 → pulp_python-3.15.0}/COMMITMENT +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/COPYRIGHT +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/LICENSE +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/MANIFEST.in +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/README.md +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/functest_requirements.txt +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/__init__.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/global_access_conditions.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/management/__init__.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/management/commands/__init__.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/management/commands/repair-python-metadata.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/migrations/0001_initial.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/migrations/0002_pythonpackagecontent_python_version.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/migrations/0003_new_sync_filters.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/migrations/0004_DATA_swap_distribution_model.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/migrations/0005_pythonpackagecontent_sha256.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/migrations/0006_pythonrepository_autopublish.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/migrations/0007_pythonpackagecontent_mv-2-1.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/migrations/0008_pythonpackagecontent_unique_sha256.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/migrations/0009_pythondistribution_allow_uploads.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/migrations/0010_update_json_field.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/migrations/0011_alter_pythondistribution_distribution_ptr_and_more.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/migrations/0012_add_domain.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/migrations/0013_add_rbac_permissions.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/migrations/__init__.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/modelresource.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/pypi/__init__.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/pypi/serializers.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/replica.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/serializers.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/settings.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/tasks/publish.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/tasks/sync.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/tasks/upload.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/urls.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/webserver_snippets/__init__.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/webserver_snippets/apache.conf +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/app/webserver_snippets/nginx.conf +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/pytest_plugin.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/__init__.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/functional/__init__.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/functional/api/__init__.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/functional/api/test_auto_publish.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/functional/api/test_consume_content.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/functional/api/test_crud_content_unit.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/functional/api/test_crud_publications.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/functional/api/test_crud_remotes.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/functional/api/test_domains.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/functional/api/test_download_content.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/functional/api/test_export_import.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/functional/api/test_pypi_apis.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/functional/api/test_rbac.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/functional/api/test_sync.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/functional/constants.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/functional/utils.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/unit/__init__.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/unit/test_models.py +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python.egg-info/dependency_links.txt +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python.egg-info/entry_points.txt +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python.egg-info/requires.txt +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python.egg-info/top_level.txt +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/setup.cfg +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/test_requirements.txt +0 -0
- {pulp_python-3.13.5 → pulp_python-3.15.0}/unittest_requirements.txt +0 -0
|
@@ -8,6 +8,35 @@
|
|
|
8
8
|
|
|
9
9
|
[//]: # (towncrier release notes start)
|
|
10
10
|
|
|
11
|
+
## 3.15.0 (2025-05-13) {: #3.15.0 }
|
|
12
|
+
|
|
13
|
+
#### Features {: #3.15.0-feature }
|
|
14
|
+
|
|
15
|
+
- Added new `repair_metadata` endpoint to `Repository` for fixing packages' metadata.
|
|
16
|
+
[#805](https://github.com/pulp/pulp_python/issues/805)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 3.14.0 (2025-04-10) {: #3.14.0 }
|
|
21
|
+
|
|
22
|
+
#### Features {: #3.14.0-feature }
|
|
23
|
+
|
|
24
|
+
- Pull-through caching now respects the include/exclude filters on the upstream remote.
|
|
25
|
+
[#706](https://github.com/pulp/pulp_python/issues/706)
|
|
26
|
+
- Added support for automatically saving pull-through content to a repository.
|
|
27
|
+
Requires pulpcore 3.74 or later.
|
|
28
|
+
[#815](https://github.com/pulp/pulp_python/issues/815)
|
|
29
|
+
|
|
30
|
+
#### Bugfixes {: #3.14.0-bugfix }
|
|
31
|
+
|
|
32
|
+
- Fixed a proxy sync regression introduced in 3.13.0.
|
|
33
|
+
|
|
34
|
+
#### Misc {: #3.14.0-misc }
|
|
35
|
+
|
|
36
|
+
- [#809](https://github.com/pulp/pulp_python/issues/809)
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
11
40
|
## 3.13.5 (2025-04-23) {: #3.13.5 }
|
|
12
41
|
|
|
13
42
|
No significant changes.
|
|
@@ -74,6 +103,14 @@ No significant changes.
|
|
|
74
103
|
|
|
75
104
|
---
|
|
76
105
|
|
|
106
|
+
## 3.12.6 (2025-02-26) {: #3.12.6 }
|
|
107
|
+
|
|
108
|
+
#### Misc {: #3.12.6-misc }
|
|
109
|
+
|
|
110
|
+
-
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
77
114
|
## 3.12.5 (2024-10-25) {: #3.12.5 }
|
|
78
115
|
|
|
79
116
|
#### Bugfixes {: #3.12.5-bugfix }
|
|
@@ -101,7 +138,7 @@ No significant changes.
|
|
|
101
138
|
|
|
102
139
|
---
|
|
103
140
|
|
|
104
|
-
|
|
141
|
+
## 3.12.2 (2024-08-21) {: #3.12.2 }
|
|
105
142
|
|
|
106
143
|
#### Bugfixes {: #3.12.2-bugfix }
|
|
107
144
|
|
|
@@ -112,7 +149,6 @@ No significant changes.
|
|
|
112
149
|
|
|
113
150
|
## 3.12.1 (2024-06-27) {: #3.12.1 }
|
|
114
151
|
|
|
115
|
-
|
|
116
152
|
#### Bugfixes {: #3.12.1-bugfix }
|
|
117
153
|
|
|
118
154
|
- Fixed the `package_types` filter breaking other remote filters.
|
|
@@ -122,7 +158,6 @@ No significant changes.
|
|
|
122
158
|
|
|
123
159
|
## 3.12.0 (2024-06-25) {: #3.12.0 }
|
|
124
160
|
|
|
125
|
-
|
|
126
161
|
#### Features {: #3.12.0-feature }
|
|
127
162
|
|
|
128
163
|
- Added RBAC support.
|
|
@@ -148,6 +183,25 @@ No significant changes.
|
|
|
148
183
|
|
|
149
184
|
---
|
|
150
185
|
|
|
186
|
+
## 3.11.5 (2025-04-15) {: #3.11.5 }
|
|
187
|
+
|
|
188
|
+
No significant changes.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 3.11.4 (2025-02-20) {: #3.11.4 }
|
|
193
|
+
|
|
194
|
+
#### Bugfixes {: #3.11.4-bugfix }
|
|
195
|
+
|
|
196
|
+
- Fixed the JSONField specification so it doesn't break ruby bindings.
|
|
197
|
+
See context [here](https://github.com/pulp/pulp_rpm/issues/3639).
|
|
198
|
+
|
|
199
|
+
#### Misc {: #3.11.4-misc }
|
|
200
|
+
|
|
201
|
+
-
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
151
205
|
## 3.11.3 (2024-08-21) {: #3.11.3 }
|
|
152
206
|
|
|
153
207
|
#### Bugfixes {: #3.11.3-bugfix }
|
|
@@ -161,7 +215,6 @@ No significant changes.
|
|
|
161
215
|
|
|
162
216
|
## 3.11.2 (2024-06-27) {: #3.11.2 }
|
|
163
217
|
|
|
164
|
-
|
|
165
218
|
#### Bugfixes {: #3.11.2-bugfix }
|
|
166
219
|
|
|
167
220
|
- Fixed the `package_types` filter breaking other remote filters.
|
|
@@ -379,7 +432,7 @@ No significant changes.
|
|
|
379
432
|
|
|
380
433
|
---
|
|
381
434
|
|
|
382
|
-
3.4.0 (2021-06-17)
|
|
435
|
+
## 3.4.0 (2021-06-17) {: #3.4.0 }
|
|
383
436
|
|
|
384
437
|
### Features
|
|
385
438
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import logging
|
|
2
|
-
import requests
|
|
3
3
|
|
|
4
|
+
from aiohttp.client_exceptions import ClientError
|
|
4
5
|
from rest_framework.viewsets import ViewSet
|
|
5
6
|
from rest_framework.response import Response
|
|
6
7
|
from django.core.exceptions import ObjectDoesNotExist
|
|
@@ -15,7 +16,8 @@ from django.http.response import (
|
|
|
15
16
|
Http404,
|
|
16
17
|
HttpResponseForbidden,
|
|
17
18
|
HttpResponseBadRequest,
|
|
18
|
-
StreamingHttpResponse
|
|
19
|
+
StreamingHttpResponse,
|
|
20
|
+
HttpResponse,
|
|
19
21
|
)
|
|
20
22
|
from drf_spectacular.utils import extend_schema
|
|
21
23
|
from dynaconf import settings
|
|
@@ -23,11 +25,12 @@ from itertools import chain
|
|
|
23
25
|
from packaging.utils import canonicalize_name
|
|
24
26
|
from urllib.parse import urljoin, urlparse, urlunsplit
|
|
25
27
|
from pathlib import PurePath
|
|
26
|
-
from pypi_simple import
|
|
28
|
+
from pypi_simple import ACCEPT_JSON_PREFERRED, ProjectPage
|
|
27
29
|
|
|
28
30
|
from pulpcore.plugin.viewsets import OperationPostponedResponse
|
|
29
31
|
from pulpcore.plugin.tasking import dispatch
|
|
30
32
|
from pulpcore.plugin.util import get_domain
|
|
33
|
+
from pulpcore.plugin.exceptions import TimeoutException
|
|
31
34
|
from pulp_python.app.models import (
|
|
32
35
|
PythonDistribution,
|
|
33
36
|
PythonPackageContent,
|
|
@@ -37,7 +40,7 @@ from pulp_python.app.pypi.serializers import (
|
|
|
37
40
|
SummarySerializer,
|
|
38
41
|
PackageMetadataSerializer,
|
|
39
42
|
PackageUploadSerializer,
|
|
40
|
-
PackageUploadTaskSerializer
|
|
43
|
+
PackageUploadTaskSerializer,
|
|
41
44
|
)
|
|
42
45
|
from pulp_python.app.utils import (
|
|
43
46
|
write_simple_index,
|
|
@@ -45,6 +48,7 @@ from pulp_python.app.utils import (
|
|
|
45
48
|
python_content_to_json,
|
|
46
49
|
PYPI_LAST_SERIAL,
|
|
47
50
|
PYPI_SERIAL_CONSTANT,
|
|
51
|
+
get_remote_package_filter,
|
|
48
52
|
)
|
|
49
53
|
|
|
50
54
|
from pulp_python.app import tasks
|
|
@@ -233,27 +237,36 @@ class SimpleView(PackageUploadMixin, ViewSet):
|
|
|
233
237
|
|
|
234
238
|
def pull_through_package_simple(self, package, path, remote):
|
|
235
239
|
"""Gets the package's simple page from remote."""
|
|
236
|
-
def
|
|
237
|
-
parsed = urlparse(
|
|
238
|
-
digest, _, value = parsed.fragment.partition('=')
|
|
240
|
+
def parse_package(release_package):
|
|
241
|
+
parsed = urlparse(release_package.url)
|
|
239
242
|
stripped_url = urlunsplit(chain(parsed[:3], ("", "")))
|
|
240
|
-
|
|
241
|
-
d_url = urljoin(self.base_content_url,
|
|
242
|
-
return
|
|
243
|
+
redirect_path = f'{path}/{release_package.filename}?redirect={stripped_url}'
|
|
244
|
+
d_url = urljoin(self.base_content_url, redirect_path)
|
|
245
|
+
return release_package.filename, d_url, release_package.digests.get("sha256", "")
|
|
246
|
+
|
|
247
|
+
rfilter = get_remote_package_filter(remote)
|
|
248
|
+
if not rfilter.filter_project(package):
|
|
249
|
+
raise Http404(f"{package} does not exist.")
|
|
243
250
|
|
|
244
251
|
url = remote.get_remote_artifact_url(f'simple/{package}/')
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
252
|
+
remote.headers = remote.headers or []
|
|
253
|
+
remote.headers.append({"Accept": ACCEPT_JSON_PREFERRED})
|
|
254
|
+
downloader = remote.get_downloader(url=url, max_retries=1)
|
|
255
|
+
try:
|
|
256
|
+
d = downloader.fetch()
|
|
257
|
+
except ClientError:
|
|
258
|
+
return HttpResponse(f"Failed to fetch {package} from {remote.url}.", status=502)
|
|
259
|
+
except TimeoutException:
|
|
260
|
+
return HttpResponse(f"{remote.url} timed out while fetching {package}.", status=504)
|
|
261
|
+
|
|
262
|
+
if d.headers["content-type"] == "application/vnd.pypi.simple.v1+json":
|
|
263
|
+
page = ProjectPage.from_json_data(json.load(open(d.path, "rb")), base_url=remote.url)
|
|
264
|
+
else:
|
|
265
|
+
page = ProjectPage.from_html(package, open(d.path, "rb").read(), base_url=remote.url)
|
|
266
|
+
packages = [
|
|
267
|
+
parse_package(p) for p in page.packages if rfilter.filter_release(package, p.version)
|
|
268
|
+
]
|
|
269
|
+
return HttpResponse(write_simple_detail(package, packages))
|
|
257
270
|
|
|
258
271
|
@extend_schema(operation_id="pypi_simple_package_read", summary="Get package simple page")
|
|
259
272
|
def retrieve(self, request, path, package):
|
|
@@ -262,8 +275,7 @@ class SimpleView(PackageUploadMixin, ViewSet):
|
|
|
262
275
|
# Should I redirect if the normalized name is different?
|
|
263
276
|
normalized = canonicalize_name(package)
|
|
264
277
|
if self.distribution.remote:
|
|
265
|
-
|
|
266
|
-
return self.pull_through_package_simple(normalized, path, self.distribution.remote)
|
|
278
|
+
return self.pull_through_package_simple(normalized, path, self.distribution.remote)
|
|
267
279
|
if self.should_redirect(repo_version=repo_ver):
|
|
268
280
|
return redirect(urljoin(self.base_content_url, f'{path}/simple/{normalized}/'))
|
|
269
281
|
packages = (
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import uuid
|
|
3
|
+
from gettext import gettext as _
|
|
4
|
+
|
|
5
|
+
from django.db.models.query import QuerySet
|
|
6
|
+
from pulpcore.plugin.models import ProgressReport
|
|
7
|
+
from pulpcore.plugin.util import get_domain
|
|
8
|
+
|
|
9
|
+
from pulp_python.app.models import PythonPackageContent, PythonRepository
|
|
10
|
+
from pulp_python.app.utils import artifact_to_python_content_data
|
|
11
|
+
|
|
12
|
+
log = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def repair(repository_pk: uuid.UUID) -> None:
|
|
16
|
+
"""
|
|
17
|
+
Repairs metadata of all packages for the specified repository.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
repository_pk (uuid.UUID): The primary key of the repository to repair.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
None
|
|
24
|
+
"""
|
|
25
|
+
repository = PythonRepository.objects.get(pk=repository_pk)
|
|
26
|
+
|
|
27
|
+
log.info(
|
|
28
|
+
_(
|
|
29
|
+
"Repairing packages' metadata for the latest version of repository {}."
|
|
30
|
+
).format(repository.name)
|
|
31
|
+
)
|
|
32
|
+
content_set = repository.latest_version().content.values_list("pk", flat=True)
|
|
33
|
+
content = PythonPackageContent.objects.filter(pk__in=content_set)
|
|
34
|
+
|
|
35
|
+
num_repaired = repair_metadata(content)
|
|
36
|
+
log.info(_("{} packages' metadata repaired.").format(num_repaired))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def repair_metadata(content: QuerySet[PythonPackageContent]) -> int:
|
|
40
|
+
"""
|
|
41
|
+
Repairs metadata for a queryset of PythonPackageContent objects
|
|
42
|
+
and updates the progress report.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
content (QuerySet[PythonPackageContent]): The queryset of items to repair.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
int: The number of packages that were repaired.
|
|
49
|
+
"""
|
|
50
|
+
# TODO: Add on_demand content repair
|
|
51
|
+
immediate_content = content.filter(contentartifact__artifact__isnull=False)
|
|
52
|
+
domain = get_domain()
|
|
53
|
+
|
|
54
|
+
batch = []
|
|
55
|
+
set_of_update_fields = set()
|
|
56
|
+
total_repaired = 0
|
|
57
|
+
|
|
58
|
+
progress_report = ProgressReport(
|
|
59
|
+
message="Repairing packages' metadata",
|
|
60
|
+
code="repair.metadata",
|
|
61
|
+
total=immediate_content.count(),
|
|
62
|
+
)
|
|
63
|
+
progress_report.save()
|
|
64
|
+
with progress_report:
|
|
65
|
+
for package in progress_report.iter(
|
|
66
|
+
immediate_content.prefetch_related("_artifacts").iterator(chunk_size=1000)
|
|
67
|
+
):
|
|
68
|
+
new_data = artifact_to_python_content_data(
|
|
69
|
+
package.filename, package._artifacts.get(), domain
|
|
70
|
+
)
|
|
71
|
+
changed = False
|
|
72
|
+
for field, value in new_data.items():
|
|
73
|
+
if getattr(package, field) != value:
|
|
74
|
+
setattr(package, field, value)
|
|
75
|
+
set_of_update_fields.add(field)
|
|
76
|
+
changed = True
|
|
77
|
+
if changed:
|
|
78
|
+
batch.append(package)
|
|
79
|
+
if len(batch) == 1000:
|
|
80
|
+
total_repaired += len(batch)
|
|
81
|
+
PythonPackageContent.objects.bulk_update(batch, set_of_update_fields)
|
|
82
|
+
batch = []
|
|
83
|
+
set_of_update_fields.clear()
|
|
84
|
+
|
|
85
|
+
if batch:
|
|
86
|
+
total_repaired += len(batch)
|
|
87
|
+
PythonPackageContent.objects.bulk_update(batch, set_of_update_fields)
|
|
88
|
+
|
|
89
|
+
return total_repaired
|
|
@@ -7,7 +7,8 @@ from collections import defaultdict
|
|
|
7
7
|
from django.conf import settings
|
|
8
8
|
from jinja2 import Template
|
|
9
9
|
from packaging.utils import canonicalize_name
|
|
10
|
-
from packaging.
|
|
10
|
+
from packaging.requirements import Requirement
|
|
11
|
+
from packaging.version import parse, InvalidVersion
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
PYPI_LAST_SERIAL = "X-PYPI-LAST-SERIAL"
|
|
@@ -356,3 +357,80 @@ def write_simple_detail(project_name, project_packages, streamed=False):
|
|
|
356
357
|
detail = Template(simple_detail_template)
|
|
357
358
|
context = {"project_name": project_name, "project_packages": project_packages}
|
|
358
359
|
return detail.stream(**context) if streamed else detail.render(**context)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
class PackageIncludeFilter:
|
|
363
|
+
"""A special class to help filter Package's based on a remote's include/exclude"""
|
|
364
|
+
|
|
365
|
+
def __init__(self, remote):
|
|
366
|
+
self.remote = remote.cast()
|
|
367
|
+
self._filter_includes = self._parse_packages(self.remote.includes)
|
|
368
|
+
self._filter_excludes = self._parse_packages(self.remote.excludes)
|
|
369
|
+
|
|
370
|
+
def _parse_packages(self, packages):
|
|
371
|
+
config = defaultdict(lambda: defaultdict(list))
|
|
372
|
+
for value in packages:
|
|
373
|
+
requirement = Requirement(value)
|
|
374
|
+
requirement.name = canonicalize_name(requirement.name)
|
|
375
|
+
if requirement.specifier:
|
|
376
|
+
requirement.specifier.prereleases = True
|
|
377
|
+
config["range"][requirement.name].append(requirement)
|
|
378
|
+
else:
|
|
379
|
+
config["full"][requirement.name].append(requirement)
|
|
380
|
+
return config
|
|
381
|
+
|
|
382
|
+
def filter_project(self, project_name):
|
|
383
|
+
"""Return true/false if project_name would be allowed through remote's filters."""
|
|
384
|
+
project_name = canonicalize_name(project_name)
|
|
385
|
+
include_full = self._filter_includes.get("full", {})
|
|
386
|
+
include_range = self._filter_includes.get("range", {})
|
|
387
|
+
include = set(include_range.keys()).union(include_full.keys())
|
|
388
|
+
if include and project_name not in include:
|
|
389
|
+
return False
|
|
390
|
+
|
|
391
|
+
exclude_full = self._filter_excludes.get("full", {})
|
|
392
|
+
if project_name in exclude_full:
|
|
393
|
+
return False
|
|
394
|
+
|
|
395
|
+
return True
|
|
396
|
+
|
|
397
|
+
def filter_release(self, project_name, version):
|
|
398
|
+
"""Returns true/false if release would be allowed through remote's filters."""
|
|
399
|
+
project_name = canonicalize_name(project_name)
|
|
400
|
+
if not self.filter_project(project_name):
|
|
401
|
+
return False
|
|
402
|
+
|
|
403
|
+
try:
|
|
404
|
+
version = parse(version)
|
|
405
|
+
except InvalidVersion:
|
|
406
|
+
return False
|
|
407
|
+
|
|
408
|
+
include_range = self._filter_includes.get("range", {})
|
|
409
|
+
if project_name in include_range:
|
|
410
|
+
for req in include_range[project_name]:
|
|
411
|
+
if version in req.specifier:
|
|
412
|
+
break
|
|
413
|
+
else:
|
|
414
|
+
return False
|
|
415
|
+
|
|
416
|
+
exclude_range = self._filter_excludes.get("range", {})
|
|
417
|
+
if project_name in exclude_range:
|
|
418
|
+
for req in exclude_range[project_name]:
|
|
419
|
+
if version in req.specifier:
|
|
420
|
+
return False
|
|
421
|
+
|
|
422
|
+
return True
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
_remote_filters = {}
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def get_remote_package_filter(remote):
|
|
429
|
+
if date_filter_tuple := _remote_filters.get(remote.pulp_id):
|
|
430
|
+
last_update, rfilter = date_filter_tuple
|
|
431
|
+
if last_update == remote.pulp_last_updated:
|
|
432
|
+
return rfilter
|
|
433
|
+
|
|
434
|
+
rfilter = PackageIncludeFilter(remote)
|
|
435
|
+
_remote_filters[remote.pulp_id] = (remote.pulp_last_updated, rfilter)
|
|
436
|
+
return rfilter
|
|
@@ -83,7 +83,7 @@ class PythonRepositoryViewSet(
|
|
|
83
83
|
],
|
|
84
84
|
},
|
|
85
85
|
{
|
|
86
|
-
"action": ["modify"],
|
|
86
|
+
"action": ["modify", "repair_metadata"],
|
|
87
87
|
"principal": "authenticated",
|
|
88
88
|
"effect": "allow",
|
|
89
89
|
"condition": [
|
|
@@ -122,6 +122,25 @@ class PythonRepositoryViewSet(
|
|
|
122
122
|
"python.pythonrepository_viewer": ["python.view_pythonrepository"],
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
@extend_schema(
|
|
126
|
+
summary="Repair metadata",
|
|
127
|
+
responses={202: AsyncOperationResponseSerializer},
|
|
128
|
+
)
|
|
129
|
+
@action(detail=True, methods=["post"], serializer_class=None)
|
|
130
|
+
def repair_metadata(self, request, pk):
|
|
131
|
+
"""
|
|
132
|
+
Trigger an asynchronous task to repair Python metadata. This task will repair metadata
|
|
133
|
+
of all packages for the specified `Repository`, without creating a new `RepositoryVersion`.
|
|
134
|
+
"""
|
|
135
|
+
repository = self.get_object()
|
|
136
|
+
|
|
137
|
+
result = dispatch(
|
|
138
|
+
tasks.repair,
|
|
139
|
+
exclusive_resources=[repository],
|
|
140
|
+
kwargs={"repository_pk": str(repository.pk)},
|
|
141
|
+
)
|
|
142
|
+
return core_viewsets.OperationPostponedResponse(result, request)
|
|
143
|
+
|
|
125
144
|
@extend_schema(
|
|
126
145
|
summary="Sync from remote",
|
|
127
146
|
responses={202: AsyncOperationResponseSerializer}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import requests
|
|
3
|
+
import subprocess
|
|
4
|
+
|
|
5
|
+
from pulp_python.tests.functional.constants import (
|
|
6
|
+
PYPI_URL,
|
|
7
|
+
PYTHON_XS_FIXTURE_CHECKSUMS,
|
|
8
|
+
PYTHON_SM_PROJECT_SPECIFIER,
|
|
9
|
+
PYTHON_SM_FIXTURE_RELEASES,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from pypi_simple import ProjectPage
|
|
13
|
+
from packaging.version import parse
|
|
14
|
+
from urllib.parse import urljoin, urlsplit
|
|
15
|
+
from random import sample
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_pull_through_install(
|
|
19
|
+
python_bindings, python_remote_factory, python_distribution_factory, delete_orphans_pre
|
|
20
|
+
):
|
|
21
|
+
"""Tests that a pull-through distro can be installed from."""
|
|
22
|
+
remote = python_remote_factory(url=PYPI_URL, includes=[])
|
|
23
|
+
distro = python_distribution_factory(remote=remote.pulp_href)
|
|
24
|
+
PACKAGE = "pulpcore-releases"
|
|
25
|
+
|
|
26
|
+
# Check if already installed
|
|
27
|
+
stdout = subprocess.run(("pip", "list"), capture_output=True).stdout.decode("utf-8")
|
|
28
|
+
if stdout.find(PACKAGE) != -1:
|
|
29
|
+
subprocess.run(("pip", "uninstall", PACKAGE, "-y"))
|
|
30
|
+
|
|
31
|
+
# Perform pull-through install
|
|
32
|
+
host = urlsplit(distro.base_url).hostname
|
|
33
|
+
url = f"{distro.base_url}simple/"
|
|
34
|
+
cmd = ("pip", "install", "--trusted-host", host, "-i", url, PACKAGE)
|
|
35
|
+
subprocess.run(cmd, check=True)
|
|
36
|
+
|
|
37
|
+
stdout = subprocess.run(("pip", "list"), capture_output=True).stdout.decode("utf-8")
|
|
38
|
+
assert stdout.find(PACKAGE) != -1
|
|
39
|
+
subprocess.run(("pip", "uninstall", PACKAGE, "-y"))
|
|
40
|
+
content = python_bindings.ContentPackagesApi.list(name=PACKAGE)
|
|
41
|
+
assert content.count == 1
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@pytest.mark.parallel
|
|
45
|
+
def test_pull_through_simple(python_remote_factory, python_distribution_factory, pulp_content_url):
|
|
46
|
+
"""Tests that the simple page is properly modified when requesting a pull-through."""
|
|
47
|
+
remote = python_remote_factory(url=PYPI_URL, includes=["shelf-reader"])
|
|
48
|
+
distro = python_distribution_factory(remote=remote.pulp_href)
|
|
49
|
+
|
|
50
|
+
url = f"{distro.base_url}simple/shelf-reader/"
|
|
51
|
+
project_page = ProjectPage.from_response(requests.get(url), "shelf-reader")
|
|
52
|
+
|
|
53
|
+
assert len(project_page.packages) == 2
|
|
54
|
+
for package in project_page.packages:
|
|
55
|
+
assert package.filename in PYTHON_XS_FIXTURE_CHECKSUMS
|
|
56
|
+
relative_path = f"{distro.base_path}/{package.filename}?redirect="
|
|
57
|
+
assert urljoin(pulp_content_url, relative_path) in package.url
|
|
58
|
+
assert PYTHON_XS_FIXTURE_CHECKSUMS[package.filename] == package.digests["sha256"]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@pytest.mark.parallel
|
|
62
|
+
def test_pull_through_filter(python_remote_factory, python_distribution_factory):
|
|
63
|
+
"""Tests that pull-through respects the includes/excludes filter on the remote."""
|
|
64
|
+
remote = python_remote_factory(url=PYPI_URL, includes=["shelf-reader"])
|
|
65
|
+
distro = python_distribution_factory(remote=remote.pulp_href)
|
|
66
|
+
|
|
67
|
+
r = requests.get(f"{distro.base_url}simple/pulpcore/")
|
|
68
|
+
assert r.status_code == 404
|
|
69
|
+
assert r.json() == {"detail": "pulpcore does not exist."}
|
|
70
|
+
|
|
71
|
+
r = requests.get(f"{distro.base_url}simple/shelf-reader/")
|
|
72
|
+
assert r.status_code == 200
|
|
73
|
+
|
|
74
|
+
# Test complex include specifiers
|
|
75
|
+
remote = python_remote_factory(includes=PYTHON_SM_PROJECT_SPECIFIER)
|
|
76
|
+
distro = python_distribution_factory(remote=remote.pulp_href)
|
|
77
|
+
for package, releases in PYTHON_SM_FIXTURE_RELEASES.items():
|
|
78
|
+
url = f"{distro.base_url}simple/{package}/"
|
|
79
|
+
project_page = ProjectPage.from_response(requests.get(url), package)
|
|
80
|
+
packages = {p.filename for p in project_page.packages if not parse(p.version).is_prerelease}
|
|
81
|
+
assert packages == set(releases)
|
|
82
|
+
|
|
83
|
+
# Test exclude logic
|
|
84
|
+
remote = python_remote_factory(includes=[], excludes=["django"])
|
|
85
|
+
distro = python_distribution_factory(remote=remote.pulp_href)
|
|
86
|
+
|
|
87
|
+
r = requests.get(f"{distro.base_url}simple/django/")
|
|
88
|
+
assert r.status_code == 404
|
|
89
|
+
assert r.json() == {"detail": "django does not exist."}
|
|
90
|
+
|
|
91
|
+
r = requests.get(f"{distro.base_url}simple/pulpcore/")
|
|
92
|
+
assert r.status_code == 502
|
|
93
|
+
assert r.text == f"Failed to fetch pulpcore from {remote.url}."
|
|
94
|
+
|
|
95
|
+
r = requests.get(f"{distro.base_url}simple/shelf-reader/")
|
|
96
|
+
assert r.status_code == 200
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@pytest.mark.parallel
|
|
100
|
+
def test_pull_through_with_repo(
|
|
101
|
+
python_repo_factory,
|
|
102
|
+
python_remote_factory,
|
|
103
|
+
python_distribution_factory,
|
|
104
|
+
python_bindings,
|
|
105
|
+
pulpcore_bindings,
|
|
106
|
+
monitor_task,
|
|
107
|
+
has_pulp_plugin,
|
|
108
|
+
):
|
|
109
|
+
"""Tests that content is saved to repository."""
|
|
110
|
+
remote = python_remote_factory(includes=[])
|
|
111
|
+
repo = python_repo_factory()
|
|
112
|
+
distro = python_distribution_factory(repository=repo.pulp_href, remote=remote.pulp_href)
|
|
113
|
+
|
|
114
|
+
# Perform a download of aiohttp to ensure it's saved to the repo
|
|
115
|
+
url = urljoin(distro.base_url, "simple/aiohttp/")
|
|
116
|
+
project_page = ProjectPage.from_response(requests.get(url), "aiohttp")
|
|
117
|
+
for package in sample(project_page.packages, 3):
|
|
118
|
+
assert "?redirect=" in package.url
|
|
119
|
+
r = requests.get(package.url)
|
|
120
|
+
assert r.status_code == 200
|
|
121
|
+
|
|
122
|
+
if has_pulp_plugin("core", max="3.73"):
|
|
123
|
+
pytest.skip("Pull-through repository save added in 3.74")
|
|
124
|
+
|
|
125
|
+
tasks = pulpcore_bindings.TasksApi.list(reserved_resources=repo.prn)
|
|
126
|
+
assert tasks.count == 3
|
|
127
|
+
|
|
128
|
+
for task in tasks.results:
|
|
129
|
+
monitor_task(task.pulp_href)
|
|
130
|
+
|
|
131
|
+
repo = python_bindings.RepositoriesPythonApi.read(repo.pulp_href)
|
|
132
|
+
assert repo.latest_version_href[-2] == "3"
|
|
133
|
+
content = python_bindings.ContentPackagesApi.list(repository_version=repo.latest_version_href)
|
|
134
|
+
assert content.count == 3
|
|
135
|
+
|
|
136
|
+
# Check that getting content that is already present doesn't trigger a task
|
|
137
|
+
r = requests.get(package.url)
|
|
138
|
+
assert r.status_code == 200
|
|
139
|
+
tasks = pulpcore_bindings.TasksApi.list(reserved_resources=repo.prn)
|
|
140
|
+
assert tasks.count == 3
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
import subprocess
|
|
3
|
+
from urllib.parse import urljoin
|
|
3
4
|
|
|
4
|
-
from pulp_python.tests.functional.constants import
|
|
5
|
+
from pulp_python.tests.functional.constants import (
|
|
6
|
+
PYTHON_EGG_FILENAME,
|
|
7
|
+
PYTHON_FIXTURES_URL,
|
|
8
|
+
)
|
|
5
9
|
|
|
6
10
|
|
|
7
11
|
@pytest.fixture
|
|
@@ -76,3 +80,47 @@ def test_metadata_repair_command(
|
|
|
76
80
|
assert content.packagetype == "sdist"
|
|
77
81
|
assert content.requires_python == "" # technically null
|
|
78
82
|
assert content.author == "Austin Macdonald"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_metadata_repair_endpoint(
|
|
86
|
+
create_content_direct,
|
|
87
|
+
download_python_file,
|
|
88
|
+
monitor_task,
|
|
89
|
+
move_to_repository,
|
|
90
|
+
python_bindings,
|
|
91
|
+
python_repo,
|
|
92
|
+
):
|
|
93
|
+
"""
|
|
94
|
+
Test repairing of package metadata via `Repositories.repair_metadata` endpoint.
|
|
95
|
+
"""
|
|
96
|
+
python_egg_filename = "scipy-1.1.0.tar.gz"
|
|
97
|
+
python_egg_url = urljoin(
|
|
98
|
+
urljoin(PYTHON_FIXTURES_URL, "packages/"), python_egg_filename
|
|
99
|
+
)
|
|
100
|
+
python_file = download_python_file(python_egg_filename, python_egg_url)
|
|
101
|
+
|
|
102
|
+
data = {
|
|
103
|
+
"name": "scipy",
|
|
104
|
+
# Wrong metadata
|
|
105
|
+
"author": "ME",
|
|
106
|
+
"packagetype": "bdist",
|
|
107
|
+
"requires_python": ">=3.8",
|
|
108
|
+
"version": "0.2",
|
|
109
|
+
}
|
|
110
|
+
content = create_content_direct(python_file, python_egg_filename, data)
|
|
111
|
+
for field, wrong_value in data.items():
|
|
112
|
+
if field == "python_version":
|
|
113
|
+
continue
|
|
114
|
+
assert getattr(content, field) == wrong_value
|
|
115
|
+
move_to_repository(python_repo.pulp_href, [content.pulp_href])
|
|
116
|
+
|
|
117
|
+
response = python_bindings.RepositoriesPythonApi.repair_metadata(
|
|
118
|
+
python_repo.pulp_href
|
|
119
|
+
)
|
|
120
|
+
monitor_task(response.task)
|
|
121
|
+
|
|
122
|
+
content = python_bindings.ContentPackagesApi.read(content.pulp_href)
|
|
123
|
+
assert content.version == "1.1.0"
|
|
124
|
+
assert content.packagetype == "sdist"
|
|
125
|
+
assert content.requires_python == ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
|
|
126
|
+
assert content.author == ""
|
|
@@ -48,6 +48,7 @@ pulp_python/app/pypi/serializers.py
|
|
|
48
48
|
pulp_python/app/pypi/views.py
|
|
49
49
|
pulp_python/app/tasks/__init__.py
|
|
50
50
|
pulp_python/app/tasks/publish.py
|
|
51
|
+
pulp_python/app/tasks/repair.py
|
|
51
52
|
pulp_python/app/tasks/sync.py
|
|
52
53
|
pulp_python/app/tasks/upload.py
|
|
53
54
|
pulp_python/app/webserver_snippets/__init__.py
|
|
@@ -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.15.0"
|
|
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.
|
|
80
|
+
current_version = "3.15.0"
|
|
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,72 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
import requests
|
|
3
|
-
import subprocess
|
|
4
|
-
|
|
5
|
-
from pulp_python.tests.functional.constants import (
|
|
6
|
-
PYPI_URL,
|
|
7
|
-
PYTHON_XS_FIXTURE_CHECKSUMS,
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
from pypi_simple import ProjectPage
|
|
11
|
-
from urllib.parse import urljoin, urlsplit
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def test_pull_through_install(
|
|
15
|
-
python_bindings, python_remote_factory, python_distribution_factory, delete_orphans_pre
|
|
16
|
-
):
|
|
17
|
-
"""Tests that a pull-through distro can be installed from."""
|
|
18
|
-
remote = python_remote_factory(url=PYPI_URL)
|
|
19
|
-
distro = python_distribution_factory(remote=remote.pulp_href)
|
|
20
|
-
PACKAGE = "pulpcore-releases"
|
|
21
|
-
|
|
22
|
-
# Check if already installed
|
|
23
|
-
stdout = subprocess.run(("pip", "list"), capture_output=True).stdout.decode("utf-8")
|
|
24
|
-
if stdout.find(PACKAGE) != -1:
|
|
25
|
-
subprocess.run(("pip", "uninstall", PACKAGE, "-y"))
|
|
26
|
-
|
|
27
|
-
# Perform pull-through install
|
|
28
|
-
host = urlsplit(distro.base_url).hostname
|
|
29
|
-
url = f"{distro.base_url}simple/"
|
|
30
|
-
cmd = ("pip", "install", "--trusted-host", host, "-i", url, PACKAGE)
|
|
31
|
-
subprocess.run(cmd, check=True)
|
|
32
|
-
|
|
33
|
-
stdout = subprocess.run(("pip", "list"), capture_output=True).stdout.decode("utf-8")
|
|
34
|
-
assert stdout.find(PACKAGE) != -1
|
|
35
|
-
subprocess.run(("pip", "uninstall", PACKAGE, "-y"))
|
|
36
|
-
content = python_bindings.ContentPackagesApi.list(name=PACKAGE)
|
|
37
|
-
assert content.count == 1
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
@pytest.mark.parallel
|
|
41
|
-
def test_pull_through_simple(python_remote_factory, python_distribution_factory, pulp_content_url):
|
|
42
|
-
"""Tests that the simple page is properly modified when requesting a pull-through."""
|
|
43
|
-
remote = python_remote_factory(url=PYPI_URL)
|
|
44
|
-
distro = python_distribution_factory(remote=remote.pulp_href)
|
|
45
|
-
|
|
46
|
-
url = f"{distro.base_url}simple/shelf-reader/"
|
|
47
|
-
project_page = ProjectPage.from_response(requests.get(url), "shelf-reader")
|
|
48
|
-
|
|
49
|
-
assert len(project_page.packages) == 2
|
|
50
|
-
for package in project_page.packages:
|
|
51
|
-
assert package.filename in PYTHON_XS_FIXTURE_CHECKSUMS
|
|
52
|
-
relative_path = f"{distro.base_path}/{package.filename}?redirect="
|
|
53
|
-
assert urljoin(pulp_content_url, relative_path) in package.url
|
|
54
|
-
assert PYTHON_XS_FIXTURE_CHECKSUMS[package.filename] == package.digests["sha256"]
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
@pytest.mark.parallel
|
|
58
|
-
def test_pull_through_with_repo(
|
|
59
|
-
python_repo_with_sync, python_remote_factory, python_distribution_factory
|
|
60
|
-
):
|
|
61
|
-
"""Tests that if content is already in repository, pull-through isn't used."""
|
|
62
|
-
remote = python_remote_factory()
|
|
63
|
-
repo = python_repo_with_sync(remote)
|
|
64
|
-
distro = python_distribution_factory(repository=repo.pulp_href, remote=remote.pulp_href)
|
|
65
|
-
|
|
66
|
-
url = urljoin(distro.base_url, "simple/shelf-reader/")
|
|
67
|
-
project_page = ProjectPage.from_response(requests.get(url), "shelf-reader")
|
|
68
|
-
|
|
69
|
-
assert len(project_page.packages) == 2
|
|
70
|
-
for package in project_page.packages:
|
|
71
|
-
assert package.filename in PYTHON_XS_FIXTURE_CHECKSUMS
|
|
72
|
-
assert "?redirect=" not in package.url
|
|
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.13.5 → pulp_python-3.15.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.13.5 → pulp_python-3.15.0}/pulp_python/app/migrations/0010_update_json_field.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.13.5 → pulp_python-3.15.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
|
|
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.13.5 → pulp_python-3.15.0}/pulp_python/tests/functional/api/test_auto_publish.py
RENAMED
|
File without changes
|
{pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/functional/api/test_consume_content.py
RENAMED
|
File without changes
|
{pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/functional/api/test_crud_content_unit.py
RENAMED
|
File without changes
|
{pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/functional/api/test_crud_publications.py
RENAMED
|
File without changes
|
{pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/functional/api/test_crud_remotes.py
RENAMED
|
File without changes
|
|
File without changes
|
{pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/functional/api/test_download_content.py
RENAMED
|
File without changes
|
{pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/functional/api/test_export_import.py
RENAMED
|
File without changes
|
{pulp_python-3.13.5 → pulp_python-3.15.0}/pulp_python/tests/functional/api/test_pypi_apis.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
|