Skip to content

Commit 4c8ccfb

Browse files
committed
Add initial support for License-Expression (PEP 639)
1 parent 6998ef2 commit 4c8ccfb

File tree

7 files changed

+86
-12
lines changed

7 files changed

+86
-12
lines changed

Diff for: docs/userguide/pyproject_config.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ The ``project`` table contains metadata fields as described by the
4949
readme = "README.rst"
5050
requires-python = ">=3.8"
5151
keywords = ["one", "two"]
52-
license = {text = "BSD-3-Clause"}
52+
license = "BSD-3-Clause"
5353
classifiers = [
5454
"Framework :: Django",
5555
"Programming Language :: Python :: 3",

Diff for: newsfragments/4706.feature.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added initial support for ``License-Expression`` (`PEP 639 <https://peps.python.org/pep-0639/#add-license-expression-field>`_). -- by :user:`cdce8p`

Diff for: pyproject.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ backend-path = ["."]
66
[project]
77
name = "setuptools"
88
version = "75.2.0"
9+
license = "MIT"
910
authors = [
1011
{ name = "Python Packaging Authority", email = "[email protected]" },
1112
]
@@ -14,7 +15,6 @@ readme = "README.rst"
1415
classifiers = [
1516
"Development Status :: 5 - Production/Stable",
1617
"Intended Audience :: Developers",
17-
"License :: OSI Approved :: MIT License",
1818
"Programming Language :: Python :: 3",
1919
"Programming Language :: Python :: 3 :: Only",
2020
"Topic :: Software Development :: Libraries :: Python Modules",
@@ -135,7 +135,7 @@ type = [
135135

136136
# pin mypy version so a new version doesn't suddenly cause the CI to fail,
137137
# until types-setuptools is removed from typeshed.
138-
# For help with static-typing issues, or mypy update, ping @Avasam
138+
# For help with static-typing issues, or mypy update, ping @Avasam
139139
"mypy==1.12.*",
140140
# Typing fixes in version newer than we require at runtime
141141
"importlib_metadata>=7.0.2; python_version < '3.10'",

Diff for: setuptools/_core_metadata.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,13 @@ def write_field(key, value):
174174
if attr_val is not None:
175175
write_field(field, attr_val)
176176

177-
license = self.get_license()
178-
if license:
179-
write_field('License', rfc822_escape(license))
177+
license_expression = self.get_license_expression()
178+
if license_expression:
179+
write_field('License-Expression', rfc822_escape(license_expression))
180+
else:
181+
license = self.get_license()
182+
if license:
183+
write_field('License', rfc822_escape(license))
180184

181185
for project_url in self.project_urls.items():
182186
write_field('Project-URL', '%s, %s' % project_url)

Diff for: setuptools/_distutils/dist.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1052,6 +1052,7 @@ def __init__(self, path=None):
10521052
self.maintainer_email = None
10531053
self.url = None
10541054
self.license = None
1055+
self.license_expression = None
10551056
self.description = None
10561057
self.long_description = None
10571058
self.keywords = None
@@ -1089,6 +1090,7 @@ def _read_list(name):
10891090
self.maintainer_email = None
10901091
self.url = _read_field('home-page')
10911092
self.license = _read_field('license')
1093+
self.license_expression = _read_field('license-expression')
10921094

10931095
if 'download-url' in msg:
10941096
self.download_url = _read_field('download-url')
@@ -1219,7 +1221,8 @@ def get_url(self):
12191221
def get_license(self):
12201222
return self.license
12211223

1222-
get_licence = get_license
1224+
def get_license_expression(self):
1225+
return self.license_expression
12231226

12241227
def get_description(self):
12251228
return self.description

Diff for: setuptools/config/_apply_pyprojecttoml.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -173,14 +173,17 @@ def _long_description(
173173
dist._referenced_files.add(file)
174174

175175

176-
def _license(dist: Distribution, val: dict, root_dir: StrPath | None):
176+
def _license(dist: Distribution, val: str | dict, root_dir: StrPath | None):
177177
from setuptools.config import expand
178178

179-
if "file" in val:
180-
_set_config(dist, "license", expand.read_files([val["file"]], root_dir))
181-
dist._referenced_files.add(val["file"])
179+
if isinstance(val, str):
180+
_set_config(dist, "license_expression", val)
182181
else:
183-
_set_config(dist, "license", val["text"])
182+
if "file" in val:
183+
_set_config(dist, "license", expand.read_files([val["file"]], root_dir))
184+
dist._referenced_files.add(val["file"])
185+
else:
186+
_set_config(dist, "license", val["text"])
184187

185188

186189
def _people(dist: Distribution, val: list[dict], _root_dir: StrPath | None, kind: str):

Diff for: setuptools/tests/config/test_apply_pyprojecttoml.py

+63
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,28 @@ def main_gui(): pass
155155
def main_tomatoes(): pass
156156
"""
157157

158+
PEP639_LICENSE_TEXT = """\
159+
[project]
160+
name = "spam"
161+
version = "2020.0.0"
162+
authors = [
163+
{email = "[email protected]"},
164+
{name = "Tzu-Ping Chung"}
165+
]
166+
license = {text = "MIT"}
167+
"""
168+
169+
PEP639_LICENSE_EXPRESSION = """\
170+
[project]
171+
name = "spam"
172+
version = "2020.0.0"
173+
authors = [
174+
{email = "[email protected]"},
175+
{name = "Tzu-Ping Chung"}
176+
]
177+
license = "MIT"
178+
"""
179+
158180

159181
def _pep621_example_project(
160182
tmp_path,
@@ -250,6 +272,47 @@ def test_utf8_maintainer_in_metadata( # issue-3663
250272
assert f"Maintainer-email: {expected_maintainers_meta_value}" in content
251273

252274

275+
@pytest.mark.parametrize(
276+
('pyproject_text', 'license', 'license_expression', 'content_str'),
277+
(
278+
pytest.param(
279+
PEP639_LICENSE_TEXT,
280+
'MIT',
281+
None,
282+
'License: MIT',
283+
id='license-text',
284+
),
285+
pytest.param(
286+
PEP639_LICENSE_EXPRESSION,
287+
None,
288+
'MIT',
289+
'License-Expression: MIT',
290+
id='license-expression',
291+
),
292+
),
293+
)
294+
def test_license_in_metadata(
295+
license,
296+
license_expression,
297+
content_str,
298+
pyproject_text,
299+
tmp_path,
300+
):
301+
pyproject = _pep621_example_project(
302+
tmp_path,
303+
"README",
304+
pyproject_text=pyproject_text,
305+
)
306+
dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
307+
assert dist.metadata.license == license
308+
assert dist.metadata.license_expression == license_expression
309+
pkg_file = tmp_path / "PKG-FILE"
310+
with open(pkg_file, "w", encoding="utf-8") as fh:
311+
dist.metadata.write_pkg_file(fh)
312+
content = pkg_file.read_text(encoding="utf-8")
313+
assert content_str in content
314+
315+
253316
class TestLicenseFiles:
254317
# TODO: After PEP 639 is accepted, we have to move the license-files
255318
# to the `project` table instead of `tool.setuptools`

0 commit comments

Comments
 (0)