diff options
| author | Anderson Bravalheri <andersonbravalheri@gmail.com> | 2023-04-20 14:24:08 +0100 |
|---|---|---|
| committer | Anderson Bravalheri <andersonbravalheri@gmail.com> | 2023-04-20 14:24:08 +0100 |
| commit | 73b5c62939cd2ef039da9d5f784083fa956ef4a1 (patch) | |
| tree | c24848a7a4c705362a9c076f2c7b16aec8d752c7 /setuptools/config | |
| parent | 000efbfae1e79d0a9fa9b16b55c4f5a2e90a64dd (diff) | |
| parent | 54da8b6d69b7333424eff305218a10d9605a7e36 (diff) | |
| download | python-setuptools-git-73b5c62939cd2ef039da9d5f784083fa956ef4a1.tar.gz | |
Overhaul for better visibility of warnings (#3849)
Diffstat (limited to 'setuptools/config')
| -rw-r--r-- | setuptools/config/__init__.py | 31 | ||||
| -rw-r--r-- | setuptools/config/_apply_pyprojecttoml.py | 38 | ||||
| -rw-r--r-- | setuptools/config/expand.py | 4 | ||||
| -rw-r--r-- | setuptools/config/pyprojecttoml.py | 42 | ||||
| -rw-r--r-- | setuptools/config/setupcfg.py | 93 |
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" |
