pulp-python 3.28.2__py3-none-any.whl → 3.29.0__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.
@@ -10,7 +10,7 @@ class PulpPythonPluginAppConfig(PulpPluginAppConfig):
10
10
 
11
11
  name = "pulp_python.app"
12
12
  label = "python"
13
- version = "3.28.2"
13
+ version = "3.29.0"
14
14
  python_package_name = "pulp-python"
15
15
  domain_compatible = True
16
16
 
@@ -0,0 +1,48 @@
1
+ # Generated by Django 5.2.10 on 2026-04-16 14:00
2
+
3
+ import django.db.models.deletion
4
+ import django_lifecycle.mixins
5
+ import pulpcore.app.models.base
6
+ from django.db import migrations, models
7
+
8
+
9
+ class Migration(migrations.Migration):
10
+
11
+ dependencies = [
12
+ ("python", "0021_pythonrepository_upload_duplicate_filenames"),
13
+ ]
14
+
15
+ operations = [
16
+ migrations.CreateModel(
17
+ name="PythonBlocklistEntry",
18
+ fields=[
19
+ (
20
+ "pulp_id",
21
+ models.UUIDField(
22
+ default=pulpcore.app.models.base.pulp_uuid,
23
+ editable=False,
24
+ primary_key=True,
25
+ serialize=False,
26
+ ),
27
+ ),
28
+ ("pulp_created", models.DateTimeField(auto_now_add=True)),
29
+ ("pulp_last_updated", models.DateTimeField(auto_now=True, null=True)),
30
+ ("name", models.TextField(default=None, null=True)),
31
+ ("version", models.TextField(default=None, null=True)),
32
+ ("filename", models.TextField(default=None, null=True)),
33
+ ("added_by", models.TextField(default="")),
34
+ (
35
+ "repository",
36
+ models.ForeignKey(
37
+ on_delete=django.db.models.deletion.CASCADE,
38
+ related_name="blocklist_entries",
39
+ to="python.pythonrepository",
40
+ ),
41
+ ),
42
+ ],
43
+ options={
44
+ "default_related_name": "%(app_label)s_%(model_name)s",
45
+ },
46
+ bases=(django_lifecycle.mixins.LifecycleModelMixin, models.Model),
47
+ ),
48
+ ]
pulp_python/app/models.py CHANGED
@@ -14,6 +14,7 @@ from django_lifecycle import (
14
14
  from rest_framework.serializers import ValidationError
15
15
  from pulpcore.plugin.models import (
16
16
  AutoAddObjPermsMixin,
17
+ BaseModel,
17
18
  Content,
18
19
  Publication,
19
20
  Distribution,
@@ -399,9 +400,12 @@ class PythonRepository(Repository, AutoAddObjPermsMixin):
399
400
 
400
401
  When allow_package_substitution is False, reject any new version that would implicitly
401
402
  replace existing content with different checksums (content substitution).
403
+
404
+ Also checks newly added content against the repository's blocklist entries.
402
405
  """
403
406
  if not self.allow_package_substitution:
404
407
  self._check_for_package_substitution(new_version)
408
+ self._check_blocklist(new_version)
405
409
  remove_duplicates(new_version)
406
410
  validate_repo_version(new_version)
407
411
 
@@ -418,3 +422,63 @@ class PythonRepository(Repository, AutoAddObjPermsMixin):
418
422
  "To allow this, set 'allow_package_substitution' to True on the repository. "
419
423
  f"Conflicting packages: {duplicates}"
420
424
  )
425
+
426
+ def _check_blocklist(self, new_version):
427
+ """
428
+ Check newly added content in a repository version against the blocklist.
429
+ """
430
+ added_content = PythonPackageContent.objects.filter(
431
+ pk__in=new_version.added().values_list("pk", flat=True)
432
+ ).only("filename", "name_normalized", "version")
433
+ if added_content.exists():
434
+ self.check_blocklist_for_packages(added_content)
435
+
436
+ def check_blocklist_for_packages(self, packages):
437
+ """
438
+ Raise a ValidationError if any of the given packages match a blocklist entry.
439
+ """
440
+ entries = PythonBlocklistEntry.objects.filter(repository=self)
441
+ if not entries.exists():
442
+ return
443
+
444
+ blocked = []
445
+ for pkg in packages:
446
+ for entry in entries:
447
+ if entry.filename and entry.filename == pkg.filename:
448
+ blocked.append(pkg.filename)
449
+ break
450
+ if entry.name == pkg.name_normalized:
451
+ if not entry.version or entry.version == pkg.version:
452
+ blocked.append(pkg.filename)
453
+ break
454
+ if blocked:
455
+ raise ValidationError(
456
+ "Blocklisted packages cannot be added to this repository: "
457
+ "{}".format(", ".join(blocked))
458
+ )
459
+
460
+
461
+ class PythonBlocklistEntry(BaseModel):
462
+ """
463
+ An entry in a PythonRepository's package blocklist.
464
+
465
+ Blocklist entries prevent packages from being added to the repository.
466
+ Entries can match by package `name` (all versions), package `name` + `version`,
467
+ or exact `filename`. Exactly one of `name` or `filename` must be provided.
468
+ """
469
+
470
+ name = models.TextField(null=True, default=None)
471
+ version = models.TextField(null=True, default=None)
472
+ filename = models.TextField(null=True, default=None)
473
+ added_by = models.TextField(default="")
474
+ repository = models.ForeignKey(
475
+ PythonRepository, on_delete=models.CASCADE, related_name="blocklist_entries"
476
+ )
477
+
478
+ def __str__(self):
479
+ if self.filename:
480
+ return f"<{self._meta.object_name}: {self.filename}>"
481
+ return f"<{self._meta.object_name}: {self.name} [{self.version or 'all'}]>"
482
+
483
+ class Meta:
484
+ default_related_name = "%(app_label)s_%(model_name)s"
@@ -6,6 +6,7 @@ from django.conf import settings
6
6
  from django.db.utils import IntegrityError
7
7
  from drf_spectacular.utils import extend_schema_serializer
8
8
  from packaging.requirements import Requirement
9
+ from packaging.version import Version, InvalidVersion
9
10
  from rest_framework import serializers
10
11
  from pypi_attestations import AttestationError
11
12
  from pydantic import TypeAdapter, ValidationError
@@ -13,9 +14,10 @@ from urllib.parse import urljoin
13
14
 
14
15
  from pulpcore.plugin import models as core_models
15
16
  from pulpcore.plugin import serializers as core_serializers
16
- from pulpcore.plugin.util import get_domain, get_prn, get_current_authenticated_user
17
+ from pulpcore.plugin.util import get_domain, get_prn, get_current_authenticated_user, reverse
17
18
 
18
19
  from pulp_python.app import models as python_models
20
+ from pulp_python.app.utils import canonicalize_name
19
21
  from pulp_python.app.provenance import (
20
22
  Attestation,
21
23
  Provenance,
@@ -53,6 +55,11 @@ class PythonRepositorySerializer(core_serializers.RepositorySerializer):
53
55
  default=False,
54
56
  required=False,
55
57
  )
58
+ blocklist_entries_href = serializers.SerializerMethodField(
59
+ help_text=_("URL to the blocklist entries for this repository."),
60
+ read_only=True,
61
+ )
62
+
56
63
  allow_package_substitution = serializers.BooleanField(
57
64
  help_text=_(
58
65
  "Whether to allow package substitution (replacing existing packages with packages "
@@ -65,10 +72,15 @@ class PythonRepositorySerializer(core_serializers.RepositorySerializer):
65
72
  required=False,
66
73
  )
67
74
 
75
+ def get_blocklist_entries_href(self, obj):
76
+ repo_href = reverse("repositories-python/python-detail", kwargs={"pk": obj.pk})
77
+ return f"{repo_href}blocklist_entries/"
78
+
68
79
  class Meta:
69
80
  fields = core_serializers.RepositorySerializer.Meta.fields + (
70
81
  "autopublish",
71
82
  "allow_package_substitution",
83
+ "blocklist_entries_href",
72
84
  )
73
85
  model = python_models.PythonRepository
74
86
 
@@ -780,6 +792,115 @@ class PythonRemoteSerializer(core_serializers.RemoteSerializer):
780
792
  model = python_models.PythonRemote
781
793
 
782
794
 
795
+ class PythonBlocklistEntrySerializer(core_serializers.ModelSerializer):
796
+ """
797
+ Serializer for PythonBlocklistEntry.
798
+
799
+ The `repository` is supplied by the URL (not the request body) and is injected
800
+ by the viewset before saving.
801
+ """
802
+
803
+ pulp_href = serializers.SerializerMethodField(
804
+ read_only=True,
805
+ help_text=_("The URL of this blocklist entry."),
806
+ )
807
+ repository = core_serializers.DetailRelatedField(
808
+ read_only=True,
809
+ view_name_pattern=r"repositories(-.*/.*)?-detail",
810
+ help_text=_("Repository this blocklist entry belongs to."),
811
+ )
812
+ name = serializers.CharField(
813
+ required=False,
814
+ allow_null=True,
815
+ default=None,
816
+ help_text=_(
817
+ "Package name to block (for all versions). Compared after PEP 503 normalization. "
818
+ "Required when 'filename' is not provided."
819
+ ),
820
+ )
821
+ version = serializers.CharField(
822
+ required=False,
823
+ allow_null=True,
824
+ default=None,
825
+ help_text=_("Exact version string to block (e.g. '1.0'). Only used when 'name' is set."),
826
+ )
827
+ filename = serializers.CharField(
828
+ required=False,
829
+ allow_null=True,
830
+ default=None,
831
+ help_text=_("Exact filename to block. Required when 'name' is not provided."),
832
+ )
833
+ added_by = serializers.CharField(
834
+ read_only=True,
835
+ help_text=_("PRN of the user who added this blocklist entry."),
836
+ )
837
+
838
+ def get_pulp_href(self, obj):
839
+ repo_href = reverse("repositories-python/python-detail", kwargs={"pk": obj.repository_id})
840
+ return f"{repo_href}blocklist_entries/{obj.pk}/"
841
+
842
+ def validate(self, data):
843
+ """
844
+ Validate that the blocklist entry is well-formed and not a duplicate.
845
+ """
846
+ name = data.get("name")
847
+ filename = data.get("filename")
848
+ version = data.get("version")
849
+
850
+ if version and filename:
851
+ raise serializers.ValidationError(_("'version' cannot be used with 'filename'."))
852
+ if version and not name:
853
+ raise serializers.ValidationError(_("'version' requires 'name' to be provided."))
854
+ if bool(name) == bool(filename):
855
+ raise serializers.ValidationError(
856
+ _("Exactly one of 'name' or 'filename' must be provided.")
857
+ )
858
+
859
+ if version:
860
+ try:
861
+ Version(version)
862
+ except InvalidVersion:
863
+ raise serializers.ValidationError(
864
+ {"version": _("'{}' is not a valid version.").format(version)}
865
+ )
866
+ if name:
867
+ data["name"] = canonicalize_name(name)
868
+ name = data["name"]
869
+
870
+ repository = self.context.get("repository")
871
+ if repository:
872
+ qs = python_models.PythonBlocklistEntry.objects.filter(repository=repository)
873
+ if name and qs.filter(name=name, version=version).exists():
874
+ raise serializers.ValidationError(
875
+ _("A blocklist entry with this name and version already exists.")
876
+ )
877
+ if filename and qs.filter(filename=filename).exists():
878
+ raise serializers.ValidationError(
879
+ _("A blocklist entry with this filename already exists.")
880
+ )
881
+ data["repository"] = repository
882
+
883
+ return data
884
+
885
+ def create(self, validated_data):
886
+ """
887
+ Create a new blocklist entry, recording the authenticated user in `added_by`.
888
+ """
889
+ user = get_current_authenticated_user()
890
+ validated_data["added_by"] = get_prn(user) if user else ""
891
+ return super().create(validated_data)
892
+
893
+ class Meta:
894
+ fields = core_serializers.ModelSerializer.Meta.fields + (
895
+ "repository",
896
+ "name",
897
+ "version",
898
+ "filename",
899
+ "added_by",
900
+ )
901
+ model = python_models.PythonBlocklistEntry
902
+
903
+
783
904
  class PythonBanderRemoteSerializer(serializers.Serializer):
784
905
  """
785
906
  A Serializer for the initial step of creating a Python Remote from a Bandersnatch config file
@@ -7,6 +7,12 @@ from packaging.utils import canonicalize_name
7
7
  from pathlib import Path
8
8
  from rest_framework import status
9
9
  from rest_framework.decorators import action
10
+ from rest_framework.mixins import (
11
+ CreateModelMixin,
12
+ DestroyModelMixin,
13
+ ListModelMixin,
14
+ RetrieveModelMixin,
15
+ )
10
16
  from rest_framework.response import Response
11
17
  from rest_framework.serializers import ValidationError
12
18
 
@@ -143,15 +149,20 @@ class PythonRepositoryViewSet(
143
149
  If allow_package_substitution is False and the request is **only** adding packages, then a
144
150
  package substitution check is performed to provide a quicker error response. Otherwise, the
145
151
  check is delegated to the task.
152
+
153
+ Also performs an early blocklist check on added packages.
146
154
  """
147
155
  repository = self.get_object()
156
+ add_content_units = request.data.get("add_content_units", [])
157
+ content_ids = [extract_pk(x) for x in add_content_units]
158
+
159
+ self._early_blocklist_check(repository, content_ids)
160
+
148
161
  if not repository.allow_package_substitution:
149
162
  remove_content_units = request.data.get("remove_content_units", [])
150
163
  if remove_content_units or "base_version" in request.data:
151
164
  return super().modify(request, pk)
152
165
  rvc = repository.latest_version().content
153
- add_content_units = request.data.get("add_content_units", [])
154
- content_ids = [extract_pk(x) for x in add_content_units]
155
166
  packages = (
156
167
  python_models.PythonPackageContent.objects.filter(pk__in=content_ids)
157
168
  .exclude(pk__in=rvc)
@@ -167,6 +178,17 @@ class PythonRepositoryViewSet(
167
178
  )
168
179
  return super().modify(request, pk)
169
180
 
181
+ def _early_blocklist_check(self, repository, content_ids):
182
+ """
183
+ Raise early if any added packages match a blocklist entry.
184
+ """
185
+ if not content_ids:
186
+ return
187
+ packages = python_models.PythonPackageContent.objects.filter(pk__in=content_ids).only(
188
+ "filename", "name_normalized", "version"
189
+ )
190
+ repository.check_blocklist_for_packages(packages)
191
+
170
192
  @extend_schema(
171
193
  summary="Repair metadata",
172
194
  responses={202: AsyncOperationResponseSerializer},
@@ -216,6 +238,60 @@ class PythonRepositoryViewSet(
216
238
  return core_viewsets.OperationPostponedResponse(result, request)
217
239
 
218
240
 
241
+ class PythonBlocklistEntryViewSet(
242
+ core_viewsets.NamedModelViewSet,
243
+ CreateModelMixin,
244
+ RetrieveModelMixin,
245
+ ListModelMixin,
246
+ DestroyModelMixin,
247
+ ):
248
+ """
249
+ ViewSet for managing blocklist entries on a PythonRepository.
250
+
251
+ Blocklist entries prevent packages from being added to the repository.
252
+ Entries can match by package `name` (all versions), package `name` + `version`,
253
+ or exact `filename`. Exactly one of `name` or `filename` must be provided.
254
+ """
255
+
256
+ endpoint_name = "blocklist_entries"
257
+ router_lookup = "pythonblocklistentry"
258
+ parent_viewset = PythonRepositoryViewSet
259
+ parent_lookup_kwargs = {"repository_pk": "repository__pk"}
260
+ serializer_class = python_serializers.PythonBlocklistEntrySerializer
261
+ queryset = python_models.PythonBlocklistEntry.objects.all()
262
+ ordering = ("-pulp_created",)
263
+
264
+ DEFAULT_ACCESS_POLICY = {
265
+ "statements": [
266
+ {
267
+ "action": ["list", "retrieve"],
268
+ "principal": "authenticated",
269
+ "effect": "allow",
270
+ "condition": "has_repository_model_or_domain_or_obj_perms:python.view_pythonrepository", # noqa: E501
271
+ },
272
+ {
273
+ "action": ["create", "destroy"],
274
+ "principal": "authenticated",
275
+ "effect": "allow",
276
+ "condition": [
277
+ "has_repository_model_or_domain_or_obj_perms:python.modify_pythonrepository",
278
+ "has_repository_model_or_domain_or_obj_perms:python.view_pythonrepository",
279
+ ],
280
+ },
281
+ ],
282
+ }
283
+
284
+ def get_serializer_context(self):
285
+ """
286
+ Inject the parent repository into the serializer context so that `validate()` can check for
287
+ duplicate entries. The guard on `repository_pk` prevents errors during schema generation.
288
+ """
289
+ context = super().get_serializer_context()
290
+ if self.kwargs.get("repository_pk"):
291
+ context["repository"] = self.get_parent_object()
292
+ return context
293
+
294
+
219
295
  class PythonRepositoryVersionViewSet(core_viewsets.RepositoryVersionViewSet):
220
296
  """
221
297
  PythonRepositoryVersion represents a single Python repository version.
@@ -0,0 +1,151 @@
1
+ import pytest
2
+
3
+ from pulpcore.tests.functional.utils import PulpTaskError
4
+ from pulp_python.tests.functional.constants import PYTHON_EGG_FILENAME, PYTHON_EGG_URL
5
+
6
+ CONTENT_BODY = {"relative_path": PYTHON_EGG_FILENAME, "file_url": PYTHON_EGG_URL}
7
+ BLOCKED_MSG = "Blocklisted packages cannot be added to this repository"
8
+
9
+
10
+ @pytest.mark.parallel
11
+ def test_crd_entry(python_bindings, python_repo):
12
+ """
13
+ CRD operations on blocklist entries return correct fields and update the entry count.
14
+ """
15
+ entries_data = [
16
+ ({"name": "shelf-reader"}, "shelf-reader", None, None),
17
+ ({"name": "shelf-reader", "version": "0.1"}, "shelf-reader", "0.1", None),
18
+ ({"filename": PYTHON_EGG_FILENAME}, None, None, PYTHON_EGG_FILENAME),
19
+ ]
20
+ for body_kwargs, name, version, filename in entries_data:
21
+ entry = python_bindings.RepositoriesPythonBlocklistEntriesApi.create(
22
+ python_repo.pulp_href, python_bindings.PythonPythonBlocklistEntry(**body_kwargs)
23
+ )
24
+ assert entry.name == name
25
+ assert entry.version == version
26
+ assert entry.filename == filename
27
+ assert entry.added_by == "prn:auth.user:1"
28
+ assert entry.pulp_href is not None
29
+ assert entry.prn is not None
30
+
31
+ result = python_bindings.RepositoriesPythonBlocklistEntriesApi.list(python_repo.pulp_href)
32
+ assert result.count == 3
33
+
34
+ entry = result.results[0]
35
+ python_bindings.RepositoriesPythonBlocklistEntriesApi.read(entry.pulp_href)
36
+
37
+ python_bindings.RepositoriesPythonBlocklistEntriesApi.delete(entry.pulp_href)
38
+ result = python_bindings.RepositoriesPythonBlocklistEntriesApi.list(python_repo.pulp_href)
39
+ assert result.count == 2
40
+
41
+
42
+ @pytest.mark.parallel
43
+ @pytest.mark.parametrize(
44
+ "body_kwargs, expected_msg",
45
+ [
46
+ ({"name": "shelf-reader"}, "this name and version already exists"),
47
+ ({"name": "shelf-reader", "version": "0.1"}, "this name and version already exists"),
48
+ ({"filename": PYTHON_EGG_FILENAME}, "this filename already exists"),
49
+ ],
50
+ )
51
+ def test_duplicate_entry_rejected(python_bindings, python_repo, body_kwargs, expected_msg):
52
+ """
53
+ Creating a duplicate entry should fail.
54
+ """
55
+ python_bindings.RepositoriesPythonBlocklistEntriesApi.create(
56
+ python_repo.pulp_href, python_bindings.PythonPythonBlocklistEntry(**body_kwargs)
57
+ )
58
+ with pytest.raises(python_bindings.ApiException) as ctx:
59
+ python_bindings.RepositoriesPythonBlocklistEntriesApi.create(
60
+ python_repo.pulp_href, python_bindings.PythonPythonBlocklistEntry(**body_kwargs)
61
+ )
62
+ assert ctx.value.status == 400
63
+ assert expected_msg in ctx.value.body
64
+
65
+
66
+ @pytest.mark.parallel
67
+ @pytest.mark.parametrize(
68
+ "body_kwargs, expected_msg",
69
+ [
70
+ ({"version": "0.1", "filename": PYTHON_EGG_FILENAME}, "version' cannot be used with"),
71
+ ({"version": "0.1"}, "version' requires 'name'"),
72
+ ({"name": "shelf-reader", "filename": PYTHON_EGG_FILENAME}, "Exactly one of"),
73
+ ({}, "Exactly one of"),
74
+ ({"name": "shelf-reader", "version": "not-a-version"}, "not a valid version"),
75
+ ],
76
+ )
77
+ def test_invalid_entry_rejected(python_bindings, python_repo, body_kwargs, expected_msg):
78
+ """
79
+ Creating an entry with invalid data should fail.
80
+ """
81
+ with pytest.raises(python_bindings.ApiException) as ctx:
82
+ python_bindings.RepositoriesPythonBlocklistEntriesApi.create(
83
+ python_repo.pulp_href, python_bindings.PythonPythonBlocklistEntry(**body_kwargs)
84
+ )
85
+ assert ctx.value.status == 400
86
+ assert expected_msg in ctx.value.body
87
+
88
+
89
+ @pytest.mark.parallel
90
+ def test_upload_blocked(monitor_task, python_bindings, python_repo):
91
+ """
92
+ Uploading a package matching a blocklist entry is rejected.
93
+ """
94
+ python_bindings.RepositoriesPythonBlocklistEntriesApi.create(
95
+ python_repo.pulp_href,
96
+ python_bindings.PythonPythonBlocklistEntry(name="shelf-reader", version="0.1"),
97
+ )
98
+
99
+ with pytest.raises(PulpTaskError) as exc:
100
+ response = python_bindings.ContentPackagesApi.create(
101
+ repository=python_repo.pulp_href, **CONTENT_BODY
102
+ )
103
+ monitor_task(response.task)
104
+ assert BLOCKED_MSG in exc.value.task.error["description"]
105
+
106
+ repo = python_bindings.RepositoriesPythonApi.read(python_repo.pulp_href)
107
+ assert repo.latest_version_href.endswith("/0/")
108
+
109
+
110
+ @pytest.mark.parallel
111
+ def test_upload_allowed(monitor_task, python_bindings, python_repo):
112
+ """
113
+ Uploading a package is allowed when the blocklist entry targets a different version.
114
+ """
115
+ python_bindings.RepositoriesPythonBlocklistEntriesApi.create(
116
+ python_repo.pulp_href,
117
+ python_bindings.PythonPythonBlocklistEntry(name="shelf-reader", version="9.9"),
118
+ )
119
+
120
+ response = python_bindings.ContentPackagesApi.create(
121
+ repository=python_repo.pulp_href, **CONTENT_BODY
122
+ )
123
+ monitor_task(response.task)
124
+
125
+ repo = python_bindings.RepositoriesPythonApi.read(python_repo.pulp_href)
126
+ assert repo.latest_version_href.endswith("/1/")
127
+
128
+
129
+ @pytest.mark.parallel
130
+ def test_modify_blocked(monitor_task, python_bindings, python_repo):
131
+ """
132
+ Adding a blocklisted package via repository modify is rejected.
133
+ """
134
+ python_bindings.RepositoriesPythonBlocklistEntriesApi.create(
135
+ python_repo.pulp_href,
136
+ python_bindings.PythonPythonBlocklistEntry(name="shelf-reader", version="0.1"),
137
+ )
138
+
139
+ response = python_bindings.ContentPackagesApi.create(**CONTENT_BODY)
140
+ task = monitor_task(response.task)
141
+ content = python_bindings.ContentPackagesApi.read(task.created_resources[0])
142
+
143
+ with pytest.raises(python_bindings.ApiException) as exc:
144
+ python_bindings.RepositoriesPythonApi.modify(
145
+ python_repo.pulp_href, {"add_content_units": [content.pulp_href]}
146
+ )
147
+ assert exc.value.status == 400
148
+ assert BLOCKED_MSG in exc.value.body
149
+
150
+ repo = python_bindings.RepositoriesPythonApi.read(python_repo.pulp_href)
151
+ assert repo.latest_version_href.endswith("/0/")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pulp-python
3
- Version: 3.28.2
3
+ Version: 3.29.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
@@ -1,16 +1,16 @@
1
1
  pulp_python/__init__.py,sha256=GIuTLoBTc-07dSLJUh8xrZPRz8x-jJ61pfR0J1IjnzI,65
2
2
  pulp_python/pytest_plugin.py,sha256=EBivPJa39ZE1QaDWea9candeeJL_W_cBk6Q7Onhw4bE,8604
3
- pulp_python/app/__init__.py,sha256=mLATRQDvq2slgcrsrT6cRO5JQ0UYd5M5jb7u1wHqA1A,2488
3
+ pulp_python/app/__init__.py,sha256=rWQatXxN5F1fdkajaunRkP5PUdVrWTH0qCZGU8jRHuw,2488
4
4
  pulp_python/app/global_access_conditions.py,sha256=MZJtyoVsr-4hRaty6mKDqh3caOHd5UKJjEWLV2crOLs,1080
5
5
  pulp_python/app/modelresource.py,sha256=dogoBWibBmQyFpcV-Hp1lu7D2WwSECa5PEShWSIg7mg,1248
6
- pulp_python/app/models.py,sha256=954pFPP05zbwRQmicA0NxFT3VIEur17HFOxPGSYBgp0,15218
6
+ pulp_python/app/models.py,sha256=1lyO4Po_vTS8prrtBL1Lo5qh12fUQpvX6H3x2kVOCrY,17685
7
7
  pulp_python/app/provenance.py,sha256=iyhkuNahHiTDK0Djrd4-PlgErA5SJVI0uQOIPj46tEI,2352
8
8
  pulp_python/app/replica.py,sha256=W60Kt9CwZSfztRi3-f82Rfp0F_zhzpOTiz35bvRFBBE,1881
9
- pulp_python/app/serializers.py,sha256=67D1fGg-GxD3iQlEo-TI-3JOyaI2-VHqU_UNn9RDha8,29433
9
+ pulp_python/app/serializers.py,sha256=Pulopm6BFfDrH4sUmt2x1ZI7mLzHtrQrOp3K9ILrxlw,33853
10
10
  pulp_python/app/settings.py,sha256=Cyc_p6U0HQjKpyrRL6JFrK3R7RMQJ9MAgNMJCfzPEiA,255
11
11
  pulp_python/app/urls.py,sha256=3-UG9-bkPW_bs8362XBnxbpGqQetyuMOfPaayadMYFo,1387
12
12
  pulp_python/app/utils.py,sha256=2MEHK_fuLpZA9w4ohbhcs1D2ii478gwW_iAuSTsu-kw,26740
13
- pulp_python/app/viewsets.py,sha256=yAXvuHNLdOkLznwiqIPSQPKf8-ttPGsmWy-mG1cbO38,31410
13
+ pulp_python/app/viewsets.py,sha256=8-FPEgNGY_KEaoJbdE_N5hQg-KOe--OXvxQZ6p6wFhE,34120
14
14
  pulp_python/app/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  pulp_python/app/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  pulp_python/app/management/commands/repair-python-metadata.py,sha256=i-mC3UYxIiHJoknRV4xZZ7cQ44lyqWshpbaeYi7as8E,4457
@@ -36,6 +36,7 @@ pulp_python/app/migrations/0018_packageprovenance.py,sha256=FU67aw6u77TjlPuzxRrR
36
36
  pulp_python/app/migrations/0019_create_missing_metadata_artifacts.py,sha256=glHecwu0X17RdRHo1MvjDRvrqpJ7EgxvYXy1FOXJvn4,817
37
37
  pulp_python/app/migrations/0020_pythonpackagecontent_name_normalized.py,sha256=n-gtLKmhDiydgkM5Fv8OisoFZYPPOhOa8-9nExCCq14,1317
38
38
  pulp_python/app/migrations/0021_pythonrepository_upload_duplicate_filenames.py,sha256=Yx-Cgk8DR9J9e9ht00xy0s6ogh2Rh5JS7AbdzSngKLY,384
39
+ pulp_python/app/migrations/0022_pythonblocklistentry.py,sha256=EbtjZuN65myTAHVWrJYbt4mWgWL7EElEYs1XulHB6Bo,1732
39
40
  pulp_python/app/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
41
  pulp_python/app/pypi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
42
  pulp_python/app/pypi/serializers.py,sha256=w6_doLpaO8Dnzur57MYrTIqEP4I3LI8biu9ipLC66fk,5028
@@ -56,6 +57,7 @@ pulp_python/tests/functional/utils.py,sha256=ZmOVSa94o0KvH7l42YwuUAcNJo8Eb733QBH
56
57
  pulp_python/tests/functional/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
58
  pulp_python/tests/functional/api/test_attestations.py,sha256=_opUZT65c5H2j_8ous-nKGHmDOw8EMjxdooTIQhiXQ8,8640
58
59
  pulp_python/tests/functional/api/test_auto_publish.py,sha256=uCIt4LsO61oMk3bDs3LMQDJI8zkKqvY0b1uX16bTxzM,1747
60
+ pulp_python/tests/functional/api/test_blocklist.py,sha256=ichJo5UxDdWcR3pnuRouAsPLVBZ7Wcss7oB-AJEV-s8,5993
59
61
  pulp_python/tests/functional/api/test_consume_content.py,sha256=QUOZ_bQ_Ortzitc7sjlMEJzRhPm00wayxjZvEeK18jI,1000
60
62
  pulp_python/tests/functional/api/test_crud_content_unit.py,sha256=IjkyVC761ZTOhdM3ksT_5zvvWhMk326BSJlUyql0vFM,13793
61
63
  pulp_python/tests/functional/api/test_crud_publications.py,sha256=uoTGHbhKq_mYOYtNV8Tt9cyhzY0zsoG1DhYrfIdYcMc,5646
@@ -75,9 +77,9 @@ pulp_python/tests/functional/assets/shelf-reader-0.1.tar.gz.publish.attestation,
75
77
  pulp_python/tests/functional/assets/shelf_reader-0.1-py2-none-any.whl.publish.attestation,sha256=muTQ8dqYSSdx76DlaPjB1REcNIS-aak-Na0TkASxu8M,10426
76
78
  pulp_python/tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
77
79
  pulp_python/tests/unit/test_models.py,sha256=TBI0yKsrdbnJSPeBFfxSqhXK7zaNvR6qg5JehGH3Pds,229
78
- pulp_python-3.28.2.dist-info/licenses/LICENSE,sha256=2ylvL381vKOhdO-w6zkrOxe9lLNBhRQpo9_0EbHC_HM,18046
79
- pulp_python-3.28.2.dist-info/METADATA,sha256=6tb2negw7mKY5EJ913yNvQg5fRJVBB8qc--Eo4atRIo,1744
80
- pulp_python-3.28.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
81
- pulp_python-3.28.2.dist-info/entry_points.txt,sha256=HvqLEXjw_dS5jqAwnE5JiRZFE6f-y5SRtitKLPml2To,115
82
- pulp_python-3.28.2.dist-info/top_level.txt,sha256=X0hXgXc_bpbiKqVrkt8jD5_QEiQviKbHDwveQcOcJjo,12
83
- pulp_python-3.28.2.dist-info/RECORD,,
80
+ pulp_python-3.29.0.dist-info/licenses/LICENSE,sha256=2ylvL381vKOhdO-w6zkrOxe9lLNBhRQpo9_0EbHC_HM,18046
81
+ pulp_python-3.29.0.dist-info/METADATA,sha256=alLnLZtMqhz4pl-EyaETY-NOOrRG7ziRJEQFO4bcfms,1744
82
+ pulp_python-3.29.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
83
+ pulp_python-3.29.0.dist-info/entry_points.txt,sha256=HvqLEXjw_dS5jqAwnE5JiRZFE6f-y5SRtitKLPml2To,115
84
+ pulp_python-3.29.0.dist-info/top_level.txt,sha256=X0hXgXc_bpbiKqVrkt8jD5_QEiQviKbHDwveQcOcJjo,12
85
+ pulp_python-3.29.0.dist-info/RECORD,,