pulp-python 3.13.6__py3-none-any.whl → 3.13.7__py3-none-any.whl

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 (38) hide show
  1. pulp_python/__init__.py +1 -1
  2. pulp_python/app/__init__.py +5 -3
  3. pulp_python/app/global_access_conditions.py +0 -1
  4. pulp_python/app/management/commands/repair-python-metadata.py +5 -4
  5. pulp_python/app/modelresource.py +1 -0
  6. pulp_python/app/models.py +8 -7
  7. pulp_python/app/pypi/serializers.py +11 -9
  8. pulp_python/app/pypi/views.py +69 -54
  9. pulp_python/app/replica.py +3 -2
  10. pulp_python/app/serializers.py +187 -110
  11. pulp_python/app/settings.py +1 -1
  12. pulp_python/app/tasks/publish.py +24 -25
  13. pulp_python/app/tasks/sync.py +15 -29
  14. pulp_python/app/tasks/upload.py +7 -8
  15. pulp_python/app/urls.py +5 -5
  16. pulp_python/app/utils.py +52 -45
  17. pulp_python/app/viewsets.py +43 -46
  18. pulp_python/pytest_plugin.py +14 -5
  19. pulp_python/tests/functional/api/test_consume_content.py +0 -1
  20. pulp_python/tests/functional/api/test_crud_content_unit.py +8 -5
  21. pulp_python/tests/functional/api/test_crud_publications.py +9 -6
  22. pulp_python/tests/functional/api/test_crud_remotes.py +3 -2
  23. pulp_python/tests/functional/api/test_domains.py +9 -7
  24. pulp_python/tests/functional/api/test_download_content.py +2 -2
  25. pulp_python/tests/functional/api/test_export_import.py +7 -4
  26. pulp_python/tests/functional/api/test_full_mirror.py +4 -4
  27. pulp_python/tests/functional/api/test_pypi_apis.py +13 -18
  28. pulp_python/tests/functional/api/test_rbac.py +6 -5
  29. pulp_python/tests/functional/api/test_repair.py +3 -2
  30. pulp_python/tests/functional/api/test_sync.py +28 -19
  31. pulp_python/tests/functional/constants.py +15 -33
  32. pulp_python/tests/functional/utils.py +15 -6
  33. {pulp_python-3.13.6.dist-info → pulp_python-3.13.7.dist-info}/METADATA +1 -1
  34. {pulp_python-3.13.6.dist-info → pulp_python-3.13.7.dist-info}/RECORD +38 -38
  35. {pulp_python-3.13.6.dist-info → pulp_python-3.13.7.dist-info}/WHEEL +0 -0
  36. {pulp_python-3.13.6.dist-info → pulp_python-3.13.7.dist-info}/entry_points.txt +0 -0
  37. {pulp_python-3.13.6.dist-info → pulp_python-3.13.7.dist-info}/licenses/LICENSE +0 -0
  38. {pulp_python-3.13.6.dist-info → pulp_python-3.13.7.dist-info}/top_level.txt +0 -0
pulp_python/__init__.py CHANGED
@@ -1 +1 @@
1
- default_app_config = 'pulp_python.app.PulpPythonPluginAppConfig'
1
+ default_app_config = "pulp_python.app.PulpPythonPluginAppConfig"
@@ -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.13.6"
15
+ version = "3.13.7"
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,6 +1,5 @@
1
1
  from django.conf import settings
2
2
 
3
-
4
3
  # Access Condition methods that can be used with PyPI access policies
5
4
 
6
5
 
@@ -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
 
@@ -23,7 +24,7 @@ def repair_metadata(content):
23
24
  batch = []
24
25
  set_of_update_fields = set()
25
26
  total_repaired = 0
26
- for package in immediate_content.prefetch_related('_artifacts').iterator(chunk_size=1000):
27
+ for package in immediate_content.prefetch_related("_artifacts").iterator(chunk_size=1000):
27
28
  new_data = artifact_to_python_content_data(
28
29
  package.filename, package._artifacts.get(), package.pulp_domain
29
30
  )
@@ -55,7 +56,7 @@ def href_prn_list_handler(value):
55
56
  (?:{settings.API_ROOT}(?:[-_a-zA-Z0-9]+/)?api/v3/repositories/python/python/[-a-f0-9]+/)
56
57
  |(?:prn:python\.pythonrepository:[-a-f0-9]+)
57
58
  """,
58
- re.VERBOSE
59
+ re.VERBOSE,
59
60
  )
60
61
  values = []
61
62
  for v in value.split(","):
@@ -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,
pulp_python/app/models.py CHANGED
@@ -1,30 +1,31 @@
1
1
  from logging import getLogger
2
+ from pathlib import PurePath
2
3
 
3
4
  from aiohttp.web import json_response
5
+ from django.conf import settings
4
6
  from django.contrib.postgres.fields import ArrayField
5
7
  from django.core.exceptions import ObjectDoesNotExist
6
8
  from django.db import models
7
- from django.conf import settings
9
+
8
10
  from pulpcore.plugin.models import (
9
11
  AutoAddObjPermsMixin,
10
12
  Content,
11
- Publication,
12
13
  Distribution,
14
+ Publication,
13
15
  Remote,
14
16
  Repository,
15
17
  )
18
+ from pulpcore.plugin.repo_version_utils import remove_duplicates, validate_repo_version
16
19
  from pulpcore.plugin.responses import ArtifactResponse
20
+ from pulpcore.plugin.util import get_domain, get_domain_pk
17
21
 
18
- from pathlib import PurePath
19
22
  from .utils import (
23
+ PYPI_LAST_SERIAL,
24
+ PYPI_SERIAL_CONSTANT,
20
25
  artifact_to_python_content_data,
21
26
  canonicalize_name,
22
27
  python_content_to_json,
23
- PYPI_LAST_SERIAL,
24
- PYPI_SERIAL_CONSTANT,
25
28
  )
26
- from pulpcore.plugin.repo_version_utils import remove_duplicates, validate_repo_version
27
- from pulpcore.plugin.util import get_domain_pk, get_domain
28
29
 
29
30
  log = getLogger(__name__)
30
31
 
@@ -1,11 +1,13 @@
1
1
  import logging
2
2
  from gettext import gettext as _
3
3
 
4
+ from django.db.utils import IntegrityError
4
5
  from rest_framework import serializers
5
- from pulp_python.app.utils import DIST_EXTENSIONS
6
+
6
7
  from pulpcore.plugin.models import Artifact
7
8
  from pulpcore.plugin.util import get_domain
8
- from django.db.utils import IntegrityError
9
+
10
+ from pulp_python.app.utils import DIST_EXTENSIONS
9
11
 
10
12
  log = logging.getLogger(__name__)
11
13
 
@@ -46,7 +48,7 @@ class PackageUploadSerializer(serializers.Serializer):
46
48
  action = serializers.CharField(
47
49
  help_text=_("Defaults to `file_upload`, don't change it or request will fail!"),
48
50
  default="file_upload",
49
- source=":action"
51
+ source=":action",
50
52
  )
51
53
  sha256_digest = serializers.CharField(
52
54
  help_text=_("SHA256 of package to validate upload integrity."),
@@ -59,17 +61,17 @@ class PackageUploadSerializer(serializers.Serializer):
59
61
  """Validates the request."""
60
62
  action = data.get(":action")
61
63
  if action != "file_upload":
62
- raise serializers.ValidationError(
63
- _("We do not support the :action {}").format(action)
64
- )
64
+ raise serializers.ValidationError(_("We do not support the :action {}").format(action))
65
65
  file = data.get("content")
66
66
  for ext, packagetype in DIST_EXTENSIONS.items():
67
67
  if file.name.endswith(ext):
68
68
  break
69
69
  else:
70
- raise serializers.ValidationError(_(
71
- "Extension on {} is not a valid python extension "
72
- "(.whl, .exe, .egg, .tar.gz, .tar.bz2, .zip)").format(file.name)
70
+ raise serializers.ValidationError(
71
+ _(
72
+ "Extension on {} is not a valid python extension "
73
+ "(.whl, .exe, .egg, .tar.gz, .tar.bz2, .zip)"
74
+ ).format(file.name)
73
75
  )
74
76
  sha256 = data.get("sha256_digest")
75
77
  digests = {"sha256": sha256} if sha256 else None
@@ -1,54 +1,53 @@
1
1
  import logging
2
- import requests
3
-
4
- from rest_framework.viewsets import ViewSet
5
- from rest_framework.response import Response
6
- from django.core.exceptions import ObjectDoesNotExist
7
- from django.shortcuts import redirect
8
- 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
9
6
 
10
- from rest_framework.reverse import reverse
7
+ import requests
11
8
  from django.contrib.sessions.models import Session
9
+ from django.core.exceptions import ObjectDoesNotExist
12
10
  from django.db import transaction
13
11
  from django.db.utils import DatabaseError
14
12
  from django.http.response import (
15
13
  Http404,
16
- HttpResponseForbidden,
17
14
  HttpResponseBadRequest,
18
- StreamingHttpResponse
15
+ HttpResponseForbidden,
16
+ StreamingHttpResponse,
19
17
  )
18
+ from django.shortcuts import redirect
20
19
  from drf_spectacular.utils import extend_schema
21
20
  from dynaconf import settings
22
- from itertools import chain
23
21
  from packaging.utils import canonicalize_name
24
- from urllib.parse import urljoin, urlparse, urlunsplit
25
- from pathlib import PurePath
26
22
  from pypi_simple import parse_links_stream_response
23
+ from rest_framework.response import Response
24
+ from rest_framework.reverse import reverse
25
+ from rest_framework.viewsets import ViewSet
27
26
 
28
- from pulpcore.plugin.viewsets import OperationPostponedResponse
29
27
  from pulpcore.plugin.tasking import dispatch
30
28
  from pulpcore.plugin.util import get_domain
29
+ from pulpcore.plugin.viewsets import OperationPostponedResponse
30
+
31
+ from pulp_python.app import tasks
31
32
  from pulp_python.app.models import (
32
33
  PythonDistribution,
33
34
  PythonPackageContent,
34
35
  PythonPublication,
35
36
  )
36
37
  from pulp_python.app.pypi.serializers import (
37
- SummarySerializer,
38
38
  PackageMetadataSerializer,
39
39
  PackageUploadSerializer,
40
- PackageUploadTaskSerializer
40
+ PackageUploadTaskSerializer,
41
+ SummarySerializer,
41
42
  )
42
43
  from pulp_python.app.utils import (
43
- write_simple_index,
44
- write_simple_detail,
45
- python_content_to_json,
46
44
  PYPI_LAST_SERIAL,
47
45
  PYPI_SERIAL_CONSTANT,
46
+ python_content_to_json,
47
+ write_simple_detail,
48
+ write_simple_index,
48
49
  )
49
50
 
50
- from pulp_python.app import tasks
51
-
52
51
  log = logging.getLogger(__name__)
53
52
 
54
53
  ORIGIN_HOST = settings.CONTENT_ORIGIN if settings.CONTENT_ORIGIN else settings.PYPI_API_HOSTNAME
@@ -159,10 +158,15 @@ class PackageUploadMixin(PyPIMixin):
159
158
  if settings.PYTHON_GROUP_UPLOADS:
160
159
  return self.upload_package_group(repo, artifact, filename, request.session)
161
160
 
162
- result = dispatch(tasks.upload, exclusive_resources=[artifact, repo],
163
- kwargs={"artifact_sha256": artifact.sha256,
164
- "filename": filename,
165
- "repository_pk": str(repo.pk)})
161
+ result = dispatch(
162
+ tasks.upload,
163
+ exclusive_resources=[artifact, repo],
164
+ kwargs={
165
+ "artifact_sha256": artifact.sha256,
166
+ "filename": filename,
167
+ "repository_pk": str(repo.pk),
168
+ },
169
+ )
166
170
  return OperationPostponedResponse(result, request)
167
171
 
168
172
  def upload_package_group(self, repo, artifact, filename, session):
@@ -176,10 +180,10 @@ class PackageUploadMixin(PyPIMixin):
176
180
  try:
177
181
  with transaction.atomic():
178
182
  sq.first()
179
- current_start = datetime.fromisoformat(session['start'])
183
+ current_start = datetime.fromisoformat(session["start"])
180
184
  if current_start >= datetime.now(tz=timezone.utc):
181
- session['artifacts'].append((str(artifact.sha256), filename))
182
- session['start'] = str(start_time)
185
+ session["artifacts"].append((str(artifact.sha256), filename))
186
+ session["start"] = str(start_time)
183
187
  session.modified = False
184
188
  session.save()
185
189
  else:
@@ -192,14 +196,19 @@ class PackageUploadMixin(PyPIMixin):
192
196
 
193
197
  def create_group_upload_task(self, cur_session, repository, artifact, filename, start_time):
194
198
  """Creates the actual task that adds the packages to the index."""
195
- cur_session['start'] = str(start_time)
196
- cur_session['artifacts'] = [(str(artifact.sha256), filename)]
199
+ cur_session["start"] = str(start_time)
200
+ cur_session["artifacts"] = [(str(artifact.sha256), filename)]
197
201
  cur_session.modified = False
198
202
  cur_session.save()
199
- result = dispatch(tasks.upload_group, exclusive_resources=[artifact, repository],
200
- kwargs={"session_pk": str(cur_session.session_key),
201
- "repository_pk": str(repository.pk)})
202
- return reverse('tasks-detail', args=[result.pk], request=None)
203
+ result = dispatch(
204
+ tasks.upload_group,
205
+ exclusive_resources=[artifact, repository],
206
+ kwargs={
207
+ "session_pk": str(cur_session.session_key),
208
+ "repository_pk": str(repository.pk),
209
+ },
210
+ )
211
+ return reverse("tasks-detail", args=[result.pk], request=None)
203
212
 
204
213
 
205
214
  class SimpleView(PackageUploadMixin, ViewSet):
@@ -227,21 +236,22 @@ class SimpleView(PackageUploadMixin, ViewSet):
227
236
  """Gets the simple api html page for the index."""
228
237
  repo_version, content = self.get_rvc()
229
238
  if self.should_redirect(repo_version=repo_version):
230
- return redirect(urljoin(self.base_content_url, f'{path}/simple/'))
231
- names = content.order_by('name').values_list('name', flat=True).distinct().iterator()
239
+ return redirect(urljoin(self.base_content_url, f"{path}/simple/"))
240
+ names = content.order_by("name").values_list("name", flat=True).distinct().iterator()
232
241
  return StreamingHttpResponse(write_simple_index(names, streamed=True))
233
242
 
234
243
  def pull_through_package_simple(self, package, path, remote):
235
244
  """Gets the package's simple page from remote."""
245
+
236
246
  def parse_url(link):
237
247
  parsed = urlparse(link.url)
238
- digest, _, value = parsed.fragment.partition('=')
248
+ digest, _, value = parsed.fragment.partition("=")
239
249
  stripped_url = urlunsplit(chain(parsed[:3], ("", "")))
240
- redirect = f'{path}/{link.text}?redirect={stripped_url}'
250
+ redirect = f"{path}/{link.text}?redirect={stripped_url}"
241
251
  d_url = urljoin(self.base_content_url, redirect)
242
- return link.text, d_url, value if digest == 'sha256' else ''
252
+ return link.text, d_url, value if digest == "sha256" else ""
243
253
 
244
- url = remote.get_remote_artifact_url(f'simple/{package}/')
254
+ url = remote.get_remote_artifact_url(f"simple/{package}/")
245
255
  kwargs = {}
246
256
  if proxy_url := remote.proxy_url:
247
257
  if remote.proxy_username or remote.proxy_password:
@@ -265,10 +275,10 @@ class SimpleView(PackageUploadMixin, ViewSet):
265
275
  if not repo_ver or not content.filter(name__normalize=normalized).exists():
266
276
  return self.pull_through_package_simple(normalized, path, self.distribution.remote)
267
277
  if self.should_redirect(repo_version=repo_ver):
268
- return redirect(urljoin(self.base_content_url, f'{path}/simple/{normalized}/'))
278
+ return redirect(urljoin(self.base_content_url, f"{path}/simple/{normalized}/"))
269
279
  packages = (
270
280
  content.filter(name__normalize=normalized)
271
- .values_list('filename', 'sha256', 'name')
281
+ .values_list("filename", "sha256", "name")
272
282
  .iterator()
273
283
  )
274
284
  try:
@@ -278,12 +288,14 @@ class SimpleView(PackageUploadMixin, ViewSet):
278
288
  else:
279
289
  packages = chain([present], packages)
280
290
  name = present[2]
281
- releases = ((f, urljoin(self.base_content_url, f'{path}/{f}'), d) for f, d, _ in packages)
291
+ releases = ((f, urljoin(self.base_content_url, f"{path}/{f}"), d) for f, d, _ in packages)
282
292
  return StreamingHttpResponse(write_simple_detail(name, releases, streamed=True))
283
293
 
284
- @extend_schema(request=PackageUploadSerializer,
285
- responses={200: PackageUploadTaskSerializer},
286
- summary="Upload a package")
294
+ @extend_schema(
295
+ request=PackageUploadSerializer,
296
+ responses={200: PackageUploadTaskSerializer},
297
+ summary="Upload a package",
298
+ )
287
299
  def create(self, request, path):
288
300
  """
289
301
  Upload package to the index.
@@ -308,9 +320,11 @@ class MetadataView(PyPIMixin, ViewSet):
308
320
  ],
309
321
  }
310
322
 
311
- @extend_schema(tags=["Pypi: Metadata"],
312
- responses={200: PackageMetadataSerializer},
313
- summary="Get package metadata")
323
+ @extend_schema(
324
+ tags=["Pypi: Metadata"],
325
+ responses={200: PackageMetadataSerializer},
326
+ summary="Get package metadata",
327
+ )
314
328
  def retrieve(self, request, path, meta):
315
329
  """
316
330
  Retrieves the package's core-metadata specified by
@@ -356,8 +370,7 @@ class PyPIView(PyPIMixin, ViewSet):
356
370
  ],
357
371
  }
358
372
 
359
- @extend_schema(responses={200: SummarySerializer},
360
- summary="Get index summary")
373
+ @extend_schema(responses={200: SummarySerializer}, summary="Get index summary")
361
374
  def retrieve(self, request, path):
362
375
  """Gets package summary stats of index."""
363
376
  repo_ver, content = self.get_rvc()
@@ -383,9 +396,11 @@ class UploadView(PackageUploadMixin, ViewSet):
383
396
  ],
384
397
  }
385
398
 
386
- @extend_schema(request=PackageUploadSerializer,
387
- responses={200: PackageUploadTaskSerializer},
388
- summary="Upload a package")
399
+ @extend_schema(
400
+ request=PackageUploadSerializer,
401
+ responses={200: PackageUploadTaskSerializer},
402
+ summary="Upload a package",
403
+ )
389
404
  def create(self, request, path):
390
405
  """
391
406
  Upload package to the index.
@@ -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