Repository: pypa/setuptools (python)

Issue:
[BUG] Version 78.0.1 breaks install of ansible-vault package
### setuptools version

78.0.1

### Python version

3.11

### OS

Ubuntu

### Additional environment information

_No response_

### Description

When installing the "ansible-vault" package, setuptools raises this exception:
```

$ pip install ansible-vault --upgrade
Collecting ansible-vault
  Using cached ansible-vault-2.1.0.tar.gz (3.5 kB)
  Installing build dependencies ... done
  Getting requirements to build wheel ... error
  error: subprocess-exited-with-error
  
  × Getting requirements to build wheel did not run successfully.
  │ exit code: 1
  ╰─> [34 lines of output]
      Traceback (most recent call last):
        File "/Users/maiera/virtualenvs/vpcauto311/lib/python3.11/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 389, in <module>
          main()
        File "/Users/maiera/virtualenvs/vpcauto311/lib/python3.11/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 373, in main
          json_out["return_val"] = hook(**hook_input["kwargs"])
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/Users/maiera/virtualenvs/vpcauto311/lib/python3.11/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 143, in get_requires_for_build_wheel
          return hook(config_settings)
                 ^^^^^^^^^^^^^^^^^^^^^
        File "/private/var/folders/tg/fy7jbcg96lv1_hvlmcv263kr0000gn/T/pip-build-env-1hnvit19/overlay/lib/python3.11/site-packages/setuptools/build_meta.py", line 334, in get_requires_for_build_wheel
          return self._get_build_requires(config_settings, requirements=[])
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/private/var/folders/tg/fy7jbcg96lv1_hvlmcv263kr0000gn/T/pip-build-env-1hnvit19/overlay/lib/python3.11/site-packages/setuptools/build_meta.py", line 304, in _get_build_requires
          self.run_setup()
        File "/private/var/folders/tg/fy7jbcg96lv1_hvlmcv263kr0000gn/T/pip-build-env-1hnvit19/overlay/lib/python3.11/site-packages/setuptools/build_meta.py", line 522, in run_setup
          super().run_setup(setup_script=setup_script)
        File "/private/var/folders/tg/fy7jbcg96lv1_hvlmcv263kr0000gn/T/pip-build-env-1hnvit19/overlay/lib/python3.11/site-packages/setuptools/build_meta.py", line 320, in run_setup
          exec(code, locals())
        File "<string>", line 11, in <module>
        File "/private/var/folders/tg/fy7jbcg96lv1_hvlmcv263kr0000gn/T/pip-build-env-1hnvit19/overlay/lib/python3.11/site-packages/setuptools/__init__.py", line 116, in setup
          _install_setup_requires(attrs)
        File "/private/var/folders/tg/fy7jbcg96lv1_hvlmcv263kr0000gn/T/pip-build-env-1hnvit19/overlay/lib/python3.11/site-packages/setuptools/__init__.py", line 87, in _install_setup_requires
          dist.parse_config_files(ignore_option_errors=True)
        File "/Users/maiera/virtualenvs/vpcauto311/lib/python3.11/site-packages/_virtualenv.py", line 22, in parse_config_files
          result = old_parse_config_files(self, *args, **kwargs)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/private/var/folders/tg/fy7jbcg96lv1_hvlmcv263kr0000gn/T/pip-build-env-1hnvit19/overlay/lib/python3.11/site-packages/setuptools/dist.py", line 730, in parse_config_files
          self._parse_config_files(filenames=inifiles)
        File "/private/var/folders/tg/fy7jbcg96lv1_hvlmcv263kr0000gn/T/pip-build-env-1hnvit19/overlay/lib/python3.11/site-packages/setuptools/dist.py", line 599, in _parse_config_files
          opt = self._enforce_underscore(opt, section)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/private/var/folders/tg/fy7jbcg96lv1_hvlmcv263kr0000gn/T/pip-build-env-1hnvit19/overlay/lib/python3.11/site-packages/setuptools/dist.py", line 629, in _enforce_underscore
          raise InvalidConfigError(
      setuptools.errors.InvalidConfigError: Invalid dash-separated key 'description-file' in 'metadata' (setup.cfg), please use the underscore name 'description_file' instead.
      [end of output]
  
  note: This error originates from a subprocess, and is likely not a problem with pip.
error: subprocess-exited-with-error

× Getting requirements to build wheel did not run successfully.
│ exit code: 1
╰─> See above for output.

note: This error originates from a subprocess, and is likely not a problem with pip.
```

### Expected behavior

ansible-vault has not changed since 2021 and should install as before.

### How to Reproduce

1. Create and activate a fresh Pyhton virtual env, using Python 3.11:
```
mkvirtualenv test -p python3.11
```
2. Install version 78.0.1 of setuptools
```
pip install setuptools==78.0.1
```
3. Install ansible-vault
```
pip install ansible-vault
```

### Output

See above


Pull request:
Revert removals introduced in `v78.0.0`
<!-- 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

<!-- Summary goes here -->

Closes #4910

### Pull Request Checklist
- [x] 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/.github/workflows/main.yml b/.github/workflows/main.yml
index 6e5892f2e..14f3151d3 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -64,7 +64,8 @@ jobs:
           python: "3.10"
           distutils: stdlib
     runs-on: ${{ matrix.platform }}
-    continue-on-error: ${{ matrix.python == '3.14' }}
+    continue-on-error: ${{ matrix.python == '3.14' || matrix.python == 'pypy3.10' }}
+    # XXX: pypy seems to be flaky with unrelated tests in #6345
     env:
       SETUPTOOLS_USE_DISTUTILS: ${{ matrix.distutils || 'local' }}
     timeout-minutes: 75
diff --git a/newsfragments/4911.bugfix.rst b/newsfragments/4911.bugfix.rst
new file mode 100644
index 000000000..49db1a49e
--- /dev/null
+++ b/newsfragments/4911.bugfix.rst
@@ -0,0 +1,2 @@
+Postponed removals of deprecated dash-separated and uppercase fields in ``setup.cfg``.
+All packages with deprecated configurations are advised to move before 2026.
diff --git a/setuptools/dist.py b/setuptools/dist.py
index fa734c651..8d972cc49 100644
--- a/setuptools/dist.py
+++ b/setuptools/dist.py
@@ -626,21 +626,41 @@ class Distribution(_Distribution):
         if "-" not in opt or self._skip_setupcfg_normalization(section):
             return opt
 
-        raise InvalidConfigError(
+        underscore_opt = opt.replace('-', '_')
+        affected = f"(Affected: {self.metadata.name})." if self.metadata.name else ""
+        SetuptoolsDeprecationWarning.emit(
             f"Invalid dash-separated key {opt!r} in {section!r} (setup.cfg), "
-            f"please use the underscore name {opt.replace('-', '_')!r} instead."
+            f"please use the underscore name {underscore_opt!r} instead.",
+            f"""
+            Usage of dash-separated {opt!r} will not be supported in future
+            versions. Please use the underscore name {underscore_opt!r} instead.
+            {affected}
+            """,
+            see_docs="userguide/declarative_config.html",
+            due_date=(2026, 3, 3),
             # Warning initially introduced in 3 Mar 2021
         )
+        return underscore_opt
 
     def _enforce_option_lowercase(self, opt: str, section: str) -> str:
         if opt.islower() or self._skip_setupcfg_normalization(section):
             return opt
 
-        raise InvalidConfigError(
+        lowercase_opt = opt.lower()
+        affected = f"(Affected: {self.metadata.name})." if self.metadata.name else ""
+        SetuptoolsDeprecationWarning.emit(
             f"Invalid uppercase key {opt!r} in {section!r} (setup.cfg), "
-            f"please use lowercase {opt.lower()!r} instead."
+            f"please use lowercase {lowercase_opt!r} instead.",
+            f"""
+            Usage of uppercase key {opt!r} in {section!r} will not be supported in
+            future versions. Please use lowercase {lowercase_opt!r} instead.
+            {affected}
+            """,
+            see_docs="userguide/declarative_config.html",
+            due_date=(2026, 3, 3),
             # Warning initially introduced in 6 Mar 2021
         )
+        return lowercase_opt
 
     def _skip_setupcfg_normalization(self, section: str) -> bool:
         skip = (


Test patch:
diff --git a/setuptools/tests/config/test_setupcfg.py b/setuptools/tests/config/test_setupcfg.py
index a199871ff..61af99044 100644
--- a/setuptools/tests/config/test_setupcfg.py
+++ b/setuptools/tests/config/test_setupcfg.py
@@ -10,7 +10,6 @@ from packaging.requirements import InvalidRequirement
 
 from setuptools.config.setupcfg import ConfigHandler, Target, read_configuration
 from setuptools.dist import Distribution, _Distribution
-from setuptools.errors import InvalidConfigError
 from setuptools.warnings import SetuptoolsDeprecationWarning
 
 from ..textwrap import DALS
@@ -423,7 +422,7 @@ class TestMetadata:
                 pass
 
     @pytest.mark.parametrize(
-        ("error_msg", "config"),
+        ("error_msg", "config", "invalid"),
         [
             (
                 "Invalid dash-separated key 'author-email' in 'metadata' (setup.cfg)",
@@ -434,6 +433,7 @@ class TestMetadata:
                     maintainer_email = foo@foo.com
                     """
                 ),
+                {"author-email": "test@test.com"},
             ),
             (
                 "Invalid uppercase key 'Name' in 'metadata' (setup.cfg)",
@@ -444,14 +444,25 @@ class TestMetadata:
                     description = Some description
                     """
                 ),
+                {"Name": "foo"},
             ),
         ],
     )
-    def test_invalid_options_previously_deprecated(self, tmpdir, error_msg, config):
-        # this test and related methods can be removed when no longer needed
+    def test_invalid_options_previously_deprecated(
+        self, tmpdir, error_msg, config, invalid
+    ):
+        # This test and related methods can be removed when no longer needed.
+        # Deprecation postponed due to push-back from the community in
+        # https://github.com/pypa/setuptools/issues/4910
         fake_env(tmpdir, config)
-        with pytest.raises(InvalidConfigError, match=re.escape(error_msg)):
-            get_dist(tmpdir).__enter__()
+        with pytest.warns(SetuptoolsDeprecationWarning, match=re.escape(error_msg)):
+            dist = get_dist(tmpdir).__enter__()
+
+        tmpdir.join('setup.cfg').remove()
+
+        for field, value in invalid.items():
+            attr = field.replace("-", "_").lower()
+            assert getattr(dist.metadata, attr) == value
 
 
 class TestOptions: