Skip to content

Commit 88bee15

Browse files
committed
Disalow deprecated dash-separated and uppercase options in setup.cfg
1 parent a36b7ba commit 88bee15

File tree

2 files changed

+76
-82
lines changed

2 files changed

+76
-82
lines changed

setuptools/dist.py

+44-52
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import functools
34
import io
45
import itertools
56
import numbers
@@ -27,6 +28,7 @@
2728
from ._reqs import _StrOrIter
2829
from .config import pyprojecttoml, setupcfg
2930
from .discovery import ConfigDiscovery
31+
from .errors import InvalidConfigError
3032
from .monkey import get_unpatched
3133
from .warnings import InformationOnly, SetuptoolsDeprecationWarning
3234

@@ -490,8 +492,8 @@ def _parse_config_files(self, filenames=None): # noqa: C901
490492
continue
491493

492494
val = parser.get(section, opt)
493-
opt = self.warn_dash_deprecation(opt, section)
494-
opt = self.make_option_lowercase(opt, section)
495+
opt = self._enforce_underscore(opt, section)
496+
opt = self._enforce_option_lowercase(opt, section)
495497
opt_dict[opt] = (filename, val)
496498

497499
# Make the ConfigParser forget everything (so we retain
@@ -516,64 +518,42 @@ def _parse_config_files(self, filenames=None): # noqa: C901
516518
except ValueError as e:
517519
raise DistutilsOptionError(e) from e
518520

519-
def warn_dash_deprecation(self, opt: str, section: str) -> str:
520-
if section in (
521-
'options.extras_require',
522-
'options.data_files',
523-
):
521+
def _enforce_underscore(self, opt: str, section: str) -> str:
522+
if "-" not in opt or not self._config_requires_normalization(section):
524523
return opt
525524

526-
underscore_opt = opt.replace('-', '_')
527-
commands = list(
528-
itertools.chain(
529-
distutils.command.__all__,
530-
self._setuptools_commands(),
531-
)
525+
raise InvalidConfigError(
526+
f"Invalid dash-separated key {opt!r} in {section!r} (setup.cfg), "
527+
f"please use the underscore name {opt.replace('-', '_')!r} instead."
528+
# Warning initially introduced in 3 Mar 2021
532529
)
533-
if (
534-
not section.startswith('options')
535-
and section != 'metadata'
536-
and section not in commands
537-
):
538-
return underscore_opt
539-
540-
if '-' in opt:
541-
SetuptoolsDeprecationWarning.emit(
542-
"Invalid dash-separated options",
543-
f"""
544-
Usage of dash-separated {opt!r} will not be supported in future
545-
versions. Please use the underscore name {underscore_opt!r} instead.
546-
""",
547-
see_docs="userguide/declarative_config.html",
548-
due_date=(2025, 3, 3),
549-
# Warning initially introduced in 3 Mar 2021
550-
)
551-
return underscore_opt
552530

553-
def _setuptools_commands(self):
554-
try:
555-
entry_points = metadata.distribution('setuptools').entry_points
556-
return {ep.name for ep in entry_points} # Avoid newer API for compatibility
557-
except metadata.PackageNotFoundError:
558-
# during bootstrapping, distribution doesn't exist
559-
return []
560-
561-
def make_option_lowercase(self, opt: str, section: str) -> str:
562-
if section != 'metadata' or opt.islower():
531+
def _enforce_option_lowercase(self, opt: str, section: str) -> str:
532+
if opt.islower() or not self._config_requires_normalization(section):
563533
return opt
564534

565-
lowercase_opt = opt.lower()
566-
SetuptoolsDeprecationWarning.emit(
567-
"Invalid uppercase configuration",
568-
f"""
569-
Usage of uppercase key {opt!r} in {section!r} will not be supported in
570-
future versions. Please use lowercase {lowercase_opt!r} instead.
571-
""",
572-
see_docs="userguide/declarative_config.html",
573-
due_date=(2025, 3, 3),
535+
raise InvalidConfigError(
536+
f"Invalid uppercase key {opt!r} in {section!r} (setup.cfg), "
537+
f"please use lowercase {opt.lower()!r} instead."
574538
# Warning initially introduced in 6 Mar 2021
575539
)
576-
return lowercase_opt
540+
541+
def _config_requires_normalization(self, section: str) -> bool:
542+
skip = (
543+
'options.extras_require',
544+
'options.data_files',
545+
'options.entry_points',
546+
'options.package_data',
547+
'options.exclude_package_data',
548+
)
549+
return section not in skip and self._is_setuptools_section(section)
550+
551+
def _is_setuptools_section(self, section: str) -> bool:
552+
return (
553+
section == "metadata"
554+
or section.startswith("option")
555+
or section in _setuptools_commands()
556+
)
577557

578558
# FIXME: 'Distribution._set_command_options' is too complex (14)
579559
def _set_command_options(self, command_obj, option_dict=None): # noqa: C901
@@ -999,6 +979,18 @@ def run_command(self, command) -> None:
999979
super().run_command(command)
1000980

1001981

982+
@functools.cache
983+
def _setuptools_commands() -> set[str]:
984+
try:
985+
# Use older API for importlib.metadata compatibility
986+
entry_points = metadata.distribution('setuptools').entry_points
987+
eps = (ep.name for ep in entry_points)
988+
except metadata.PackageNotFoundError:
989+
# during bootstrapping, distribution doesn't exist
990+
return set(distutils.command.__all__)
991+
return {*distutils.command.__all__, *eps}
992+
993+
1002994
class DistDeprecationWarning(SetuptoolsDeprecationWarning):
1003995
"""Class for warning about deprecations in dist in
1004996
setuptools. Not ignored by default, unlike DeprecationWarning."""

setuptools/tests/config/test_setupcfg.py

+32-30
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import configparser
22
import contextlib
33
import inspect
4+
import re
45
from pathlib import Path
56
from unittest.mock import Mock, patch
67

@@ -9,6 +10,7 @@
910

1011
from setuptools.config.setupcfg import ConfigHandler, Target, read_configuration
1112
from setuptools.dist import Distribution, _Distribution
13+
from setuptools.errors import InvalidConfigError
1214
from setuptools.warnings import SetuptoolsDeprecationWarning
1315

1416
from ..textwrap import DALS
@@ -420,36 +422,36 @@ def test_not_utf8(self, tmpdir):
420422
with get_dist(tmpdir):
421423
pass
422424

423-
@pytest.mark.xfail(reason="#4864")
424-
def test_warn_dash_deprecation(self, tmpdir):
425-
# warn_dash_deprecation() is a method in setuptools.dist
426-
# remove this test and the method when no longer needed
427-
fake_env(
428-
tmpdir,
429-
'[metadata]\n'
430-
'author-email = [email protected]\n'
431-
'maintainer_email = foo@foo.com\n',
432-
)
433-
msg = "Usage of dash-separated 'author-email' will not be supported"
434-
with pytest.warns(SetuptoolsDeprecationWarning, match=msg):
435-
with get_dist(tmpdir) as dist:
436-
metadata = dist.metadata
437-
438-
assert metadata.author_email == '[email protected]'
439-
assert metadata.maintainer_email == '[email protected]'
440-
441-
@pytest.mark.xfail(reason="#4864")
442-
def test_make_option_lowercase(self, tmpdir):
443-
# remove this test and the method make_option_lowercase() in setuptools.dist
444-
# when no longer needed
445-
fake_env(tmpdir, '[metadata]\nName = foo\ndescription = Some description\n')
446-
msg = "Usage of uppercase key 'Name' in 'metadata' will not be supported"
447-
with pytest.warns(SetuptoolsDeprecationWarning, match=msg):
448-
with get_dist(tmpdir) as dist:
449-
metadata = dist.metadata
450-
451-
assert metadata.name == 'foo'
452-
assert metadata.description == 'Some description'
425+
@pytest.mark.parametrize(
426+
("error_msg", "config"),
427+
[
428+
(
429+
"Invalid dash-separated key 'author-email' in 'metadata' (setup.cfg)",
430+
DALS(
431+
"""
432+
[metadata]
433+
author-email = test@test.com
434+
maintainer_email = [email protected]
435+
"""
436+
),
437+
),
438+
(
439+
"Invalid uppercase key 'Name' in 'metadata' (setup.cfg)",
440+
DALS(
441+
"""
442+
[metadata]
443+
Name = foo
444+
description = Some description
445+
"""
446+
),
447+
),
448+
],
449+
)
450+
def test_invalid_options_previously_deprecated(self, tmpdir, error_msg, config):
451+
# this test and related methods can be removed when no longer needed
452+
fake_env(tmpdir, config)
453+
with pytest.raises(InvalidConfigError, match=re.escape(error_msg)):
454+
get_dist(tmpdir).__enter__()
453455

454456

455457
class TestOptions:

0 commit comments

Comments
 (0)