summaryrefslogtreecommitdiff
path: root/setuptools/config
diff options
context:
space:
mode:
authorAnderson Bravalheri <andersonbravalheri@gmail.com>2023-04-20 14:24:08 +0100
committerAnderson Bravalheri <andersonbravalheri@gmail.com>2023-04-20 14:24:08 +0100
commit73b5c62939cd2ef039da9d5f784083fa956ef4a1 (patch)
treec24848a7a4c705362a9c076f2c7b16aec8d752c7 /setuptools/config
parent000efbfae1e79d0a9fa9b16b55c4f5a2e90a64dd (diff)
parent54da8b6d69b7333424eff305218a10d9605a7e36 (diff)
downloadpython-setuptools-git-73b5c62939cd2ef039da9d5f784083fa956ef4a1.tar.gz
Overhaul for better visibility of warnings (#3849)
Diffstat (limited to 'setuptools/config')
-rw-r--r--setuptools/config/__init__.py31
-rw-r--r--setuptools/config/_apply_pyprojecttoml.py38
-rw-r--r--setuptools/config/expand.py4
-rw-r--r--setuptools/config/pyprojecttoml.py42
-rw-r--r--setuptools/config/setupcfg.py93
5 files changed, 115 insertions, 93 deletions
diff --git a/setuptools/config/__init__.py b/setuptools/config/__init__.py
index 1a5153ad..ffea3944 100644
--- a/setuptools/config/__init__.py
+++ b/setuptools/config/__init__.py
@@ -1,12 +1,10 @@
"""For backward compatibility, expose main functions from
``setuptools.config.setupcfg``
"""
-import warnings
from functools import wraps
-from textwrap import dedent
from typing import Callable, TypeVar, cast
-from .._deprecation_warning import SetuptoolsDeprecationWarning
+from ..warnings import SetuptoolsDeprecationWarning
from . import setupcfg
Fn = TypeVar("Fn", bound=Callable)
@@ -17,15 +15,24 @@ __all__ = ('parse_configuration', 'read_configuration')
def _deprecation_notice(fn: Fn) -> Fn:
@wraps(fn)
def _wrapper(*args, **kwargs):
- msg = f"""\
- As setuptools moves its configuration towards `pyproject.toml`,
- `{__name__}.{fn.__name__}` became deprecated.
-
- For the time being, you can use the `{setupcfg.__name__}` module
- to access a backward compatible API, but this module is provisional
- and might be removed in the future.
- """
- warnings.warn(dedent(msg), SetuptoolsDeprecationWarning, stacklevel=2)
+ SetuptoolsDeprecationWarning.emit(
+ "Deprecated API usage.",
+ f"""
+ As setuptools moves its configuration towards `pyproject.toml`,
+ `{__name__}.{fn.__name__}` became deprecated.
+
+ For the time being, you can use the `{setupcfg.__name__}` module
+ to access a backward compatible API, but this module is provisional
+ and might be removed in the future.
+
+ To read project metadata, consider using
+ ``build.util.project_wheel_metadata`` (https://pypi.org/project/build/).
+ For simple scenarios, you can also try parsing the file directly
+ with the help of ``configparser``.
+ """,
+ # due_date not defined yet, because the community still heavily relies on it
+ # Warning introduced in 24 Mar 2022
+ )
return fn(*args, **kwargs)
return cast(Fn, _wrapper)
diff --git a/setuptools/config/_apply_pyprojecttoml.py b/setuptools/config/_apply_pyprojecttoml.py
index a2b44365..3091e3b5 100644
--- a/setuptools/config/_apply_pyprojecttoml.py
+++ b/setuptools/config/_apply_pyprojecttoml.py
@@ -9,7 +9,6 @@ need to be processed before being applied.
"""
import logging
import os
-import warnings
from collections.abc import Mapping
from email.headerregistry import Address
from functools import partial, reduce
@@ -18,7 +17,7 @@ from types import MappingProxyType
from typing import (TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set, Tuple,
Type, Union, cast)
-from setuptools._deprecation_warning import SetuptoolsDeprecationWarning
+from ..warnings import SetuptoolsWarning, SetuptoolsDeprecationWarning
if TYPE_CHECKING:
from setuptools._importlib import metadata # noqa
@@ -81,9 +80,11 @@ def _apply_tool_table(dist: "Distribution", config: dict, filename: _Path):
norm_key = json_compatible_key(field)
if norm_key in TOOL_TABLE_DEPRECATIONS:
- suggestion = TOOL_TABLE_DEPRECATIONS[norm_key]
+ suggestion, kwargs = TOOL_TABLE_DEPRECATIONS[norm_key]
msg = f"The parameter `{norm_key}` is deprecated, {suggestion}"
- warnings.warn(msg, SetuptoolsDeprecationWarning)
+ SetuptoolsDeprecationWarning.emit(
+ "Deprecated config", msg, **kwargs # type: ignore
+ )
norm_key = TOOL_TABLE_RENAMES.get(norm_key, norm_key)
_set_config(dist, norm_key, value)
@@ -99,8 +100,7 @@ def _handle_missing_dynamic(dist: "Distribution", project_table: dict):
if not (field in project_table or field in dynamic):
value = getter(dist)
if value:
- msg = _WouldIgnoreField.message(field, value)
- warnings.warn(msg, _WouldIgnoreField)
+ _WouldIgnoreField.emit(field=field, value=value)
def json_compatible_key(key: str) -> str:
@@ -200,7 +200,7 @@ def _python_requires(dist: "Distribution", val: dict, _root_dir):
def _dependencies(dist: "Distribution", val: list, _root_dir):
if getattr(dist, "install_requires", []):
msg = "`install_requires` overwritten in `pyproject.toml` (dependencies)"
- warnings.warn(msg)
+ SetuptoolsWarning.emit(msg)
_set_config(dist, "install_requires", val)
@@ -331,7 +331,10 @@ PYPROJECT_CORRESPONDENCE: Dict[str, _Correspondence] = {
TOOL_TABLE_RENAMES = {"script_files": "scripts"}
TOOL_TABLE_DEPRECATIONS = {
- "namespace_packages": "consider using implicit namespaces instead (PEP 420)."
+ "namespace_packages": (
+ "consider using implicit namespaces instead (PEP 420).",
+ {"due_date": (2023, 10, 30)}, # warning introduced in May 2022
+ )
}
SETUPTOOLS_PATCHES = {"long_description_content_type", "project_urls",
@@ -355,12 +358,10 @@ _PREVIOUSLY_DEFINED = {
}
-class _WouldIgnoreField(UserWarning):
- """Inform users that ``pyproject.toml`` would overwrite previous metadata."""
+class _WouldIgnoreField(SetuptoolsDeprecationWarning):
+ _SUMMARY = "`{field}` defined outside of `pyproject.toml` would be ignored."
- MESSAGE = """\
- {field!r} defined outside of `pyproject.toml` would be ignored.
- !!\n\n
+ _DETAILS = """
##########################################################################
# configuration would be ignored/result in error due to `pyproject.toml` #
##########################################################################
@@ -370,7 +371,7 @@ class _WouldIgnoreField(UserWarning):
`{field} = {value!r}`
According to the spec (see the link below), however, setuptools CANNOT
- consider this value unless {field!r} is listed as `dynamic`.
+ consider this value unless `{field}` is listed as `dynamic`.
https://packaging.python.org/en/latest/specifications/declaring-project-metadata/
@@ -378,13 +379,8 @@ class _WouldIgnoreField(UserWarning):
**transitional** measure), but please note that future releases of setuptools will
follow strictly the standard.
- To prevent this warning, you can list {field!r} under `dynamic` or alternatively
+ To prevent this warning, you can list `{field}` under `dynamic` or alternatively
remove the `[project]` table from your file and rely entirely on other means of
configuration.
- \n\n!!
"""
-
- @classmethod
- def message(cls, field, value):
- from inspect import cleandoc
- return cleandoc(cls.MESSAGE.format(field=field, value=value))
+ _DUE_DATE = (2023, 10, 30) # Initially introduced in 27 May 2022
diff --git a/setuptools/config/expand.py b/setuptools/config/expand.py
index c8db2c4b..30988843 100644
--- a/setuptools/config/expand.py
+++ b/setuptools/config/expand.py
@@ -23,7 +23,6 @@ import io
import os
import pathlib
import sys
-import warnings
from glob import iglob
from configparser import ConfigParser
from importlib.machinery import ModuleSpec
@@ -48,6 +47,7 @@ from types import ModuleType
from distutils.errors import DistutilsOptionError
from .._path import same_path as _same_path
+from ..warnings import SetuptoolsWarning
if TYPE_CHECKING:
from setuptools.dist import Distribution # noqa
@@ -141,7 +141,7 @@ def _filter_existing_files(filepaths: Iterable[_Path]) -> Iterator[_Path]:
if os.path.isfile(path):
yield path
else:
- warnings.warn(f"File {path!r} cannot be found")
+ SetuptoolsWarning.emit(f"File {path!r} cannot be found")
def _read_file(filepath: Union[bytes, _Path]) -> str:
diff --git a/setuptools/config/pyprojecttoml.py b/setuptools/config/pyprojecttoml.py
index 9ce55022..8d1dcaed 100644
--- a/setuptools/config/pyprojecttoml.py
+++ b/setuptools/config/pyprojecttoml.py
@@ -2,19 +2,23 @@
Load setuptools configuration from ``pyproject.toml`` files.
**PRIVATE MODULE**: API reserved for setuptools internal usage only.
+
+To read project metadata, consider using
+``build.util.project_wheel_metadata`` (https://pypi.org/project/build/).
+For simple scenarios, you can also try parsing the file directly
+with the help of ``tomllib`` or ``tomli``.
"""
import logging
import os
-import warnings
from contextlib import contextmanager
from functools import partial
-from typing import TYPE_CHECKING, Callable, Dict, Optional, Mapping, Set, Union
-
-from setuptools.errors import FileError, OptionError
+from typing import TYPE_CHECKING, Callable, Dict, Mapping, Optional, Set, Union
+from ..errors import FileError, OptionError
+from ..warnings import SetuptoolsWarning
from . import expand as _expand
-from ._apply_pyprojecttoml import apply as _apply
from ._apply_pyprojecttoml import _PREVIOUSLY_DEFINED, _WouldIgnoreField
+from ._apply_pyprojecttoml import apply as _apply
if TYPE_CHECKING:
from setuptools.dist import Distribution # noqa
@@ -104,8 +108,7 @@ def read_configuration(
if setuptools_table:
# TODO: Remove the following once the feature stabilizes:
- msg = "Support for `[tool.setuptools]` in `pyproject.toml` is still *beta*."
- warnings.warn(msg, _BetaConfiguration)
+ _BetaConfiguration.emit()
# There is an overall sense in the community that making include_package_data=True
# the default would be an improvement.
@@ -166,7 +169,7 @@ def _skip_bad_config(
# It seems that the docs in cibuildtool has been inadvertently encouraging users
# to create `pyproject.toml` files that are not compliant with the standards.
# Let's be forgiving for the time being.
- warnings.warn(_InvalidFile.message(), _InvalidFile, stacklevel=2)
+ _InvalidFile.emit()
return True
return False
@@ -369,8 +372,7 @@ class _ConfigExpander:
if group in groups:
value = groups.pop(group)
if field not in self.dynamic:
- msg = _WouldIgnoreField.message(field, value)
- warnings.warn(msg, _WouldIgnoreField)
+ _WouldIgnoreField.emit(field=field, value=value)
# TODO: Don't set field when support for pyproject.toml stabilizes
# instead raise an error as specified in PEP 621
expanded[field] = value
@@ -472,13 +474,13 @@ class _EnsurePackagesDiscovered(_expand.EnsurePackagesDiscovered):
return super().__exit__(exc_type, exc_value, traceback)
-class _BetaConfiguration(UserWarning):
- """Explicitly inform users that some `pyproject.toml` configuration is *beta*"""
+class _BetaConfiguration(SetuptoolsWarning):
+ _SUMMARY = "Support for `[tool.setuptools]` in `pyproject.toml` is still *beta*."
-class _InvalidFile(UserWarning):
- """The given `pyproject.toml` file is invalid and would be ignored.
- !!\n\n
+class _InvalidFile(SetuptoolsWarning):
+ _SUMMARY = "The given `pyproject.toml` file is invalid and would be ignored."
+ _DETAILS = """
############################
# Invalid `pyproject.toml` #
############################
@@ -488,11 +490,7 @@ class _InvalidFile(UserWarning):
if an invalid file is given.
To prevent setuptools from considering `pyproject.toml` please
- DO NOT include the `[project]` or `[tool.setuptools]` tables in your file.
- \n\n!!
+ DO NOT include both `[project]` or `[tool.setuptools]` tables in your file.
"""
-
- @classmethod
- def message(cls):
- from inspect import cleandoc
- return cleandoc(cls.__doc__)
+ _DUE_DATE = (2023, 6, 1) # warning introduced in 2022-03-26
+ _SEE_DOCS = "userguide/pyproject_config.html"
diff --git a/setuptools/config/setupcfg.py b/setuptools/config/setupcfg.py
index 7b7d57e6..050e5385 100644
--- a/setuptools/config/setupcfg.py
+++ b/setuptools/config/setupcfg.py
@@ -2,12 +2,15 @@
Load setuptools configuration from ``setup.cfg`` files.
**API will be made private in the future**
-"""
-import os
+To read project metadata, consider using
+``build.util.project_wheel_metadata`` (https://pypi.org/project/build/).
+For simple scenarios, you can also try parsing the file directly
+with the help of ``configparser``.
+"""
import contextlib
import functools
-import warnings
+import os
from collections import defaultdict
from functools import partial
from functools import wraps
@@ -26,19 +29,19 @@ from typing import (
Union,
)
-from distutils.errors import DistutilsOptionError, DistutilsFileError
-from setuptools.extern.packaging.requirements import Requirement, InvalidRequirement
-from setuptools.extern.packaging.markers import default_environment as marker_env
-from setuptools.extern.packaging.version import Version, InvalidVersion
-from setuptools.extern.packaging.specifiers import SpecifierSet
-from setuptools._deprecation_warning import SetuptoolsDeprecationWarning
-
+from ..errors import FileError, OptionError
+from ..extern.packaging.markers import default_environment as marker_env
+from ..extern.packaging.requirements import InvalidRequirement, Requirement
+from ..extern.packaging.specifiers import SpecifierSet
+from ..extern.packaging.version import InvalidVersion, Version
+from ..warnings import SetuptoolsDeprecationWarning
from . import expand
if TYPE_CHECKING:
- from setuptools.dist import Distribution # noqa
from distutils.dist import DistributionMetadata # noqa
+ from setuptools.dist import Distribution # noqa
+
_Path = Union[str, os.PathLike]
SingleCommandOptions = Dict["str", Tuple["str", Any]]
"""Dict that associate the name of the options of a particular command to a
@@ -97,7 +100,7 @@ def _apply(
filepath = os.path.abspath(filepath)
if not os.path.isfile(filepath):
- raise DistutilsFileError('Configuration file %s does not exist.' % filepath)
+ raise FileError(f'Configuration file {filepath} does not exist.')
current_directory = os.getcwd()
os.chdir(os.path.dirname(filepath))
@@ -121,7 +124,7 @@ def _get_option(target_obj: Target, key: str):
the target object, either through a get_{key} method or
from an attribute directly.
"""
- getter_name = 'get_{key}'.format(**locals())
+ getter_name = f'get_{key}'
by_attribute = functools.partial(getattr, target_obj, key)
getter = getattr(target_obj, getter_name, by_attribute)
return getter()
@@ -212,19 +215,14 @@ def _warn_accidental_env_marker_misconfig(label: str, orig_value: str, parsed: l
return
markers = marker_env().keys()
- msg = (
- f"One of the parsed requirements in `{label}` "
- f"looks like a valid environment marker: '{parsed[1]}'\n"
- "Make sure that the config is correct and check "
- "https://setuptools.pypa.io/en/latest/userguide/declarative_config.html#opt-2" # noqa: E501
- )
try:
req = Requirement(parsed[1])
if req.name in markers:
- warnings.warn(msg)
+ _AmbiguousMarker.emit(field=label, req=parsed[1])
except InvalidRequirement as ex:
if any(parsed[1].startswith(marker) for marker in markers):
+ msg = _AmbiguousMarker.message(field=label, req=parsed[1])
raise InvalidRequirement(msg) from ex
@@ -334,9 +332,7 @@ class ConfigHandler(Generic[Target]):
for line in cls._parse_list(value):
key, sep, val = line.partition(separator)
if sep != separator:
- raise DistutilsOptionError(
- 'Unable to parse option value to dict: %s' % value
- )
+ raise OptionError(f"Unable to parse option value to dict: {value}")
result[key.strip()] = val.strip()
return result
@@ -496,24 +492,24 @@ class ConfigHandler(Generic[Target]):
)
if section_parser_method is None:
- raise DistutilsOptionError(
- 'Unsupported distribution option section: [%s.%s]'
- % (self.section_prefix, section_name)
+ raise OptionError(
+ "Unsupported distribution option section: "
+ f"[{self.section_prefix}.{section_name}]"
)
section_parser_method(section_options)
- def _deprecated_config_handler(self, func, msg, warning_class):
+ def _deprecated_config_handler(self, func, msg, **kw):
"""this function will wrap around parameters that are deprecated
:param msg: deprecation message
- :param warning_class: class of warning exception to be raised
:param func: function to be wrapped around
"""
@wraps(func)
def config_handler(*args, **kwargs):
- warnings.warn(msg, warning_class, stacklevel=2)
+ kw.setdefault("stacklevel", 2)
+ _DeprecatedConfig.emit("Deprecated config in `setup.cfg`", msg, **kw)
return func(*args, **kwargs)
return config_handler
@@ -564,7 +560,8 @@ class ConfigMetadataHandler(ConfigHandler["DistributionMetadata"]):
parse_list,
"The requires parameter is deprecated, please use "
"install_requires for runtime dependencies.",
- SetuptoolsDeprecationWarning,
+ due_date=(2023, 10, 30),
+ # Warning introduced in 27 Oct 2018
),
'obsoletes': parse_list,
'classifiers': self._get_parser_compound(parse_file, parse_list),
@@ -573,7 +570,8 @@ class ConfigMetadataHandler(ConfigHandler["DistributionMetadata"]):
exclude_files_parser('license_file'),
"The license_file parameter is deprecated, "
"use license_files instead.",
- SetuptoolsDeprecationWarning,
+ due_date=(2023, 10, 30),
+ # Warning introduced in 23 May 2021
),
'license_files': parse_list,
'description': parse_file,
@@ -598,11 +596,10 @@ class ConfigMetadataHandler(ConfigHandler["DistributionMetadata"]):
try:
Version(version)
except InvalidVersion:
- tmpl = (
- 'Version loaded from {value} does not '
- 'comply with PEP 440: {version}'
+ raise OptionError(
+ f'Version loaded from {value} does not '
+ f'comply with PEP 440: {version}'
)
- raise DistutilsOptionError(tmpl.format(**locals()))
return version
@@ -657,7 +654,7 @@ class ConfigOptionsHandler(ConfigHandler["Distribution"]):
parse_list,
"The namespace_packages parameter is deprecated, "
"consider using implicit namespaces instead (PEP 420).",
- SetuptoolsDeprecationWarning,
+ # TODO: define due date, see setuptools.dist:check_nsp.
),
'install_requires': partial(
self._parse_requirements_list, "install_requires"
@@ -766,3 +763,27 @@ class ConfigOptionsHandler(ConfigHandler["Distribution"]):
"""
parsed = self._parse_section_to_dict(section_options, self._parse_list)
self['data_files'] = expand.canonic_data_files(parsed, self.root_dir)
+
+
+class _AmbiguousMarker(SetuptoolsDeprecationWarning):
+ _SUMMARY = "Ambiguous requirement marker."
+ _DETAILS = """
+ One of the parsed requirements in `{field}` looks like a valid environment marker:
+
+ {req!r}
+
+ Please make sure that the configuration file is correct.
+ You can use dangling lines to avoid this problem.
+ """
+ _SEE_DOCS = "userguide/declarative_config.html#opt-2"
+ # TODO: should we include due_date here? Initially introduced in 6 Aug 2022.
+ # Does this make sense with latest version of packaging?
+
+ @classmethod
+ def message(cls, **kw):
+ docs = f"https://setuptools.pypa.io/en/latest/{cls._SEE_DOCS}"
+ return cls._format(cls._SUMMARY, cls._DETAILS, see_url=docs, format_args=kw)
+
+
+class _DeprecatedConfig(SetuptoolsDeprecationWarning):
+ _SEE_DOCS = "https://setuptools.pypa.io/en/latest/userguide/declarative_config.html"