pulp-python 3.16.0__tar.gz → 3.17.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.
Files changed (80) hide show
  1. {pulp_python-3.16.0 → pulp_python-3.17.1}/CHANGES.md +27 -0
  2. {pulp_python-3.16.0 → pulp_python-3.17.1}/PKG-INFO +4 -3
  3. pulp_python-3.17.1/README.md +9 -0
  4. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/__init__.py +1 -1
  5. pulp_python-3.17.1/pulp_python/app/migrations/0014_pythonpackagecontent_dynamic_and_more.py +34 -0
  6. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/models.py +50 -33
  7. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/pypi/views.py +10 -6
  8. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/serializers.py +107 -74
  9. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/utils.py +45 -29
  10. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/tests/functional/api/test_crud_content_unit.py +32 -2
  11. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/tests/functional/api/test_repair.py +0 -2
  12. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/tests/functional/constants.py +6 -5
  13. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python.egg-info/PKG-INFO +4 -3
  14. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python.egg-info/SOURCES.txt +1 -0
  15. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python.egg-info/requires.txt +1 -1
  16. {pulp_python-3.16.0 → pulp_python-3.17.1}/pyproject.toml +3 -3
  17. pulp_python-3.16.0/README.md +0 -8
  18. {pulp_python-3.16.0 → pulp_python-3.17.1}/COMMITMENT +0 -0
  19. {pulp_python-3.16.0 → pulp_python-3.17.1}/COPYRIGHT +0 -0
  20. {pulp_python-3.16.0 → pulp_python-3.17.1}/LICENSE +0 -0
  21. {pulp_python-3.16.0 → pulp_python-3.17.1}/MANIFEST.in +0 -0
  22. {pulp_python-3.16.0 → pulp_python-3.17.1}/functest_requirements.txt +0 -0
  23. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/__init__.py +0 -0
  24. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/global_access_conditions.py +0 -0
  25. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/management/__init__.py +0 -0
  26. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/management/commands/__init__.py +0 -0
  27. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/management/commands/repair-python-metadata.py +0 -0
  28. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/migrations/0001_initial.py +0 -0
  29. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/migrations/0002_pythonpackagecontent_python_version.py +0 -0
  30. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/migrations/0003_new_sync_filters.py +0 -0
  31. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/migrations/0004_DATA_swap_distribution_model.py +0 -0
  32. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/migrations/0005_pythonpackagecontent_sha256.py +0 -0
  33. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/migrations/0006_pythonrepository_autopublish.py +0 -0
  34. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/migrations/0007_pythonpackagecontent_mv-2-1.py +0 -0
  35. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/migrations/0008_pythonpackagecontent_unique_sha256.py +0 -0
  36. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/migrations/0009_pythondistribution_allow_uploads.py +0 -0
  37. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/migrations/0010_update_json_field.py +0 -0
  38. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/migrations/0011_alter_pythondistribution_distribution_ptr_and_more.py +0 -0
  39. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/migrations/0012_add_domain.py +0 -0
  40. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/migrations/0013_add_rbac_permissions.py +0 -0
  41. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/migrations/__init__.py +0 -0
  42. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/modelresource.py +0 -0
  43. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/pypi/__init__.py +0 -0
  44. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/pypi/serializers.py +0 -0
  45. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/replica.py +0 -0
  46. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/settings.py +0 -0
  47. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/tasks/__init__.py +0 -0
  48. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/tasks/publish.py +0 -0
  49. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/tasks/repair.py +0 -0
  50. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/tasks/sync.py +0 -0
  51. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/tasks/upload.py +0 -0
  52. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/urls.py +0 -0
  53. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/viewsets.py +0 -0
  54. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/webserver_snippets/__init__.py +0 -0
  55. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/webserver_snippets/apache.conf +0 -0
  56. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/app/webserver_snippets/nginx.conf +0 -0
  57. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/pytest_plugin.py +0 -0
  58. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/tests/__init__.py +0 -0
  59. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/tests/functional/__init__.py +0 -0
  60. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/tests/functional/api/__init__.py +0 -0
  61. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/tests/functional/api/test_auto_publish.py +0 -0
  62. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/tests/functional/api/test_consume_content.py +0 -0
  63. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/tests/functional/api/test_crud_publications.py +0 -0
  64. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/tests/functional/api/test_crud_remotes.py +0 -0
  65. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/tests/functional/api/test_domains.py +0 -0
  66. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/tests/functional/api/test_download_content.py +0 -0
  67. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/tests/functional/api/test_export_import.py +0 -0
  68. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/tests/functional/api/test_full_mirror.py +0 -0
  69. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/tests/functional/api/test_pypi_apis.py +0 -0
  70. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/tests/functional/api/test_rbac.py +0 -0
  71. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/tests/functional/api/test_sync.py +0 -0
  72. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/tests/functional/utils.py +0 -0
  73. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/tests/unit/__init__.py +0 -0
  74. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python/tests/unit/test_models.py +0 -0
  75. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python.egg-info/dependency_links.txt +0 -0
  76. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python.egg-info/entry_points.txt +0 -0
  77. {pulp_python-3.16.0 → pulp_python-3.17.1}/pulp_python.egg-info/top_level.txt +0 -0
  78. {pulp_python-3.16.0 → pulp_python-3.17.1}/setup.cfg +0 -0
  79. {pulp_python-3.16.0 → pulp_python-3.17.1}/test_requirements.txt +0 -0
  80. {pulp_python-3.16.0 → pulp_python-3.17.1}/unittest_requirements.txt +0 -0
@@ -8,6 +8,24 @@
8
8
 
9
9
  [//]: # (towncrier release notes start)
10
10
 
11
+ ## 3.17.1 (2025-08-12) {: #3.17.1 }
12
+
13
+ #### Bugfixes {: #3.17.1-bugfix }
14
+
15
+ - Fixed PYTHON_GROUP_UPLOADS failing when domains are turned on.
16
+ [#929](https://github.com/pulp/pulp_python/issues/929)
17
+
18
+ ---
19
+
20
+ ## 3.17.0 (2025-07-23) {: #3.17.0 }
21
+
22
+ #### Features {: #3.17.0-feature }
23
+
24
+ - Added full support for the latest core metadata (up to 2.4).
25
+ [#689](https://github.com/pulp/pulp_python/issues/689)
26
+
27
+ ---
28
+
11
29
  ## 3.16.0 (2025-06-10) {: #3.16.0 }
12
30
 
13
31
  #### Features {: #3.16.0-feature }
@@ -22,6 +40,15 @@
22
40
 
23
41
  ---
24
42
 
43
+ ## 3.15.1 (2025-06-10) {: #3.15.1 }
44
+
45
+ #### Bugfixes {: #3.15.1-bugfix }
46
+
47
+ - Fixed pull-through caching not working for indexes that use relative URLs.
48
+ [#842](https://github.com/pulp/pulp_python/issues/842)
49
+
50
+ ---
51
+
25
52
  ## 3.15.0 (2025-05-13) {: #3.15.0 }
26
53
 
27
54
  #### Features {: #3.15.0-feature }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pulp-python
3
- Version: 3.16.0
3
+ Version: 3.17.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
@@ -22,14 +22,15 @@ Requires-Python: >=3.9
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
24
  Requires-Dist: pulpcore<3.85,>=3.49.0
25
- Requires-Dist: pkginfo<1.13.0,>=1.10.0
25
+ Requires-Dist: pkginfo<1.13.0,>=1.12.0
26
26
  Requires-Dist: bandersnatch<6.4,>=6.3.0
27
27
  Requires-Dist: pypi-simple<2.0,>=1.5.0
28
28
  Dynamic: license-file
29
29
 
30
30
  # pulp_python
31
31
 
32
- ![Pulp Nightly CI/CD](https://github.com/pulp/pulp_python/actions/workflows/nightly.yml/badge.svg)
32
+ [![PyPI](https://img.shields.io/pypi/v/pulp_python.svg)](https://pypi.python.org/pypi/pulp_python)
33
+ [![Pulp Nightly CI/CD](https://github.com/pulp/pulp_python/actions/workflows/nightly.yml/badge.svg)](https://github.com/pulp/pulp_python/actions/workflows/nightly.yml)
33
34
 
34
35
  A Pulp plugin to support hosting your own pip compatible Python packages.
35
36
 
@@ -0,0 +1,9 @@
1
+ # pulp_python
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/pulp_python.svg)](https://pypi.python.org/pypi/pulp_python)
4
+ [![Pulp Nightly CI/CD](https://github.com/pulp/pulp_python/actions/workflows/nightly.yml/badge.svg)](https://github.com/pulp/pulp_python/actions/workflows/nightly.yml)
5
+
6
+ A Pulp plugin to support hosting your own pip compatible Python packages.
7
+
8
+ For more information, please see the [documentation](https://docs.pulpproject.org/pulp_python/) or the
9
+ [Pulp project page](https://pulpproject.org).
@@ -10,7 +10,7 @@ class PulpPythonPluginAppConfig(PulpPluginAppConfig):
10
10
 
11
11
  name = "pulp_python.app"
12
12
  label = "python"
13
- version = "3.16.0"
13
+ version = "3.17.1"
14
14
  python_package_name = "pulp-python"
15
15
  domain_compatible = True
16
16
 
@@ -0,0 +1,34 @@
1
+ # Generated by Django 4.2.19 on 2025-07-09 08:05
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('python', '0013_add_rbac_permissions'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='pythonpackagecontent',
15
+ name='dynamic',
16
+ field=models.JSONField(default=list),
17
+ ),
18
+ migrations.AddField(
19
+ model_name='pythonpackagecontent',
20
+ name='license_expression',
21
+ field=models.TextField(default=''),
22
+ preserve_default=False,
23
+ ),
24
+ migrations.AddField(
25
+ model_name='pythonpackagecontent',
26
+ name='license_file',
27
+ field=models.JSONField(default=list),
28
+ ),
29
+ migrations.AddField(
30
+ model_name='pythonpackagecontent',
31
+ name='provides_extras',
32
+ field=models.JSONField(default=list),
33
+ ),
34
+ ]
@@ -140,49 +140,66 @@ class PythonPackageContent(Content):
140
140
  """
141
141
  A Content Type representing Python's Distribution Package.
142
142
 
143
- As defined in pep-0426 and pep-0345.
143
+ Core Metadata:
144
+ https://packaging.python.org/en/latest/specifications/core-metadata/
144
145
 
145
- https://www.python.org/dev/peps/pep-0491/
146
- https://www.python.org/dev/peps/pep-0345/
147
- """
148
-
149
- PROTECTED_FROM_RECLAIM = False
146
+ Release metadata (JSON API):
147
+ https://docs.pypi.org/api/json/
150
148
 
151
- TYPE = "python"
152
- repo_key_fields = ("filename",)
153
- # Required metadata
154
- filename = models.TextField(db_index=True)
155
- packagetype = models.TextField(choices=PACKAGE_TYPES)
156
- name = models.TextField()
157
- name.register_lookup(NormalizeName)
158
- version = models.TextField()
159
- sha256 = models.CharField(db_index=True, max_length=64)
160
- # Optional metadata
161
- python_version = models.TextField()
162
- metadata_version = models.TextField()
163
- summary = models.TextField()
164
- description = models.TextField()
165
- keywords = models.TextField()
166
- home_page = models.TextField()
167
- download_url = models.TextField()
149
+ File Formats:
150
+ https://packaging.python.org/en/latest/specifications/source-distribution-format/
151
+ https://packaging.python.org/en/latest/specifications/binary-distribution-format/
152
+ """
153
+ # Core metadata
154
+ # Version 1.0
168
155
  author = models.TextField()
169
156
  author_email = models.TextField()
157
+ description = models.TextField()
158
+ home_page = models.TextField() # Deprecated in favour of Project-URL
159
+ keywords = models.TextField()
160
+ license = models.TextField() # Deprecated in favour of License-Expression
161
+ metadata_version = models.TextField()
162
+ name = models.TextField()
163
+ platform = models.TextField()
164
+ summary = models.TextField()
165
+ version = models.TextField()
166
+ # Version 1.1
167
+ classifiers = models.JSONField(default=list)
168
+ download_url = models.TextField() # Deprecated in favour of Project-URL
169
+ supported_platform = models.TextField()
170
+ # Version 1.2
170
171
  maintainer = models.TextField()
171
172
  maintainer_email = models.TextField()
172
- license = models.TextField()
173
- requires_python = models.TextField()
173
+ obsoletes_dist = models.JSONField(default=list)
174
174
  project_url = models.TextField()
175
- platform = models.TextField()
176
- supported_platform = models.TextField()
177
- requires_dist = models.JSONField(default=list)
175
+ project_urls = models.JSONField(default=dict)
178
176
  provides_dist = models.JSONField(default=list)
179
- obsoletes_dist = models.JSONField(default=list)
180
177
  requires_external = models.JSONField(default=list)
181
- classifiers = models.JSONField(default=list)
182
- project_urls = models.JSONField(default=dict)
178
+ requires_dist = models.JSONField(default=list)
179
+ requires_python = models.TextField()
180
+ # Version 2.1
183
181
  description_content_type = models.TextField()
184
- # Pulp Domains
185
- _pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT)
182
+ provides_extras = models.JSONField(default=list)
183
+ # Version 2.2
184
+ dynamic = models.JSONField(default=list)
185
+ # Version 2.4
186
+ license_expression = models.TextField()
187
+ license_file = models.JSONField(default=list)
188
+
189
+ # Release metadata
190
+ filename = models.TextField(db_index=True)
191
+ packagetype = models.TextField(choices=PACKAGE_TYPES)
192
+ python_version = models.TextField()
193
+ sha256 = models.CharField(db_index=True, max_length=64)
194
+
195
+ # From pulpcore
196
+ PROTECTED_FROM_RECLAIM = False
197
+ TYPE = "python"
198
+ _pulp_domain = models.ForeignKey(
199
+ "core.Domain", default=get_domain_pk, on_delete=models.PROTECT
200
+ )
201
+ name.register_lookup(NormalizeName)
202
+ repo_key_fields = ("filename",)
186
203
 
187
204
  @staticmethod
188
205
  def init_from_artifact_and_relative_path(artifact, relative_path):
@@ -8,7 +8,6 @@ from django.core.exceptions import ObjectDoesNotExist
8
8
  from django.shortcuts import redirect
9
9
  from datetime import datetime, timezone, timedelta
10
10
 
11
- from rest_framework.reverse import reverse
12
11
  from django.contrib.sessions.models import Session
13
12
  from django.db import transaction
14
13
  from django.db.utils import DatabaseError
@@ -29,7 +28,7 @@ from pypi_simple import ACCEPT_JSON_PREFERRED, ProjectPage
29
28
 
30
29
  from pulpcore.plugin.viewsets import OperationPostponedResponse
31
30
  from pulpcore.plugin.tasking import dispatch
32
- from pulpcore.plugin.util import get_domain
31
+ from pulpcore.plugin.util import get_domain, get_url
33
32
  from pulpcore.plugin.exceptions import TimeoutException
34
33
  from pulp_python.app.models import (
35
34
  PythonDistribution,
@@ -200,10 +199,15 @@ class PackageUploadMixin(PyPIMixin):
200
199
  cur_session['artifacts'] = [(str(artifact.sha256), filename)]
201
200
  cur_session.modified = False
202
201
  cur_session.save()
203
- result = dispatch(tasks.upload_group, exclusive_resources=[artifact, repository],
204
- kwargs={"session_pk": str(cur_session.session_key),
205
- "repository_pk": str(repository.pk)})
206
- return reverse('tasks-detail', args=[result.pk], request=None)
202
+ task = dispatch(
203
+ tasks.upload_group,
204
+ exclusive_resources=[artifact, repository],
205
+ kwargs={
206
+ "session_pk": str(cur_session.session_key),
207
+ "repository_pk": str(repository.pk),
208
+ },
209
+ )
210
+ return get_url(task)
207
211
 
208
212
 
209
213
  class SimpleView(PackageUploadMixin, ViewSet):
@@ -72,69 +72,69 @@ class PythonPackageContentSerializer(core_serializers.SingleArtifactContentUploa
72
72
  """
73
73
  A Serializer for PythonPackageContent.
74
74
  """
75
-
76
- filename = serializers.CharField(
77
- help_text=_('The name of the distribution package, usually of the format:'
78
- ' {distribution}-{version}(-{build tag})?-{python tag}-{abi tag}'
79
- '-{platform tag}.{packagetype}'),
80
- read_only=True,
81
- )
82
- packagetype = serializers.CharField(
83
- help_text=_('The type of the distribution package '
84
- '(e.g. sdist, bdist_wheel, bdist_egg, etc)'),
85
- read_only=True,
86
- )
87
- name = serializers.CharField(
88
- help_text=_('The name of the python project.'),
89
- read_only=True,
90
- )
91
- version = serializers.CharField(
92
- help_text=_('The packages version number.'),
93
- read_only=True,
94
- )
95
- sha256 = serializers.CharField(
96
- default='',
97
- help_text=_('The SHA256 digest of this package.'),
98
- )
99
- metadata_version = serializers.CharField(
100
- help_text=_('Version of the file format'),
101
- read_only=True,
75
+ # Core metadata
76
+ # Version 1.0
77
+ author = serializers.CharField(
78
+ required=False, allow_blank=True,
79
+ help_text=_('Text containing the author\'s name. Contact information can also be added,'
80
+ ' separated with newlines.')
102
81
  )
103
- summary = serializers.CharField(
82
+ author_email = serializers.CharField(
104
83
  required=False, allow_blank=True,
105
- help_text=_('A one-line summary of what the package does.')
84
+ help_text=_('The author\'s e-mail address. ')
106
85
  )
107
86
  description = serializers.CharField(
108
87
  required=False, allow_blank=True,
109
88
  help_text=_('A longer description of the package that can run to several paragraphs.')
110
89
  )
111
- description_content_type = serializers.CharField(
90
+ home_page = serializers.CharField(
112
91
  required=False, allow_blank=True,
113
- help_text=_('A string stating the markup syntax (if any) used in the distribution’s'
114
- ' description, so that tools can intelligently render the description.')
92
+ help_text=_('The URL for the package\'s home page.')
115
93
  )
116
94
  keywords = serializers.CharField(
117
95
  required=False, allow_blank=True,
118
96
  help_text=_('Additional keywords to be used to assist searching for the '
119
97
  'package in a larger catalog.')
120
98
  )
121
- home_page = serializers.CharField(
99
+ license = serializers.CharField(
122
100
  required=False, allow_blank=True,
123
- help_text=_('The URL for the package\'s home page.')
101
+ help_text=_('Text indicating the license covering the distribution')
124
102
  )
125
- download_url = serializers.CharField(
103
+ metadata_version = serializers.CharField(
104
+ help_text=_('Version of the file format'),
105
+ read_only=True,
106
+ )
107
+ name = serializers.CharField(
108
+ help_text=_('The name of the python project.'),
109
+ read_only=True,
110
+ )
111
+ platform = serializers.CharField(
126
112
  required=False, allow_blank=True,
127
- help_text=_('Legacy field denoting the URL from which this package can be downloaded.')
113
+ help_text=_('A comma-separated list of platform specifications, '
114
+ 'summarizing the operating systems supported by the package.')
128
115
  )
129
- author = serializers.CharField(
116
+ summary = serializers.CharField(
130
117
  required=False, allow_blank=True,
131
- help_text=_('Text containing the author\'s name. Contact information can also be added,'
132
- ' separated with newlines.')
118
+ help_text=_('A one-line summary of what the package does.')
133
119
  )
134
- author_email = serializers.CharField(
120
+ version = serializers.CharField(
121
+ help_text=_('The packages version number.'),
122
+ read_only=True,
123
+ )
124
+ # Version 1.1
125
+ classifiers = serializers.JSONField(
126
+ required=False, default=list,
127
+ help_text=_('A JSON list containing classification values for a Python package.')
128
+ )
129
+ download_url = serializers.CharField(
135
130
  required=False, allow_blank=True,
136
- help_text=_('The author\'s e-mail address. ')
131
+ help_text=_('Legacy field denoting the URL from which this package can be downloaded.')
137
132
  )
133
+ supported_platform = serializers.CharField(
134
+ required=False, allow_blank=True,
135
+ help_text=_('Field to specify the OS and CPU for which the binary package was compiled. ')
136
+ )
137
+ # Version 1.2
138
138
  maintainer = serializers.CharField(
139
139
  required=False, allow_blank=True,
140
140
  help_text=_('The maintainer\'s name at a minimum; '
@@ -144,14 +144,11 @@ class PythonPackageContentSerializer(core_serializers.SingleArtifactContentUploa
144
144
  required=False, allow_blank=True,
145
145
  help_text=_('The maintainer\'s e-mail address.')
146
146
  )
147
- license = serializers.CharField(
148
- required=False, allow_blank=True,
149
- help_text=_('Text indicating the license covering the distribution')
150
- )
151
- requires_python = serializers.CharField(
152
- required=False, allow_blank=True,
153
- help_text=_('The Python version(s) that the distribution is guaranteed to be '
154
- 'compatible with.')
147
+ obsoletes_dist = serializers.JSONField(
148
+ required=False, default=list,
149
+ help_text=_('A JSON list containing names of a distutils project\'s distribution which '
150
+ 'this distribution renders obsolete, meaning that the two projects should not '
151
+ 'be installed at the same time.')
155
152
  )
156
153
  project_url = serializers.CharField(
157
154
  required=False, allow_blank=True,
@@ -161,39 +158,73 @@ class PythonPackageContentSerializer(core_serializers.SingleArtifactContentUploa
161
158
  required=False, default=dict,
162
159
  help_text=_('A dictionary of labels and URLs for the project.')
163
160
  )
164
- platform = serializers.CharField(
165
- required=False, allow_blank=True,
166
- help_text=_('A comma-separated list of platform specifications, '
167
- 'summarizing the operating systems supported by the package.')
161
+ provides_dist = serializers.JSONField(
162
+ required=False, default=list,
163
+ help_text=_('A JSON list containing names of a Distutils project which is contained'
164
+ ' within this distribution.')
168
165
  )
169
- supported_platform = serializers.CharField(
170
- required=False, allow_blank=True,
171
- help_text=_('Field to specify the OS and CPU for which the binary package was compiled. ')
166
+ requires_external = serializers.JSONField(
167
+ required=False, default=list,
168
+ help_text=_('A JSON list containing some dependency in the system that the distribution '
169
+ 'is to be used.')
172
170
  )
173
171
  requires_dist = serializers.JSONField(
174
172
  required=False, default=list,
175
173
  help_text=_('A JSON list containing names of some other distutils project '
176
174
  'required by this distribution.')
177
175
  )
178
- provides_dist = serializers.JSONField(
179
- required=False, default=list,
180
- help_text=_('A JSON list containing names of a Distutils project which is contained'
181
- ' within this distribution.')
176
+ requires_python = serializers.CharField(
177
+ required=False, allow_blank=True,
178
+ help_text=_('The Python version(s) that the distribution is guaranteed to be '
179
+ 'compatible with.')
182
180
  )
183
- obsoletes_dist = serializers.JSONField(
181
+ # Version 2.1
182
+ description_content_type = serializers.CharField(
183
+ required=False, allow_blank=True,
184
+ help_text=_('A string stating the markup syntax (if any) used in the distribution’s'
185
+ ' description, so that tools can intelligently render the description.')
186
+ )
187
+ provides_extras = serializers.JSONField(
184
188
  required=False, default=list,
185
- help_text=_('A JSON list containing names of a distutils project\'s distribution which '
186
- 'this distribution renders obsolete, meaning that the two projects should not '
187
- 'be installed at the same time.')
189
+ help_text=_('A JSON list containing names of optional features provided by the package.')
188
190
  )
189
- requires_external = serializers.JSONField(
191
+ # Version 2.2
192
+ dynamic = serializers.JSONField(
190
193
  required=False, default=list,
191
- help_text=_('A JSON list containing some dependency in the system that the distribution '
192
- 'is to be used.')
194
+ help_text=_('A JSON list containing names of other core metadata fields which are '
195
+ 'permitted to vary between sdist and bdist packages. Fields NOT marked '
196
+ 'dynamic MUST be the same between bdist and sdist.')
193
197
  )
194
- classifiers = serializers.JSONField(
198
+ # Version 2.4
199
+ license_expression = serializers.CharField(
200
+ required=False, allow_blank=True,
201
+ help_text=_('Text string that is a valid SPDX license expression.')
202
+ )
203
+ license_file = serializers.JSONField(
195
204
  required=False, default=list,
196
- help_text=_('A JSON list containing classification values for a Python package.')
205
+ help_text=_('A JSON list containing names of the paths to license-related files.')
206
+ )
207
+ # Release metadata
208
+ filename = serializers.CharField(
209
+ help_text=_('The name of the distribution package, usually of the format:'
210
+ ' {distribution}-{version}(-{build tag})?-{python tag}-{abi tag}'
211
+ '-{platform tag}.{packagetype}'),
212
+ read_only=True,
213
+ )
214
+ packagetype = serializers.CharField(
215
+ help_text=_('The type of the distribution package '
216
+ '(e.g. sdist, bdist_wheel, bdist_egg, etc)'),
217
+ read_only=True,
218
+ )
219
+ python_version = serializers.CharField(
220
+ help_text=_(
221
+ 'The tag that indicates which Python implementation or version the package requires.'
222
+ ),
223
+ read_only=True,
224
+ )
225
+ sha256 = serializers.CharField(
226
+ default='',
227
+ help_text=_('The SHA256 digest of this package.'),
197
228
  )
198
229
 
199
230
  def deferred_validate(self, data):
@@ -242,11 +273,13 @@ class PythonPackageContentSerializer(core_serializers.SingleArtifactContentUploa
242
273
 
243
274
  class Meta:
244
275
  fields = core_serializers.SingleArtifactContentUploadSerializer.Meta.fields + (
245
- 'filename', 'packagetype', 'name', 'version', 'sha256', 'metadata_version', 'summary',
246
- 'description', 'description_content_type', 'keywords', 'home_page', 'download_url',
247
- 'author', 'author_email', 'maintainer', 'maintainer_email', 'license',
248
- 'requires_python', 'project_url', 'project_urls', 'platform', 'supported_platform',
249
- 'requires_dist', 'provides_dist', 'obsoletes_dist', 'requires_external', 'classifiers'
276
+ 'author', 'author_email', 'description', 'home_page', 'keywords', 'license',
277
+ 'metadata_version', 'name', 'platform', 'summary', 'version', 'classifiers',
278
+ 'download_url', 'supported_platform', 'maintainer', 'maintainer_email',
279
+ 'obsoletes_dist', 'project_url', 'project_urls', 'provides_dist', 'requires_external',
280
+ 'requires_dist', 'requires_python', 'description_content_type',
281
+ 'provides_extras', 'dynamic', 'license_expression', 'license_file',
282
+ 'filename', 'packagetype', 'python_version', 'sha256'
250
283
  )
251
284
  model = python_models.PythonPackageContent
252
285
 
@@ -87,35 +87,46 @@ def parse_project_metadata(project):
87
87
  dictionary: of python project metadata
88
88
 
89
89
  """
90
- package = {}
91
- package['name'] = project.get('name') or ""
92
- package['version'] = project.get('version') or ""
93
- package['packagetype'] = project.get('packagetype') or ""
94
- package['metadata_version'] = project.get('metadata_version') or ""
95
- package['summary'] = project.get('summary') or ""
96
- package['description'] = project.get('description') or ""
97
- package['keywords'] = project.get('keywords') or ""
98
- package['home_page'] = project.get('home_page') or ""
99
- package['download_url'] = project.get('download_url') or ""
100
- package['author'] = project.get('author') or ""
101
- package['author_email'] = project.get('author_email') or ""
102
- package['maintainer'] = project.get('maintainer') or ""
103
- package['maintainer_email'] = project.get('maintainer_email') or ""
104
- package['license'] = project.get('license') or ""
105
- package['project_url'] = project.get('project_url') or ""
106
- package['platform'] = project.get('platform') or ""
107
- package['supported_platform'] = project.get('supported_platform') or ""
108
- package['requires_python'] = project.get('requires_python') or ""
109
- package['requires_dist'] = json.dumps(project.get('requires_dist', []))
110
- package['provides_dist'] = json.dumps(project.get('provides_dist', []))
111
- package['obsoletes_dist'] = json.dumps(project.get('obsoletes_dist', []))
112
- package['requires_external'] = json.dumps(project.get('requires_external', []))
113
- package['classifiers'] = json.dumps(project.get('classifiers', []))
114
- package['project_urls'] = json.dumps(project.get('project_urls', {}))
115
- package['description_content_type'] = project.get('description_content_type') or ""
116
- package['python_version'] = project.get('python_version') or ""
117
-
118
- return package
90
+ return {
91
+ # Core metadata
92
+ # Version 1.0
93
+ 'author': project.get('author') or "",
94
+ 'author_email': project.get('author_email') or "",
95
+ 'description': project.get('description') or "",
96
+ 'home_page': project.get('home_page') or "",
97
+ 'keywords': project.get('keywords') or "",
98
+ 'license': project.get('license') or "",
99
+ 'metadata_version': project.get('metadata_version') or "",
100
+ 'name': project.get('name') or "",
101
+ 'platform': project.get('platform') or "",
102
+ 'summary': project.get('summary') or "",
103
+ 'version': project.get('version') or "",
104
+ # Version 1.1
105
+ 'classifiers': json.dumps(project.get('classifiers', [])),
106
+ 'download_url': project.get('download_url') or "",
107
+ 'supported_platform': project.get('supported_platform') or "",
108
+ # Version 1.2
109
+ 'maintainer': project.get('maintainer') or "",
110
+ 'maintainer_email': project.get('maintainer_email') or "",
111
+ 'obsoletes_dist': json.dumps(project.get('obsoletes_dist', [])),
112
+ 'project_url': project.get('project_url') or "",
113
+ 'project_urls': json.dumps(project.get('project_urls', {})),
114
+ 'provides_dist': json.dumps(project.get('provides_dist', [])),
115
+ 'requires_external': json.dumps(project.get('requires_external', [])),
116
+ 'requires_dist': json.dumps(project.get('requires_dist', [])),
117
+ 'requires_python': project.get('requires_python') or "",
118
+ # Version 2.1
119
+ 'description_content_type': project.get('description_content_type') or "",
120
+ 'provides_extras': json.dumps(project.get('provides_extras', [])),
121
+ # Version 2.2
122
+ 'dynamic': json.dumps(project.get('dynamic', [])),
123
+ # Version 2.4
124
+ 'license_expression': project.get('license_expression') or "",
125
+ 'license_file': json.dumps(project.get('license_file', [])),
126
+ # Release metadata
127
+ 'packagetype': project.get('packagetype') or "",
128
+ 'python_version': project.get('python_version') or "",
129
+ }
119
130
 
120
131
 
121
132
  def parse_metadata(project, version, distribution):
@@ -311,6 +322,11 @@ def python_content_to_info(content):
311
322
  "classifiers": json_to_dict(content.classifiers) or None,
312
323
  "yanked": False, # These are no longer used on PyPI, but are still present
313
324
  "yanked_reason": None,
325
+ # New core metadata (Version 2.1, 2.2, 2.4)
326
+ "provides_extras": json_to_dict(content.provides_extras) or None,
327
+ "dynamic": json_to_dict(content.dynamic) or None,
328
+ "license_expression": content.license_expression or "",
329
+ "license_file": json_to_dict(content.license_file) or None,
314
330
  }
315
331
 
316
332
 
@@ -111,6 +111,34 @@ def test_content_crud(
111
111
  assert msg in e.value.task.error["description"]
112
112
 
113
113
 
114
+ def test_content_create_new_metadata(
115
+ delete_orphans_pre, download_python_file, monitor_task, python_bindings
116
+ ):
117
+ """
118
+ Test the creation of python content unit with newly added core metadata (provides_extras,
119
+ dynamic, license_expression, license_file).
120
+ """
121
+ python_egg_filename = "setuptools-80.9.0.tar.gz"
122
+ python_egg_url = urljoin(urljoin(PYTHON_FIXTURES_URL, "packages/"), python_egg_filename)
123
+ python_file = download_python_file(python_egg_filename, python_egg_url)
124
+
125
+ body = {"relative_path": python_egg_filename, "file": python_file}
126
+ response = python_bindings.ContentPackagesApi.create(**body)
127
+ task = monitor_task(response.task)
128
+ content = python_bindings.ContentPackagesApi.read(task.created_resources[0])
129
+
130
+ python_package_data = {
131
+ "filename": "setuptools-80.9.0.tar.gz",
132
+ "provides_extras":
133
+ '["test", "doc", "ssl", "certs", "core", "check", "cover", "enabler", "type"]',
134
+ "dynamic": '["license-file"]',
135
+ "license_expression": "MIT",
136
+ "license_file": '["LICENSE"]',
137
+ }
138
+ for k, v in python_package_data.items():
139
+ assert getattr(content, k) == v
140
+
141
+
114
142
  @pytest.mark.parallel
115
143
  def test_upload_metadata_23_spec(python_content_factory):
116
144
  """Test that packages using metadata spec 2.3 can be uploaded to pulp."""
@@ -139,11 +167,13 @@ def test_upload_requires_python(python_content_factory):
139
167
  @pytest.mark.parallel
140
168
  def test_upload_metadata_24_spec(python_content_factory):
141
169
  """Test that packages using metadata spec 2.4 can be uploaded to pulp."""
142
- filename = "urllib3-2.3.0-py3-none-any.whl"
170
+ filename = "setuptools-80.9.0.tar.gz"
143
171
  with PyPISimple() as client:
144
- page = client.get_project_page("urllib3")
172
+ page = client.get_project_page("setuptools")
145
173
  for package in page.packages:
146
174
  if package.filename == filename:
147
175
  content = python_content_factory(filename, url=package.url)
148
176
  assert content.metadata_version == "2.4"
177
+ assert content.license_expression == "MIT"
178
+ assert content.license_file == '["LICENSE"]'
149
179
  break
@@ -96,8 +96,6 @@ def test_metadata_repair_command(
96
96
  }
97
97
  content = create_content_direct(python_file, data)
98
98
  for field, wrong_value in data.items():
99
- if field == "python_version":
100
- continue
101
99
  assert getattr(content, field) == wrong_value
102
100
 
103
101
  move_to_repository(python_repo.pulp_href, [content.pulp_href])
@@ -127,15 +127,16 @@ PYTHON_LG_PROJECT_SPECIFIER = [
127
127
  "Django", # matches 31
128
128
  "pytz", # matches 6
129
129
  "scipy", # matches 23
130
+ "setuptools", # matches 2
130
131
  "shelf-reader", # matches 2
131
132
  ]
132
- PYTHON_LG_PACKAGE_COUNT = 90
133
+ PYTHON_LG_PACKAGE_COUNT = 92
133
134
  PYTHON_LG_FIXTURE_SUMMARY = {PYTHON_CONTENT_NAME: PYTHON_LG_PACKAGE_COUNT}
134
135
  PYTHON_LG_FIXTURE_COUNTS = {
135
- "latest_3": 49,
136
- "sdist": 27,
137
- "bdist_wheel": 63,
138
- "multi": 33, # keep_latest=1, package_types="bdist_wheel", prereleases=False
136
+ "latest_3": 51,
137
+ "sdist": 28,
138
+ "bdist_wheel": 64,
139
+ "multi": 34, # keep_latest=1, package_types="bdist_wheel", prereleases=False
139
140
  }
140
141
 
141
142
  DJANGO_LATEST_3 = 4 # latest version has 2 dists, each other has 1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pulp-python
3
- Version: 3.16.0
3
+ Version: 3.17.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
@@ -22,14 +22,15 @@ Requires-Python: >=3.9
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
24
  Requires-Dist: pulpcore<3.85,>=3.49.0
25
- Requires-Dist: pkginfo<1.13.0,>=1.10.0
25
+ Requires-Dist: pkginfo<1.13.0,>=1.12.0
26
26
  Requires-Dist: bandersnatch<6.4,>=6.3.0
27
27
  Requires-Dist: pypi-simple<2.0,>=1.5.0
28
28
  Dynamic: license-file
29
29
 
30
30
  # pulp_python
31
31
 
32
- ![Pulp Nightly CI/CD](https://github.com/pulp/pulp_python/actions/workflows/nightly.yml/badge.svg)
32
+ [![PyPI](https://img.shields.io/pypi/v/pulp_python.svg)](https://pypi.python.org/pypi/pulp_python)
33
+ [![Pulp Nightly CI/CD](https://github.com/pulp/pulp_python/actions/workflows/nightly.yml/badge.svg)](https://github.com/pulp/pulp_python/actions/workflows/nightly.yml)
33
34
 
34
35
  A Pulp plugin to support hosting your own pip compatible Python packages.
35
36
 
@@ -42,6 +42,7 @@ pulp_python/app/migrations/0010_update_json_field.py
42
42
  pulp_python/app/migrations/0011_alter_pythondistribution_distribution_ptr_and_more.py
43
43
  pulp_python/app/migrations/0012_add_domain.py
44
44
  pulp_python/app/migrations/0013_add_rbac_permissions.py
45
+ pulp_python/app/migrations/0014_pythonpackagecontent_dynamic_and_more.py
45
46
  pulp_python/app/migrations/__init__.py
46
47
  pulp_python/app/pypi/__init__.py
47
48
  pulp_python/app/pypi/serializers.py
@@ -1,4 +1,4 @@
1
1
  pulpcore<3.85,>=3.49.0
2
- pkginfo<1.13.0,>=1.10.0
2
+ pkginfo<1.13.0,>=1.12.0
3
3
  bandersnatch<6.4,>=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.16.0"
10
+ version = "3.17.1"
11
11
  description = "pulp-python plugin for the Pulp Project"
12
12
  readme = "README.md"
13
13
  authors = [
@@ -28,7 +28,7 @@ classifiers=[
28
28
  requires-python = ">=3.9"
29
29
  dependencies = [
30
30
  "pulpcore>=3.49.0,<3.85",
31
- "pkginfo>=1.10.0,<1.13.0",
31
+ "pkginfo>=1.12.0,<1.13.0",
32
32
  "bandersnatch>=6.3.0,<6.4", # Anything >=6.4 requires Python 3.10+
33
33
  "pypi-simple>=1.5.0,<2.0",
34
34
  ]
@@ -77,7 +77,7 @@ ignore = [
77
77
  [tool.bumpversion]
78
78
  # This section is managed by the plugin template. Do not edit manually.
79
79
 
80
- current_version = "3.16.0"
80
+ current_version = "3.17.1"
81
81
  commit = false
82
82
  tag = false
83
83
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<alpha>0a)?(?P<patch>\\d+)(\\.(?P<release>[a-z]+))?"
@@ -1,8 +0,0 @@
1
- # pulp_python
2
-
3
- ![Pulp Nightly CI/CD](https://github.com/pulp/pulp_python/actions/workflows/nightly.yml/badge.svg)
4
-
5
- A Pulp plugin to support hosting your own pip compatible Python packages.
6
-
7
- For more information, please see the [documentation](https://docs.pulpproject.org/pulp_python/) or the
8
- [Pulp project page](https://pulpproject.org).
File without changes
File without changes
File without changes
File without changes
File without changes