Skip to content

Commit b80ee83

Browse files
authored
Prevent deprecated license classifiers from being written to core metadata (pypa#4833)
2 parents 0d0d516 + a20512e commit b80ee83

File tree

3 files changed

+44
-22
lines changed

3 files changed

+44
-22
lines changed

newsfragments/4833.feature.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Added exception (or warning) when deprecated license classifiers are used,
2+
according to `PEP 639 <https://peps.python.org/pep-0639/#deprecate-license-classifiers>`_.

setuptools/dist.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from ._reqs import _StrOrIter
2929
from .config import pyprojecttoml, setupcfg
3030
from .discovery import ConfigDiscovery
31+
from .errors import InvalidConfigError
3132
from .monkey import get_unpatched
3233
from .warnings import InformationOnly, SetuptoolsDeprecationWarning
3334

@@ -406,22 +407,32 @@ def _normalize_requires(self):
406407

407408
def _finalize_license_expression(self) -> None:
408409
"""Normalize license and license_expression."""
410+
classifiers = self.metadata.get_classifiers()
411+
license_classifiers = [cl for cl in classifiers if cl.startswith("License :: ")]
412+
409413
license_expr = self.metadata.license_expression
410414
if license_expr:
411415
normalized = canonicalize_license_expression(license_expr)
412416
if license_expr != normalized:
413417
InformationOnly.emit(f"Normalizing '{license_expr}' to '{normalized}'")
414418
self.metadata.license_expression = normalized
415-
416-
for cl in self.metadata.get_classifiers():
417-
if not cl.startswith("License :: "):
418-
continue
419-
SetuptoolsDeprecationWarning.emit(
420-
"License classifier are deprecated in favor of the license expression.",
421-
f"Please remove the '{cl}' classifier.",
422-
see_url="https://peps.python.org/pep-0639/",
423-
due_date=(2027, 2, 17), # Introduced 2025-02-17
419+
if license_classifiers:
420+
raise InvalidConfigError(
421+
"License classifiers have been superseded by license expressions "
422+
"(see https://peps.python.org/pep-0639/). Please remove:\n\n"
423+
+ "\n".join(license_classifiers),
424424
)
425+
elif license_classifiers:
426+
pypa_guides = "guides/writing-pyproject-toml/#license"
427+
SetuptoolsDeprecationWarning.emit(
428+
"License classifiers are deprecated.",
429+
"Please consider removing the following classifiers in favor of a "
430+
"SPDX license expression:\n\n" + "\n".join(license_classifiers),
431+
see_url=f"https://packaging.python.org/en/latest/{pypa_guides}",
432+
# Warning introduced on 2025-02-17
433+
# TODO: Should we add a due date? It may affect old/unmaintained
434+
# packages in the ecosystem and cause problems...
435+
)
425436

426437
def _finalize_license_files(self) -> None:
427438
"""Compute names of all license files which should be included."""

setuptools/tests/config/test_apply_pyprojecttoml.py

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import io
1010
import re
1111
import tarfile
12-
import warnings
1312
from inspect import cleandoc
1413
from pathlib import Path
1514
from unittest.mock import Mock
@@ -24,7 +23,7 @@
2423
from setuptools.config import expand, pyprojecttoml, setupcfg
2524
from setuptools.config._apply_pyprojecttoml import _MissingDynamic, _some_attrgetter
2625
from setuptools.dist import Distribution
27-
from setuptools.errors import RemovedConfigError
26+
from setuptools.errors import InvalidConfigError, RemovedConfigError
2827
from setuptools.warnings import SetuptoolsDeprecationWarning
2928

3029
from .downloads import retrieve_file, urls_from_file
@@ -331,26 +330,36 @@ def test_license_in_metadata(
331330
assert not_content_str not in content
332331

333332

334-
def test_license_expression_with_bad_classifier(tmp_path):
333+
def test_license_classifier_with_license_expression(tmp_path):
335334
text = PEP639_LICENSE_EXPRESSION.rsplit("\n", 2)[0]
336335
pyproject = _pep621_example_project(
337336
tmp_path,
338337
"README",
339338
f"{text}\n \"License :: OSI Approved :: MIT License\"\n]",
340339
)
341-
msg = "License classifier are deprecated(?:.|\n)*'License :: OSI Approved :: MIT License'"
342-
with pytest.raises(SetuptoolsDeprecationWarning, match=msg):
340+
msg = "License classifiers have been superseded by license expressions"
341+
with pytest.raises(InvalidConfigError, match=msg) as exc:
343342
pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
344343

345-
with warnings.catch_warnings():
346-
warnings.simplefilter("ignore", SetuptoolsDeprecationWarning)
344+
assert "License :: OSI Approved :: MIT License" in str(exc.value)
345+
346+
347+
def test_license_classifier_without_license_expression(tmp_path):
348+
text = """\
349+
[project]
350+
name = "spam"
351+
version = "2020.0.0"
352+
license = {text = "mit or apache-2.0"}
353+
classifiers = ["License :: OSI Approved :: MIT License"]
354+
"""
355+
pyproject = _pep621_example_project(tmp_path, "README", text)
356+
357+
msg = "License classifiers are deprecated(?:.|\n)*MIT License"
358+
with pytest.warns(SetuptoolsDeprecationWarning, match=msg):
347359
dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
348-
# Check license classifier is still included
349-
assert dist.metadata.get_classifiers() == [
350-
"Development Status :: 5 - Production/Stable",
351-
"Programming Language :: Python",
352-
"License :: OSI Approved :: MIT License",
353-
]
360+
361+
# Check license classifier is still included
362+
assert dist.metadata.get_classifiers() == ["License :: OSI Approved :: MIT License"]
354363

355364

356365
class TestLicenseFiles:

0 commit comments

Comments
 (0)