pulp-python 3.18.0__tar.gz → 3.19.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pulp_python-3.18.0 → pulp_python-3.19.1}/CHANGES.md +18 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/PKG-INFO +2 -2
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/__init__.py +1 -1
- pulp_python-3.19.1/pulp_python/app/migrations/0015_alter_pythonpackagecontent_options.py +22 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/models.py +3 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/serializers.py +80 -5
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/tasks/publish.py +7 -7
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/utils.py +20 -19
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/viewsets.py +40 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/pytest_plugin.py +1 -1
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/api/test_crud_publications.py +27 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/api/test_pypi_apis.py +0 -20
- pulp_python-3.19.1/pulp_python/tests/functional/api/test_upload.py +44 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python.egg-info/PKG-INFO +2 -2
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python.egg-info/SOURCES.txt +2 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python.egg-info/requires.txt +1 -1
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pyproject.toml +3 -3
- {pulp_python-3.18.0 → pulp_python-3.19.1}/COMMITMENT +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/COPYRIGHT +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/LICENSE +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/MANIFEST.in +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/README.md +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/functest_requirements.txt +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/__init__.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/global_access_conditions.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/management/__init__.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/management/commands/__init__.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/management/commands/repair-python-metadata.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/migrations/0001_initial.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/migrations/0001_squashed_0010_update_json_field.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/migrations/0002_pythonpackagecontent_python_version.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/migrations/0003_new_sync_filters.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/migrations/0004_DATA_swap_distribution_model.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/migrations/0005_pythonpackagecontent_sha256.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/migrations/0006_pythonrepository_autopublish.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/migrations/0007_pythonpackagecontent_mv-2-1.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/migrations/0008_pythonpackagecontent_unique_sha256.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/migrations/0009_pythondistribution_allow_uploads.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/migrations/0010_update_json_field.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/migrations/0011_alter_pythondistribution_distribution_ptr_and_more.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/migrations/0012_add_domain.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/migrations/0013_add_rbac_permissions.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/migrations/0014_pythonpackagecontent_dynamic_and_more.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/migrations/__init__.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/modelresource.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/pypi/__init__.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/pypi/serializers.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/pypi/views.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/replica.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/settings.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/tasks/__init__.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/tasks/repair.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/tasks/sync.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/tasks/upload.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/urls.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/webserver_snippets/__init__.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/webserver_snippets/apache.conf +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/webserver_snippets/nginx.conf +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/__init__.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/__init__.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/api/__init__.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/api/test_auto_publish.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/api/test_consume_content.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/api/test_crud_content_unit.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/api/test_crud_remotes.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/api/test_domains.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/api/test_download_content.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/api/test_export_import.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/api/test_full_mirror.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/api/test_rbac.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/api/test_repair.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/api/test_sync.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/constants.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/utils.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/unit/__init__.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/unit/test_models.py +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python.egg-info/dependency_links.txt +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python.egg-info/entry_points.txt +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python.egg-info/top_level.txt +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/setup.cfg +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/test_requirements.txt +0 -0
- {pulp_python-3.18.0 → pulp_python-3.19.1}/unittest_requirements.txt +0 -0
|
@@ -8,6 +8,24 @@
|
|
|
8
8
|
|
|
9
9
|
[//]: # (towncrier release notes start)
|
|
10
10
|
|
|
11
|
+
## 3.19.1 (2025-09-14) {: #3.19.1 }
|
|
12
|
+
|
|
13
|
+
#### Bugfixes {: #3.19.1-bugfix }
|
|
14
|
+
|
|
15
|
+
- Fixed publication error when package's dists contain differing versions of its name.
|
|
16
|
+
[#907](https://github.com/pulp/pulp_python/issues/907)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 3.19.0 (2025-08-26) {: #3.19.0 }
|
|
21
|
+
|
|
22
|
+
#### Features {: #3.19.0-feature }
|
|
23
|
+
|
|
24
|
+
- Added a synchronous upload API.
|
|
25
|
+
[#933](https://github.com/pulp/pulp_python/issues/933)
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
11
29
|
## 3.18.0 (2025-08-13) {: #3.18.0 }
|
|
12
30
|
|
|
13
31
|
#### Features {: #3.18.0-feature }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pulp-python
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.19.1
|
|
4
4
|
Summary: pulp-python plugin for the Pulp Project
|
|
5
5
|
Author-email: Pulp Team <pulp-list@redhat.com>
|
|
6
6
|
Project-URL: Homepage, https://pulpproject.org
|
|
@@ -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.
|
|
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
|
|
@@ -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
|
|
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
|
|
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 distribution
|
|
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
|
|
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
|
|
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",
|
|
@@ -59,9 +59,9 @@ def write_simple_api(publication):
|
|
|
59
59
|
python_models.PythonPackageContent.objects.filter(
|
|
60
60
|
pk__in=publication.repository_version.content, _pulp_domain=domain
|
|
61
61
|
)
|
|
62
|
-
.order_by("
|
|
62
|
+
.order_by("name__normalize")
|
|
63
63
|
.values_list("name", flat=True)
|
|
64
|
-
.distinct()
|
|
64
|
+
.distinct("name__normalize")
|
|
65
65
|
)
|
|
66
66
|
|
|
67
67
|
# write the root index, which lists all of the projects for which there is a package available
|
|
@@ -80,22 +80,22 @@ def write_simple_api(publication):
|
|
|
80
80
|
packages = python_models.PythonPackageContent.objects.filter(
|
|
81
81
|
pk__in=publication.repository_version.content, _pulp_domain=domain
|
|
82
82
|
)
|
|
83
|
-
releases = packages.order_by("
|
|
83
|
+
releases = packages.order_by("name__normalize").values("name", "filename", "sha256")
|
|
84
84
|
|
|
85
85
|
ind = 0
|
|
86
|
-
current_name = project_names[ind]
|
|
86
|
+
current_name = canonicalize_name(project_names[ind])
|
|
87
87
|
package_releases = []
|
|
88
88
|
for release in releases.iterator():
|
|
89
|
-
if release["name"] != current_name:
|
|
89
|
+
if canonicalize_name(release["name"]) != current_name:
|
|
90
90
|
write_project_page(
|
|
91
|
-
name=
|
|
91
|
+
name=current_name,
|
|
92
92
|
simple_dir=simple_dir,
|
|
93
93
|
package_releases=package_releases,
|
|
94
94
|
publication=publication,
|
|
95
95
|
)
|
|
96
96
|
package_releases = []
|
|
97
97
|
ind += 1
|
|
98
|
-
current_name = project_names[ind]
|
|
98
|
+
current_name = canonicalize_name(project_names[ind])
|
|
99
99
|
relative_path = release["filename"]
|
|
100
100
|
path = f"../../{relative_path}"
|
|
101
101
|
checksum = release["sha256"]
|
|
@@ -162,7 +162,7 @@ def parse_metadata(project, version, distribution):
|
|
|
162
162
|
return package
|
|
163
163
|
|
|
164
164
|
|
|
165
|
-
def
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
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
|
"""
|
|
@@ -178,7 +178,7 @@ def python_content_factory(python_bindings, download_python_file, monitor_task):
|
|
|
178
178
|
|
|
179
179
|
task = python_bindings.ContentPackagesApi.create(**body).task
|
|
180
180
|
response = monitor_task(task)
|
|
181
|
-
return python_bindings.ContentPackagesApi.read(response.created_resources[
|
|
181
|
+
return python_bindings.ContentPackagesApi.read(response.created_resources[-1])
|
|
182
182
|
|
|
183
183
|
yield _gen_python_content
|
|
184
184
|
|
{pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/api/test_crud_publications.py
RENAMED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
import random
|
|
3
|
+
from pypi_simple import PyPISimple
|
|
3
4
|
from urllib.parse import urljoin
|
|
4
5
|
|
|
5
6
|
from pulp_python.tests.functional.constants import (
|
|
@@ -109,3 +110,29 @@ def test_new_content_is_published(python_publication_workflow, python_distributi
|
|
|
109
110
|
url = urljoin(distro.base_url, "simple/")
|
|
110
111
|
proper, msgs = ensure_simple(url, {"shelf-reader": releases})
|
|
111
112
|
assert proper is True, msgs
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@pytest.mark.parallel
|
|
116
|
+
def test_non_matching_canonicalized_name(
|
|
117
|
+
python_repo, python_content_factory, python_publication_factory, python_distribution_factory
|
|
118
|
+
):
|
|
119
|
+
"""Ensures a package with dists that have non-matching canonicalized names is published."""
|
|
120
|
+
packages = []
|
|
121
|
+
filenames = ["msg_parser-1.0.0-py2.py3-none-any.whl", "msg_parser-1.0.0.tar.gz"]
|
|
122
|
+
with PyPISimple() as client:
|
|
123
|
+
page = client.get_project_page("msg-parser")
|
|
124
|
+
for pkg in page.packages:
|
|
125
|
+
if pkg.filename in filenames:
|
|
126
|
+
c = python_content_factory(pkg.filename, url=pkg.url, repository=python_repo)
|
|
127
|
+
if c.filename.endswith(".tar.gz"):
|
|
128
|
+
# The metadata name in the SDist is not the same as the Wheel's name
|
|
129
|
+
assert c.name == "msg_parser"
|
|
130
|
+
else:
|
|
131
|
+
assert c.name == "msg-parser"
|
|
132
|
+
packages.append(c)
|
|
133
|
+
pub = python_publication_factory(repository=python_repo)
|
|
134
|
+
distro = python_distribution_factory(publication=pub)
|
|
135
|
+
|
|
136
|
+
url = urljoin(distro.base_url, "simple/")
|
|
137
|
+
proper, msgs = ensure_simple(url, {"msg-parser": filenames})
|
|
138
|
+
assert proper is True, msgs
|
{pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/api/test_pypi_apis.py
RENAMED
|
@@ -225,26 +225,6 @@ def test_twine_upload(
|
|
|
225
225
|
check=True,
|
|
226
226
|
)
|
|
227
227
|
|
|
228
|
-
# Test re-uploading same packages with --skip-existing works
|
|
229
|
-
output = subprocess.run(
|
|
230
|
-
(
|
|
231
|
-
"twine",
|
|
232
|
-
"upload",
|
|
233
|
-
"--repository-url",
|
|
234
|
-
url,
|
|
235
|
-
dist_dir / "*",
|
|
236
|
-
"-u",
|
|
237
|
-
username,
|
|
238
|
-
"-p",
|
|
239
|
-
password,
|
|
240
|
-
"--skip-existing",
|
|
241
|
-
),
|
|
242
|
-
capture_output=True,
|
|
243
|
-
check=True,
|
|
244
|
-
text=True,
|
|
245
|
-
)
|
|
246
|
-
assert output.stdout.count("Skipping") == 2
|
|
247
|
-
|
|
248
228
|
|
|
249
229
|
@pytest.mark.parallel
|
|
250
230
|
def test_simple_redirect_with_publications(
|
|
@@ -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.
|
|
3
|
+
Version: 3.19.1
|
|
4
4
|
Summary: pulp-python plugin for the Pulp Project
|
|
5
5
|
Author-email: Pulp Team <pulp-list@redhat.com>
|
|
6
6
|
Project-URL: Homepage, https://pulpproject.org
|
|
@@ -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.
|
|
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
|
|
@@ -7,7 +7,7 @@ build-backend = 'setuptools.build_meta'
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "pulp-python"
|
|
10
|
-
version = "3.
|
|
10
|
+
version = "3.19.1"
|
|
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.
|
|
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.
|
|
79
|
+
current_version = "3.19.1"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/migrations/0003_new_sync_filters.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/migrations/0010_update_json_field.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/app/migrations/0013_add_rbac_permissions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/api/test_auto_publish.py
RENAMED
|
File without changes
|
{pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/api/test_consume_content.py
RENAMED
|
File without changes
|
{pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/api/test_crud_content_unit.py
RENAMED
|
File without changes
|
{pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/api/test_crud_remotes.py
RENAMED
|
File without changes
|
|
File without changes
|
{pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/api/test_download_content.py
RENAMED
|
File without changes
|
{pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/api/test_export_import.py
RENAMED
|
File without changes
|
{pulp_python-3.18.0 → pulp_python-3.19.1}/pulp_python/tests/functional/api/test_full_mirror.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|