pulp-python 3.13.4__tar.gz → 3.14.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.4 → pulp_python-3.14.0}/CHANGES.md +36 -3
- {pulp_python-3.13.4 → pulp_python-3.14.0}/PKG-INFO +1 -1
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/__init__.py +1 -1
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/models.py +1 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/pypi/views.py +36 -24
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/utils.py +79 -1
- pulp_python-3.14.0/pulp_python/tests/functional/api/test_full_mirror.py +140 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python.egg-info/PKG-INFO +1 -1
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pyproject.toml +2 -2
- pulp_python-3.13.4/pulp_python/tests/functional/api/test_full_mirror.py +0 -72
- {pulp_python-3.13.4 → pulp_python-3.14.0}/COMMITMENT +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/COPYRIGHT +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/LICENSE +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/MANIFEST.in +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/README.md +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/functest_requirements.txt +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/__init__.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/global_access_conditions.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/management/__init__.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/management/commands/__init__.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/management/commands/repair-python-metadata.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/migrations/0001_initial.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/migrations/0002_pythonpackagecontent_python_version.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/migrations/0003_new_sync_filters.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/migrations/0004_DATA_swap_distribution_model.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/migrations/0005_pythonpackagecontent_sha256.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/migrations/0006_pythonrepository_autopublish.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/migrations/0007_pythonpackagecontent_mv-2-1.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/migrations/0008_pythonpackagecontent_unique_sha256.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/migrations/0009_pythondistribution_allow_uploads.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/migrations/0010_update_json_field.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/migrations/0011_alter_pythondistribution_distribution_ptr_and_more.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/migrations/0012_add_domain.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/migrations/0013_add_rbac_permissions.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/migrations/__init__.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/modelresource.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/pypi/__init__.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/pypi/serializers.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/replica.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/serializers.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/settings.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/tasks/__init__.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/tasks/publish.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/tasks/sync.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/tasks/upload.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/urls.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/viewsets.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/webserver_snippets/__init__.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/webserver_snippets/apache.conf +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/app/webserver_snippets/nginx.conf +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/pytest_plugin.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/__init__.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/functional/__init__.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/functional/api/__init__.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/functional/api/test_auto_publish.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/functional/api/test_consume_content.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/functional/api/test_crud_content_unit.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/functional/api/test_crud_publications.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/functional/api/test_crud_remotes.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/functional/api/test_domains.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/functional/api/test_download_content.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/functional/api/test_export_import.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/functional/api/test_pypi_apis.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/functional/api/test_rbac.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/functional/api/test_repair.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/functional/api/test_sync.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/functional/constants.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/functional/utils.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/unit/__init__.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/unit/test_models.py +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python.egg-info/SOURCES.txt +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python.egg-info/dependency_links.txt +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python.egg-info/entry_points.txt +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python.egg-info/requires.txt +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python.egg-info/top_level.txt +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/setup.cfg +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/test_requirements.txt +0 -0
- {pulp_python-3.13.4 → pulp_python-3.14.0}/unittest_requirements.txt +0 -0
|
@@ -8,9 +8,21 @@
|
|
|
8
8
|
|
|
9
9
|
[//]: # (towncrier release notes start)
|
|
10
10
|
|
|
11
|
-
## 3.
|
|
11
|
+
## 3.14.0 (2025-04-10) {: #3.14.0 }
|
|
12
12
|
|
|
13
|
-
####
|
|
13
|
+
#### Features {: #3.14.0-feature }
|
|
14
|
+
|
|
15
|
+
- Pull-through caching now respects the include/exclude filters on the upstream remote.
|
|
16
|
+
[#706](https://github.com/pulp/pulp_python/issues/706)
|
|
17
|
+
- Added support for automatically saving pull-through content to a repository.
|
|
18
|
+
Requires pulpcore 3.74 or later.
|
|
19
|
+
[#815](https://github.com/pulp/pulp_python/issues/815)
|
|
20
|
+
|
|
21
|
+
#### Bugfixes {: #3.14.0-bugfix }
|
|
22
|
+
|
|
23
|
+
- Fixed a proxy sync regression introduced in 3.13.0.
|
|
24
|
+
|
|
25
|
+
#### Misc {: #3.14.0-misc }
|
|
14
26
|
|
|
15
27
|
- [#809](https://github.com/pulp/pulp_python/issues/809)
|
|
16
28
|
|
|
@@ -68,6 +80,14 @@ No significant changes.
|
|
|
68
80
|
|
|
69
81
|
---
|
|
70
82
|
|
|
83
|
+
## 3.12.6 (2025-02-26) {: #3.12.6 }
|
|
84
|
+
|
|
85
|
+
#### Misc {: #3.12.6-misc }
|
|
86
|
+
|
|
87
|
+
-
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
71
91
|
## 3.12.5 (2024-10-25) {: #3.12.5 }
|
|
72
92
|
|
|
73
93
|
#### Bugfixes {: #3.12.5-bugfix }
|
|
@@ -142,7 +162,20 @@ No significant changes.
|
|
|
142
162
|
|
|
143
163
|
---
|
|
144
164
|
|
|
145
|
-
## 3.11.
|
|
165
|
+
## 3.11.4 (2025-02-20) {: #3.11.4 }
|
|
166
|
+
|
|
167
|
+
#### Bugfixes {: #3.11.4-bugfix }
|
|
168
|
+
|
|
169
|
+
- Fixed the JSONField specification so it doesn't break ruby bindings.
|
|
170
|
+
See context [here](https://github.com/pulp/pulp_rpm/issues/3639).
|
|
171
|
+
|
|
172
|
+
#### Misc {: #3.11.4-misc }
|
|
173
|
+
|
|
174
|
+
-
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
# ## 3.11.3 (2024-08-21) {: #3.11.3 }
|
|
146
179
|
|
|
147
180
|
#### Bugfixes {: #3.11.3-bugfix }
|
|
148
181
|
|
|
@@ -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 = (
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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.14.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.14.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.4 → pulp_python-3.14.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.4 → pulp_python-3.14.0}/pulp_python/app/migrations/0010_update_json_field.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.13.4 → pulp_python-3.14.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
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/functional/api/test_auto_publish.py
RENAMED
|
File without changes
|
{pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/functional/api/test_consume_content.py
RENAMED
|
File without changes
|
{pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/functional/api/test_crud_content_unit.py
RENAMED
|
File without changes
|
{pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/functional/api/test_crud_publications.py
RENAMED
|
File without changes
|
{pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/functional/api/test_crud_remotes.py
RENAMED
|
File without changes
|
|
File without changes
|
{pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/functional/api/test_download_content.py
RENAMED
|
File without changes
|
{pulp_python-3.13.4 → pulp_python-3.14.0}/pulp_python/tests/functional/api/test_export_import.py
RENAMED
|
File without changes
|
{pulp_python-3.13.4 → pulp_python-3.14.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
|
|
File without changes
|
|
File without changes
|