pulp-python 3.29.0__tar.gz → 3.30.1__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 (97) hide show
  1. {pulp_python-3.29.0 → pulp_python-3.30.1}/CHANGES.md +46 -0
  2. {pulp_python-3.29.0 → pulp_python-3.30.1}/PKG-INFO +1 -1
  3. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/__init__.py +5 -3
  4. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/management/commands/repair-python-metadata.py +7 -3
  5. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/modelresource.py +1 -0
  6. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/models.py +16 -14
  7. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/pypi/serializers.py +7 -5
  8. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/pypi/views.py +40 -28
  9. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/replica.py +3 -2
  10. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/serializers.py +17 -16
  11. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/tasks/publish.py +2 -2
  12. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/tasks/repair.py +4 -2
  13. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/tasks/sync.py +13 -15
  14. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/tasks/upload.py +7 -6
  15. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/utils.py +23 -9
  16. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/viewsets.py +3 -1
  17. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/pytest_plugin.py +8 -6
  18. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/functional/api/test_blocklist.py +1 -0
  19. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/functional/api/test_consume_content.py +0 -1
  20. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/functional/api/test_crud_content_unit.py +5 -4
  21. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/functional/api/test_crud_publications.py +6 -5
  22. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/functional/api/test_crud_remotes.py +3 -2
  23. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/functional/api/test_domains.py +6 -5
  24. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/functional/api/test_export_import.py +5 -3
  25. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/functional/api/test_pypi_apis.py +48 -5
  26. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/functional/api/test_pypi_simple_api.py +5 -6
  27. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/functional/api/test_rbac.py +5 -4
  28. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/functional/api/test_repair.py +2 -1
  29. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/functional/api/test_upload.py +5 -3
  30. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/functional/constants.py +4 -0
  31. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python.egg-info/PKG-INFO +1 -1
  32. {pulp_python-3.29.0 → pulp_python-3.30.1}/pyproject.toml +31 -2
  33. {pulp_python-3.29.0 → pulp_python-3.30.1}/COMMITMENT +0 -0
  34. {pulp_python-3.29.0 → pulp_python-3.30.1}/COPYRIGHT +0 -0
  35. {pulp_python-3.29.0 → pulp_python-3.30.1}/LICENSE +0 -0
  36. {pulp_python-3.29.0 → pulp_python-3.30.1}/MANIFEST.in +0 -0
  37. {pulp_python-3.29.0 → pulp_python-3.30.1}/README.md +0 -0
  38. {pulp_python-3.29.0 → pulp_python-3.30.1}/functest_requirements.txt +0 -0
  39. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/__init__.py +0 -0
  40. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/global_access_conditions.py +0 -0
  41. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/management/__init__.py +0 -0
  42. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/management/commands/__init__.py +0 -0
  43. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/migrations/0001_initial.py +0 -0
  44. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/migrations/0001_squashed_0010_update_json_field.py +0 -0
  45. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/migrations/0002_pythonpackagecontent_python_version.py +0 -0
  46. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/migrations/0003_new_sync_filters.py +0 -0
  47. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/migrations/0004_DATA_swap_distribution_model.py +0 -0
  48. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/migrations/0005_pythonpackagecontent_sha256.py +0 -0
  49. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/migrations/0006_pythonrepository_autopublish.py +0 -0
  50. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/migrations/0007_pythonpackagecontent_mv-2-1.py +0 -0
  51. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/migrations/0008_pythonpackagecontent_unique_sha256.py +0 -0
  52. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/migrations/0009_pythondistribution_allow_uploads.py +0 -0
  53. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/migrations/0010_update_json_field.py +0 -0
  54. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/migrations/0011_alter_pythondistribution_distribution_ptr_and_more.py +0 -0
  55. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/migrations/0012_add_domain.py +0 -0
  56. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/migrations/0013_add_rbac_permissions.py +0 -0
  57. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/migrations/0014_pythonpackagecontent_dynamic_and_more.py +0 -0
  58. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/migrations/0015_alter_pythonpackagecontent_options.py +0 -0
  59. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/migrations/0016_pythonpackagecontent_metadata_sha256.py +0 -0
  60. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/migrations/0017_pythonpackagecontent_size.py +0 -0
  61. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/migrations/0018_packageprovenance.py +0 -0
  62. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/migrations/0019_create_missing_metadata_artifacts.py +0 -0
  63. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/migrations/0020_pythonpackagecontent_name_normalized.py +0 -0
  64. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/migrations/0021_pythonrepository_upload_duplicate_filenames.py +0 -0
  65. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/migrations/0022_pythonblocklistentry.py +0 -0
  66. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/migrations/__init__.py +0 -0
  67. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/provenance.py +0 -0
  68. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/pypi/__init__.py +0 -0
  69. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/settings.py +0 -0
  70. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/tasks/__init__.py +0 -0
  71. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/tasks/vulnerability_report.py +0 -0
  72. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/urls.py +2 -2
  73. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/webserver_snippets/__init__.py +0 -0
  74. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/webserver_snippets/apache.conf +0 -0
  75. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/app/webserver_snippets/nginx.conf +0 -0
  76. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/__init__.py +0 -0
  77. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/functional/__init__.py +0 -0
  78. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/functional/api/__init__.py +0 -0
  79. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/functional/api/test_attestations.py +2 -2
  80. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/functional/api/test_auto_publish.py +0 -0
  81. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/functional/api/test_download_content.py +2 -2
  82. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/functional/api/test_full_mirror.py +9 -9
  83. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/functional/api/test_sync.py +11 -11
  84. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/functional/api/test_vulnerability_report.py +0 -0
  85. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/functional/assets/shelf-reader-0.1.tar.gz.publish.attestation +0 -0
  86. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/functional/assets/shelf_reader-0.1-py2-none-any.whl.publish.attestation +0 -0
  87. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/functional/utils.py +2 -2
  88. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/unit/__init__.py +0 -0
  89. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python/tests/unit/test_models.py +0 -0
  90. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python.egg-info/SOURCES.txt +0 -0
  91. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python.egg-info/dependency_links.txt +0 -0
  92. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python.egg-info/entry_points.txt +0 -0
  93. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python.egg-info/requires.txt +0 -0
  94. {pulp_python-3.29.0 → pulp_python-3.30.1}/pulp_python.egg-info/top_level.txt +0 -0
  95. {pulp_python-3.29.0 → pulp_python-3.30.1}/setup.cfg +0 -0
  96. {pulp_python-3.29.0 → pulp_python-3.30.1}/test_requirements.txt +0 -0
  97. {pulp_python-3.29.0 → pulp_python-3.30.1}/unittest_requirements.txt +0 -0
@@ -8,6 +8,29 @@
8
8
 
9
9
  [//]: # (towncrier release notes start)
10
10
 
11
+ ## 3.30.1 (2026-05-19) {: #3.30.1 }
12
+
13
+ #### Bugfixes {: #3.30.1-bugfix }
14
+
15
+ - Fixed `upload_time` in Simple and JSON APIs to reflect repository addition time instead of content creation time.
16
+ [#1232](https://github.com/pulp/pulp_python/issues/1232)
17
+
18
+ ---
19
+
20
+ ## 3.30.0 (2026-05-13) {: #3.30.0 }
21
+
22
+ #### Features {: #3.30.0-feature }
23
+
24
+ - Added filtering by `name`, `version`, and `filename` to the blocklist entries API endpoint.
25
+ [#1229](https://github.com/pulp/pulp_python/issues/1229)
26
+
27
+ #### Deprecations and Removals {: #3.30.0-removal }
28
+
29
+ - Deprecated the `repair-python-metadata` management command in favor of the repository `repair_metadata` task.
30
+ [#1207](https://github.com/pulp/pulp_python/issues/1207)
31
+
32
+ ---
33
+
11
34
  ## 3.29.0 (2026-04-17) {: #3.29.0 }
12
35
 
13
36
  #### Features {: #3.29.0-feature }
@@ -275,6 +298,23 @@
275
298
 
276
299
  ---
277
300
 
301
+ ## 3.19.3 (2026-05-04) {: #3.19.3 }
302
+
303
+ #### Bugfixes {: #3.19.3-bugfix }
304
+
305
+ - Fixed pull-through caching not checking the repository if package was not present on remote.
306
+ [#1004](https://github.com/pulp/pulp_python/issues/1004)
307
+ - Fixed pull-through caching failing for packages with bad names.
308
+ [#1040](https://github.com/pulp/pulp_python/issues/1040)
309
+
310
+ ---
311
+
312
+ ## 3.19.2 (2026-04-28) {: #3.19.2 }
313
+
314
+ No significant changes.
315
+
316
+ ---
317
+
278
318
  ## 3.19.1 (2025-09-14) {: #3.19.1 }
279
319
 
280
320
  #### Bugfixes {: #3.19.1-bugfix }
@@ -555,6 +595,12 @@ No significant changes.
555
595
 
556
596
  ---
557
597
 
598
+ ## 3.11.8 (2026-04-21) {: #3.11.8 }
599
+
600
+ No significant changes.
601
+
602
+ ---
603
+
558
604
  ## 3.11.7 (2025-11-18) {: #3.11.7 }
559
605
 
560
606
  No significant changes.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pulp-python
3
- Version: 3.29.0
3
+ Version: 3.30.1
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
@@ -1,6 +1,8 @@
1
+ from gettext import gettext as _
2
+
1
3
  from django.db.models.signals import post_migrate
4
+
2
5
  from pulpcore.plugin import PulpPluginAppConfig
3
- from gettext import gettext as _
4
6
 
5
7
 
6
8
  class PulpPythonPluginAppConfig(PulpPluginAppConfig):
@@ -10,7 +12,7 @@ class PulpPythonPluginAppConfig(PulpPluginAppConfig):
10
12
 
11
13
  name = "pulp_python.app"
12
14
  label = "python"
13
- version = "3.29.0"
15
+ version = "3.30.1"
14
16
  python_package_name = "pulp-python"
15
17
  domain_compatible = True
16
18
 
@@ -26,7 +28,7 @@ class PulpPythonPluginAppConfig(PulpPluginAppConfig):
26
28
 
27
29
  # TODO: Remove this when https://github.com/pulp/pulpcore/issues/5500 is resolved
28
30
  def _populate_pypi_access_policies(sender, apps, verbosity, **kwargs):
29
- from pulp_python.app.pypi.views import PyPIView, SimpleView, UploadView, MetadataView
31
+ from pulp_python.app.pypi.views import MetadataView, PyPIView, SimpleView, UploadView
30
32
 
31
33
  try:
32
34
  AccessPolicy = apps.get_model("core", "AccessPolicy")
@@ -1,11 +1,12 @@
1
- import re
2
1
  import os
3
- from django.core.management import BaseCommand, CommandError
2
+ import re
4
3
  from gettext import gettext as _
5
4
 
6
5
  from django.conf import settings
6
+ from django.core.management import BaseCommand, CommandError
7
7
 
8
8
  from pulpcore.plugin.util import extract_pk
9
+
9
10
  from pulp_python.app.models import PythonPackageContent, PythonRepository
10
11
  from pulp_python.app.utils import artifact_to_python_content_data
11
12
 
@@ -78,7 +79,10 @@ class Command(BaseCommand):
78
79
  Management command to repair metadata of PythonPackageContent.
79
80
  """
80
81
 
81
- help = _("Repair the metadata of PythonPackageContent stored in PythonRepositories")
82
+ help = _(
83
+ "[Deprecated] Use the repository `repair_metadata` task instead. "
84
+ "Repair the metadata of PythonPackageContent stored in PythonRepositories."
85
+ )
82
86
 
83
87
  def add_arguments(self, parser):
84
88
  """Set up arguments."""
@@ -1,6 +1,7 @@
1
1
  from pulpcore.plugin.importexport import BaseContentResource
2
2
  from pulpcore.plugin.modelresources import RepositoryResource
3
3
  from pulpcore.plugin.util import get_domain
4
+
4
5
  from pulp_python.app.models import (
5
6
  PythonPackageContent,
6
7
  PythonRepository,
@@ -1,44 +1,45 @@
1
1
  import hashlib
2
2
  import json
3
3
  from logging import getLogger
4
+ from pathlib import PurePath
4
5
 
5
6
  from aiohttp.web import json_response
7
+ from django.conf import settings
6
8
  from django.contrib.postgres.fields import ArrayField
7
9
  from django.core.exceptions import ObjectDoesNotExist
8
10
  from django.db import models
9
- from django.conf import settings
10
11
  from django_lifecycle import (
11
12
  BEFORE_SAVE,
12
13
  hook,
13
14
  )
14
15
  from rest_framework.serializers import ValidationError
16
+
15
17
  from pulpcore.plugin.models import (
16
18
  AutoAddObjPermsMixin,
17
19
  BaseModel,
18
20
  Content,
19
- Publication,
20
21
  Distribution,
22
+ Publication,
21
23
  Remote,
22
24
  Repository,
23
25
  )
26
+ from pulpcore.plugin.repo_version_utils import (
27
+ collect_duplicates,
28
+ remove_duplicates,
29
+ validate_repo_version,
30
+ )
24
31
  from pulpcore.plugin.responses import ArtifactResponse
32
+ from pulpcore.plugin.util import get_domain, get_domain_pk
25
33
 
26
- from pathlib import PurePath
27
34
  from .provenance import Provenance
28
35
  from .utils import (
29
- artifact_to_python_content_data,
36
+ PYPI_LAST_SERIAL,
37
+ PYPI_SERIAL_CONSTANT,
30
38
  artifact_to_metadata_artifact,
39
+ artifact_to_python_content_data,
31
40
  canonicalize_name,
32
41
  python_content_to_json,
33
- PYPI_LAST_SERIAL,
34
- PYPI_SERIAL_CONSTANT,
35
- )
36
- from pulpcore.plugin.repo_version_utils import (
37
- collect_duplicates,
38
- remove_duplicates,
39
- validate_repo_version,
40
42
  )
41
- from pulpcore.plugin.util import get_domain_pk, get_domain
42
43
 
43
44
  log = getLogger(__name__)
44
45
 
@@ -453,8 +454,9 @@ class PythonRepository(Repository, AutoAddObjPermsMixin):
453
454
  break
454
455
  if blocked:
455
456
  raise ValidationError(
456
- "Blocklisted packages cannot be added to this repository: "
457
- "{}".format(", ".join(blocked))
457
+ "Blocklisted packages cannot be added to this repository: {}".format(
458
+ ", ".join(blocked)
459
+ )
458
460
  )
459
461
 
460
462
 
@@ -1,13 +1,15 @@
1
1
  import logging
2
2
  from gettext import gettext as _
3
3
 
4
- from rest_framework import serializers
4
+ from django.db.utils import IntegrityError
5
5
  from pydantic import TypeAdapter, ValidationError
6
- from pulp_python.app.provenance import Attestation
7
- from pulp_python.app.utils import DIST_EXTENSIONS, SUPPORTED_METADATA_VERSIONS
6
+ from rest_framework import serializers
7
+
8
8
  from pulpcore.plugin.models import Artifact
9
9
  from pulpcore.plugin.util import get_domain
10
- from django.db.utils import IntegrityError
10
+
11
+ from pulp_python.app.provenance import Attestation
12
+ from pulp_python.app.utils import DIST_EXTENSIONS, SUPPORTED_METADATA_VERSIONS
11
13
 
12
14
  log = logging.getLogger(__name__)
13
15
 
@@ -110,7 +112,7 @@ class PackageUploadSerializer(serializers.Serializer):
110
112
  attestations = TypeAdapter(list[Attestation]).validate_python(attestations)
111
113
  except ValidationError as e:
112
114
  raise serializers.ValidationError(
113
- {"attestations": _("The uploaded attestations are not valid: {}".format(e))}
115
+ {"attestations": _("The uploaded attestations are not valid: {}").format(e)}
114
116
  )
115
117
 
116
118
  sha256 = data.get("sha256_digest")
@@ -1,60 +1,61 @@
1
1
  import logging
2
-
3
- from rest_framework.viewsets import ViewSet
4
- from rest_framework.renderers import BrowsableAPIRenderer, JSONRenderer, TemplateHTMLRenderer
5
- from rest_framework.response import Response
6
- from rest_framework.exceptions import NotAcceptable
7
- from django.core.exceptions import ObjectDoesNotExist
8
- from django.shortcuts import redirect
9
- from datetime import datetime, timezone, timedelta
2
+ from datetime import datetime, timedelta, timezone
3
+ from itertools import chain
4
+ from pathlib import PurePath
5
+ from urllib.parse import urljoin, urlparse, urlunsplit
10
6
 
11
7
  from django.contrib.sessions.models import Session
8
+ from django.core.exceptions import ObjectDoesNotExist
12
9
  from django.db import transaction
10
+ from django.db.models import OuterRef, Subquery
13
11
  from django.db.utils import DatabaseError
14
12
  from django.http.response import (
15
13
  Http404,
16
- HttpResponseNotFound,
17
- HttpResponseForbidden,
14
+ HttpResponse,
18
15
  HttpResponseBadRequest,
16
+ HttpResponseForbidden,
17
+ HttpResponseNotFound,
19
18
  StreamingHttpResponse,
20
- HttpResponse,
21
19
  )
20
+ from django.shortcuts import redirect
22
21
  from drf_spectacular.utils import extend_schema
23
22
  from dynaconf import settings
24
- from itertools import chain
25
23
  from packaging.utils import canonicalize_name
26
- from urllib.parse import urljoin, urlparse, urlunsplit
27
- from pathlib import PurePath
24
+ from rest_framework.exceptions import NotAcceptable
25
+ from rest_framework.renderers import BrowsableAPIRenderer, JSONRenderer, TemplateHTMLRenderer
26
+ from rest_framework.response import Response
27
+ from rest_framework.viewsets import ViewSet
28
28
 
29
- from pulpcore.plugin.viewsets import OperationPostponedResponse
29
+ from pulpcore.plugin.models import RepositoryContent
30
30
  from pulpcore.plugin.tasking import dispatch
31
31
  from pulpcore.plugin.util import get_domain, get_url
32
+ from pulpcore.plugin.viewsets import OperationPostponedResponse
33
+
34
+ from pulp_python.app import tasks
32
35
  from pulp_python.app.models import (
36
+ PackageProvenance,
33
37
  PythonDistribution,
34
38
  PythonPackageContent,
35
39
  PythonPublication,
36
- PackageProvenance,
37
40
  )
38
41
  from pulp_python.app.pypi.serializers import (
39
- SummarySerializer,
40
42
  PackageMetadataSerializer,
41
43
  PackageUploadSerializer,
42
44
  PackageUploadTaskSerializer,
45
+ SummarySerializer,
43
46
  )
44
47
  from pulp_python.app.utils import (
45
- write_simple_index,
46
- write_simple_index_json,
47
- write_simple_detail,
48
- write_simple_detail_json,
49
- python_content_to_json,
50
48
  PYPI_LAST_SERIAL,
51
49
  PYPI_SERIAL_CONSTANT,
52
50
  get_remote_package_filter,
53
51
  get_remote_simple_page,
52
+ python_content_to_json,
53
+ write_simple_detail,
54
+ write_simple_detail_json,
55
+ write_simple_index,
56
+ write_simple_index_json,
54
57
  )
55
58
 
56
- from pulp_python.app import tasks
57
-
58
59
  log = logging.getLogger(__name__)
59
60
 
60
61
  ORIGIN_HOST = settings.CONTENT_ORIGIN if settings.CONTENT_ORIGIN else settings.PYPI_API_HOSTNAME
@@ -367,13 +368,20 @@ class SimpleView(PackageUploadMixin, ViewSet):
367
368
  return redirect(urljoin(self.base_content_url, f"{path}/simple/{normalized}/"))
368
369
  if content is not None:
369
370
  local_packages = content.filter(name_normalized=normalized)
370
- packages = local_packages.values(
371
+ repo_added_subquery = RepositoryContent.objects.filter(
372
+ content_id=OuterRef("pk"),
373
+ repository=repo_ver.repository,
374
+ version_removed=None,
375
+ ).values("pulp_created")[:1]
376
+ packages = local_packages.annotate(
377
+ repo_added_time=Subquery(repo_added_subquery)
378
+ ).values(
371
379
  "filename",
372
380
  "sha256",
373
381
  "metadata_sha256",
374
382
  "requires_python",
375
383
  "size",
376
- "pulp_created",
384
+ "repo_added_time",
377
385
  "version",
378
386
  )
379
387
  provenances = PackageProvenance.objects.filter(package__in=local_packages).values_list(
@@ -383,7 +391,7 @@ class SimpleView(PackageUploadMixin, ViewSet):
383
391
  p["filename"]: {
384
392
  **p,
385
393
  "url": urljoin(self.base_content_url, f"{path}/{p['filename']}"),
386
- "upload_time": p["pulp_created"],
394
+ "upload_time": p["repo_added_time"],
387
395
  "provenance": (
388
396
  self.get_provenance_url(normalized, p["version"], p["filename"])
389
397
  if p["filename"] in provenances
@@ -465,7 +473,11 @@ class MetadataView(PyPIMixin, ViewSet):
465
473
  if settings.DOMAIN_ENABLED:
466
474
  domain = get_domain()
467
475
  json_body = python_content_to_json(
468
- path, package_content, version=version, domain=domain
476
+ path,
477
+ package_content,
478
+ version=version,
479
+ domain=domain,
480
+ repository_version=repo_ver,
469
481
  )
470
482
  if json_body:
471
483
  return Response(data=json_body, headers=headers)
@@ -1,10 +1,11 @@
1
- from pulpcore.plugin.replica import Replicator
2
-
3
1
  from pulp_glue.python.context import (
4
2
  PulpPythonDistributionContext,
5
3
  PulpPythonPublicationContext,
6
4
  PulpPythonRepositoryContext,
7
5
  )
6
+
7
+ from pulpcore.plugin.replica import Replicator
8
+
8
9
  from pulp_python.app.models import PythonDistribution, PythonRemote, PythonRepository
9
10
  from pulp_python.app.tasks import sync as python_sync
10
11
 
@@ -2,33 +2,34 @@ import logging
2
2
  import os
3
3
  import tempfile
4
4
  from gettext import gettext as _
5
+ from urllib.parse import urljoin
6
+
5
7
  from django.conf import settings
6
8
  from django.db.utils import IntegrityError
7
9
  from drf_spectacular.utils import extend_schema_serializer
8
10
  from packaging.requirements import Requirement
9
- from packaging.version import Version, InvalidVersion
10
- from rest_framework import serializers
11
- from pypi_attestations import AttestationError
11
+ from packaging.version import InvalidVersion, Version
12
12
  from pydantic import TypeAdapter, ValidationError
13
- from urllib.parse import urljoin
13
+ from pypi_attestations import AttestationError
14
+ from rest_framework import serializers
14
15
 
15
16
  from pulpcore.plugin import models as core_models
16
17
  from pulpcore.plugin import serializers as core_serializers
17
- from pulpcore.plugin.util import get_domain, get_prn, get_current_authenticated_user, reverse
18
+ from pulpcore.plugin.util import get_current_authenticated_user, get_domain, get_prn, reverse
18
19
 
19
20
  from pulp_python.app import models as python_models
20
- from pulp_python.app.utils import canonicalize_name
21
21
  from pulp_python.app.provenance import (
22
+ AnyPublisher,
22
23
  Attestation,
24
+ AttestationBundle,
23
25
  Provenance,
24
26
  verify_provenance,
25
- AttestationBundle,
26
- AnyPublisher,
27
27
  )
28
28
  from pulp_python.app.utils import (
29
29
  DIST_EXTENSIONS,
30
30
  artifact_to_metadata_artifact,
31
31
  artifact_to_python_content_data,
32
+ canonicalize_name,
32
33
  get_project_metadata_from_file,
33
34
  parse_project_metadata,
34
35
  )
@@ -238,7 +239,7 @@ class PythonPackageContentSerializer(core_serializers.SingleArtifactContentUploa
238
239
  required=False,
239
240
  allow_blank=True,
240
241
  help_text=_(
241
- "The maintainer's name at a minimum; " "additional contact information may be provided."
242
+ "The maintainer's name at a minimum; additional contact information may be provided."
242
243
  ),
243
244
  )
244
245
  maintainer_email = serializers.CharField(
@@ -387,7 +388,7 @@ class PythonPackageContentSerializer(core_serializers.SingleArtifactContentUploa
387
388
  else:
388
389
  attestations = TypeAdapter(list[Attestation]).validate_python(value)
389
390
  except ValidationError as e:
390
- raise serializers.ValidationError(_("Invalid attestations: {}".format(e)))
391
+ raise serializers.ValidationError(_("Invalid attestations: {}").format(e))
391
392
  return attestations
392
393
 
393
394
  def handle_attestations(self, filename, sha256, attestations, offline=True):
@@ -400,7 +401,7 @@ class PythonPackageContentSerializer(core_serializers.SingleArtifactContentUploa
400
401
  verify_provenance(filename, sha256, provenance, offline=offline)
401
402
  except AttestationError as e:
402
403
  raise serializers.ValidationError(
403
- {"attestations": _("Attestations failed verification: {}".format(e))}
404
+ {"attestations": _("Attestations failed verification: {}").format(e)}
404
405
  )
405
406
  return provenance.model_dump(mode="json")
406
407
 
@@ -655,13 +656,13 @@ class PackageProvenanceSerializer(core_serializers.NoArtifactContentUploadSerial
655
656
  data["provenance"] = provenance.model_dump(mode="json")
656
657
  except ValidationError as e:
657
658
  raise serializers.ValidationError(
658
- _("The uploaded provenance is not valid: {}".format(e))
659
+ _("The uploaded provenance is not valid: {}").format(e)
659
660
  )
660
661
  if data.pop("verify"):
661
662
  try:
662
663
  verify_provenance(data["package"].filename, data["package"].sha256, provenance)
663
664
  except AttestationError as e:
664
- raise serializers.ValidationError(_("Provenance verification failed: {}".format(e)))
665
+ raise serializers.ValidationError(_("Provenance verification failed: {}").format(e))
665
666
  return data
666
667
 
667
668
  def retrieve(self, validated_data):
@@ -729,7 +730,7 @@ class PythonRemoteSerializer(core_serializers.RemoteSerializer):
729
730
  package_types = MultipleChoiceArrayField(
730
731
  required=False,
731
732
  help_text=_(
732
- "The package types to sync for Python content. Leave blank to get every" "package type."
733
+ "The package types to sync for Python content. Leave blank to get everypackage type."
733
734
  ),
734
735
  choices=python_models.PACKAGE_TYPES,
735
736
  default=list,
@@ -764,7 +765,7 @@ class PythonRemoteSerializer(core_serializers.RemoteSerializer):
764
765
  Requirement(pkg)
765
766
  except ValueError as ve:
766
767
  raise serializers.ValidationError(
767
- _("includes specifier {} is invalid. {}".format(pkg, ve))
768
+ _("includes specifier {} is invalid. {}").format(pkg, ve)
768
769
  )
769
770
  return value
770
771
 
@@ -775,7 +776,7 @@ class PythonRemoteSerializer(core_serializers.RemoteSerializer):
775
776
  Requirement(pkg)
776
777
  except ValueError as ve:
777
778
  raise serializers.ValidationError(
778
- _("excludes specifier {} is invalid. {}".format(pkg, ve))
779
+ _("excludes specifier {} is invalid. {}").format(pkg, ve)
779
780
  )
780
781
  return value
781
782
 
@@ -1,6 +1,6 @@
1
- from gettext import gettext as _
2
1
  import logging
3
2
  import os
3
+ from gettext import gettext as _
4
4
 
5
5
  from django.core.files import File
6
6
  from packaging.utils import canonicalize_name
@@ -10,7 +10,7 @@ from pulpcore.plugin.util import get_domain
10
10
 
11
11
  from pulp_python.app import models as python_models
12
12
  from pulp_python.app.serializers import PythonPublicationSerializer
13
- from pulp_python.app.utils import write_simple_index, write_simple_detail
13
+ from pulp_python.app.utils import write_simple_detail, write_simple_index
14
14
 
15
15
  log = logging.getLogger(__name__)
16
16
 
@@ -7,6 +7,10 @@ from uuid import UUID
7
7
 
8
8
  from django.db.models import Prefetch
9
9
  from django.db.models.query import QuerySet
10
+
11
+ from pulpcore.plugin.models import ContentArtifact, ProgressReport
12
+ from pulpcore.plugin.util import get_domain
13
+
10
14
  from pulp_python.app.models import PythonPackageContent, PythonRepository
11
15
  from pulp_python.app.utils import (
12
16
  artifact_to_python_content_data,
@@ -16,8 +20,6 @@ from pulp_python.app.utils import (
16
20
  metadata_content_to_artifact,
17
21
  parse_metadata,
18
22
  )
19
- from pulpcore.plugin.models import ContentArtifact, ProgressReport
20
- from pulpcore.plugin.util import get_domain
21
23
 
22
24
  log = logging.getLogger(__name__)
23
25
 
@@ -1,11 +1,17 @@
1
- import logging
2
1
  import asyncio
3
-
4
- from aiohttp import ClientResponseError, ClientError
5
- from lxml.etree import LxmlError
6
- from gettext import gettext as _
2
+ import logging
7
3
  from functools import partial
4
+ from gettext import gettext as _
5
+ from urllib.parse import urljoin
8
6
 
7
+ from aiohttp import ClientError, ClientResponseError
8
+ from bandersnatch.configuration import BandersnatchConfig
9
+ from bandersnatch.master import Master
10
+ from bandersnatch.mirror import Mirror
11
+ from lxml.etree import LxmlError
12
+ from packaging.requirements import Requirement
13
+ from pypi_attestations import Provenance
14
+ from pypi_simple import IndexPage
9
15
  from rest_framework import serializers
10
16
 
11
17
  from pulpcore.plugin.download import HttpDownloader
@@ -18,19 +24,11 @@ from pulpcore.plugin.stages import (
18
24
  )
19
25
 
20
26
  from pulp_python.app.models import (
27
+ PackageProvenance,
21
28
  PythonPackageContent,
22
29
  PythonRemote,
23
- PackageProvenance,
24
30
  )
25
- from pulp_python.app.utils import parse_metadata, PYPI_LAST_SERIAL, aget_remote_simple_page
26
- from pypi_simple import IndexPage
27
- from pypi_attestations import Provenance
28
-
29
- from bandersnatch.mirror import Mirror
30
- from bandersnatch.master import Master
31
- from bandersnatch.configuration import BandersnatchConfig
32
- from packaging.requirements import Requirement
33
- from urllib.parse import urljoin
31
+ from pulp_python.app.utils import PYPI_LAST_SERIAL, aget_remote_simple_page, parse_metadata
34
32
 
35
33
  logger = logging.getLogger(__name__)
36
34
 
@@ -1,17 +1,18 @@
1
1
  import time
2
-
3
2
  from datetime import datetime, timezone
4
- from django.db import transaction
3
+
5
4
  from django.contrib.sessions.models import Session
5
+ from django.db import transaction
6
6
  from pydantic import TypeAdapter
7
- from pulpcore.plugin.models import Artifact, CreatedResource, Content, ContentArtifact
8
- from pulpcore.plugin.util import get_domain, get_current_authenticated_user, get_prn
9
7
 
10
- from pulp_python.app.models import PythonPackageContent, PythonRepository, PackageProvenance
8
+ from pulpcore.plugin.models import Artifact, Content, ContentArtifact, CreatedResource
9
+ from pulpcore.plugin.util import get_current_authenticated_user, get_domain, get_prn
10
+
11
+ from pulp_python.app.models import PackageProvenance, PythonPackageContent, PythonRepository
11
12
  from pulp_python.app.provenance import (
13
+ AnyPublisher,
12
14
  Attestation,
13
15
  AttestationBundle,
14
- AnyPublisher,
15
16
  Provenance,
16
17
  verify_provenance,
17
18
  )
@@ -1,23 +1,26 @@
1
1
  import hashlib
2
+ import json
2
3
  import logging
3
- import pkginfo
4
4
  import re
5
5
  import shutil
6
6
  import tempfile
7
7
  import zipfile
8
- import json
9
- from aiohttp.client_exceptions import ClientError
10
8
  from collections import defaultdict
11
9
  from datetime import timezone
10
+
11
+ import pkginfo
12
+ from aiohttp.client_exceptions import ClientError
12
13
  from django.conf import settings
14
+ from django.db.models import OuterRef, Subquery
13
15
  from django.db.utils import IntegrityError
14
16
  from jinja2 import Template
15
- from packaging.utils import canonicalize_name
16
17
  from packaging.requirements import Requirement
17
- from packaging.version import parse, InvalidVersion
18
+ from packaging.utils import canonicalize_name
19
+ from packaging.version import InvalidVersion, parse
18
20
  from pypi_simple import ACCEPT_JSON_PREFERRED, ProjectPage
19
- from pulpcore.plugin.models import Artifact, Remote
21
+
20
22
  from pulpcore.plugin.exceptions import TimeoutException
23
+ from pulpcore.plugin.models import Artifact, Remote, RepositoryContent
21
24
  from pulpcore.plugin.util import get_domain
22
25
 
23
26
  log = logging.getLogger(__name__)
@@ -357,7 +360,9 @@ def fetch_json_release_metadata(name: str, version: str, remotes: set[Remote]) -
357
360
  raise Exception(f"Failed to fetch {url} from any remote.")
358
361
 
359
362
 
360
- def python_content_to_json(base_path, content_query, version=None, domain=None):
363
+ def python_content_to_json(
364
+ base_path, content_query, version=None, domain=None, repository_version=None
365
+ ):
361
366
  """
362
367
  Converts a QuerySet of PythonPackageContent into the PyPi JSON format
363
368
  https://www.python.org/dev/peps/pep-0566/
@@ -369,6 +374,13 @@ def python_content_to_json(base_path, content_query, version=None, domain=None):
369
374
 
370
375
  Returns None if version is specified but not found within content_query
371
376
  """
377
+ if repository_version:
378
+ repo_added_subquery = RepositoryContent.objects.filter(
379
+ content_id=OuterRef("pk"),
380
+ repository=repository_version.repository,
381
+ version_removed=None,
382
+ ).values("pulp_created")[:1]
383
+ content_query = content_query.annotate(repo_added_time=Subquery(repo_added_subquery))
372
384
  full_metadata = {"last_serial": 0} # For now the serial field isn't supported by Pulp
373
385
  latest_content = latest_content_version(content_query, version)
374
386
  if not latest_content:
@@ -513,8 +525,10 @@ def python_content_to_download_info(content, base_path, domain=None):
513
525
  "python_version": content.python_version,
514
526
  "requires_python": content.requires_python or None,
515
527
  "size": content.size,
516
- "upload_time": str(content.pulp_created),
517
- "upload_time_iso_8601": str(content.pulp_created.isoformat()),
528
+ "upload_time": str(getattr(content, "repo_added_time", None) or content.pulp_created),
529
+ "upload_time_iso_8601": str(
530
+ (getattr(content, "repo_added_time", None) or content.pulp_created).isoformat()
531
+ ),
518
532
  "url": url,
519
533
  "yanked": False,
520
534
  "yanked_reason": None,