pulp-python 3.34.0__tar.gz → 3.35.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.34.0 → pulp_python-3.35.0}/CHANGES.md +9 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/PKG-INFO +1 -1
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/__init__.py +1 -1
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/pypi/views.py +22 -5
- pulp_python-3.35.0/pulp_python/tests/functional/api/test_simple_cache.py +314 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python.egg-info/PKG-INFO +1 -1
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pyproject.toml +2 -2
- pulp_python-3.34.0/pulp_python/tests/functional/api/test_simple_cache.py +0 -141
- {pulp_python-3.34.0 → pulp_python-3.35.0}/COMMITMENT +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/COPYRIGHT +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/LICENSE +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/MANIFEST.in +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/README.md +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/functest_requirements.txt +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/__init__.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/cache.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/exceptions.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/global_access_conditions.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/management/__init__.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/management/commands/__init__.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/management/commands/repair-python-metadata.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0001_initial.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0001_squashed_0010_update_json_field.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0002_pythonpackagecontent_python_version.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0003_new_sync_filters.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0004_DATA_swap_distribution_model.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0005_pythonpackagecontent_sha256.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0006_pythonrepository_autopublish.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0007_pythonpackagecontent_mv-2-1.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0008_pythonpackagecontent_unique_sha256.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0009_pythondistribution_allow_uploads.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0010_update_json_field.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0011_alter_pythondistribution_distribution_ptr_and_more.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0012_add_domain.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0013_add_rbac_permissions.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0014_pythonpackagecontent_dynamic_and_more.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0015_alter_pythonpackagecontent_options.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0016_pythonpackagecontent_metadata_sha256.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0017_pythonpackagecontent_size.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0018_packageprovenance.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0019_create_missing_metadata_artifacts.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0020_pythonpackagecontent_name_normalized.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0021_pythonrepository_upload_duplicate_filenames.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0022_pythonblocklistentry.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0023_packageyank.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0024_pythonrepository_error_on_reject.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/__init__.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/modelresource.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/models.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/provenance.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/pypi/__init__.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/pypi/serializers.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/replica.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/serializers.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/settings.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/tasks/__init__.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/tasks/publish.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/tasks/repair.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/tasks/sync.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/tasks/upload.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/tasks/vulnerability_report.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/tasks/yank.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/urls.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/utils.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/viewsets.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/webserver_snippets/__init__.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/webserver_snippets/apache.conf +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/webserver_snippets/nginx.conf +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/pytest_plugin.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/__init__.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/__init__.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/__init__.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_attestations.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_auto_publish.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_blocklist.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_consume_content.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_crud_content_unit.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_crud_publications.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_crud_remotes.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_domains.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_download_content.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_export_import.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_full_mirror.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_pypi_apis.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_pypi_simple_api.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_rbac.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_repair.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_sync.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_upload.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_version_specifier_filter.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_vulnerability_report.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_yank.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/constants.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/utils.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/unit/__init__.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/unit/test_models.py +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python.egg-info/SOURCES.txt +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python.egg-info/dependency_links.txt +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python.egg-info/entry_points.txt +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python.egg-info/requires.txt +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python.egg-info/top_level.txt +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/setup.cfg +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/test_requirements.txt +0 -0
- {pulp_python-3.34.0 → pulp_python-3.35.0}/unittest_requirements.txt +0 -0
|
@@ -8,6 +8,15 @@
|
|
|
8
8
|
|
|
9
9
|
[//]: # (towncrier release notes start)
|
|
10
10
|
|
|
11
|
+
## 3.35.0 (2026-08-26) {: #3.35.0 }
|
|
12
|
+
|
|
13
|
+
#### Features {: #3.35.0-feature }
|
|
14
|
+
|
|
15
|
+
- Added conditional request support (`Last-Modified` / `If-Modified-Since` and `ETag` / `If-None-Match`) to the Simple API and JSON Metadata API for improved cache efficiency.
|
|
16
|
+
[#1338](https://github.com/pulp/pulp_python/issues/1338)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
11
20
|
## 3.34.0 (2026-08-18) {: #3.34.0 }
|
|
12
21
|
|
|
13
22
|
#### Features {: #3.34.0-feature }
|
|
@@ -72,17 +72,32 @@ PYPI_SIMPLE_V1_HTML = "application/vnd.pypi.simple.v1+html"
|
|
|
72
72
|
PYPI_SIMPLE_V1_JSON = "application/vnd.pypi.simple.v1+json"
|
|
73
73
|
|
|
74
74
|
|
|
75
|
-
def
|
|
76
|
-
"""
|
|
75
|
+
def _get_repo_version(path):
|
|
76
|
+
"""Resolve path to a RepositoryVersion, or None if not found."""
|
|
77
77
|
try:
|
|
78
78
|
distro = PyPIMixin.get_distribution(path)
|
|
79
|
-
|
|
79
|
+
return PyPIMixin.get_repository_version(distro)
|
|
80
80
|
except Http404:
|
|
81
81
|
return None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _etag_func(request, path, **kwargs):
|
|
85
|
+
"""Compute unquoted ETag for the condition decorator. Returns None if no repo."""
|
|
86
|
+
repo_ver = _get_repo_version(path)
|
|
87
|
+
if repo_ver is None:
|
|
88
|
+
return None
|
|
82
89
|
raw = f"{repo_ver.number}:{repo_ver.pulp_created.isoformat()}"
|
|
83
90
|
return hashlib.sha256(raw.encode()).hexdigest()[:16]
|
|
84
91
|
|
|
85
92
|
|
|
93
|
+
def _last_modified_func(request, path, **kwargs):
|
|
94
|
+
"""Return the repository version creation timestamp for Last-Modified."""
|
|
95
|
+
repo_ver = _get_repo_version(path)
|
|
96
|
+
if repo_ver is None:
|
|
97
|
+
return None
|
|
98
|
+
return repo_ver.pulp_created
|
|
99
|
+
|
|
100
|
+
|
|
86
101
|
class PyPISimpleHTMLRenderer(TemplateHTMLRenderer):
|
|
87
102
|
media_type = PYPI_SIMPLE_V1_HTML
|
|
88
103
|
|
|
@@ -317,7 +332,7 @@ class SimpleView(PackageUploadMixin, ViewSet):
|
|
|
317
332
|
|
|
318
333
|
@extend_schema(summary="Get index simple page")
|
|
319
334
|
@method_decorator(cache_control(max_age=600, public=True))
|
|
320
|
-
@method_decorator(condition(etag_func=_etag_func))
|
|
335
|
+
@method_decorator(condition(etag_func=_etag_func, last_modified_func=_last_modified_func))
|
|
321
336
|
@PythonApiCache(base_key=find_base_path_cached)
|
|
322
337
|
def list(self, request, path):
|
|
323
338
|
"""Gets the simple api html page for the index."""
|
|
@@ -379,7 +394,7 @@ class SimpleView(PackageUploadMixin, ViewSet):
|
|
|
379
394
|
|
|
380
395
|
@extend_schema(operation_id="pypi_simple_package_read", summary="Get package simple page")
|
|
381
396
|
@method_decorator(cache_control(max_age=600, public=True))
|
|
382
|
-
@method_decorator(condition(etag_func=_etag_func))
|
|
397
|
+
@method_decorator(condition(etag_func=_etag_func, last_modified_func=_last_modified_func))
|
|
383
398
|
@PythonApiCache(base_key=find_base_path_cached)
|
|
384
399
|
def retrieve(self, request, path, package):
|
|
385
400
|
"""Retrieves the simple api html/json page for a package."""
|
|
@@ -482,6 +497,8 @@ class MetadataView(PyPIMixin, ViewSet):
|
|
|
482
497
|
responses={200: PackageMetadataSerializer},
|
|
483
498
|
summary="Get package metadata",
|
|
484
499
|
)
|
|
500
|
+
@method_decorator(cache_control(max_age=900, public=True))
|
|
501
|
+
@method_decorator(condition(etag_func=_etag_func, last_modified_func=_last_modified_func))
|
|
485
502
|
def retrieve(self, request, path, meta):
|
|
486
503
|
"""
|
|
487
504
|
Retrieves the package's core-metadata specified by
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
from email.utils import parsedate_to_datetime
|
|
2
|
+
from urllib.parse import urljoin
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from pulp_python.tests.functional.constants import (
|
|
8
|
+
PYPI_SIMPLE_V1_HTML,
|
|
9
|
+
PYPI_SIMPLE_V1_JSON,
|
|
10
|
+
PYPI_TEXT_HTML,
|
|
11
|
+
PYTHON_SM_PROJECT_SPECIFIER,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture
|
|
16
|
+
def skip_without_cache(pulp_settings):
|
|
17
|
+
"""
|
|
18
|
+
Skip test if server-side caching is not enabled.
|
|
19
|
+
"""
|
|
20
|
+
if not pulp_settings.CACHE_ENABLED:
|
|
21
|
+
pytest.skip("CACHE_ENABLED is not set")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.fixture
|
|
25
|
+
def synced_distro(
|
|
26
|
+
skip_without_cache,
|
|
27
|
+
python_remote_factory,
|
|
28
|
+
python_repo_with_sync,
|
|
29
|
+
python_distribution_factory,
|
|
30
|
+
):
|
|
31
|
+
"""
|
|
32
|
+
Sync a repo and create a distribution for cache tests.
|
|
33
|
+
"""
|
|
34
|
+
remote = python_remote_factory(includes=PYTHON_SM_PROJECT_SPECIFIER)
|
|
35
|
+
repo = python_repo_with_sync(remote)
|
|
36
|
+
return python_distribution_factory(repository=repo)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@pytest.fixture
|
|
40
|
+
def synced_distro_no_cache(
|
|
41
|
+
python_remote_factory,
|
|
42
|
+
python_repo_with_sync,
|
|
43
|
+
python_distribution_factory,
|
|
44
|
+
):
|
|
45
|
+
"""
|
|
46
|
+
Sync a repo and create a distribution (no cache requirement).
|
|
47
|
+
"""
|
|
48
|
+
remote = python_remote_factory(includes=PYTHON_SM_PROJECT_SPECIFIER)
|
|
49
|
+
repo = python_repo_with_sync(remote)
|
|
50
|
+
return python_distribution_factory(repository=repo)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@pytest.mark.parallel
|
|
54
|
+
def test_simple_cache_hit_miss_and_headers(synced_distro):
|
|
55
|
+
"""
|
|
56
|
+
First request is a MISS, second is a HIT. Cache headers are present and stable.
|
|
57
|
+
"""
|
|
58
|
+
index_url = urljoin(synced_distro.base_url, "simple/")
|
|
59
|
+
detail_url = f"{index_url}aiohttp"
|
|
60
|
+
|
|
61
|
+
for url in [index_url, detail_url]:
|
|
62
|
+
r1 = requests.get(url)
|
|
63
|
+
assert r1.status_code == 200
|
|
64
|
+
assert r1.headers["X-PULP-CACHE"] == "MISS"
|
|
65
|
+
assert r1.headers["Cache-Control"] == "max-age=600, public"
|
|
66
|
+
assert r1.headers["ETag"].startswith('"') and r1.headers["ETag"].endswith('"')
|
|
67
|
+
|
|
68
|
+
r2 = requests.get(url)
|
|
69
|
+
assert r2.status_code == 200
|
|
70
|
+
assert r2.headers["X-PULP-CACHE"] == "HIT"
|
|
71
|
+
assert r2.headers["Cache-Control"] == r1.headers["Cache-Control"]
|
|
72
|
+
assert r2.headers["ETag"] == r1.headers["ETag"]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@pytest.mark.parallel
|
|
76
|
+
def test_simple_cache_separate_accept_headers(synced_distro):
|
|
77
|
+
"""
|
|
78
|
+
HTML and JSON responses are cached separately by negotiated media type.
|
|
79
|
+
"""
|
|
80
|
+
url = urljoin(synced_distro.base_url, "simple/")
|
|
81
|
+
|
|
82
|
+
for header in [PYPI_SIMPLE_V1_HTML, PYPI_SIMPLE_V1_JSON]:
|
|
83
|
+
r = requests.get(url, headers={"Accept": header})
|
|
84
|
+
assert r.status_code == 200
|
|
85
|
+
assert r.headers["X-PULP-CACHE"] == "MISS"
|
|
86
|
+
|
|
87
|
+
for header in [PYPI_SIMPLE_V1_HTML, PYPI_SIMPLE_V1_JSON]:
|
|
88
|
+
r = requests.get(url, headers={"Accept": header})
|
|
89
|
+
assert r.status_code == 200
|
|
90
|
+
assert r.headers["X-PULP-CACHE"] == "HIT"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@pytest.mark.parallel
|
|
94
|
+
def test_simple_cache_format_json_does_not_poison_html(synced_distro):
|
|
95
|
+
"""
|
|
96
|
+
A ?format=json response must not poison a later request with the same Accept.
|
|
97
|
+
|
|
98
|
+
Clients like uv/pip send an Accept that allows both JSON and HTML. DRF's
|
|
99
|
+
?format=json overrides negotiation to JSON, while the same Accept without
|
|
100
|
+
that query param selects HTML. Caching must key on the negotiated type so
|
|
101
|
+
the JSON entry is not served (and re-rendered) for the HTML request.
|
|
102
|
+
"""
|
|
103
|
+
url = f"{urljoin(synced_distro.base_url, 'simple/')}aiohttp"
|
|
104
|
+
# pip/uv-style Accept: JSON preferred, HTML still acceptable
|
|
105
|
+
headers = {
|
|
106
|
+
"Accept": (f"{PYPI_SIMPLE_V1_JSON}, {PYPI_SIMPLE_V1_HTML};q=0.1, {PYPI_TEXT_HTML};q=0.01")
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
r_json = requests.get(url, headers=headers, params={"format": "json"})
|
|
110
|
+
assert r_json.status_code == 200
|
|
111
|
+
assert PYPI_SIMPLE_V1_JSON in r_json.headers["Content-Type"]
|
|
112
|
+
assert r_json.headers["X-PULP-CACHE"] == "MISS"
|
|
113
|
+
assert r_json.json()["name"] == "aiohttp"
|
|
114
|
+
|
|
115
|
+
r_html = requests.get(url, headers=headers)
|
|
116
|
+
assert r_html.status_code == 200
|
|
117
|
+
assert PYPI_TEXT_HTML in r_html.headers["Content-Type"]
|
|
118
|
+
assert r_html.headers["X-PULP-CACHE"] == "MISS"
|
|
119
|
+
assert b"<a href=" in r_html.content
|
|
120
|
+
|
|
121
|
+
r_html_hit = requests.get(url, headers=headers)
|
|
122
|
+
assert r_html_hit.status_code == 200
|
|
123
|
+
assert r_html_hit.headers["X-PULP-CACHE"] == "HIT"
|
|
124
|
+
assert PYPI_TEXT_HTML in r_html_hit.headers["Content-Type"]
|
|
125
|
+
|
|
126
|
+
r_json_hit = requests.get(url, headers=headers, params={"format": "json"})
|
|
127
|
+
assert r_json_hit.status_code == 200
|
|
128
|
+
assert r_json_hit.headers["X-PULP-CACHE"] == "HIT"
|
|
129
|
+
assert PYPI_SIMPLE_V1_JSON in r_json_hit.headers["Content-Type"]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@pytest.mark.parallel
|
|
133
|
+
def test_simple_cache_etag_conditional_request(synced_distro):
|
|
134
|
+
"""
|
|
135
|
+
Matching If-None-Match returns 304, non-matching returns 200.
|
|
136
|
+
"""
|
|
137
|
+
url = urljoin(synced_distro.base_url, "simple/")
|
|
138
|
+
|
|
139
|
+
r1 = requests.get(url)
|
|
140
|
+
assert r1.status_code == 200
|
|
141
|
+
etag = r1.headers["ETag"]
|
|
142
|
+
cache_control = r1.headers["Cache-Control"]
|
|
143
|
+
|
|
144
|
+
r2 = requests.get(url, headers={"If-None-Match": etag})
|
|
145
|
+
assert r2.status_code == 304
|
|
146
|
+
assert r2.headers["ETag"] == etag
|
|
147
|
+
assert r2.headers["Cache-Control"] == cache_control
|
|
148
|
+
assert "X-PULP-CACHE" not in r2.headers
|
|
149
|
+
assert len(r2.content) == 0
|
|
150
|
+
|
|
151
|
+
r3 = requests.get(url, headers={"If-None-Match": '"old"'})
|
|
152
|
+
assert r3.status_code == 200
|
|
153
|
+
assert r3.headers["ETag"] == etag
|
|
154
|
+
assert r3.headers["Cache-Control"] == cache_control
|
|
155
|
+
assert r3.headers["X-PULP-CACHE"] == "HIT"
|
|
156
|
+
assert len(r3.content) > 0
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@pytest.mark.parallel
|
|
160
|
+
def test_simple_last_modified_header(synced_distro_no_cache):
|
|
161
|
+
"""Simple API responses include Last-Modified header."""
|
|
162
|
+
index_url = urljoin(synced_distro_no_cache.base_url, "simple/")
|
|
163
|
+
detail_url = f"{index_url}aiohttp"
|
|
164
|
+
|
|
165
|
+
for url in [index_url, detail_url]:
|
|
166
|
+
r = requests.get(url)
|
|
167
|
+
assert r.status_code == 200
|
|
168
|
+
assert "Last-Modified" in r.headers
|
|
169
|
+
parsedate_to_datetime(r.headers["Last-Modified"])
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@pytest.mark.parallel
|
|
173
|
+
def test_simple_if_modified_since_304(synced_distro_no_cache):
|
|
174
|
+
"""If-Modified-Since with matching timestamp returns 304."""
|
|
175
|
+
url = urljoin(synced_distro_no_cache.base_url, "simple/")
|
|
176
|
+
|
|
177
|
+
r1 = requests.get(url)
|
|
178
|
+
assert r1.status_code == 200
|
|
179
|
+
last_modified = r1.headers["Last-Modified"]
|
|
180
|
+
|
|
181
|
+
r2 = requests.get(url, headers={"If-Modified-Since": last_modified})
|
|
182
|
+
assert r2.status_code == 304
|
|
183
|
+
assert len(r2.content) == 0
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@pytest.mark.parallel
|
|
187
|
+
def test_simple_if_modified_since_old_timestamp_200(synced_distro_no_cache):
|
|
188
|
+
"""If-Modified-Since with old timestamp returns 200 with content."""
|
|
189
|
+
url = urljoin(synced_distro_no_cache.base_url, "simple/")
|
|
190
|
+
|
|
191
|
+
r1 = requests.get(url)
|
|
192
|
+
assert r1.status_code == 200
|
|
193
|
+
|
|
194
|
+
r2 = requests.get(url, headers={"If-Modified-Since": "Thu, 01 Jan 2009 00:00:00 GMT"})
|
|
195
|
+
assert r2.status_code == 200
|
|
196
|
+
assert len(r2.content) > 0
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@pytest.mark.parallel
|
|
200
|
+
def test_metadata_conditional_request_headers(synced_distro_no_cache):
|
|
201
|
+
"""JSON metadata responses include ETag, Last-Modified, and Cache-Control headers."""
|
|
202
|
+
url = urljoin(synced_distro_no_cache.base_url, "pypi/aiohttp/json/")
|
|
203
|
+
|
|
204
|
+
r = requests.get(url)
|
|
205
|
+
assert r.status_code == 200
|
|
206
|
+
assert r.headers["Cache-Control"] == "max-age=900, public"
|
|
207
|
+
assert "ETag" in r.headers
|
|
208
|
+
assert r.headers["ETag"].startswith('"') and r.headers["ETag"].endswith('"')
|
|
209
|
+
assert "Last-Modified" in r.headers
|
|
210
|
+
parsedate_to_datetime(r.headers["Last-Modified"])
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@pytest.mark.parallel
|
|
214
|
+
def test_metadata_etag_conditional_request(synced_distro_no_cache):
|
|
215
|
+
"""JSON metadata: matching If-None-Match returns 304, non-matching returns 200."""
|
|
216
|
+
url = urljoin(synced_distro_no_cache.base_url, "pypi/aiohttp/json/")
|
|
217
|
+
|
|
218
|
+
r1 = requests.get(url)
|
|
219
|
+
assert r1.status_code == 200
|
|
220
|
+
etag = r1.headers["ETag"]
|
|
221
|
+
|
|
222
|
+
r2 = requests.get(url, headers={"If-None-Match": etag})
|
|
223
|
+
assert r2.status_code == 304
|
|
224
|
+
assert len(r2.content) == 0
|
|
225
|
+
|
|
226
|
+
r3 = requests.get(url, headers={"If-None-Match": '"old"'})
|
|
227
|
+
assert r3.status_code == 200
|
|
228
|
+
assert r3.headers["ETag"] == etag
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@pytest.mark.parallel
|
|
232
|
+
def test_metadata_if_modified_since_304(synced_distro_no_cache):
|
|
233
|
+
"""JSON metadata: If-Modified-Since with matching timestamp returns 304."""
|
|
234
|
+
url = urljoin(synced_distro_no_cache.base_url, "pypi/aiohttp/json/")
|
|
235
|
+
|
|
236
|
+
r1 = requests.get(url)
|
|
237
|
+
assert r1.status_code == 200
|
|
238
|
+
last_modified = r1.headers["Last-Modified"]
|
|
239
|
+
|
|
240
|
+
r2 = requests.get(url, headers={"If-Modified-Since": last_modified})
|
|
241
|
+
assert r2.status_code == 304
|
|
242
|
+
assert len(r2.content) == 0
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@pytest.mark.parallel
|
|
246
|
+
def test_metadata_if_modified_since_old_timestamp_200(synced_distro_no_cache):
|
|
247
|
+
"""JSON metadata: If-Modified-Since with old timestamp returns 200."""
|
|
248
|
+
url = urljoin(synced_distro_no_cache.base_url, "pypi/aiohttp/json/")
|
|
249
|
+
|
|
250
|
+
r1 = requests.get(url)
|
|
251
|
+
assert r1.status_code == 200
|
|
252
|
+
|
|
253
|
+
r2 = requests.get(url, headers={"If-Modified-Since": "Thu, 01 Jan 2009 00:00:00 GMT"})
|
|
254
|
+
assert r2.status_code == 200
|
|
255
|
+
assert len(r2.content) > 0
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def test_unauthenticated_gets_401_not_304(synced_distro_no_cache, pulpcore_bindings, bindings_cfg):
|
|
259
|
+
"""Unauthenticated client gets 401, not 304, even with conditional request headers."""
|
|
260
|
+
admin_auth = (bindings_cfg.username, bindings_cfg.password)
|
|
261
|
+
simple_url = urljoin(synced_distro_no_cache.base_url, "simple/")
|
|
262
|
+
|
|
263
|
+
r1 = requests.get(simple_url, auth=admin_auth)
|
|
264
|
+
assert r1.status_code == 200
|
|
265
|
+
last_modified = r1.headers["Last-Modified"]
|
|
266
|
+
etag = r1.headers["ETag"]
|
|
267
|
+
|
|
268
|
+
ap_response = pulpcore_bindings.AccessPoliciesApi.list(viewset_name="pypi/simple")
|
|
269
|
+
assert ap_response.count == 1
|
|
270
|
+
ap_href = ap_response.results[0].pulp_href
|
|
271
|
+
original_statements = pulpcore_bindings.AccessPoliciesApi.read(ap_href).statements
|
|
272
|
+
|
|
273
|
+
anon = requests.Session()
|
|
274
|
+
anon.trust_env = False
|
|
275
|
+
anon.verify = False
|
|
276
|
+
|
|
277
|
+
try:
|
|
278
|
+
pulpcore_bindings.AccessPoliciesApi.partial_update(
|
|
279
|
+
ap_href,
|
|
280
|
+
{
|
|
281
|
+
"statements": [
|
|
282
|
+
{
|
|
283
|
+
"action": ["list", "retrieve"],
|
|
284
|
+
"principal": "authenticated",
|
|
285
|
+
"effect": "allow",
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
"action": ["create"],
|
|
289
|
+
"principal": "authenticated",
|
|
290
|
+
"effect": "allow",
|
|
291
|
+
"condition": "index_has_repo_perm:python.modify_pythonrepository",
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
},
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
r_ims = anon.get(simple_url, headers={"If-Modified-Since": last_modified})
|
|
298
|
+
assert r_ims.status_code == 401, (
|
|
299
|
+
f"Expected 401 for unauthenticated If-Modified-Since, got {r_ims.status_code}"
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
r_inm = anon.get(simple_url, headers={"If-None-Match": etag})
|
|
303
|
+
assert r_inm.status_code == 401, (
|
|
304
|
+
f"Expected 401 for unauthenticated If-None-Match, got {r_inm.status_code}"
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
r_authed = requests.get(
|
|
308
|
+
simple_url, auth=admin_auth, headers={"If-Modified-Since": last_modified}
|
|
309
|
+
)
|
|
310
|
+
assert r_authed.status_code == 304
|
|
311
|
+
finally:
|
|
312
|
+
pulpcore_bindings.AccessPoliciesApi.partial_update(
|
|
313
|
+
ap_href, {"statements": original_statements}
|
|
314
|
+
)
|
|
@@ -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.35.0"
|
|
11
11
|
description = "pulp-python plugin for the Pulp Project"
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
authors = [
|
|
@@ -79,7 +79,7 @@ ignore = [
|
|
|
79
79
|
[tool.bumpversion]
|
|
80
80
|
# This section is managed by the plugin template. Do not edit manually.
|
|
81
81
|
|
|
82
|
-
current_version = "3.
|
|
82
|
+
current_version = "3.35.0"
|
|
83
83
|
commit = false
|
|
84
84
|
tag = false
|
|
85
85
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<alpha>0a)?(?P<patch>\\d+)(\\.(?P<release>[a-z]+))?"
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
from urllib.parse import urljoin
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
import requests
|
|
5
|
-
|
|
6
|
-
from pulp_python.tests.functional.constants import (
|
|
7
|
-
PYPI_SIMPLE_V1_HTML,
|
|
8
|
-
PYPI_SIMPLE_V1_JSON,
|
|
9
|
-
PYPI_TEXT_HTML,
|
|
10
|
-
PYTHON_SM_PROJECT_SPECIFIER,
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@pytest.fixture
|
|
15
|
-
def skip_without_cache(pulp_settings):
|
|
16
|
-
"""
|
|
17
|
-
Skip test if server-side caching is not enabled.
|
|
18
|
-
"""
|
|
19
|
-
if not pulp_settings.CACHE_ENABLED:
|
|
20
|
-
pytest.skip("CACHE_ENABLED is not set")
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
@pytest.fixture
|
|
24
|
-
def synced_distro(
|
|
25
|
-
skip_without_cache,
|
|
26
|
-
python_remote_factory,
|
|
27
|
-
python_repo_with_sync,
|
|
28
|
-
python_distribution_factory,
|
|
29
|
-
):
|
|
30
|
-
"""
|
|
31
|
-
Sync a repo and create a distribution for cache tests.
|
|
32
|
-
"""
|
|
33
|
-
remote = python_remote_factory(includes=PYTHON_SM_PROJECT_SPECIFIER)
|
|
34
|
-
repo = python_repo_with_sync(remote)
|
|
35
|
-
return python_distribution_factory(repository=repo)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
@pytest.mark.parallel
|
|
39
|
-
def test_simple_cache_hit_miss_and_headers(synced_distro):
|
|
40
|
-
"""
|
|
41
|
-
First request is a MISS, second is a HIT. Cache headers are present and stable.
|
|
42
|
-
"""
|
|
43
|
-
index_url = urljoin(synced_distro.base_url, "simple/")
|
|
44
|
-
detail_url = f"{index_url}aiohttp"
|
|
45
|
-
|
|
46
|
-
for url in [index_url, detail_url]:
|
|
47
|
-
r1 = requests.get(url)
|
|
48
|
-
assert r1.status_code == 200
|
|
49
|
-
assert r1.headers["X-PULP-CACHE"] == "MISS"
|
|
50
|
-
assert r1.headers["Cache-Control"] == "max-age=600, public"
|
|
51
|
-
assert r1.headers["ETag"].startswith('"') and r1.headers["ETag"].endswith('"')
|
|
52
|
-
|
|
53
|
-
r2 = requests.get(url)
|
|
54
|
-
assert r2.status_code == 200
|
|
55
|
-
assert r2.headers["X-PULP-CACHE"] == "HIT"
|
|
56
|
-
assert r2.headers["Cache-Control"] == r1.headers["Cache-Control"]
|
|
57
|
-
assert r2.headers["ETag"] == r1.headers["ETag"]
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
@pytest.mark.parallel
|
|
61
|
-
def test_simple_cache_separate_accept_headers(synced_distro):
|
|
62
|
-
"""
|
|
63
|
-
HTML and JSON responses are cached separately by negotiated media type.
|
|
64
|
-
"""
|
|
65
|
-
url = urljoin(synced_distro.base_url, "simple/")
|
|
66
|
-
|
|
67
|
-
for header in [PYPI_SIMPLE_V1_HTML, PYPI_SIMPLE_V1_JSON]:
|
|
68
|
-
r = requests.get(url, headers={"Accept": header})
|
|
69
|
-
assert r.status_code == 200
|
|
70
|
-
assert r.headers["X-PULP-CACHE"] == "MISS"
|
|
71
|
-
|
|
72
|
-
for header in [PYPI_SIMPLE_V1_HTML, PYPI_SIMPLE_V1_JSON]:
|
|
73
|
-
r = requests.get(url, headers={"Accept": header})
|
|
74
|
-
assert r.status_code == 200
|
|
75
|
-
assert r.headers["X-PULP-CACHE"] == "HIT"
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
@pytest.mark.parallel
|
|
79
|
-
def test_simple_cache_format_json_does_not_poison_html(synced_distro):
|
|
80
|
-
"""
|
|
81
|
-
A ?format=json response must not poison a later request with the same Accept.
|
|
82
|
-
|
|
83
|
-
Clients like uv/pip send an Accept that allows both JSON and HTML. DRF's
|
|
84
|
-
?format=json overrides negotiation to JSON, while the same Accept without
|
|
85
|
-
that query param selects HTML. Caching must key on the negotiated type so
|
|
86
|
-
the JSON entry is not served (and re-rendered) for the HTML request.
|
|
87
|
-
"""
|
|
88
|
-
url = f"{urljoin(synced_distro.base_url, 'simple/')}aiohttp"
|
|
89
|
-
# pip/uv-style Accept: JSON preferred, HTML still acceptable
|
|
90
|
-
headers = {
|
|
91
|
-
"Accept": (f"{PYPI_SIMPLE_V1_JSON}, {PYPI_SIMPLE_V1_HTML};q=0.1, {PYPI_TEXT_HTML};q=0.01")
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
r_json = requests.get(url, headers=headers, params={"format": "json"})
|
|
95
|
-
assert r_json.status_code == 200
|
|
96
|
-
assert PYPI_SIMPLE_V1_JSON in r_json.headers["Content-Type"]
|
|
97
|
-
assert r_json.headers["X-PULP-CACHE"] == "MISS"
|
|
98
|
-
assert r_json.json()["name"] == "aiohttp"
|
|
99
|
-
|
|
100
|
-
r_html = requests.get(url, headers=headers)
|
|
101
|
-
assert r_html.status_code == 200
|
|
102
|
-
assert PYPI_TEXT_HTML in r_html.headers["Content-Type"]
|
|
103
|
-
assert r_html.headers["X-PULP-CACHE"] == "MISS"
|
|
104
|
-
assert b"<a href=" in r_html.content
|
|
105
|
-
|
|
106
|
-
r_html_hit = requests.get(url, headers=headers)
|
|
107
|
-
assert r_html_hit.status_code == 200
|
|
108
|
-
assert r_html_hit.headers["X-PULP-CACHE"] == "HIT"
|
|
109
|
-
assert PYPI_TEXT_HTML in r_html_hit.headers["Content-Type"]
|
|
110
|
-
|
|
111
|
-
r_json_hit = requests.get(url, headers=headers, params={"format": "json"})
|
|
112
|
-
assert r_json_hit.status_code == 200
|
|
113
|
-
assert r_json_hit.headers["X-PULP-CACHE"] == "HIT"
|
|
114
|
-
assert PYPI_SIMPLE_V1_JSON in r_json_hit.headers["Content-Type"]
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
@pytest.mark.parallel
|
|
118
|
-
def test_simple_cache_etag_conditional_request(synced_distro):
|
|
119
|
-
"""
|
|
120
|
-
Matching If-None-Match returns 304, non-matching returns 200.
|
|
121
|
-
"""
|
|
122
|
-
url = urljoin(synced_distro.base_url, "simple/")
|
|
123
|
-
|
|
124
|
-
r1 = requests.get(url)
|
|
125
|
-
assert r1.status_code == 200
|
|
126
|
-
etag = r1.headers["ETag"]
|
|
127
|
-
cache_control = r1.headers["Cache-Control"]
|
|
128
|
-
|
|
129
|
-
r2 = requests.get(url, headers={"If-None-Match": etag})
|
|
130
|
-
assert r2.status_code == 304
|
|
131
|
-
assert r2.headers["ETag"] == etag
|
|
132
|
-
assert r2.headers["Cache-Control"] == cache_control
|
|
133
|
-
assert "X-PULP-CACHE" not in r2.headers
|
|
134
|
-
assert len(r2.content) == 0
|
|
135
|
-
|
|
136
|
-
r3 = requests.get(url, headers={"If-None-Match": '"old"'})
|
|
137
|
-
assert r3.status_code == 200
|
|
138
|
-
assert r3.headers["ETag"] == etag
|
|
139
|
-
assert r3.headers["Cache-Control"] == cache_control
|
|
140
|
-
assert r3.headers["X-PULP-CACHE"] == "HIT"
|
|
141
|
-
assert len(r3.content) > 0
|
|
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.34.0 → pulp_python-3.35.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.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0010_update_json_field.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.34.0 → pulp_python-3.35.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.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0018_packageprovenance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/app/migrations/0022_pythonblocklistentry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_attestations.py
RENAMED
|
File without changes
|
{pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_auto_publish.py
RENAMED
|
File without changes
|
{pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_blocklist.py
RENAMED
|
File without changes
|
{pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_consume_content.py
RENAMED
|
File without changes
|
{pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_crud_content_unit.py
RENAMED
|
File without changes
|
{pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_crud_publications.py
RENAMED
|
File without changes
|
{pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_crud_remotes.py
RENAMED
|
File without changes
|
|
File without changes
|
{pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_download_content.py
RENAMED
|
File without changes
|
{pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_export_import.py
RENAMED
|
File without changes
|
{pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_full_mirror.py
RENAMED
|
File without changes
|
{pulp_python-3.34.0 → pulp_python-3.35.0}/pulp_python/tests/functional/api/test_pypi_apis.py
RENAMED
|
File without changes
|
{pulp_python-3.34.0 → pulp_python-3.35.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|