pulp-python 3.27.1__tar.gz → 3.27.3__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 (93) hide show
  1. {pulp_python-3.27.1 → pulp_python-3.27.3}/CHANGES.md +18 -0
  2. {pulp_python-3.27.1 → pulp_python-3.27.3}/MANIFEST.in +2 -0
  3. {pulp_python-3.27.1 → pulp_python-3.27.3}/PKG-INFO +1 -1
  4. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/__init__.py +5 -3
  5. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/management/commands/repair-python-metadata.py +3 -2
  6. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/modelresource.py +1 -0
  7. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/models.py +9 -8
  8. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/pypi/serializers.py +7 -5
  9. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/pypi/views.py +40 -28
  10. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/replica.py +3 -2
  11. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/serializers.py +14 -13
  12. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/tasks/publish.py +2 -2
  13. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/tasks/repair.py +28 -13
  14. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/tasks/sync.py +13 -15
  15. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/tasks/upload.py +7 -6
  16. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/utils.py +58 -15
  17. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/viewsets.py +2 -1
  18. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/pytest_plugin.py +8 -6
  19. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/functional/api/test_consume_content.py +0 -1
  20. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/functional/api/test_crud_content_unit.py +5 -4
  21. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/functional/api/test_crud_publications.py +6 -5
  22. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/functional/api/test_crud_remotes.py +3 -2
  23. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/functional/api/test_domains.py +6 -5
  24. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/functional/api/test_export_import.py +5 -3
  25. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/functional/api/test_pypi_apis.py +48 -5
  26. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/functional/api/test_pypi_simple_api.py +5 -6
  27. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/functional/api/test_rbac.py +5 -4
  28. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/functional/api/test_repair.py +2 -1
  29. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/functional/api/test_upload.py +5 -3
  30. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/functional/constants.py +4 -0
  31. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python.egg-info/PKG-INFO +1 -1
  32. {pulp_python-3.27.1 → pulp_python-3.27.3}/pyproject.toml +31 -2
  33. {pulp_python-3.27.1 → pulp_python-3.27.3}/COMMITMENT +0 -0
  34. {pulp_python-3.27.1 → pulp_python-3.27.3}/COPYRIGHT +0 -0
  35. {pulp_python-3.27.1 → pulp_python-3.27.3}/LICENSE +0 -0
  36. {pulp_python-3.27.1 → pulp_python-3.27.3}/README.md +0 -0
  37. {pulp_python-3.27.1 → pulp_python-3.27.3}/functest_requirements.txt +0 -0
  38. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/__init__.py +0 -0
  39. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/global_access_conditions.py +0 -0
  40. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/management/__init__.py +0 -0
  41. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/management/commands/__init__.py +0 -0
  42. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/migrations/0001_initial.py +0 -0
  43. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/migrations/0001_squashed_0010_update_json_field.py +0 -0
  44. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/migrations/0002_pythonpackagecontent_python_version.py +0 -0
  45. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/migrations/0003_new_sync_filters.py +0 -0
  46. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/migrations/0004_DATA_swap_distribution_model.py +0 -0
  47. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/migrations/0005_pythonpackagecontent_sha256.py +0 -0
  48. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/migrations/0006_pythonrepository_autopublish.py +0 -0
  49. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/migrations/0007_pythonpackagecontent_mv-2-1.py +0 -0
  50. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/migrations/0008_pythonpackagecontent_unique_sha256.py +0 -0
  51. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/migrations/0009_pythondistribution_allow_uploads.py +0 -0
  52. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/migrations/0010_update_json_field.py +0 -0
  53. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/migrations/0011_alter_pythondistribution_distribution_ptr_and_more.py +0 -0
  54. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/migrations/0012_add_domain.py +0 -0
  55. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/migrations/0013_add_rbac_permissions.py +0 -0
  56. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/migrations/0014_pythonpackagecontent_dynamic_and_more.py +0 -0
  57. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/migrations/0015_alter_pythonpackagecontent_options.py +0 -0
  58. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/migrations/0016_pythonpackagecontent_metadata_sha256.py +0 -0
  59. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/migrations/0017_pythonpackagecontent_size.py +0 -0
  60. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/migrations/0018_packageprovenance.py +0 -0
  61. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/migrations/0019_create_missing_metadata_artifacts.py +0 -0
  62. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/migrations/__init__.py +0 -0
  63. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/provenance.py +0 -0
  64. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/pypi/__init__.py +0 -0
  65. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/settings.py +0 -0
  66. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/tasks/__init__.py +0 -0
  67. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/tasks/vulnerability_report.py +0 -0
  68. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/urls.py +2 -2
  69. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/webserver_snippets/__init__.py +0 -0
  70. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/webserver_snippets/apache.conf +0 -0
  71. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/app/webserver_snippets/nginx.conf +0 -0
  72. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/__init__.py +0 -0
  73. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/functional/__init__.py +0 -0
  74. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/functional/api/__init__.py +0 -0
  75. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/functional/api/test_attestations.py +2 -2
  76. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/functional/api/test_auto_publish.py +0 -0
  77. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/functional/api/test_download_content.py +2 -2
  78. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/functional/api/test_full_mirror.py +9 -9
  79. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/functional/api/test_sync.py +11 -11
  80. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/functional/api/test_vulnerability_report.py +0 -0
  81. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/functional/assets/shelf-reader-0.1.tar.gz.publish.attestation +0 -0
  82. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/functional/assets/shelf_reader-0.1-py2-none-any.whl.publish.attestation +0 -0
  83. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/functional/utils.py +2 -2
  84. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/unit/__init__.py +0 -0
  85. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python/tests/unit/test_models.py +0 -0
  86. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python.egg-info/SOURCES.txt +0 -0
  87. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python.egg-info/dependency_links.txt +0 -0
  88. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python.egg-info/entry_points.txt +0 -0
  89. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python.egg-info/requires.txt +0 -0
  90. {pulp_python-3.27.1 → pulp_python-3.27.3}/pulp_python.egg-info/top_level.txt +0 -0
  91. {pulp_python-3.27.1 → pulp_python-3.27.3}/setup.cfg +0 -0
  92. {pulp_python-3.27.1 → pulp_python-3.27.3}/test_requirements.txt +0 -0
  93. {pulp_python-3.27.1 → pulp_python-3.27.3}/unittest_requirements.txt +0 -0
@@ -8,6 +8,24 @@
8
8
 
9
9
  [//]: # (towncrier release notes start)
10
10
 
11
+ ## 3.27.3 (2026-05-19) {: #3.27.3 }
12
+
13
+ #### Bugfixes {: #3.27.3-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.27.2 (2026-04-14) {: #3.27.2 }
21
+
22
+ #### Bugfixes {: #3.27.2-bugfix }
23
+
24
+ - Fixed "Worker has gone missing" errors during repair_metadata on large repositories (1000+ packages) by reducing peak memory consumption.
25
+ [#1188](https://github.com/pulp/pulp_python/issues/1188)
26
+
27
+ ---
28
+
11
29
  ## 3.27.1 (2026-04-01) {: #3.27.1 }
12
30
 
13
31
  #### Bugfixes {: #3.27.1-bugfix }
@@ -11,3 +11,5 @@ include unittest_requirements.txt
11
11
  include pulp_python/app/webserver_snippets/*
12
12
  include pulp_python/tests/functional/assets/*
13
13
  exclude releasing.md
14
+
15
+ exclude .gitleaks.toml
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pulp-python
3
- Version: 3.27.1
3
+ Version: 3.27.3
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.27.1"
15
+ version = "3.27.3"
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
 
@@ -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,38 +1,39 @@
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
  )
15
+
14
16
  from pulpcore.plugin.models import (
15
17
  AutoAddObjPermsMixin,
16
18
  Content,
17
- Publication,
18
19
  Distribution,
20
+ Publication,
19
21
  Remote,
20
22
  Repository,
21
23
  )
24
+ from pulpcore.plugin.repo_version_utils import remove_duplicates, validate_repo_version
22
25
  from pulpcore.plugin.responses import ArtifactResponse
26
+ from pulpcore.plugin.util import get_domain, get_domain_pk
23
27
 
24
- from pathlib import PurePath
25
28
  from .provenance import Provenance
26
29
  from .utils import (
27
- artifact_to_python_content_data,
30
+ PYPI_LAST_SERIAL,
31
+ PYPI_SERIAL_CONSTANT,
28
32
  artifact_to_metadata_artifact,
33
+ artifact_to_python_content_data,
29
34
  canonicalize_name,
30
35
  python_content_to_json,
31
- PYPI_LAST_SERIAL,
32
- PYPI_SERIAL_CONSTANT,
33
36
  )
34
- from pulpcore.plugin.repo_version_utils import remove_duplicates, validate_repo_version
35
- from pulpcore.plugin.util import get_domain_pk, get_domain
36
37
 
37
38
  log = getLogger(__name__)
38
39
 
@@ -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
@@ -362,13 +363,20 @@ class SimpleView(PackageUploadMixin, ViewSet):
362
363
  return redirect(urljoin(self.base_content_url, f"{path}/simple/{normalized}/"))
363
364
  if content:
364
365
  local_packages = content.filter(name__normalize=normalized)
365
- packages = local_packages.values(
366
+ repo_added_subquery = RepositoryContent.objects.filter(
367
+ content_id=OuterRef("pk"),
368
+ repository=repo_ver.repository,
369
+ version_removed=None,
370
+ ).values("pulp_created")[:1]
371
+ packages = local_packages.annotate(
372
+ repo_added_time=Subquery(repo_added_subquery)
373
+ ).values(
366
374
  "filename",
367
375
  "sha256",
368
376
  "metadata_sha256",
369
377
  "requires_python",
370
378
  "size",
371
- "pulp_created",
379
+ "repo_added_time",
372
380
  "version",
373
381
  )
374
382
  provenances = PackageProvenance.objects.filter(package__in=local_packages).values_list(
@@ -378,7 +386,7 @@ class SimpleView(PackageUploadMixin, ViewSet):
378
386
  p["filename"]: {
379
387
  **p,
380
388
  "url": urljoin(self.base_content_url, f"{path}/{p['filename']}"),
381
- "upload_time": p["pulp_created"],
389
+ "upload_time": p["repo_added_time"],
382
390
  "provenance": (
383
391
  self.get_provenance_url(normalized, p["version"], p["filename"])
384
392
  if p["filename"] in provenances
@@ -460,7 +468,11 @@ class MetadataView(PyPIMixin, ViewSet):
460
468
  if settings.DOMAIN_ENABLED:
461
469
  domain = get_domain()
462
470
  json_body = python_content_to_json(
463
- path, package_content, version=version, domain=domain
471
+ path,
472
+ package_content,
473
+ version=version,
474
+ domain=domain,
475
+ repository_version=repo_ver,
464
476
  )
465
477
  if json_body:
466
478
  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,25 +2,26 @@ import logging
2
2
  import os
3
3
  import tempfile
4
4
  from gettext import gettext as _
5
+
5
6
  from django.conf import settings
6
7
  from django.db.utils import IntegrityError
7
8
  from drf_spectacular.utils import extend_schema_serializer
8
9
  from packaging.requirements import Requirement
9
- from rest_framework import serializers
10
- from pypi_attestations import AttestationError
11
10
  from pydantic import TypeAdapter, ValidationError
11
+ from pypi_attestations import AttestationError
12
+ from rest_framework import serializers
12
13
 
13
14
  from pulpcore.plugin import models as core_models
14
15
  from pulpcore.plugin import serializers as core_serializers
15
- from pulpcore.plugin.util import get_domain, get_prn, get_current_authenticated_user
16
+ from pulpcore.plugin.util import get_current_authenticated_user, get_domain, get_prn
16
17
 
17
18
  from pulp_python.app import models as python_models
18
19
  from pulp_python.app.provenance import (
20
+ AnyPublisher,
19
21
  Attestation,
22
+ AttestationBundle,
20
23
  Provenance,
21
24
  verify_provenance,
22
- AttestationBundle,
23
- AnyPublisher,
24
25
  )
25
26
  from pulp_python.app.utils import (
26
27
  DIST_EXTENSIONS,
@@ -210,7 +211,7 @@ class PythonPackageContentSerializer(core_serializers.SingleArtifactContentUploa
210
211
  required=False,
211
212
  allow_blank=True,
212
213
  help_text=_(
213
- "The maintainer's name at a minimum; " "additional contact information may be provided."
214
+ "The maintainer's name at a minimum; additional contact information may be provided."
214
215
  ),
215
216
  )
216
217
  maintainer_email = serializers.CharField(
@@ -359,7 +360,7 @@ class PythonPackageContentSerializer(core_serializers.SingleArtifactContentUploa
359
360
  else:
360
361
  attestations = TypeAdapter(list[Attestation]).validate_python(value)
361
362
  except ValidationError as e:
362
- raise serializers.ValidationError(_("Invalid attestations: {}".format(e)))
363
+ raise serializers.ValidationError(_("Invalid attestations: {}").format(e))
363
364
  return attestations
364
365
 
365
366
  def handle_attestations(self, filename, sha256, attestations, offline=True):
@@ -372,7 +373,7 @@ class PythonPackageContentSerializer(core_serializers.SingleArtifactContentUploa
372
373
  verify_provenance(filename, sha256, provenance, offline=offline)
373
374
  except AttestationError as e:
374
375
  raise serializers.ValidationError(
375
- {"attestations": _("Attestations failed verification: {}".format(e))}
376
+ {"attestations": _("Attestations failed verification: {}").format(e)}
376
377
  )
377
378
  return provenance.model_dump(mode="json")
378
379
 
@@ -627,13 +628,13 @@ class PackageProvenanceSerializer(core_serializers.NoArtifactContentUploadSerial
627
628
  data["provenance"] = provenance.model_dump(mode="json")
628
629
  except ValidationError as e:
629
630
  raise serializers.ValidationError(
630
- _("The uploaded provenance is not valid: {}".format(e))
631
+ _("The uploaded provenance is not valid: {}").format(e)
631
632
  )
632
633
  if data.pop("verify"):
633
634
  try:
634
635
  verify_provenance(data["package"].filename, data["package"].sha256, provenance)
635
636
  except AttestationError as e:
636
- raise serializers.ValidationError(_("Provenance verification failed: {}".format(e)))
637
+ raise serializers.ValidationError(_("Provenance verification failed: {}").format(e))
637
638
  return data
638
639
 
639
640
  def retrieve(self, validated_data):
@@ -701,7 +702,7 @@ class PythonRemoteSerializer(core_serializers.RemoteSerializer):
701
702
  package_types = MultipleChoiceArrayField(
702
703
  required=False,
703
704
  help_text=_(
704
- "The package types to sync for Python content. Leave blank to get every" "package type."
705
+ "The package types to sync for Python content. Leave blank to get everypackage type."
705
706
  ),
706
707
  choices=python_models.PACKAGE_TYPES,
707
708
  default=list,
@@ -736,7 +737,7 @@ class PythonRemoteSerializer(core_serializers.RemoteSerializer):
736
737
  Requirement(pkg)
737
738
  except ValueError as ve:
738
739
  raise serializers.ValidationError(
739
- _("includes specifier {} is invalid. {}".format(pkg, ve))
740
+ _("includes specifier {} is invalid. {}").format(pkg, ve)
740
741
  )
741
742
  return value
742
743
 
@@ -747,7 +748,7 @@ class PythonRemoteSerializer(core_serializers.RemoteSerializer):
747
748
  Requirement(pkg)
748
749
  except ValueError as ve:
749
750
  raise serializers.ValidationError(
750
- _("excludes specifier {} is invalid. {}".format(pkg, ve))
751
+ _("excludes specifier {} is invalid. {}").format(pkg, ve)
751
752
  )
752
753
  return value
753
754
 
@@ -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
 
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ import os
2
3
  from collections import defaultdict
3
4
  from gettext import gettext as _
4
5
  from itertools import groupby
@@ -6,20 +7,24 @@ from uuid import UUID
6
7
 
7
8
  from django.db.models import Prefetch
8
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
+
9
14
  from pulp_python.app.models import PythonPackageContent, PythonRepository
10
15
  from pulp_python.app.utils import (
11
- artifact_to_metadata_artifact,
12
16
  artifact_to_python_content_data,
17
+ copy_artifact_to_temp_file,
18
+ extract_wheel_metadata,
13
19
  fetch_json_release_metadata,
20
+ metadata_content_to_artifact,
14
21
  parse_metadata,
15
22
  )
16
- from pulpcore.plugin.models import Artifact, ContentArtifact, ProgressReport
17
- from pulpcore.plugin.util import get_domain
18
23
 
19
24
  log = logging.getLogger(__name__)
20
25
 
21
26
 
22
- BULK_SIZE = 1000
27
+ BULK_SIZE = 250
23
28
 
24
29
 
25
30
  def repair(repository_pk: UUID) -> None:
@@ -118,11 +123,21 @@ def repair_metadata(content: QuerySet[PythonPackageContent]) -> tuple[int, set[s
118
123
  .first()
119
124
  .artifact
120
125
  )
121
- new_data = artifact_to_python_content_data(package.filename, main_artifact, domain)
126
+ # Copy artifact to temp file once, extract both content data and metadata
127
+ temp_path = copy_artifact_to_temp_file(main_artifact, package.filename)
128
+ try:
129
+ new_data = artifact_to_python_content_data(
130
+ package.filename, main_artifact, domain, temp_path=temp_path
131
+ )
132
+ metadata_content = (
133
+ extract_wheel_metadata(temp_path) if package.filename.endswith(".whl") else None
134
+ )
135
+ finally:
136
+ os.unlink(temp_path)
122
137
  total_metadata_repaired += update_metadata_artifact_if_needed(
123
138
  package,
124
139
  new_data.get("metadata_sha256"),
125
- main_artifact,
140
+ metadata_content,
126
141
  metadata_batch,
127
142
  pkgs_metadata_not_repaired,
128
143
  )
@@ -236,7 +251,7 @@ def update_package_if_needed(
236
251
  def update_metadata_artifact_if_needed(
237
252
  package: PythonPackageContent,
238
253
  new_metadata_sha256: str | None,
239
- main_artifact: Artifact,
254
+ metadata_content: bytes | None,
240
255
  metadata_batch: list[tuple],
241
256
  pkgs_metadata_not_repaired: set[str],
242
257
  ) -> int:
@@ -248,7 +263,7 @@ def update_metadata_artifact_if_needed(
248
263
  Args:
249
264
  package: Package to check for metadata changes.
250
265
  new_metadata_sha256: The correct metadata_sha256 extracted from the main artifact, or None.
251
- main_artifact: The main package artifact used to generate metadata.
266
+ metadata_content: Raw metadata bytes extracted from the wheel, or None.
252
267
  metadata_batch: List of tuples for batch processing (updated in-place).
253
268
  pkgs_metadata_not_repaired: Set of package PKs that failed repair (updated in-place).
254
269
 
@@ -265,13 +280,13 @@ def update_metadata_artifact_if_needed(
265
280
 
266
281
  # Create missing
267
282
  if not cas:
268
- metadata_batch.append((package, main_artifact))
283
+ metadata_batch.append((package, metadata_content))
269
284
  # Fix existing
270
285
  elif new_metadata_sha256 != original_metadata_sha256:
271
286
  ca = cas.first()
272
287
  metadata_artifact = ca.artifact
273
288
  if metadata_artifact is None or (metadata_artifact.sha256 != new_metadata_sha256):
274
- metadata_batch.append((package, main_artifact))
289
+ metadata_batch.append((package, metadata_content))
275
290
 
276
291
  if len(metadata_batch) == BULK_SIZE:
277
292
  not_repaired = _process_metadata_batch(metadata_batch)
@@ -288,7 +303,7 @@ def _process_metadata_batch(metadata_batch: list[tuple]) -> set[str]:
288
303
  and their corresponding ContentArtifacts.
289
304
 
290
305
  Args:
291
- metadata_batch: List of (package, main_artifact) tuples.
306
+ metadata_batch: List of (package, metadata_content) tuples.
292
307
 
293
308
  Returns:
294
309
  Set of package PKs for which metadata artifacts could not be created.
@@ -296,8 +311,8 @@ def _process_metadata_batch(metadata_batch: list[tuple]) -> set[str]:
296
311
  not_repaired = set()
297
312
  content_artifacts = []
298
313
 
299
- for package, main_artifact in metadata_batch:
300
- metadata_artifact = artifact_to_metadata_artifact(package.filename, main_artifact)
314
+ for package, metadata_content in metadata_batch:
315
+ metadata_artifact = metadata_content_to_artifact(metadata_content)
301
316
  if metadata_artifact:
302
317
  ca = ContentArtifact(
303
318
  artifact=metadata_artifact,
@@ -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
  )