Skip to content

Commit 10c3e7c

Browse files
authored
Add warning for deprecated project.license as TOML table (pypa#4840 )
2 parents 6f0aee2 + 68397dc commit 10c3e7c

File tree

5 files changed

+90
-31
lines changed

5 files changed

+90
-31
lines changed

newsfragments/4840.feature.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Deprecated ``project.license`` as a TOML table in
2+
``pyproject.toml``. Users are expected to move towards using
3+
``project.license-files`` and/or SPDX expressions (as strings) in
4+
``pyproject.license``.
5+
See :pep:`PEP 639 <639#deprecate-license-key-table-subkeys>`.

setuptools/config/_apply_pyprojecttoml.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,14 @@ def _license(dist: Distribution, val: str | dict, root_dir: StrPath | None):
203203
if isinstance(val, str):
204204
_set_config(dist, "license_expression", _static.Str(val))
205205
else:
206+
pypa_guides = "guides/writing-pyproject-toml/#license"
207+
SetuptoolsDeprecationWarning.emit(
208+
"`project.license` as a TOML table is deprecated",
209+
"Please use a simple string containing a SPDX expression for "
210+
"`project.license`. You can also use `project.license-files`.",
211+
see_url=f"https://packaging.python.org/en/latest/{pypa_guides}",
212+
due_date=(2026, 2, 18), # Introduced on 2025-02-18
213+
)
206214
if "file" in val:
207215
# XXX: Is it completely safe to assume static?
208216
value = expand.read_files([val["file"]], root_dir)

setuptools/tests/config/test_apply_pyprojecttoml.py

Lines changed: 58 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def test_apply_pyproject_equivalent_to_setupcfg(url, monkeypatch, tmp_path):
9393
description = "Lovely Spam! Wonderful Spam!"
9494
readme = "README.rst"
9595
requires-python = ">=3.8"
96-
license = {file = "LICENSE.txt"}
96+
license-files = ["LICENSE.txt"] # Updated to be PEP 639 compliant
9797
keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"]
9898
authors = [
9999
{email = "[email protected]"},
@@ -206,7 +206,6 @@ def test_pep621_example(tmp_path):
206206
"""Make sure the example in PEP 621 works"""
207207
pyproject = _pep621_example_project(tmp_path)
208208
dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
209-
assert dist.metadata.license == "--- LICENSE stub ---"
210209
assert set(dist.metadata.license_files) == {"LICENSE.txt"}
211210

212211

@@ -294,6 +293,11 @@ def test_utf8_maintainer_in_metadata( # issue-3663
294293
'License: MIT',
295294
'License-Expression: ',
296295
id='license-text',
296+
marks=[
297+
pytest.mark.filterwarnings(
298+
"ignore:.project.license. as a TOML table is deprecated",
299+
)
300+
],
297301
),
298302
pytest.param(
299303
PEP639_LICENSE_EXPRESSION,
@@ -354,47 +358,51 @@ def test_license_classifier_without_license_expression(tmp_path):
354358
"""
355359
pyproject = _pep621_example_project(tmp_path, "README", text)
356360

357-
msg = "License classifiers are deprecated(?:.|\n)*MIT License"
358-
with pytest.warns(SetuptoolsDeprecationWarning, match=msg):
361+
msg1 = "License classifiers are deprecated(?:.|\n)*MIT License"
362+
msg2 = ".project.license. as a TOML table is deprecated"
363+
with (
364+
pytest.warns(SetuptoolsDeprecationWarning, match=msg1),
365+
pytest.warns(SetuptoolsDeprecationWarning, match=msg2),
366+
):
359367
dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
360368

361369
# Check license classifier is still included
362370
assert dist.metadata.get_classifiers() == ["License :: OSI Approved :: MIT License"]
363371

364372

365373
class TestLicenseFiles:
366-
def base_pyproject(self, tmp_path, additional_text):
367-
pyproject = _pep621_example_project(tmp_path, "README")
368-
text = pyproject.read_text(encoding="utf-8")
369-
370-
# Sanity-check
371-
assert 'license = {file = "LICENSE.txt"}' in text
372-
assert "[tool.setuptools]" not in text
373-
374-
text = f"{text}\n{additional_text}\n"
375-
pyproject.write_text(text, encoding="utf-8")
376-
return pyproject
377-
378-
def base_pyproject_license_pep639(self, tmp_path, additional_text=""):
379-
pyproject = _pep621_example_project(tmp_path, "README")
380-
text = pyproject.read_text(encoding="utf-8")
374+
def base_pyproject(
375+
self,
376+
tmp_path,
377+
additional_text="",
378+
license_toml='license = {file = "LICENSE.txt"}\n',
379+
):
380+
text = PEP639_LICENSE_EXPRESSION
381381

382382
# Sanity-check
383-
assert 'license = {file = "LICENSE.txt"}' in text
383+
assert 'license = "mit or apache-2.0"' in text
384384
assert 'license-files' not in text
385385
assert "[tool.setuptools]" not in text
386386

387387
text = re.sub(
388-
r"(license = {file = \"LICENSE.txt\"})\n",
389-
("license = \"licenseref-Proprietary\"\nlicense-files = [\"_FILE*\"]\n"),
388+
r"(license = .*)\n",
389+
license_toml,
390390
text,
391391
count=1,
392392
)
393-
if additional_text:
394-
text = f"{text}\n{additional_text}\n"
395-
pyproject.write_text(text, encoding="utf-8")
393+
assert license_toml in text # sanity check
394+
text = f"{text}\n{additional_text}\n"
395+
pyproject = _pep621_example_project(tmp_path, "README", pyproject_text=text)
396396
return pyproject
397397

398+
def base_pyproject_license_pep639(self, tmp_path, additional_text=""):
399+
return self.base_pyproject(
400+
tmp_path,
401+
additional_text=additional_text,
402+
license_toml='license = "licenseref-Proprietary"'
403+
'\nlicense-files = ["_FILE*"]\n',
404+
)
405+
398406
def test_both_license_and_license_files_defined(self, tmp_path):
399407
setuptools_config = '[tool.setuptools]\nlicense-files = ["_FILE*"]'
400408
pyproject = self.base_pyproject(tmp_path, setuptools_config)
@@ -407,8 +415,12 @@ def test_both_license_and_license_files_defined(self, tmp_path):
407415
license = tmp_path / "LICENSE.txt"
408416
license.write_text("LicenseRef-Proprietary\n", encoding="utf-8")
409417

410-
msg = "'tool.setuptools.license-files' is deprecated in favor of 'project.license-files'"
411-
with pytest.warns(SetuptoolsDeprecationWarning, match=msg):
418+
msg1 = "'tool.setuptools.license-files' is deprecated in favor of 'project.license-files'"
419+
msg2 = ".project.license. as a TOML table is deprecated"
420+
with (
421+
pytest.warns(SetuptoolsDeprecationWarning, match=msg1),
422+
pytest.warns(SetuptoolsDeprecationWarning, match=msg2),
423+
):
412424
dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
413425
assert set(dist.metadata.license_files) == {"_FILE.rst", "_FILE.txt"}
414426
assert dist.metadata.license == "LicenseRef-Proprietary\n"
@@ -437,17 +449,35 @@ def test_license_files_defined_twice(self, tmp_path):
437449
def test_default_patterns(self, tmp_path):
438450
setuptools_config = '[tool.setuptools]\nzip-safe = false'
439451
# ^ used just to trigger section validation
440-
pyproject = self.base_pyproject(tmp_path, setuptools_config)
452+
pyproject = self.base_pyproject(tmp_path, setuptools_config, license_toml="")
441453

442454
license_files = "LICENCE-a.html COPYING-abc.txt AUTHORS-xyz NOTICE,def".split()
443455

444456
for fname in license_files:
445457
(tmp_path / fname).write_text(f"{fname}\n", encoding="utf-8")
446458

447459
dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
460+
448461
assert (tmp_path / "LICENSE.txt").exists() # from base example
449462
assert set(dist.metadata.license_files) == {*license_files, "LICENSE.txt"}
450463

464+
def test_deprecated_file_expands_to_text(self, tmp_path):
465+
"""Make sure the old example with ``license = {text = ...}`` works"""
466+
467+
assert 'license-files = ["LICENSE.txt"]' in PEP621_EXAMPLE # sanity check
468+
text = PEP621_EXAMPLE.replace(
469+
'license-files = ["LICENSE.txt"]',
470+
'license = {file = "LICENSE.txt"}',
471+
)
472+
pyproject = _pep621_example_project(tmp_path, pyproject_text=text)
473+
474+
msg = ".project.license. as a TOML table is deprecated"
475+
with pytest.warns(SetuptoolsDeprecationWarning, match=msg):
476+
dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
477+
478+
assert dist.metadata.license == "--- LICENSE stub ---"
479+
assert set(dist.metadata.license_files) == {"LICENSE.txt"} # auto-filled
480+
451481

452482
class TestPyModules:
453483
# https://github.com/pypa/setuptools/issues/4316

setuptools/tests/test_build_meta.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -387,9 +387,13 @@ def test_build_with_pyproject_config(self, tmpdir, setup_script):
387387
build_backend = self.get_build_backend()
388388
with tmpdir.as_cwd():
389389
path.build(files)
390+
msgs = [
391+
"'tool.setuptools.license-files' is deprecated in favor of 'project.license-files'",
392+
"`project.license` as a TOML table is deprecated",
393+
]
390394
with warnings.catch_warnings():
391-
msg = "'tool.setuptools.license-files' is deprecated in favor of 'project.license-files'"
392-
warnings.filterwarnings("ignore", msg, SetuptoolsDeprecationWarning)
395+
for msg in msgs:
396+
warnings.filterwarnings("ignore", msg, SetuptoolsDeprecationWarning)
393397
sdist_path = build_backend.build_sdist("temp")
394398
wheel_file = build_backend.build_wheel("temp")
395399

setuptools/tests/test_sdist.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -708,12 +708,21 @@ def test_sdist_with_latin1_encoded_filename(self):
708708
[project]
709709
name = "testing"
710710
readme = "USAGE.rst"
711-
license = {file = "DOWHATYOUWANT"}
711+
license-files = ["DOWHATYOUWANT"]
712712
dynamic = ["version"]
713713
[tool.setuptools.dynamic]
714714
version = {file = ["src/VERSION.txt"]}
715715
""",
716716
"pyproject.toml - directive with str instead of list": """
717+
[project]
718+
name = "testing"
719+
readme = "USAGE.rst"
720+
license-files = ["DOWHATYOUWANT"]
721+
dynamic = ["version"]
722+
[tool.setuptools.dynamic]
723+
version = {file = "src/VERSION.txt"}
724+
""",
725+
"pyproject.toml - deprecated license table with file entry": """
717726
[project]
718727
name = "testing"
719728
readme = "USAGE.rst"
@@ -725,6 +734,9 @@ def test_sdist_with_latin1_encoded_filename(self):
725734
}
726735

727736
@pytest.mark.parametrize("config", _EXAMPLE_DIRECTIVES.keys())
737+
@pytest.mark.filterwarnings(
738+
"ignore:.project.license. as a TOML table is deprecated"
739+
)
728740
def test_add_files_referenced_by_config_directives(self, source_dir, config):
729741
config_file, _, _ = config.partition(" - ")
730742
config_text = self._EXAMPLE_DIRECTIVES[config]

0 commit comments

Comments
 (0)