Repository: pypa/setuptools
Pull request #4839 (2025-02-18 13:22:58)

Attempt to comply with `sphinx` warning when building docs
<!-- First time contributors: Take a moment to review https://setuptools.pypa.io/en/latest/development/developer-guide.html! -->
<!-- Remove sections if not applicable -->

## Summary of changes

It seems that `sphinx` do not like something in the docs:

![image](https://github.com/user-attachments/assets/1e49b1df-9de5-4b83-99a7-7b3a97e2c7e7)


This is an attempt to handle that.

Closes <!-- issue number here -->

### Pull Request Checklist
- [ ] Changes have tests
- [ ] News fragment added in [`newsfragments/`].
  _(See [documentation][PR docs] for details)_


[`newsfragments/`]: https://github.com/pypa/setuptools/tree/master/newsfragments
[PR docs]:
https://setuptools.pypa.io/en/latest/development/developer-guide.html#making-a-pull-request



Patch:
diff --git a/newsfragments/4706.feature.rst b/newsfragments/4706.feature.rst
index 1d34f5f47..be8aea645 100644
--- a/newsfragments/4706.feature.rst
+++ b/newsfragments/4706.feature.rst
@@ -1,1 +1,1 @@
-Added initial support for license expression (`PEP 639 <https://peps.python.org/pep-0639/#add-license-expression-field>`_). -- by :user:`cdce8p`
+Added initial support for license expression (:pep:`PEP 639 <639#add-license-expression-field>`). -- by :user:`cdce8p`
diff --git a/newsfragments/4728.feature.rst b/newsfragments/4728.feature.rst
index 61906656c..ea19b31a3 100644
--- a/newsfragments/4728.feature.rst
+++ b/newsfragments/4728.feature.rst
@@ -1,1 +1,1 @@
-Store ``License-File``s in ``.dist-info/licenses`` subfolder and added support for recursive globs for ``license_files`` (`PEP 639 <https://peps.python.org/pep-0639/#add-license-expression-field>`_). -- by :user:`cdce8p`
+Store ``License-File``s in ``.dist-info/licenses`` subfolder and added support for recursive globs for ``license_files`` (:pep:`PEP 639 <639#add-license-expression-field>`). -- by :user:`cdce8p`
diff --git a/newsfragments/4833.feature.rst b/newsfragments/4833.feature.rst
deleted file mode 100644
index d8801becf..000000000
--- a/newsfragments/4833.feature.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Added exception (or warning) when deprecated license classifiers are used,
-according to `PEP 639 <https://peps.python.org/pep-0639/#deprecate-license-classifiers>`_.
diff --git a/setuptools/config/_apply_pyprojecttoml.py b/setuptools/config/_apply_pyprojecttoml.py
index bcb3c0389..6bb2bea51 100644
--- a/setuptools/config/_apply_pyprojecttoml.py
+++ b/setuptools/config/_apply_pyprojecttoml.py
@@ -423,7 +423,6 @@ SETUPTOOLS_PATCHES = {
     "provides_extras",
     "license_file",
     "license_files",
-    "license_expression",
 }
 
 _PREPROCESS = {
diff --git a/setuptools/dist.py b/setuptools/dist.py
index d202dbf50..27e809570 100644
--- a/setuptools/dist.py
+++ b/setuptools/dist.py
@@ -28,7 +28,6 @@ from ._path import StrPath
 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
 
@@ -407,32 +406,22 @@ class Distribution(_Distribution):
 
     def _finalize_license_expression(self) -> None:
         """Normalize license and license_expression."""
-        classifiers = self.metadata.get_classifiers()
-        license_classifiers = [cl for cl in classifiers if cl.startswith("License :: ")]
-
         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
-            if license_classifiers:
-                raise InvalidConfigError(
-                    "License classifiers have been superseded by license expressions "
-                    "(see https://peps.python.org/pep-0639/). Please remove:\n\n"
-                    + "\n".join(license_classifiers),
+
+            for cl in self.metadata.get_classifiers():
+                if not cl.startswith("License :: "):
+                    continue
+                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
                 )
-        elif license_classifiers:
-            pypa_guides = "guides/writing-pyproject-toml/#license"
-            SetuptoolsDeprecationWarning.emit(
-                "License classifiers are deprecated.",
-                "Please consider removing the following classifiers in favor of a "
-                "SPDX license expression:\n\n" + "\n".join(license_classifiers),
-                see_url=f"https://packaging.python.org/en/latest/{pypa_guides}",
-                # Warning introduced on 2025-02-17
-                # TODO: Should we add a due date? It may affect old/unmaintained
-                #       packages in the ecosystem and cause problems...
-            )
 
     def _finalize_license_files(self) -> None:
         """Compute names of all license files which should be included."""


Test patch:
diff --git a/setuptools/tests/config/test_apply_pyprojecttoml.py b/setuptools/tests/config/test_apply_pyprojecttoml.py
index 5a82893bf..c43798870 100644
--- a/setuptools/tests/config/test_apply_pyprojecttoml.py
+++ b/setuptools/tests/config/test_apply_pyprojecttoml.py
@@ -9,6 +9,7 @@ from __future__ import annotations
 import io
 import re
 import tarfile
+import warnings
 from inspect import cleandoc
 from pathlib import Path
 from unittest.mock import Mock
@@ -23,7 +24,7 @@ from setuptools.command.egg_info import write_requirements
 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
@@ -330,36 +331,26 @@ def test_license_in_metadata(
     assert not_content_str not in content
 
 
-def test_license_classifier_with_license_expression(tmp_path):
+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 classifiers have been superseded by license expressions"
-    with pytest.raises(InvalidConfigError, match=msg) as exc:
+    msg = "License classifier are deprecated(?:.|\n)*'License :: OSI Approved :: MIT License'"
+    with pytest.raises(SetuptoolsDeprecationWarning, match=msg):
         pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
 
-    assert "License :: OSI Approved :: MIT License" in str(exc.value)
-
-
-def test_license_classifier_without_license_expression(tmp_path):
-    text = """\
-    [project]
-    name = "spam"
-    version = "2020.0.0"
-    license = {text = "mit or apache-2.0"}
-    classifiers = ["License :: OSI Approved :: MIT License"]
-    """
-    pyproject = _pep621_example_project(tmp_path, "README", text)
-
-    msg = "License classifiers are deprecated(?:.|\n)*MIT License"
-    with pytest.warns(SetuptoolsDeprecationWarning, match=msg):
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", SetuptoolsDeprecationWarning)
         dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
-
-    # Check license classifier is still included
-    assert dist.metadata.get_classifiers() == ["License :: OSI Approved :: MIT License"]
+        # Check license classifier is still included
+        assert dist.metadata.get_classifiers() == [
+            "Development Status :: 5 - Production/Stable",
+            "Programming Language :: Python",
+            "License :: OSI Approved :: MIT License",
+        ]
 
 
 class TestLicenseFiles: