From 285d681810d38c5745283e1c5385b6eb0345dece Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 28 Oct 2024 06:01:58 +0100 Subject: [PATCH 1/7] Add initial support for License-Expression (PEP 639) --- docs/userguide/pyproject_config.rst | 2 +- newsfragments/4706.feature.rst | 1 + pyproject.toml | 2 +- setuptools/_core_metadata.py | 10 ++- setuptools/config/_apply_pyprojecttoml.py | 17 ++--- setuptools/dist.py | 1 + .../tests/config/test_apply_pyprojecttoml.py | 63 +++++++++++++++++++ 7 files changed, 84 insertions(+), 12 deletions(-) create mode 100644 newsfragments/4706.feature.rst diff --git a/docs/userguide/pyproject_config.rst b/docs/userguide/pyproject_config.rst index e988fec7ac..efc68603a9 100644 --- a/docs/userguide/pyproject_config.rst +++ b/docs/userguide/pyproject_config.rst @@ -49,7 +49,7 @@ The ``project`` table contains metadata fields as described by the readme = "README.rst" requires-python = ">=3.8" keywords = ["one", "two"] - license = {text = "BSD-3-Clause"} + license = "BSD-3-Clause" classifiers = [ "Framework :: Django", "Programming Language :: Python :: 3", diff --git a/newsfragments/4706.feature.rst b/newsfragments/4706.feature.rst new file mode 100644 index 0000000000..38b09276c0 --- /dev/null +++ b/newsfragments/4706.feature.rst @@ -0,0 +1 @@ +Added initial support for ``License-Expression`` (`PEP 639 `_). -- by :user:`cdce8p` diff --git a/pyproject.toml b/pyproject.toml index a9febdbe8c..bd22d79294 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,10 +11,10 @@ authors = [ ] description = "Easily download, build, install, upgrade, and uninstall Python packages" readme = "README.rst" +license = "MIT" classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Topic :: Software Development :: Libraries :: Python Modules", diff --git a/setuptools/_core_metadata.py b/setuptools/_core_metadata.py index 850cc409f7..a407f6915b 100644 --- a/setuptools/_core_metadata.py +++ b/setuptools/_core_metadata.py @@ -88,6 +88,7 @@ def read_pkg_file(self, file): self.url = _read_field_from_msg(msg, 'home-page') self.download_url = _read_field_from_msg(msg, 'download-url') self.license = _read_field_unescaped_from_msg(msg, 'license') + self.license_expression = _read_field_unescaped_from_msg(msg, 'license_expression') self.long_description = _read_field_unescaped_from_msg(msg, 'description') if self.long_description is None and self.metadata_version >= Version('2.1'): @@ -175,9 +176,12 @@ def write_field(key, value): if attr_val is not None: write_field(field, attr_val) - license = self.get_license() - if license: - write_field('License', rfc822_escape(license)) + if self.license_expression: + write_field('License-Expression', rfc822_escape(self.license_expression)) + else: + license = self.get_license() + if license: + write_field('License', rfc822_escape(license)) for label, url in self.project_urls.items(): write_field('Project-URL', f'{label}, {url}') diff --git a/setuptools/config/_apply_pyprojecttoml.py b/setuptools/config/_apply_pyprojecttoml.py index 331596bdd7..6664c6158a 100644 --- a/setuptools/config/_apply_pyprojecttoml.py +++ b/setuptools/config/_apply_pyprojecttoml.py @@ -181,16 +181,19 @@ def _long_description( dist._referenced_files.add(file) -def _license(dist: Distribution, val: dict, root_dir: StrPath | None): +def _license(dist: Distribution, val: str | dict, root_dir: StrPath | None): from setuptools.config import expand - if "file" in val: - # XXX: Is it completely safe to assume static? - value = expand.read_files([val["file"]], root_dir) - _set_config(dist, "license", _static.Str(value)) - dist._referenced_files.add(val["file"]) + if isinstance(val, str): + _set_config(dist, "license_expression", _static.Str(val)) else: - _set_config(dist, "license", _static.Str(val["text"])) + if "file" in val: + # XXX: Is it completely safe to assume static? + value = expand.read_files([val["file"]], root_dir) + _set_config(dist, "license", _static.Str(value)) + dist._referenced_files.add(val["file"]) + else: + _set_config(dist, "license", _static.Str(val["text"])) def _people(dist: Distribution, val: list[dict], _root_dir: StrPath | None, kind: str): diff --git a/setuptools/dist.py b/setuptools/dist.py index c6a3468123..55175f5e57 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -288,6 +288,7 @@ class Distribution(_Distribution): 'long_description_content_type': lambda: None, 'project_urls': dict, 'provides_extras': dict, # behaves like an ordered set + 'license_expression': lambda: None, 'license_file': lambda: None, 'license_files': lambda: None, 'install_requires': list, diff --git a/setuptools/tests/config/test_apply_pyprojecttoml.py b/setuptools/tests/config/test_apply_pyprojecttoml.py index 20146b4a89..a528a33f0d 100644 --- a/setuptools/tests/config/test_apply_pyprojecttoml.py +++ b/setuptools/tests/config/test_apply_pyprojecttoml.py @@ -156,6 +156,28 @@ def main_gui(): pass def main_tomatoes(): pass """ +PEP639_LICENSE_TEXT = """\ +[project] +name = "spam" +version = "2020.0.0" +authors = [ + {email = "hi@pradyunsg.me"}, + {name = "Tzu-Ping Chung"} +] +license = {text = "MIT"} +""" + +PEP639_LICENSE_EXPRESSION = """\ +[project] +name = "spam" +version = "2020.0.0" +authors = [ + {email = "hi@pradyunsg.me"}, + {name = "Tzu-Ping Chung"} +] +license = "MIT" +""" + def _pep621_example_project( tmp_path, @@ -251,6 +273,47 @@ def test_utf8_maintainer_in_metadata( # issue-3663 assert f"Maintainer-email: {expected_maintainers_meta_value}" in content +@pytest.mark.parametrize( + ('pyproject_text', 'license', 'license_expression', 'content_str'), + ( + pytest.param( + PEP639_LICENSE_TEXT, + 'MIT', + None, + 'License: MIT', + id='license-text', + ), + pytest.param( + PEP639_LICENSE_EXPRESSION, + None, + 'MIT', + 'License-Expression: MIT', + id='license-expression', + ), + ), +) +def test_license_in_metadata( + license, + license_expression, + content_str, + pyproject_text, + tmp_path, +): + pyproject = _pep621_example_project( + tmp_path, + "README", + pyproject_text=pyproject_text, + ) + dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject) + assert dist.metadata.license == license + assert dist.metadata.license_expression == license_expression + pkg_file = tmp_path / "PKG-FILE" + with open(pkg_file, "w", encoding="utf-8") as fh: + dist.metadata.write_pkg_file(fh) + content = pkg_file.read_text(encoding="utf-8") + assert content_str in content + + class TestLicenseFiles: # TODO: After PEP 639 is accepted, we have to move the license-files # to the `project` table instead of `tool.setuptools` From 420766cdd02008b80d019368c37880b8565b5a7c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 25 Nov 2024 01:05:56 +0100 Subject: [PATCH 2/7] Additional test case --- .../tests/config/test_apply_pyprojecttoml.py | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/setuptools/tests/config/test_apply_pyprojecttoml.py b/setuptools/tests/config/test_apply_pyprojecttoml.py index a528a33f0d..03f950ecd1 100644 --- a/setuptools/tests/config/test_apply_pyprojecttoml.py +++ b/setuptools/tests/config/test_apply_pyprojecttoml.py @@ -315,9 +315,6 @@ def test_license_in_metadata( class TestLicenseFiles: - # TODO: After PEP 639 is accepted, we have to move the license-files - # to the `project` table instead of `tool.setuptools` - def base_pyproject(self, tmp_path, additional_text): pyproject = _pep621_example_project(tmp_path, "README") text = pyproject.read_text(encoding="utf-8") @@ -330,6 +327,24 @@ def base_pyproject(self, tmp_path, additional_text): pyproject.write_text(text, encoding="utf-8") return pyproject + def base_pyproject_license_pep639(self, tmp_path): + pyproject = _pep621_example_project(tmp_path, "README") + text = pyproject.read_text(encoding="utf-8") + + # Sanity-check + assert 'license = {file = "LICENSE.txt"}' in text + assert 'license-files' not in text + assert "[tool.setuptools]" not in text + + text = re.sub( + r"(license = {file = \"LICENSE.txt\"})\n", + ("license = \"licenseref-Proprietary\"\nlicense-files = [\"_FILE*\"]\n"), + text, + count=1, + ) + pyproject.write_text(text, encoding="utf-8") + return pyproject + def test_both_license_and_license_files_defined(self, tmp_path): setuptools_config = '[tool.setuptools]\nlicense-files = ["_FILE*"]' pyproject = self.base_pyproject(tmp_path, setuptools_config) @@ -346,6 +361,18 @@ def test_both_license_and_license_files_defined(self, tmp_path): assert set(dist.metadata.license_files) == {"_FILE.rst", "_FILE.txt"} assert dist.metadata.license == "LicenseRef-Proprietary\n" + def test_both_license_and_license_files_defined_pep639(self, tmp_path): + # Set license and license-files + pyproject = self.base_pyproject_license_pep639(tmp_path) + + (tmp_path / "_FILE.txt").touch() + (tmp_path / "_FILE.rst").touch() + + dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject) + assert set(dist.metadata.license_files) == {"_FILE.rst", "_FILE.txt"} + assert dist.metadata.license is None + assert dist.metadata.license_expression == "LicenseRef-Proprietary" + def test_default_patterns(self, tmp_path): setuptools_config = '[tool.setuptools]\nzip-safe = false' # ^ used just to trigger section validation From 346bf17e0cc8fc6e8b0ea3e6dafa3af91009da6d Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 25 Nov 2024 01:05:13 +0100 Subject: [PATCH 3/7] Normalize license expression --- setuptools/config/_apply_pyprojecttoml.py | 1 + setuptools/dist.py | 20 ++++++++++++++++ .../tests/config/test_apply_pyprojecttoml.py | 23 +++++++++++++++---- 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/setuptools/config/_apply_pyprojecttoml.py b/setuptools/config/_apply_pyprojecttoml.py index 6664c6158a..6bb2bea514 100644 --- a/setuptools/config/_apply_pyprojecttoml.py +++ b/setuptools/config/_apply_pyprojecttoml.py @@ -58,6 +58,7 @@ def apply(dist: Distribution, config: dict, filename: StrPath) -> Distribution: os.chdir(root_dir) try: dist._finalize_requires() + dist._finalize_license_expression() dist._finalize_license_files() finally: os.chdir(current_directory) diff --git a/setuptools/dist.py b/setuptools/dist.py index 55175f5e57..7a13ea6a04 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -12,6 +12,7 @@ from typing import TYPE_CHECKING, Any, Union from more_itertools import partition, unique_everseen +from packaging.licenses import canonicalize_license_expression from packaging.markers import InvalidMarker, Marker from packaging.specifiers import InvalidSpecifier, SpecifierSet from packaging.version import Version @@ -27,6 +28,7 @@ from ._reqs import _StrOrIter from .config import pyprojecttoml, setupcfg from .discovery import ConfigDiscovery +from .errors import InvalidConfigError from .monkey import get_unpatched from .warnings import InformationOnly, SetuptoolsDeprecationWarning @@ -403,6 +405,23 @@ def _normalize_requires(self): (k, list(map(str, _reqs.parse(v or [])))) for k, v in extras_require.items() ) + def _finalize_license_expression(self) -> None: + """Normalize license and license_expression.""" + license_expr = self.metadata.license_expression + if license_expr: + normalized = canonicalize_license_expression(license_expr) + if license_expr != normalized: + InformationOnly.emit(f"Normalizing '{license_expr}' to '{normalized}'") + self.metadata.license_expression = normalized + + for cl in self.metadata.get_classifiers(): + if not cl.startswith("License :: "): + continue + raise InvalidConfigError( + "License classifier are deprecated in favor of the license expression. " + f"Remove the '{cl}' classifier." + ) + def _finalize_license_files(self) -> None: """Compute names of all license files which should be included.""" license_files: list[str] | None = self.metadata.license_files @@ -656,6 +675,7 @@ def parse_config_files( pyprojecttoml.apply_configuration(self, filename, ignore_option_errors) self._finalize_requires() + self._finalize_license_expression() self._finalize_license_files() def fetch_build_eggs( diff --git a/setuptools/tests/config/test_apply_pyprojecttoml.py b/setuptools/tests/config/test_apply_pyprojecttoml.py index 03f950ecd1..91883b4618 100644 --- a/setuptools/tests/config/test_apply_pyprojecttoml.py +++ b/setuptools/tests/config/test_apply_pyprojecttoml.py @@ -23,7 +23,7 @@ from setuptools.config import expand, pyprojecttoml, setupcfg from setuptools.config._apply_pyprojecttoml import _MissingDynamic, _some_attrgetter from setuptools.dist import Distribution -from setuptools.errors import RemovedConfigError +from setuptools.errors import InvalidConfigError, RemovedConfigError from .downloads import retrieve_file, urls_from_file @@ -175,7 +175,10 @@ def main_tomatoes(): pass {email = "hi@pradyunsg.me"}, {name = "Tzu-Ping Chung"} ] -license = "MIT" +license = "mit or apache-2.0" # should be normalized in metadata +classifiers = [ + "Development Status :: 5 - Production/Stable", +] """ @@ -286,8 +289,8 @@ def test_utf8_maintainer_in_metadata( # issue-3663 pytest.param( PEP639_LICENSE_EXPRESSION, None, - 'MIT', - 'License-Expression: MIT', + 'MIT OR Apache-2.0', + 'License-Expression: MIT OR Apache-2.0', id='license-expression', ), ), @@ -314,6 +317,18 @@ def test_license_in_metadata( assert content_str in content +def test_license_expression_with_bad_classifier(tmp_path): + text = PEP639_LICENSE_EXPRESSION.rsplit("\n", 2)[0] + pyproject = _pep621_example_project( + tmp_path, + "README", + f"{text}\n \"License :: OSI Approved :: MIT License\"\n]", + ) + msg = "License classifier are deprecated.*'License :: OSI Approved :: MIT License'" + with pytest.raises(InvalidConfigError, match=msg): + pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject) + + class TestLicenseFiles: def base_pyproject(self, tmp_path, additional_text): pyproject = _pep621_example_project(tmp_path, "README") From 31d83409f26018b79ee5459445ce7e9b87752e97 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 25 Nov 2024 01:07:59 +0100 Subject: [PATCH 4/7] Remove License-Expression field --- newsfragments/4706.feature.rst | 2 +- setuptools/_core_metadata.py | 9 +++------ setuptools/tests/config/test_apply_pyprojecttoml.py | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/newsfragments/4706.feature.rst b/newsfragments/4706.feature.rst index 38b09276c0..1d34f5f476 100644 --- a/newsfragments/4706.feature.rst +++ b/newsfragments/4706.feature.rst @@ -1 +1 @@ -Added initial support for ``License-Expression`` (`PEP 639 `_). -- by :user:`cdce8p` +Added initial support for license expression (`PEP 639 `_). -- by :user:`cdce8p` diff --git a/setuptools/_core_metadata.py b/setuptools/_core_metadata.py index a407f6915b..25937f913c 100644 --- a/setuptools/_core_metadata.py +++ b/setuptools/_core_metadata.py @@ -176,12 +176,9 @@ def write_field(key, value): if attr_val is not None: write_field(field, attr_val) - if self.license_expression: - write_field('License-Expression', rfc822_escape(self.license_expression)) - else: - license = self.get_license() - if license: - write_field('License', rfc822_escape(license)) + license = self.license_expression or self.get_license() + if license: + write_field('License', rfc822_escape(license)) for label, url in self.project_urls.items(): write_field('Project-URL', f'{label}, {url}') diff --git a/setuptools/tests/config/test_apply_pyprojecttoml.py b/setuptools/tests/config/test_apply_pyprojecttoml.py index 91883b4618..c9ab1f5718 100644 --- a/setuptools/tests/config/test_apply_pyprojecttoml.py +++ b/setuptools/tests/config/test_apply_pyprojecttoml.py @@ -290,7 +290,7 @@ def test_utf8_maintainer_in_metadata( # issue-3663 PEP639_LICENSE_EXPRESSION, None, 'MIT OR Apache-2.0', - 'License-Expression: MIT OR Apache-2.0', + 'License: MIT OR Apache-2.0', # TODO Metadata version '2.4' id='license-expression', ), ), From 3744994a6b53aeaa8e0c7ed92df6bf86d01df8a7 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 16 Feb 2025 21:48:04 +0100 Subject: [PATCH 5/7] Review --- pyproject.toml | 2 +- setuptools/_core_metadata.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bd22d79294..a9febdbe8c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,10 +11,10 @@ authors = [ ] description = "Easily download, build, install, upgrade, and uninstall Python packages" readme = "README.rst" -license = "MIT" classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Topic :: Software Development :: Libraries :: Python Modules", diff --git a/setuptools/_core_metadata.py b/setuptools/_core_metadata.py index 25937f913c..5342186c0e 100644 --- a/setuptools/_core_metadata.py +++ b/setuptools/_core_metadata.py @@ -88,7 +88,7 @@ def read_pkg_file(self, file): self.url = _read_field_from_msg(msg, 'home-page') self.download_url = _read_field_from_msg(msg, 'download-url') self.license = _read_field_unescaped_from_msg(msg, 'license') - self.license_expression = _read_field_unescaped_from_msg(msg, 'license_expression') + self.license_expression = _read_field_unescaped_from_msg(msg, 'license-expression') self.long_description = _read_field_unescaped_from_msg(msg, 'description') if self.long_description is None and self.metadata_version >= Version('2.1'): From 0d8f1f2d12470efe3830e05f70996eb177287e26 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 17 Feb 2025 14:09:35 +0100 Subject: [PATCH 6/7] Replace error with warning and remove license classifier --- setuptools/dist.py | 15 +++++++++++---- .../tests/config/test_apply_pyprojecttoml.py | 18 +++++++++++++++--- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/setuptools/dist.py b/setuptools/dist.py index 7a13ea6a04..3cbe0fdc11 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -28,7 +28,6 @@ from ._reqs import _StrOrIter from .config import pyprojecttoml, setupcfg from .discovery import ConfigDiscovery -from .errors import InvalidConfigError from .monkey import get_unpatched from .warnings import InformationOnly, SetuptoolsDeprecationWarning @@ -414,13 +413,21 @@ def _finalize_license_expression(self) -> None: InformationOnly.emit(f"Normalizing '{license_expr}' to '{normalized}'") self.metadata.license_expression = normalized + classifiers = [] + license_classifiers_found = False for cl in self.metadata.get_classifiers(): if not cl.startswith("License :: "): + classifiers.append(cl) continue - raise InvalidConfigError( - "License classifier are deprecated in favor of the license expression. " - f"Remove the '{cl}' classifier." + license_classifiers_found = True + SetuptoolsDeprecationWarning.emit( + "License classifier are deprecated in favor of the license expression.", + f"Please remove the '{cl}' classifier.", + see_url="https://peps.python.org/pep-0639/", + due_date=(2027, 2, 17), # Introduced 2025-02-17 ) + if license_classifiers_found: + self.metadata.set_classifiers(classifiers) def _finalize_license_files(self) -> None: """Compute names of all license files which should be included.""" diff --git a/setuptools/tests/config/test_apply_pyprojecttoml.py b/setuptools/tests/config/test_apply_pyprojecttoml.py index c9ab1f5718..089b6ae30e 100644 --- a/setuptools/tests/config/test_apply_pyprojecttoml.py +++ b/setuptools/tests/config/test_apply_pyprojecttoml.py @@ -9,6 +9,7 @@ import io import re import tarfile +import warnings from inspect import cleandoc from pathlib import Path from unittest.mock import Mock @@ -23,7 +24,8 @@ from setuptools.config import expand, pyprojecttoml, setupcfg from setuptools.config._apply_pyprojecttoml import _MissingDynamic, _some_attrgetter from setuptools.dist import Distribution -from setuptools.errors import InvalidConfigError, RemovedConfigError +from setuptools.errors import RemovedConfigError +from setuptools.warnings import SetuptoolsDeprecationWarning from .downloads import retrieve_file, urls_from_file @@ -178,6 +180,7 @@ def main_tomatoes(): pass license = "mit or apache-2.0" # should be normalized in metadata classifiers = [ "Development Status :: 5 - Production/Stable", + "Programming Language :: Python", ] """ @@ -324,10 +327,19 @@ def test_license_expression_with_bad_classifier(tmp_path): "README", f"{text}\n \"License :: OSI Approved :: MIT License\"\n]", ) - msg = "License classifier are deprecated.*'License :: OSI Approved :: MIT License'" - with pytest.raises(InvalidConfigError, match=msg): + msg = "License classifier are deprecated(?:.|\n)*'License :: OSI Approved :: MIT License'" + with pytest.raises(SetuptoolsDeprecationWarning, match=msg): pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SetuptoolsDeprecationWarning) + dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject) + # Check 'License :: OSI Approved :: MIT License' is removed + assert dist.metadata.get_classifiers() == [ + "Development Status :: 5 - Production/Stable", + "Programming Language :: Python", + ] + class TestLicenseFiles: def base_pyproject(self, tmp_path, additional_text): From 28baa9b6d0ca7f5f316724881bebc9fe156fd1fc Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 17 Feb 2025 16:42:34 +0100 Subject: [PATCH 7/7] Revert removing the license classifier --- setuptools/dist.py | 6 ------ setuptools/tests/config/test_apply_pyprojecttoml.py | 3 ++- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/setuptools/dist.py b/setuptools/dist.py index 3cbe0fdc11..27e8095709 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -413,21 +413,15 @@ def _finalize_license_expression(self) -> None: InformationOnly.emit(f"Normalizing '{license_expr}' to '{normalized}'") self.metadata.license_expression = normalized - classifiers = [] - license_classifiers_found = False for cl in self.metadata.get_classifiers(): if not cl.startswith("License :: "): - classifiers.append(cl) continue - license_classifiers_found = True SetuptoolsDeprecationWarning.emit( "License classifier are deprecated in favor of the license expression.", f"Please remove the '{cl}' classifier.", see_url="https://peps.python.org/pep-0639/", due_date=(2027, 2, 17), # Introduced 2025-02-17 ) - if license_classifiers_found: - self.metadata.set_classifiers(classifiers) def _finalize_license_files(self) -> None: """Compute names of all license files which should be included.""" diff --git a/setuptools/tests/config/test_apply_pyprojecttoml.py b/setuptools/tests/config/test_apply_pyprojecttoml.py index 089b6ae30e..468a1ba01d 100644 --- a/setuptools/tests/config/test_apply_pyprojecttoml.py +++ b/setuptools/tests/config/test_apply_pyprojecttoml.py @@ -334,10 +334,11 @@ def test_license_expression_with_bad_classifier(tmp_path): with warnings.catch_warnings(): warnings.simplefilter("ignore", SetuptoolsDeprecationWarning) dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject) - # Check 'License :: OSI Approved :: MIT License' is removed + # Check license classifier is still included assert dist.metadata.get_classifiers() == [ "Development Status :: 5 - Production/Stable", "Programming Language :: Python", + "License :: OSI Approved :: MIT License", ]