pulp-python 3.18.0__tar.gz → 3.19.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. {pulp_python-3.18.0 → pulp_python-3.19.0}/CHANGES.md +9 -0
  2. {pulp_python-3.18.0 → pulp_python-3.19.0}/PKG-INFO +2 -2
  3. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/__init__.py +1 -1
  4. pulp_python-3.19.0/pulp_python/app/migrations/0015_alter_pythonpackagecontent_options.py +22 -0
  5. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/models.py +3 -0
  6. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/serializers.py +80 -5
  7. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/utils.py +20 -19
  8. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/viewsets.py +40 -0
  9. pulp_python-3.19.0/pulp_python/tests/functional/api/test_upload.py +44 -0
  10. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python.egg-info/PKG-INFO +2 -2
  11. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python.egg-info/SOURCES.txt +2 -0
  12. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python.egg-info/requires.txt +1 -1
  13. {pulp_python-3.18.0 → pulp_python-3.19.0}/pyproject.toml +3 -3
  14. {pulp_python-3.18.0 → pulp_python-3.19.0}/COMMITMENT +0 -0
  15. {pulp_python-3.18.0 → pulp_python-3.19.0}/COPYRIGHT +0 -0
  16. {pulp_python-3.18.0 → pulp_python-3.19.0}/LICENSE +0 -0
  17. {pulp_python-3.18.0 → pulp_python-3.19.0}/MANIFEST.in +0 -0
  18. {pulp_python-3.18.0 → pulp_python-3.19.0}/README.md +0 -0
  19. {pulp_python-3.18.0 → pulp_python-3.19.0}/functest_requirements.txt +0 -0
  20. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/__init__.py +0 -0
  21. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/global_access_conditions.py +0 -0
  22. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/management/__init__.py +0 -0
  23. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/management/commands/__init__.py +0 -0
  24. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/management/commands/repair-python-metadata.py +0 -0
  25. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/migrations/0001_initial.py +0 -0
  26. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/migrations/0001_squashed_0010_update_json_field.py +0 -0
  27. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/migrations/0002_pythonpackagecontent_python_version.py +0 -0
  28. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/migrations/0003_new_sync_filters.py +0 -0
  29. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/migrations/0004_DATA_swap_distribution_model.py +0 -0
  30. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/migrations/0005_pythonpackagecontent_sha256.py +0 -0
  31. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/migrations/0006_pythonrepository_autopublish.py +0 -0
  32. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/migrations/0007_pythonpackagecontent_mv-2-1.py +0 -0
  33. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/migrations/0008_pythonpackagecontent_unique_sha256.py +0 -0
  34. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/migrations/0009_pythondistribution_allow_uploads.py +0 -0
  35. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/migrations/0010_update_json_field.py +0 -0
  36. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/migrations/0011_alter_pythondistribution_distribution_ptr_and_more.py +0 -0
  37. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/migrations/0012_add_domain.py +0 -0
  38. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/migrations/0013_add_rbac_permissions.py +0 -0
  39. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/migrations/0014_pythonpackagecontent_dynamic_and_more.py +0 -0
  40. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/migrations/__init__.py +0 -0
  41. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/modelresource.py +0 -0
  42. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/pypi/__init__.py +0 -0
  43. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/pypi/serializers.py +0 -0
  44. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/pypi/views.py +0 -0
  45. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/replica.py +0 -0
  46. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/settings.py +0 -0
  47. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/tasks/__init__.py +0 -0
  48. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/tasks/publish.py +0 -0
  49. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/tasks/repair.py +0 -0
  50. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/tasks/sync.py +0 -0
  51. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/tasks/upload.py +0 -0
  52. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/urls.py +0 -0
  53. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/webserver_snippets/__init__.py +0 -0
  54. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/webserver_snippets/apache.conf +0 -0
  55. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/app/webserver_snippets/nginx.conf +0 -0
  56. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/pytest_plugin.py +0 -0
  57. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/tests/__init__.py +0 -0
  58. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/tests/functional/__init__.py +0 -0
  59. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/tests/functional/api/__init__.py +0 -0
  60. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/tests/functional/api/test_auto_publish.py +0 -0
  61. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/tests/functional/api/test_consume_content.py +0 -0
  62. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/tests/functional/api/test_crud_content_unit.py +0 -0
  63. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/tests/functional/api/test_crud_publications.py +0 -0
  64. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/tests/functional/api/test_crud_remotes.py +0 -0
  65. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/tests/functional/api/test_domains.py +0 -0
  66. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/tests/functional/api/test_download_content.py +0 -0
  67. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/tests/functional/api/test_export_import.py +0 -0
  68. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/tests/functional/api/test_full_mirror.py +0 -0
  69. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/tests/functional/api/test_pypi_apis.py +0 -0
  70. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/tests/functional/api/test_rbac.py +0 -0
  71. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/tests/functional/api/test_repair.py +0 -0
  72. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/tests/functional/api/test_sync.py +0 -0
  73. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/tests/functional/constants.py +0 -0
  74. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/tests/functional/utils.py +0 -0
  75. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/tests/unit/__init__.py +0 -0
  76. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python/tests/unit/test_models.py +0 -0
  77. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python.egg-info/dependency_links.txt +0 -0
  78. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python.egg-info/entry_points.txt +0 -0
  79. {pulp_python-3.18.0 → pulp_python-3.19.0}/pulp_python.egg-info/top_level.txt +0 -0
  80. {pulp_python-3.18.0 → pulp_python-3.19.0}/setup.cfg +0 -0
  81. {pulp_python-3.18.0 → pulp_python-3.19.0}/test_requirements.txt +0 -0
  82. {pulp_python-3.18.0 → pulp_python-3.19.0}/unittest_requirements.txt +0 -0
@@ -8,6 +8,15 @@
8
8
 
9
9
  [//]: # (towncrier release notes start)
10
10
 
11
+ ## 3.19.0 (2025-08-26) {: #3.19.0 }
12
+
13
+ #### Features {: #3.19.0-feature }
14
+
15
+ - Added a synchronous upload API.
16
+ [#933](https://github.com/pulp/pulp_python/issues/933)
17
+
18
+ ---
19
+
11
20
  ## 3.18.0 (2025-08-13) {: #3.18.0 }
12
21
 
13
22
  #### Features {: #3.18.0-feature }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pulp-python
3
- Version: 3.18.0
3
+ Version: 3.19.0
4
4
  Summary: pulp-python plugin for the Pulp Project
5
5
  Author-email: Pulp Team <pulp-list@redhat.com>
6
6
  Project-URL: Homepage, https://pulpproject.org
@@ -20,7 +20,7 @@ Classifier: Programming Language :: Python :: 3.13
20
20
  Requires-Python: >=3.11
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
- Requires-Dist: pulpcore<3.100,>=3.49.0
23
+ Requires-Dist: pulpcore<3.100,>=3.81.0
24
24
  Requires-Dist: pkginfo<1.13.0,>=1.12.0
25
25
  Requires-Dist: bandersnatch<6.6,>=6.3.0
26
26
  Requires-Dist: pypi-simple<2.0,>=1.5.0
@@ -10,7 +10,7 @@ class PulpPythonPluginAppConfig(PulpPluginAppConfig):
10
10
 
11
11
  name = "pulp_python.app"
12
12
  label = "python"
13
- version = "3.18.0"
13
+ version = "3.19.0"
14
14
  python_package_name = "pulp-python"
15
15
  domain_compatible = True
16
16
 
@@ -0,0 +1,22 @@
1
+ # Generated by Django 4.2.23 on 2025-08-19 17:10
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("python", "0014_pythonpackagecontent_dynamic_and_more"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterModelOptions(
14
+ name="pythonpackagecontent",
15
+ options={
16
+ "default_related_name": "%(app_label)s_%(model_name)s",
17
+ "permissions": [
18
+ ("upload_python_packages", "Can upload Python packages using synchronous API.")
19
+ ],
20
+ },
21
+ ),
22
+ ]
@@ -227,6 +227,9 @@ class PythonPackageContent(Content):
227
227
  class Meta:
228
228
  default_related_name = "%(app_label)s_%(model_name)s"
229
229
  unique_together = ("sha256", "_pulp_domain")
230
+ permissions = [
231
+ ("upload_python_packages", "Can upload Python packages using synchronous API."),
232
+ ]
230
233
 
231
234
 
232
235
  class PythonPublication(Publication, AutoAddObjPermsMixin):
@@ -1,5 +1,8 @@
1
+ import logging
2
+ import os
1
3
  from gettext import gettext as _
2
4
  from django.conf import settings
5
+ from django.db.utils import IntegrityError
3
6
  from packaging.requirements import Requirement
4
7
  from rest_framework import serializers
5
8
 
@@ -8,7 +11,15 @@ from pulpcore.plugin import serializers as core_serializers
8
11
  from pulpcore.plugin.util import get_domain
9
12
 
10
13
  from pulp_python.app import models as python_models
11
- from pulp_python.app.utils import artifact_to_python_content_data
14
+ from pulp_python.app.utils import (
15
+ DIST_EXTENSIONS,
16
+ artifact_to_python_content_data,
17
+ get_project_metadata_from_file,
18
+ parse_project_metadata,
19
+ )
20
+
21
+
22
+ log = logging.getLogger(__name__)
12
23
 
13
24
 
14
25
  class PythonRepositorySerializer(core_serializers.RepositorySerializer):
@@ -207,7 +218,7 @@ class PythonPackageContentSerializer(core_serializers.SingleArtifactContentUploa
207
218
  required=False,
208
219
  allow_blank=True,
209
220
  help_text=_(
210
- "The Python version(s) that the distribution is guaranteed to be " "compatible with."
221
+ "The Python version(s) that the distribution is guaranteed to be compatible with."
211
222
  ),
212
223
  )
213
224
  # Version 2.1
@@ -215,7 +226,7 @@ class PythonPackageContentSerializer(core_serializers.SingleArtifactContentUploa
215
226
  required=False,
216
227
  allow_blank=True,
217
228
  help_text=_(
218
- "A string stating the markup syntax (if any) used in the distributions"
229
+ "A string stating the markup syntax (if any) used in the distribution's"
219
230
  " description, so that tools can intelligently render the description."
220
231
  ),
221
232
  )
@@ -256,7 +267,7 @@ class PythonPackageContentSerializer(core_serializers.SingleArtifactContentUploa
256
267
  )
257
268
  packagetype = serializers.CharField(
258
269
  help_text=_(
259
- "The type of the distribution package " "(e.g. sdist, bdist_wheel, bdist_egg, etc)"
270
+ "The type of the distribution package (e.g. sdist, bdist_wheel, bdist_egg, etc)"
260
271
  ),
261
272
  read_only=True,
262
273
  )
@@ -357,6 +368,70 @@ class PythonPackageContentSerializer(core_serializers.SingleArtifactContentUploa
357
368
  model = python_models.PythonPackageContent
358
369
 
359
370
 
371
+ class PythonPackageContentUploadSerializer(PythonPackageContentSerializer):
372
+ """
373
+ A serializer for requests to synchronously upload Python packages.
374
+ """
375
+
376
+ def validate(self, data):
377
+ """
378
+ Validates an uploaded Python package file, extracts its metadata,
379
+ and creates or retrieves an associated Artifact.
380
+
381
+ Returns updated data with artifact and metadata details.
382
+ """
383
+ file = data.pop("file")
384
+ filename = file.name
385
+
386
+ for ext, packagetype in DIST_EXTENSIONS.items():
387
+ if filename.endswith(ext):
388
+ break
389
+ else:
390
+ raise serializers.ValidationError(
391
+ _(
392
+ "Extension on {} is not a valid python extension "
393
+ "(.whl, .exe, .egg, .tar.gz, .tar.bz2, .zip)"
394
+ ).format(filename)
395
+ )
396
+
397
+ # Replace the incorrect file name in the file path with the original file name
398
+ original_filepath = file.file.name
399
+ path_to_file, tmp_str = original_filepath.rsplit("/", maxsplit=1)
400
+ tmp_str = tmp_str.split(".", maxsplit=1)[0] # Remove e.g. ".upload.gz" suffix
401
+ new_filepath = f"{path_to_file}/{tmp_str}{filename}"
402
+ os.rename(original_filepath, new_filepath)
403
+
404
+ metadata = get_project_metadata_from_file(new_filepath)
405
+ artifact = core_models.Artifact.init_and_validate(new_filepath)
406
+ try:
407
+ artifact.save()
408
+ except IntegrityError:
409
+ artifact = core_models.Artifact.objects.get(
410
+ sha256=artifact.sha256, pulp_domain=get_domain()
411
+ )
412
+ artifact.touch()
413
+ log.info(f"Artifact for {file.name} already existed in database")
414
+
415
+ data["artifact"] = artifact
416
+ data["sha256"] = artifact.sha256
417
+ data["relative_path"] = filename
418
+ data.update(parse_project_metadata(vars(metadata)))
419
+ # Overwrite filename from metadata
420
+ data["filename"] = filename
421
+ return data
422
+
423
+ class Meta(PythonPackageContentSerializer.Meta):
424
+ # This API does not support uploading to a repository or using a custom relative_path
425
+ fields = tuple(
426
+ f
427
+ for f in PythonPackageContentSerializer.Meta.fields
428
+ if f not in ["repository", "relative_path"]
429
+ )
430
+ model = python_models.PythonPackageContent
431
+ # Name used for the OpenAPI request object
432
+ ref_name = "PythonPackageContentUpload"
433
+
434
+
360
435
  class MinimalPythonPackageContentSerializer(PythonPackageContentSerializer):
361
436
  """
362
437
  A Serializer for PythonPackageContent.
@@ -503,7 +578,7 @@ class PythonPublicationSerializer(core_serializers.PublicationSerializer):
503
578
 
504
579
  distributions = core_serializers.DetailRelatedField(
505
580
  help_text=_(
506
- "This publication is currently being hosted as configured by these " "distributions."
581
+ "This publication is currently being hosted as configured by these distributions."
507
582
  ),
508
583
  source="distribution_set",
509
584
  view_name="pythondistributions-detail",
@@ -162,7 +162,7 @@ def parse_metadata(project, version, distribution):
162
162
  return package
163
163
 
164
164
 
165
- def get_project_metadata_from_artifact(filename, artifact):
165
+ def get_project_metadata_from_file(filename):
166
166
  """
167
167
  Gets the metadata of a Python Package.
168
168
 
@@ -173,30 +173,31 @@ def get_project_metadata_from_artifact(filename, artifact):
173
173
  # If no supported extension is found, ValueError is raised here
174
174
  pkg_type_index = [filename.endswith(ext) for ext in extensions].index(True)
175
175
  packagetype = DIST_EXTENSIONS[extensions[pkg_type_index]]
176
- # Copy file to a temp directory under the user provided filename, we do this
177
- # because pkginfo validates that the filename has a valid extension before
178
- # reading it
179
- with tempfile.NamedTemporaryFile("wb", dir=".", suffix=filename) as temp_file:
180
- shutil.copyfileobj(artifact.file, temp_file)
181
- temp_file.flush()
182
- metadata = DIST_TYPES[packagetype](temp_file.name)
183
- metadata.packagetype = packagetype
184
- if packagetype == "sdist":
185
- metadata.python_version = "source"
186
- else:
187
- pyver = ""
188
- regex = DIST_REGEXES[extensions[pkg_type_index]]
189
- if bdist_name := regex.match(filename):
190
- pyver = bdist_name.group("pyver") or ""
191
- metadata.python_version = pyver
192
- return metadata
176
+
177
+ metadata = DIST_TYPES[packagetype](filename)
178
+ metadata.packagetype = packagetype
179
+ if packagetype == "sdist":
180
+ metadata.python_version = "source"
181
+ else:
182
+ pyver = ""
183
+ regex = DIST_REGEXES[extensions[pkg_type_index]]
184
+ if bdist_name := regex.match(filename):
185
+ pyver = bdist_name.group("pyver") or ""
186
+ metadata.python_version = pyver
187
+ return metadata
193
188
 
194
189
 
195
190
  def artifact_to_python_content_data(filename, artifact, domain=None):
196
191
  """
197
192
  Takes the artifact/filename and returns the metadata needed to create a PythonPackageContent.
198
193
  """
199
- metadata = get_project_metadata_from_artifact(filename, artifact)
194
+ # Copy file to a temp directory under the user provided filename, we do this
195
+ # because pkginfo validates that the filename has a valid extension before
196
+ # reading it
197
+ with tempfile.NamedTemporaryFile("wb", dir=".", suffix=filename) as temp_file:
198
+ shutil.copyfileobj(artifact.file, temp_file)
199
+ temp_file.flush()
200
+ metadata = get_project_metadata_from_file(temp_file.name)
200
201
  data = parse_project_metadata(vars(metadata))
201
202
  data["sha256"] = artifact.sha256
202
203
  data["filename"] = filename
@@ -1,4 +1,5 @@
1
1
  from bandersnatch.configuration import BandersnatchConfig
2
+ from django.db import transaction
2
3
  from drf_spectacular.utils import extend_schema
3
4
  from rest_framework import status
4
5
  from rest_framework.decorators import action
@@ -355,10 +356,49 @@ class PythonPackageSingleArtifactContentUploadViewSet(
355
356
  "has_upload_param_model_or_domain_or_obj_perms:core.change_upload",
356
357
  ],
357
358
  },
359
+ {
360
+ "action": ["upload"],
361
+ "principal": "authenticated",
362
+ "effect": "allow",
363
+ "condition": [
364
+ "has_model_or_domain_perms:python.upload_python_packages",
365
+ ],
366
+ },
358
367
  ],
359
368
  "queryset_scoping": {"function": "scope_queryset"},
360
369
  }
361
370
 
371
+ LOCKED_ROLES = {
372
+ "python.python_package_uploader": [
373
+ "python.upload_python_packages",
374
+ ],
375
+ }
376
+
377
+ @extend_schema(
378
+ summary="Synchronous Python package upload",
379
+ request=python_serializers.PythonPackageContentUploadSerializer,
380
+ responses={201: python_serializers.PythonPackageContentSerializer},
381
+ )
382
+ @action(
383
+ detail=False,
384
+ methods=["post"],
385
+ serializer_class=python_serializers.PythonPackageContentUploadSerializer,
386
+ )
387
+ def upload(self, request):
388
+ """
389
+ Create a Python package.
390
+ """
391
+ serializer = self.get_serializer(data=request.data)
392
+
393
+ with transaction.atomic():
394
+ # Create the artifact
395
+ serializer.is_valid(raise_exception=True)
396
+ # Create the package
397
+ serializer.save()
398
+
399
+ headers = self.get_success_headers(serializer.data)
400
+ return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
401
+
362
402
 
363
403
  class PythonRemoteViewSet(core_viewsets.RemoteViewSet, core_viewsets.RolesMixin):
364
404
  """
@@ -0,0 +1,44 @@
1
+ import pytest
2
+ from pulp_python.tests.functional.constants import (
3
+ PYTHON_EGG_FILENAME,
4
+ PYTHON_EGG_URL,
5
+ PYTHON_WHEEL_FILENAME,
6
+ PYTHON_WHEEL_URL,
7
+ )
8
+
9
+
10
+ @pytest.mark.parametrize(
11
+ "pkg_filename, pkg_url",
12
+ [(PYTHON_WHEEL_FILENAME, PYTHON_WHEEL_URL), (PYTHON_EGG_FILENAME, PYTHON_EGG_URL)],
13
+ )
14
+ def test_synchronous_package_upload(
15
+ delete_orphans_pre, download_python_file, gen_user, python_bindings, pkg_filename, pkg_url
16
+ ):
17
+ """
18
+ Test synchronously uploading a Python package with labels.
19
+ """
20
+ python_file = download_python_file(pkg_filename, pkg_url)
21
+
22
+ # Upload a unit with labels
23
+ with gen_user(model_roles=["python.python_package_uploader"]):
24
+ labels = {"key_1": "value_1"}
25
+ content_body = {"file": python_file, "pulp_labels": labels}
26
+ package = python_bindings.ContentPackagesApi.upload(**content_body)
27
+ assert package.pulp_labels == labels
28
+ assert package.name == "shelf-reader"
29
+ assert package.filename == pkg_filename
30
+
31
+ # Check that uploading the same unit again with different (or same) labels has no effect
32
+ with gen_user(model_roles=["python.python_package_uploader"]):
33
+ labels_2 = {"key_2": "value_2"}
34
+ content_body_2 = {"file": python_file, "pulp_labels": labels_2}
35
+ duplicate_package = python_bindings.ContentPackagesApi.upload(**content_body_2)
36
+ assert duplicate_package.pulp_href == package.pulp_href
37
+ assert duplicate_package.pulp_labels == package.pulp_labels
38
+ assert duplicate_package.pulp_labels != labels_2
39
+
40
+ # Check that the upload fails if the user does not have the required permissions
41
+ with gen_user(model_roles=[]):
42
+ with pytest.raises(python_bindings.ApiException) as ctx:
43
+ python_bindings.ContentPackagesApi.upload(**content_body)
44
+ assert ctx.value.status == 403
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pulp-python
3
- Version: 3.18.0
3
+ Version: 3.19.0
4
4
  Summary: pulp-python plugin for the Pulp Project
5
5
  Author-email: Pulp Team <pulp-list@redhat.com>
6
6
  Project-URL: Homepage, https://pulpproject.org
@@ -20,7 +20,7 @@ Classifier: Programming Language :: Python :: 3.13
20
20
  Requires-Python: >=3.11
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
- Requires-Dist: pulpcore<3.100,>=3.49.0
23
+ Requires-Dist: pulpcore<3.100,>=3.81.0
24
24
  Requires-Dist: pkginfo<1.13.0,>=1.12.0
25
25
  Requires-Dist: bandersnatch<6.6,>=6.3.0
26
26
  Requires-Dist: pypi-simple<2.0,>=1.5.0
@@ -44,6 +44,7 @@ pulp_python/app/migrations/0011_alter_pythondistribution_distribution_ptr_and_mo
44
44
  pulp_python/app/migrations/0012_add_domain.py
45
45
  pulp_python/app/migrations/0013_add_rbac_permissions.py
46
46
  pulp_python/app/migrations/0014_pythonpackagecontent_dynamic_and_more.py
47
+ pulp_python/app/migrations/0015_alter_pythonpackagecontent_options.py
47
48
  pulp_python/app/migrations/__init__.py
48
49
  pulp_python/app/pypi/__init__.py
49
50
  pulp_python/app/pypi/serializers.py
@@ -74,5 +75,6 @@ pulp_python/tests/functional/api/test_pypi_apis.py
74
75
  pulp_python/tests/functional/api/test_rbac.py
75
76
  pulp_python/tests/functional/api/test_repair.py
76
77
  pulp_python/tests/functional/api/test_sync.py
78
+ pulp_python/tests/functional/api/test_upload.py
77
79
  pulp_python/tests/unit/__init__.py
78
80
  pulp_python/tests/unit/test_models.py
@@ -1,4 +1,4 @@
1
- pulpcore<3.100,>=3.49.0
1
+ pulpcore<3.100,>=3.81.0
2
2
  pkginfo<1.13.0,>=1.12.0
3
3
  bandersnatch<6.6,>=6.3.0
4
4
  pypi-simple<2.0,>=1.5.0
@@ -7,7 +7,7 @@ build-backend = 'setuptools.build_meta'
7
7
 
8
8
  [project]
9
9
  name = "pulp-python"
10
- version = "3.18.0"
10
+ version = "3.19.0"
11
11
  description = "pulp-python plugin for the Pulp Project"
12
12
  readme = "README.md"
13
13
  authors = [
@@ -26,7 +26,7 @@ classifiers=[
26
26
  ]
27
27
  requires-python = ">=3.11"
28
28
  dependencies = [
29
- "pulpcore>=3.49.0,<3.100",
29
+ "pulpcore>=3.81.0,<3.100",
30
30
  "pkginfo>=1.12.0,<1.13.0",
31
31
  "bandersnatch>=6.3.0,<6.6", # 6.6 has breaking changes
32
32
  "pypi-simple>=1.5.0,<2.0",
@@ -76,7 +76,7 @@ ignore = [
76
76
  [tool.bumpversion]
77
77
  # This section is managed by the plugin template. Do not edit manually.
78
78
 
79
- current_version = "3.18.0"
79
+ current_version = "3.19.0"
80
80
  commit = false
81
81
  tag = false
82
82
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<alpha>0a)?(?P<patch>\\d+)(\\.(?P<release>[a-z]+))?"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes