pulp-python 3.31.1__tar.gz → 3.32.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.
Files changed (100) hide show
  1. {pulp_python-3.31.1 → pulp_python-3.32.0}/CHANGES.md +54 -0
  2. {pulp_python-3.31.1 → pulp_python-3.32.0}/PKG-INFO +2 -2
  3. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/__init__.py +1 -1
  4. pulp_python-3.32.0/pulp_python/app/cache.py +32 -0
  5. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/pypi/views.py +24 -3
  6. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/tasks/sync.py +1 -1
  7. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/functional/api/test_attestations.py +48 -0
  8. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/functional/api/test_crud_content_unit.py +0 -2
  9. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/functional/api/test_pypi_apis.py +4 -3
  10. pulp_python-3.32.0/pulp_python/tests/functional/api/test_simple_cache.py +101 -0
  11. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python.egg-info/PKG-INFO +2 -2
  12. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python.egg-info/SOURCES.txt +2 -0
  13. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python.egg-info/requires.txt +1 -1
  14. {pulp_python-3.31.1 → pulp_python-3.32.0}/pyproject.toml +4 -4
  15. {pulp_python-3.31.1 → pulp_python-3.32.0}/COMMITMENT +0 -0
  16. {pulp_python-3.31.1 → pulp_python-3.32.0}/COPYRIGHT +0 -0
  17. {pulp_python-3.31.1 → pulp_python-3.32.0}/LICENSE +0 -0
  18. {pulp_python-3.31.1 → pulp_python-3.32.0}/MANIFEST.in +0 -0
  19. {pulp_python-3.31.1 → pulp_python-3.32.0}/README.md +0 -0
  20. {pulp_python-3.31.1 → pulp_python-3.32.0}/functest_requirements.txt +0 -0
  21. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/__init__.py +0 -0
  22. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/exceptions.py +0 -0
  23. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/global_access_conditions.py +0 -0
  24. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/management/__init__.py +0 -0
  25. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/management/commands/__init__.py +0 -0
  26. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/management/commands/repair-python-metadata.py +0 -0
  27. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/migrations/0001_initial.py +0 -0
  28. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/migrations/0001_squashed_0010_update_json_field.py +0 -0
  29. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/migrations/0002_pythonpackagecontent_python_version.py +0 -0
  30. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/migrations/0003_new_sync_filters.py +0 -0
  31. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/migrations/0004_DATA_swap_distribution_model.py +0 -0
  32. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/migrations/0005_pythonpackagecontent_sha256.py +0 -0
  33. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/migrations/0006_pythonrepository_autopublish.py +0 -0
  34. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/migrations/0007_pythonpackagecontent_mv-2-1.py +0 -0
  35. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/migrations/0008_pythonpackagecontent_unique_sha256.py +0 -0
  36. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/migrations/0009_pythondistribution_allow_uploads.py +0 -0
  37. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/migrations/0010_update_json_field.py +0 -0
  38. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/migrations/0011_alter_pythondistribution_distribution_ptr_and_more.py +0 -0
  39. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/migrations/0012_add_domain.py +0 -0
  40. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/migrations/0013_add_rbac_permissions.py +0 -0
  41. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/migrations/0014_pythonpackagecontent_dynamic_and_more.py +0 -0
  42. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/migrations/0015_alter_pythonpackagecontent_options.py +0 -0
  43. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/migrations/0016_pythonpackagecontent_metadata_sha256.py +0 -0
  44. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/migrations/0017_pythonpackagecontent_size.py +0 -0
  45. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/migrations/0018_packageprovenance.py +0 -0
  46. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/migrations/0019_create_missing_metadata_artifacts.py +0 -0
  47. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/migrations/0020_pythonpackagecontent_name_normalized.py +0 -0
  48. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/migrations/0021_pythonrepository_upload_duplicate_filenames.py +0 -0
  49. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/migrations/0022_pythonblocklistentry.py +0 -0
  50. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/migrations/__init__.py +0 -0
  51. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/modelresource.py +0 -0
  52. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/models.py +0 -0
  53. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/provenance.py +0 -0
  54. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/pypi/__init__.py +0 -0
  55. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/pypi/serializers.py +0 -0
  56. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/replica.py +0 -0
  57. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/serializers.py +0 -0
  58. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/settings.py +0 -0
  59. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/tasks/__init__.py +0 -0
  60. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/tasks/publish.py +0 -0
  61. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/tasks/repair.py +0 -0
  62. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/tasks/upload.py +0 -0
  63. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/tasks/vulnerability_report.py +0 -0
  64. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/urls.py +0 -0
  65. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/utils.py +0 -0
  66. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/viewsets.py +0 -0
  67. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/webserver_snippets/__init__.py +0 -0
  68. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/webserver_snippets/apache.conf +0 -0
  69. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/app/webserver_snippets/nginx.conf +0 -0
  70. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/pytest_plugin.py +0 -0
  71. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/__init__.py +0 -0
  72. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/functional/__init__.py +0 -0
  73. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/functional/api/__init__.py +0 -0
  74. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/functional/api/test_auto_publish.py +0 -0
  75. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/functional/api/test_blocklist.py +0 -0
  76. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/functional/api/test_consume_content.py +0 -0
  77. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/functional/api/test_crud_publications.py +0 -0
  78. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/functional/api/test_crud_remotes.py +0 -0
  79. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/functional/api/test_domains.py +0 -0
  80. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/functional/api/test_download_content.py +0 -0
  81. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/functional/api/test_export_import.py +0 -0
  82. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/functional/api/test_full_mirror.py +0 -0
  83. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/functional/api/test_pypi_simple_api.py +0 -0
  84. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/functional/api/test_rbac.py +0 -0
  85. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/functional/api/test_repair.py +0 -0
  86. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/functional/api/test_sync.py +0 -0
  87. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/functional/api/test_upload.py +0 -0
  88. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/functional/api/test_vulnerability_report.py +0 -0
  89. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/functional/assets/shelf-reader-0.1.tar.gz.publish.attestation +0 -0
  90. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/functional/assets/shelf_reader-0.1-py2-none-any.whl.publish.attestation +0 -0
  91. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/functional/constants.py +0 -0
  92. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/functional/utils.py +0 -0
  93. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/unit/__init__.py +0 -0
  94. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python/tests/unit/test_models.py +0 -0
  95. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python.egg-info/dependency_links.txt +0 -0
  96. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python.egg-info/entry_points.txt +0 -0
  97. {pulp_python-3.31.1 → pulp_python-3.32.0}/pulp_python.egg-info/top_level.txt +0 -0
  98. {pulp_python-3.31.1 → pulp_python-3.32.0}/setup.cfg +0 -0
  99. {pulp_python-3.31.1 → pulp_python-3.32.0}/test_requirements.txt +0 -0
  100. {pulp_python-3.31.1 → pulp_python-3.32.0}/unittest_requirements.txt +0 -0
@@ -8,6 +8,28 @@
8
8
 
9
9
  [//]: # (towncrier release notes start)
10
10
 
11
+ ## 3.32.0 (2026-07-21) {: #3.32.0 }
12
+
13
+ #### Features {: #3.32.0-feature }
14
+
15
+ - Added server-side caching and HTTP cache headers (`Cache-Control`, `ETag`) to Simple API responses.
16
+ [#1254](https://github.com/pulp/pulp_python/issues/1254)
17
+
18
+ #### Bugfixes {: #3.32.0-bugfix }
19
+
20
+ - Fixed a bug where the `python_version` field was not parsed correctly when Pulp workers had hyphens in their name.
21
+ - Fixed provenance sync failing when syncing Pulp-created attestations that use a `Pulp User` publisher.
22
+
23
+ ---
24
+
25
+ ## 3.31.2 (2026-07-15) {: #3.31.2 }
26
+
27
+ #### Bugfixes {: #3.31.2-bugfix }
28
+
29
+ - Fixed provenance sync failing when syncing Pulp-created attestations that use a `Pulp User` publisher.
30
+
31
+ ---
32
+
11
33
  ## 3.31.1 (2026-06-30) {: #3.31.1 }
12
34
 
13
35
  #### Bugfixes {: #3.31.1-bugfix }
@@ -127,6 +149,22 @@
127
149
 
128
150
  ---
129
151
 
152
+ ## 3.27.6 (2026-07-16) {: #3.27.6 }
153
+
154
+ #### Bugfixes {: #3.27.6-bugfix }
155
+
156
+ - Fixed provenance sync failing when syncing Pulp-created attestations that use a `Pulp User` publisher.
157
+
158
+ ---
159
+
160
+ ## 3.27.5 (2026-06-30) {: #3.27.5 }
161
+
162
+ #### Bugfixes {: #3.27.5-bugfix }
163
+
164
+ - Fixed a bug where the `python_version` field was not parsed correctly when Pulp workers had hyphens in their name.
165
+
166
+ ---
167
+
130
168
  ## 3.27.4 (2026-06-05) {: #3.27.4 }
131
169
 
132
170
  #### Bugfixes {: #3.27.4-bugfix }
@@ -365,6 +403,14 @@
365
403
 
366
404
  ---
367
405
 
406
+ ## 3.19.4 (2026-06-30) {: #3.19.4 }
407
+
408
+ #### Bugfixes {: #3.19.4-bugfix }
409
+
410
+ - Fixed a bug where the `python_version` field was not parsed correctly when Pulp workers had hyphens in their name.
411
+
412
+ ---
413
+
368
414
  ## 3.19.3 (2026-05-04) {: #3.19.3 }
369
415
 
370
416
  #### Bugfixes {: #3.19.3-bugfix }
@@ -488,6 +534,14 @@ No significant changes.
488
534
 
489
535
  ---
490
536
 
537
+ ## 3.13.7 (2026-06-30) {: #3.13.7 }
538
+
539
+ #### Bugfixes {: #3.13.7-bugfix }
540
+
541
+ - Fixed a bug where the `python_version` field was not parsed correctly when Pulp workers had hyphens in their name.
542
+
543
+ ---
544
+
491
545
  ## 3.13.6 (2026-04-01) {: #3.13.6 }
492
546
 
493
547
  #### Bugfixes {: #3.13.6-bugfix }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pulp-python
3
- Version: 3.31.1
3
+ Version: 3.32.0
4
4
  Summary: pulp-python plugin for the Pulp Project
5
5
  Author-email: Pulp Team <pulp-list@redhat.com>
6
6
  Project-URL: Homepage, https://pulpproject.org
@@ -20,7 +20,7 @@ Classifier: Programming Language :: Python :: 3.13
20
20
  Requires-Python: >=3.11
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
- Requires-Dist: pulpcore<3.115,>=3.105.0
23
+ Requires-Dist: pulpcore<3.130,>=3.105.0
24
24
  Requires-Dist: pkginfo<1.13.0,>=1.12.0
25
25
  Requires-Dist: bandersnatch<6.7,>=6.6.0
26
26
  Requires-Dist: pypi-simple<2.0,>=1.8.0
@@ -12,7 +12,7 @@ class PulpPythonPluginAppConfig(PulpPluginAppConfig):
12
12
 
13
13
  name = "pulp_python.app"
14
14
  label = "python"
15
- version = "3.31.1"
15
+ version = "3.32.0"
16
16
  python_package_name = "pulp-python"
17
17
  domain_compatible = True
18
18
 
@@ -0,0 +1,32 @@
1
+ from pulpcore.plugin.cache import CacheKeys, SyncContentCache
2
+ from pulpcore.plugin.util import cache_key
3
+
4
+ ACCEPT_HEADER_KEY = "accept_header"
5
+
6
+
7
+ class PythonApiCache(SyncContentCache):
8
+ """
9
+ Cache for the Simple API.
10
+
11
+ Adds Accept header to the cache key so HTML and JSON responses are cached separately.
12
+ """
13
+
14
+ def __init__(self, base_key=None):
15
+ keys = (CacheKeys.path, CacheKeys.method, ACCEPT_HEADER_KEY)
16
+ super().__init__(base_key=base_key, keys=keys)
17
+
18
+ def make_key(self, request):
19
+ all_keys = {
20
+ CacheKeys.path: request.path,
21
+ CacheKeys.method: request.method,
22
+ ACCEPT_HEADER_KEY: request.headers.get("accept", ""),
23
+ }
24
+ return ":".join(all_keys[k] for k in self.keys)
25
+
26
+
27
+ def find_base_path_cached(request, cached):
28
+ """
29
+ Resolve the distribution base_path for use as the Redis cache base_key.
30
+ """
31
+ path = request.resolver_match.kwargs["path"]
32
+ return cache_key(path)
@@ -1,3 +1,4 @@
1
+ import hashlib
1
2
  import logging
2
3
  from datetime import datetime, timedelta, timezone
3
4
  from itertools import chain
@@ -15,9 +16,11 @@ from django.http.response import (
15
16
  HttpResponseBadRequest,
16
17
  HttpResponseForbidden,
17
18
  HttpResponseNotFound,
18
- StreamingHttpResponse,
19
19
  )
20
20
  from django.shortcuts import redirect
21
+ from django.utils.decorators import method_decorator
22
+ from django.views.decorators.cache import cache_control
23
+ from django.views.decorators.http import condition
21
24
  from drf_spectacular.utils import extend_schema
22
25
  from dynaconf import settings
23
26
  from packaging.utils import canonicalize_name
@@ -31,6 +34,7 @@ from pulpcore.plugin.util import get_domain, get_url
31
34
  from pulpcore.plugin.viewsets import OperationPostponedResponse
32
35
 
33
36
  from pulp_python.app import tasks
37
+ from pulp_python.app.cache import PythonApiCache, find_base_path_cached
34
38
  from pulp_python.app.models import (
35
39
  PackageProvenance,
36
40
  PythonDistribution,
@@ -65,6 +69,17 @@ PYPI_SIMPLE_V1_HTML = "application/vnd.pypi.simple.v1+html"
65
69
  PYPI_SIMPLE_V1_JSON = "application/vnd.pypi.simple.v1+json"
66
70
 
67
71
 
72
+ def _etag_func(request, path, **kwargs):
73
+ """Compute unquoted ETag for the condition decorator. Returns None if no repo."""
74
+ try:
75
+ distro = PyPIMixin.get_distribution(path)
76
+ repo_ver = PyPIMixin.get_repository_version(distro)
77
+ except Http404:
78
+ return None
79
+ raw = f"{repo_ver.number}:{repo_ver.pulp_created.isoformat()}"
80
+ return hashlib.sha256(raw.encode()).hexdigest()[:16]
81
+
82
+
68
83
  class PyPISimpleHTMLRenderer(TemplateHTMLRenderer):
69
84
  media_type = PYPI_SIMPLE_V1_HTML
70
85
 
@@ -298,6 +313,9 @@ class SimpleView(PackageUploadMixin, ViewSet):
298
313
  )
299
314
 
300
315
  @extend_schema(summary="Get index simple page")
316
+ @method_decorator(cache_control(max_age=600, public=True))
317
+ @method_decorator(condition(etag_func=_etag_func))
318
+ @PythonApiCache(base_key=find_base_path_cached)
301
319
  def list(self, request, path):
302
320
  """Gets the simple api html page for the index."""
303
321
  repo_version, content = self.get_rvc()
@@ -316,9 +334,9 @@ class SimpleView(PackageUploadMixin, ViewSet):
316
334
  index_data = write_simple_index_json(names)
317
335
  return Response(index_data, headers=headers)
318
336
  else:
319
- index_data = write_simple_index(names, streamed=True)
337
+ index_data = write_simple_index(names)
320
338
  kwargs = {"content_type": media_type, "headers": headers}
321
- return StreamingHttpResponse(index_data, **kwargs)
339
+ return HttpResponse(index_data, **kwargs)
322
340
 
323
341
  def pull_through_package_simple(self, package, path, remote):
324
342
  """Gets the package's simple page from remote."""
@@ -355,6 +373,9 @@ class SimpleView(PackageUploadMixin, ViewSet):
355
373
  }
356
374
 
357
375
  @extend_schema(operation_id="pypi_simple_package_read", summary="Get package simple page")
376
+ @method_decorator(cache_control(max_age=600, public=True))
377
+ @method_decorator(condition(etag_func=_etag_func))
378
+ @PythonApiCache(base_key=find_base_path_cached)
358
379
  def retrieve(self, request, path, package):
359
380
  """Retrieves the simple api html/json page for a package."""
360
381
  repo_ver, content = self.get_rvc()
@@ -9,7 +9,6 @@ from bandersnatch.master import Master
9
9
  from bandersnatch.mirror import Mirror
10
10
  from lxml.etree import LxmlError
11
11
  from packaging.requirements import Requirement
12
- from pypi_attestations import Provenance
13
12
  from pypi_simple import IndexPage
14
13
 
15
14
  from pulpcore.plugin.download import HttpDownloader
@@ -28,6 +27,7 @@ from pulp_python.app.models import (
28
27
  PythonPackageContent,
29
28
  PythonRemote,
30
29
  )
30
+ from pulp_python.app.provenance import Provenance
31
31
  from pulp_python.app.utils import PYPI_LAST_SERIAL, aget_remote_simple_page, parse_metadata
32
32
 
33
33
  logger = logging.getLogger(__name__)
@@ -154,6 +154,54 @@ def test_attestation_sync_upload(python_bindings, twine_package, download_python
154
154
  assert att_bundle["publisher"]["kind"] == "Pulp User"
155
155
 
156
156
 
157
+ @pytest.mark.parallel
158
+ def test_sync_pulp_created_provenance(
159
+ domain_factory,
160
+ python_bindings,
161
+ twine_package,
162
+ python_repo_factory,
163
+ python_distribution_factory,
164
+ python_remote_factory,
165
+ python_repo_with_sync,
166
+ python_content_summary,
167
+ monitor_task,
168
+ ):
169
+ """Test syncing provenance across domains from a Pulp upload."""
170
+ source_domain = domain_factory()
171
+ attestations = get_attestations(twine_package.provenance_url)
172
+ source_repo = python_repo_factory(pulp_domain=source_domain.name)
173
+ body = {
174
+ "relative_path": twine_package.filename,
175
+ "file_url": twine_package.url,
176
+ "attestations": json.dumps(attestations),
177
+ "repository": source_repo.pulp_href,
178
+ }
179
+ task = python_bindings.ContentPackagesApi.create(pulp_domain=source_domain.name, **body).task
180
+ monitor_task(task)
181
+
182
+ source_distro = python_distribution_factory(
183
+ repository=source_repo, pulp_domain=source_domain.name
184
+ )
185
+ dest_domain = domain_factory()
186
+ remote = python_remote_factory(
187
+ url=source_distro.base_url,
188
+ provenance=True,
189
+ includes=[f"twine=={twine_package.version}"],
190
+ pulp_domain=dest_domain.name,
191
+ )
192
+ dest_repo = python_repo_with_sync(remote=remote, pulp_domain=dest_domain.name)
193
+
194
+ summary = python_content_summary(repository_version=dest_repo.latest_version_href)
195
+ assert summary.present["python.provenance"]["count"] == 1
196
+
197
+ provs = python_bindings.ContentProvenanceApi.list(
198
+ repository_version=dest_repo.latest_version_href,
199
+ pulp_domain=dest_domain.name,
200
+ )
201
+ assert provs.count == 1
202
+ assert provs.results[0].provenance["attestation_bundles"][0]["publisher"]["kind"] == "Pulp User"
203
+
204
+
157
205
  def test_attestation_twine_upload(
158
206
  pulpcore_bindings,
159
207
  python_content_summary,
@@ -203,7 +203,6 @@ def test_package_creation_with_metadata(
203
203
  )
204
204
 
205
205
 
206
- @pytest.mark.parallel
207
206
  def test_disallow_package_substitution(
208
207
  monitor_task,
209
208
  python_bindings,
@@ -272,7 +271,6 @@ def test_disallow_package_substitution(
272
271
  assert repo.latest_version_href.endswith("/2/")
273
272
 
274
273
 
275
- @pytest.mark.parallel
276
274
  def test_package_substitution_allowed_by_default(
277
275
  monitor_task,
278
276
  python_bindings,
@@ -245,8 +245,9 @@ def test_simple_redirect_with_publications(
245
245
  assert response.url == str(urljoin(pulp_content_url, f"{distro.base_path}/simple/"))
246
246
 
247
247
 
248
- @pytest.mark.parallel
249
- def test_pypi_json(python_remote_factory, python_repo_with_sync, python_distribution_factory):
248
+ def test_pypi_json(
249
+ delete_orphans_pre, python_remote_factory, python_repo_with_sync, python_distribution_factory
250
+ ):
250
251
  """Checks the data of `pypi/{package_name}/json` endpoint."""
251
252
  remote = python_remote_factory(policy="immediate")
252
253
  repo = python_repo_with_sync(remote)
@@ -260,8 +261,8 @@ def test_pypi_json(python_remote_factory, python_repo_with_sync, python_distribu
260
261
  assert_pypi_json(response.json())
261
262
 
262
263
 
263
- @pytest.mark.parallel
264
264
  def test_pypi_json_content_app(
265
+ delete_orphans_pre,
265
266
  python_remote_factory,
266
267
  python_repo_with_sync,
267
268
  python_publication_factory,
@@ -0,0 +1,101 @@
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
+ PYTHON_SM_PROJECT_SPECIFIER,
10
+ )
11
+
12
+
13
+ @pytest.fixture
14
+ def skip_without_cache(pulp_settings):
15
+ """
16
+ Skip test if server-side caching is not enabled.
17
+ """
18
+ if not pulp_settings.CACHE_ENABLED:
19
+ pytest.skip("CACHE_ENABLED is not set")
20
+
21
+
22
+ @pytest.fixture
23
+ def synced_distro(
24
+ skip_without_cache,
25
+ python_remote_factory,
26
+ python_repo_with_sync,
27
+ python_distribution_factory,
28
+ ):
29
+ """
30
+ Sync a repo and create a distribution for cache tests.
31
+ """
32
+ remote = python_remote_factory(includes=PYTHON_SM_PROJECT_SPECIFIER)
33
+ repo = python_repo_with_sync(remote)
34
+ return python_distribution_factory(repository=repo)
35
+
36
+
37
+ @pytest.mark.parallel
38
+ def test_simple_cache_hit_miss_and_headers(synced_distro):
39
+ """
40
+ First request is a MISS, second is a HIT. Cache headers are present and stable.
41
+ """
42
+ index_url = urljoin(synced_distro.base_url, "simple/")
43
+ detail_url = f"{index_url}aiohttp"
44
+
45
+ for url in [index_url, detail_url]:
46
+ r1 = requests.get(url)
47
+ assert r1.status_code == 200
48
+ assert r1.headers["X-PULP-CACHE"] == "MISS"
49
+ assert r1.headers["Cache-Control"] == "max-age=600, public"
50
+ assert r1.headers["ETag"].startswith('"') and r1.headers["ETag"].endswith('"')
51
+
52
+ r2 = requests.get(url)
53
+ assert r2.status_code == 200
54
+ assert r2.headers["X-PULP-CACHE"] == "HIT"
55
+ assert r2.headers["Cache-Control"] == r1.headers["Cache-Control"]
56
+ assert r2.headers["ETag"] == r1.headers["ETag"]
57
+
58
+
59
+ @pytest.mark.parallel
60
+ def test_simple_cache_separate_accept_headers(synced_distro):
61
+ """
62
+ HTML and JSON responses are cached separately.
63
+ """
64
+ url = urljoin(synced_distro.base_url, "simple/")
65
+
66
+ for header in [PYPI_SIMPLE_V1_HTML, PYPI_SIMPLE_V1_JSON]:
67
+ r = requests.get(url, headers={"Accept": header})
68
+ assert r.status_code == 200
69
+ assert r.headers["X-PULP-CACHE"] == "MISS"
70
+
71
+ for header in [PYPI_SIMPLE_V1_HTML, PYPI_SIMPLE_V1_JSON]:
72
+ r = requests.get(url, headers={"Accept": header})
73
+ assert r.status_code == 200
74
+ assert r.headers["X-PULP-CACHE"] == "HIT"
75
+
76
+
77
+ @pytest.mark.parallel
78
+ def test_simple_cache_etag_conditional_request(synced_distro):
79
+ """
80
+ Matching If-None-Match returns 304, non-matching returns 200.
81
+ """
82
+ url = urljoin(synced_distro.base_url, "simple/")
83
+
84
+ r1 = requests.get(url)
85
+ assert r1.status_code == 200
86
+ etag = r1.headers["ETag"]
87
+ cache_control = r1.headers["Cache-Control"]
88
+
89
+ r2 = requests.get(url, headers={"If-None-Match": etag})
90
+ assert r2.status_code == 304
91
+ assert r2.headers["ETag"] == etag
92
+ assert r2.headers["Cache-Control"] == cache_control
93
+ assert "X-PULP-CACHE" not in r2.headers
94
+ assert len(r2.content) == 0
95
+
96
+ r3 = requests.get(url, headers={"If-None-Match": '"old"'})
97
+ assert r3.status_code == 200
98
+ assert r3.headers["ETag"] == etag
99
+ assert r3.headers["Cache-Control"] == cache_control
100
+ assert r3.headers["X-PULP-CACHE"] == "HIT"
101
+ assert len(r3.content) > 0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pulp-python
3
- Version: 3.31.1
3
+ Version: 3.32.0
4
4
  Summary: pulp-python plugin for the Pulp Project
5
5
  Author-email: Pulp Team <pulp-list@redhat.com>
6
6
  Project-URL: Homepage, https://pulpproject.org
@@ -20,7 +20,7 @@ Classifier: Programming Language :: Python :: 3.13
20
20
  Requires-Python: >=3.11
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
- Requires-Dist: pulpcore<3.115,>=3.105.0
23
+ Requires-Dist: pulpcore<3.130,>=3.105.0
24
24
  Requires-Dist: pkginfo<1.13.0,>=1.12.0
25
25
  Requires-Dist: bandersnatch<6.7,>=6.6.0
26
26
  Requires-Dist: pypi-simple<2.0,>=1.8.0
@@ -17,6 +17,7 @@ pulp_python.egg-info/entry_points.txt
17
17
  pulp_python.egg-info/requires.txt
18
18
  pulp_python.egg-info/top_level.txt
19
19
  pulp_python/app/__init__.py
20
+ pulp_python/app/cache.py
20
21
  pulp_python/app/exceptions.py
21
22
  pulp_python/app/global_access_conditions.py
22
23
  pulp_python/app/modelresource.py
@@ -87,6 +88,7 @@ pulp_python/tests/functional/api/test_pypi_apis.py
87
88
  pulp_python/tests/functional/api/test_pypi_simple_api.py
88
89
  pulp_python/tests/functional/api/test_rbac.py
89
90
  pulp_python/tests/functional/api/test_repair.py
91
+ pulp_python/tests/functional/api/test_simple_cache.py
90
92
  pulp_python/tests/functional/api/test_sync.py
91
93
  pulp_python/tests/functional/api/test_upload.py
92
94
  pulp_python/tests/functional/api/test_vulnerability_report.py
@@ -1,4 +1,4 @@
1
- pulpcore<3.115,>=3.105.0
1
+ pulpcore<3.130,>=3.105.0
2
2
  pkginfo<1.13.0,>=1.12.0
3
3
  bandersnatch<6.7,>=6.6.0
4
4
  pypi-simple<2.0,>=1.8.0
@@ -7,7 +7,7 @@ build-backend = 'setuptools.build_meta'
7
7
 
8
8
  [project]
9
9
  name = "pulp-python"
10
- version = "3.31.1"
10
+ version = "3.32.0"
11
11
  description = "pulp-python plugin for the Pulp Project"
12
12
  readme = "README.md"
13
13
  authors = [
@@ -26,7 +26,7 @@ classifiers=[
26
26
  ]
27
27
  requires-python = ">=3.11"
28
28
  dependencies = [
29
- "pulpcore>=3.105.0,<3.115",
29
+ "pulpcore>=3.105.0,<3.130",
30
30
  "pkginfo>=1.12.0,<1.13.0",
31
31
  "bandersnatch>=6.6.0,<6.7",
32
32
  "pypi-simple>=1.8.0,<2.0",
@@ -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.31.1"
82
+ current_version = "3.32.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]+))?"
@@ -125,7 +125,6 @@ filename = "./pyproject.toml"
125
125
  search = "version = \"{current_version}\""
126
126
  replace = "version = \"{new_version}\""
127
127
 
128
-
129
128
  [tool.black]
130
129
  line-length = 100
131
130
 
@@ -149,6 +148,7 @@ extend-select = [
149
148
  [tool.ruff.lint.flake8-tidy-imports.banned-api]
150
149
  # This section is managed by the plugin template. Do not edit manually.
151
150
  "distutils".msg = "The 'distutils' module has been deprecated since Python 3.9."
151
+ "pulpcore.app.settings".msg = "Always import 'settings' from 'django.conf' instead."
152
152
  "pulpcore.app".msg = "The 'pulpcore' apis must only be consumed via 'pulpcore.plugin'."
153
153
 
154
154
  [tool.ruff.lint.isort]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes