Skip to content

Commit cdd1323

Browse files
committed
Fully support the latest core metadata (up to 2.4)
closes #689
1 parent 3803fe8 commit cdd1323

File tree

7 files changed

+114
-6
lines changed

7 files changed

+114
-6
lines changed

CHANGES/689.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added full support for the latest core metadata (up to 2.4).
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
]

pulp_python/app/models.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,15 @@ class PythonPackageContent(Content):
140140
"""
141141
A Content Type representing Python's Distribution Package.
142142
143-
As defined in pep-0426 and pep-0345.
143+
Core Metadata:
144+
https://packaging.python.org/en/latest/specifications/core-metadata/
144145
145-
https://www.python.org/dev/peps/pep-0491/
146-
https://www.python.org/dev/peps/pep-0345/
146+
Release metadata (JSON API):
147+
https://docs.pypi.org/api/json/
148+
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/
147152
"""
148153
# Core metadata
149154
# Version 1.0
@@ -174,6 +179,12 @@ class PythonPackageContent(Content):
174179
requires_python = models.TextField()
175180
# Version 2.1
176181
description_content_type = models.TextField()
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)
177188

178189
# Release metadata
179190
filename = models.TextField(db_index=True)

pulp_python/app/serializers.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,26 @@ class PythonPackageContentSerializer(core_serializers.SingleArtifactContentUploa
184184
help_text=_('A string stating the markup syntax (if any) used in the distribution’s'
185185
' description, so that tools can intelligently render the description.')
186186
)
187+
provides_extras = serializers.JSONField(
188+
required=False, default=list,
189+
help_text=_('A JSON list containing names of optional features provided by the package.')
190+
)
191+
# Version 2.2
192+
dynamic = serializers.JSONField(
193+
required=False, default=list,
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.')
197+
)
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(
204+
required=False, default=list,
205+
help_text=_('A JSON list containing names of the paths to license-related files.')
206+
)
187207
# Release metadata
188208
filename = serializers.CharField(
189209
help_text=_('The name of the distribution package, usually of the format:'
@@ -258,6 +278,7 @@ class Meta:
258278
'download_url', 'supported_platform', 'maintainer', 'maintainer_email',
259279
'obsoletes_dist', 'project_url', 'project_urls', 'provides_dist', 'requires_external',
260280
'requires_dist', 'requires_python', 'description_content_type',
281+
'provides_extras', 'dynamic', 'license_expression', 'license_file',
261282
'filename', 'packagetype', 'python_version', 'sha256'
262283
)
263284
model = python_models.PythonPackageContent

pulp_python/app/utils.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ def parse_project_metadata(project):
117117
'requires_python': project.get('requires_python') or "",
118118
# Version 2.1
119119
'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', [])),
120126
# Release metadata
121127
'packagetype': project.get('packagetype') or "",
122128
'python_version': project.get('python_version') or "",
@@ -316,6 +322,11 @@ def python_content_to_info(content):
316322
"classifiers": json_to_dict(content.classifiers) or None,
317323
"yanked": False, # These are no longer used on PyPI, but are still present
318324
"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,
319330
}
320331

321332

pulp_python/tests/functional/api/test_crud_content_unit.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,34 @@ def test_content_crud(
111111
assert msg in e.value.task.error["description"]
112112

113113

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+
114142
@pytest.mark.parallel
115143
def test_upload_metadata_23_spec(python_content_factory):
116144
"""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):
139167
@pytest.mark.parallel
140168
def test_upload_metadata_24_spec(python_content_factory):
141169
"""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"
143171
with PyPISimple() as client:
144-
page = client.get_project_page("urllib3")
172+
page = client.get_project_page("setuptools")
145173
for package in page.packages:
146174
if package.filename == filename:
147175
content = python_content_factory(filename, url=package.url)
148176
assert content.metadata_version == "2.4"
177+
assert content.license_expression == "MIT"
178+
assert content.license_file == '["LICENSE"]'
149179
break

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ classifiers=[
2828
requires-python = ">=3.9"
2929
dependencies = [
3030
"pulpcore>=3.49.0,<3.85",
31-
"pkginfo>=1.10.0,<1.13.0",
31+
"pkginfo>=1.12.0,<1.13.0",
3232
"bandersnatch>=6.3.0,<6.4", # Anything >=6.4 requires Python 3.10+
3333
"pypi-simple>=1.5.0,<2.0",
3434
]

0 commit comments

Comments
 (0)