Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle wave of overdue deprecations #4066

Merged
merged 20 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
60e01c7
Re-enable deprecation warning enforcement
abravalheri Sep 28, 2023
81a3547
Remove deprecation warning from _normalization.best_effort_version
abravalheri Sep 28, 2023
7760a7a
Remove egg_base option from dist_info
abravalheri Sep 28, 2023
2c96f00
Ensure tags generated by egg_info are valid
abravalheri Sep 28, 2023
db987aa
Remove 'requires' and 'license_file' from setup.cfg
abravalheri Sep 28, 2023
0910084
Improve explanation of difference between safe_version and best_effor…
abravalheri Sep 28, 2023
e247d21
Remove deprecation warning for config_settings --global-option
abravalheri Sep 28, 2023
9433e90
Remove deprecation warning for invalid versions in setuptools.dist
abravalheri Sep 28, 2023
6cc69fe
Add news fragments
abravalheri Sep 28, 2023
4f60770
Xfail on deprecated bdist_rpm tests
abravalheri Nov 17, 2023
e4b6fc9
Be strict on missing 'dynamic' in pyproject.toml
abravalheri Nov 17, 2023
9c35cd9
Enforce namespace-packages are not used in pyproject.toml
abravalheri Nov 17, 2023
4a34a1c
Use custom class for InvalidConfigError
abravalheri Nov 17, 2023
b44faba
Add newsfragments for latest removals
abravalheri Nov 17, 2023
b802c1b
Update newsfragments
abravalheri Nov 17, 2023
b243818
Fix lint errors
abravalheri Nov 17, 2023
b97814a
Add workaround for unreleased PyNaCl
abravalheri Nov 17, 2023
7c111b8
Use InvalidConfigError instead of ValueError in build_meta
abravalheri Nov 20, 2023
f431962
Remove deprecated handling of build-option passed as global-option
abravalheri Nov 20, 2023
a5a7505
Relax validation of --global-option in build_meta
abravalheri Nov 20, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ jobs:
echo "PRE_BUILT_SETUPTOOLS_SDIST=$(ls dist/*.tar.gz)" >> $GITHUB_ENV
echo "PRE_BUILT_SETUPTOOLS_WHEEL=$(ls dist/*.whl)" >> $GITHUB_ENV
rm -rf setuptools.egg-info # Avoid interfering with the other tests
- name: Workaround for unreleased PyNaCl (pyca/pynacl#805)
if: contains(matrix.python, 'pypy')
run: echo "SETUPTOOLS_ENFORCE_DEPRECATION=0" >> $GITHUB_ENV
- name: Install tox
run: |
python -m pip install tox
Expand Down
2 changes: 2 additions & 0 deletions newsfragments/4066.removal.1.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Configuring project ``version`` and ``egg_info.tag_*`` in such a way that
results in invalid version strings (according to :pep:`440`) is no longer permitted.
4 changes: 4 additions & 0 deletions newsfragments/4066.removal.2.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Removed deprecated ``egg_base`` option from ``dist_info``.
Note that the ``dist_info`` command is considered internal to the way
``setuptools`` build backend works and not intended for
public usage.
4 changes: 4 additions & 0 deletions newsfragments/4066.removal.3.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
The parsing of the deprecated ``metadata.license_file`` and
``metadata.requires`` fields in ``setup.cfg`` is no longer supported.
Users are expected to move to ``metadata.license_files`` and
``options.install_requires`` (respectively).
2 changes: 2 additions & 0 deletions newsfragments/4066.removal.4.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Passing ``config_settings`` to ``setuptools.build_meta`` with
deprecated values for ``--global-option`` is no longer allowed.
4 changes: 4 additions & 0 deletions newsfragments/4066.removal.5.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Removed deprecated ``namespace-packages`` from ``pyproject.toml``.
Users are asked to use
:doc:`implicit namespace packages <PyPUG:guides/packaging-namespace-packages>`
(as defined in :pep:`420`).
4 changes: 4 additions & 0 deletions newsfragments/4066.removal.6.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Added strict enforcement for ``project.dynamic`` in ``pyproject.toml``.
This removes the transitional ability of users configuring certain parameters
via ``setup.py`` without making the necessary changes to ``pyproject.toml``
(as mandated by :pep:`612`).
43 changes: 24 additions & 19 deletions setuptools/_normalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
from typing import Union

from .extern import packaging
from .warnings import SetuptoolsDeprecationWarning

_Path = Union[str, Path]

# https://packaging.python.org/en/latest/specifications/core-metadata/#name
_VALID_NAME = re.compile(r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.I)
_UNSAFE_NAME_CHARS = re.compile(r"[^A-Z0-9.]+", re.I)
_NON_ALPHANUMERIC = re.compile(r"[^A-Z0-9]+", re.I)
_PEP440_FALLBACK = re.compile(r"^v?(?P<safe>(?:[0-9]+!)?[0-9]+(?:\.[0-9]+)*)", re.I)


def safe_identifier(name: str) -> str:
Expand Down Expand Up @@ -42,6 +42,8 @@ def safe_name(component: str) -> str:

def safe_version(version: str) -> str:
"""Convert an arbitrary string into a valid version string.
Can still raise an ``InvalidVersion`` exception.
To avoid exceptions use ``best_effort_version``.
>>> safe_version("1988 12 25")
'1988.12.25'
>>> safe_version("v0.2.1")
Expand All @@ -65,32 +67,35 @@ def safe_version(version: str) -> str:

def best_effort_version(version: str) -> str:
"""Convert an arbitrary string into a version-like string.
Fallback when ``safe_version`` is not safe enough.
>>> best_effort_version("v0.2 beta")
'0.2b0'

>>> import warnings
>>> warnings.simplefilter("ignore", category=SetuptoolsDeprecationWarning)
>>> best_effort_version("ubuntu lts")
'ubuntu.lts'
'0.dev0+sanitized.ubuntu.lts'
>>> best_effort_version("0.23ubuntu1")
'0.23.dev0+sanitized.ubuntu1'
>>> best_effort_version("0.23-")
'0.23.dev0+sanitized'
>>> best_effort_version("0.-_")
'0.dev0+sanitized'
>>> best_effort_version("42.+?1")
'42.dev0+sanitized.1'
"""
# See pkg_resources.safe_version
# See pkg_resources._forgiving_version
try:
return safe_version(version)
except packaging.version.InvalidVersion:
SetuptoolsDeprecationWarning.emit(
f"Invalid version: {version!r}.",
f"""
Version {version!r} is not valid according to PEP 440.

Please make sure to specify a valid version for your package.
Also note that future releases of setuptools may halt the build process
if an invalid version is given.
""",
see_url="https://peps.python.org/pep-0440/",
due_date=(2023, 9, 26), # See setuptools/dist _validate_version
)
v = version.replace(' ', '.')
return safe_name(v)
match = _PEP440_FALLBACK.search(v)
if match:
safe = match["safe"]
rest = v[len(safe) :]
else:
safe = "0"
rest = version
safe_rest = _NON_ALPHANUMERIC.sub(".", rest).strip(".")
local = f"sanitized.{safe_rest}".strip(".")
return safe_version(f"{safe}.dev0+{local}")


def safe_extra(extra: str) -> str:
Expand Down
35 changes: 3 additions & 32 deletions setuptools/build_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,6 @@ def _get_config(self, key: str, config_settings: _ConfigSettings) -> List[str]:
opts = cfg.get(key) or []
return shlex.split(opts) if isinstance(opts, str) else opts

def _valid_global_options(self):
"""Global options accepted by setuptools (e.g. quiet or verbose)."""
options = (opt[:2] for opt in setuptools.dist.Distribution.global_options)
return {flag for long_and_short in options for flag in long_and_short if flag}

def _global_args(self, config_settings: _ConfigSettings) -> Iterator[str]:
"""
Let the user specify ``verbose`` or ``quiet`` + escape hatch via
Expand Down Expand Up @@ -220,9 +215,7 @@ def _global_args(self, config_settings: _ConfigSettings) -> Iterator[str]:
level = str(cfg.get("quiet") or cfg.get("--quiet") or "1")
yield ("-v" if level.lower() in falsey else "-q")

valid = self._valid_global_options()
args = self._get_config("--global-option", config_settings)
yield from (arg for arg in args if arg.strip("-") in valid)
yield from self._get_config("--global-option", config_settings)

def __dist_info_args(self, config_settings: _ConfigSettings) -> Iterator[str]:
"""
Expand Down Expand Up @@ -284,33 +277,11 @@ def _arbitrary_args(self, config_settings: _ConfigSettings) -> Iterator[str]:
['foo']
>>> list(fn({'--build-option': 'foo bar'}))
['foo', 'bar']
>>> warnings.simplefilter('error', SetuptoolsDeprecationWarning)
>>> list(fn({'--global-option': 'foo'})) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
SetuptoolsDeprecationWarning: ...arguments given via `--global-option`...
>>> list(fn({'--global-option': 'foo'}))
[]
"""
args = self._get_config("--global-option", config_settings)
global_opts = self._valid_global_options()
bad_args = []

for arg in args:
if arg.strip("-") not in global_opts:
bad_args.append(arg)
yield arg

yield from self._get_config("--build-option", config_settings)

if bad_args:
SetuptoolsDeprecationWarning.emit(
"Incompatible `config_settings` passed to build backend.",
f"""
The arguments {bad_args!r} were given via `--global-option`.
Please use `--build-option` instead,
`--global-option` is reserved for flags like `--verbose` or `--quiet`.
""",
due_date=(2023, 9, 26), # Warning introduced in v64.0.1, 11/Aug/2022.
)


class _BuildMetaBackend(_ConfigSettingsTranslator):
def _get_build_requires(self, config_settings, requirements):
Expand Down
16 changes: 0 additions & 16 deletions setuptools/command/dist_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from pathlib import Path

from .. import _normalization
from ..warnings import SetuptoolsDeprecationWarning


class dist_info(Command):
Expand All @@ -24,13 +23,6 @@ class dist_info(Command):
description = "DO NOT CALL DIRECTLY, INTERNAL ONLY: create .dist-info directory"

user_options = [
(
'egg-base=',
'e',
"directory containing .egg-info directories"
" (default: top of the source tree)"
" DEPRECATED: use --output-dir.",
),
(
'output-dir=',
'o',
Expand All @@ -47,7 +39,6 @@ class dist_info(Command):
negative_opt = {'no-date': 'tag-date'}

def initialize_options(self):
self.egg_base = None
self.output_dir = None
self.name = None
self.dist_info_dir = None
Expand All @@ -56,13 +47,6 @@ def initialize_options(self):
self.keep_egg_info = False

def finalize_options(self):
if self.egg_base:
msg = "--egg-base is deprecated for dist_info command. Use --output-dir."
SetuptoolsDeprecationWarning.emit(msg, due_date=(2023, 9, 26))
# This command is internal to setuptools, therefore it should be safe
# to remove the deprecated support soon.
self.output_dir = self.egg_base or self.output_dir

dist = self.distribution
project_dir = dist.src_root or os.curdir
self.output_dir = Path(self.output_dir or project_dir)
Expand Down
7 changes: 5 additions & 2 deletions setuptools/command/egg_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def name(self):

def tagged_version(self):
tagged = self._maybe_tag(self.distribution.get_version())
return _normalization.best_effort_version(tagged)
return _normalization.safe_version(tagged)

def _maybe_tag(self, version):
"""
Expand All @@ -148,7 +148,10 @@ def _already_tagged(self, version: str) -> bool:
def _safe_tags(self) -> str:
# To implement this we can rely on `safe_version` pretending to be version 0
# followed by tags. Then we simply discard the starting 0 (fake version number)
return _normalization.best_effort_version(f"0{self.vtags}")[1:]
try:
return _normalization.safe_version(f"0{self.vtags}")[1:]
except packaging.version.InvalidVersion:
return _normalization.safe_name(self.vtags.replace(' ', '.'))

def tags(self) -> str:
version = ''
Expand Down
76 changes: 49 additions & 27 deletions setuptools/config/_apply_pyprojecttoml.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from collections.abc import Mapping
from email.headerregistry import Address
from functools import partial, reduce
from inspect import cleandoc
from itertools import chain
from types import MappingProxyType
from typing import (
Expand All @@ -28,7 +29,8 @@
cast,
)

from ..warnings import SetuptoolsWarning, SetuptoolsDeprecationWarning
from ..errors import RemovedConfigError
from ..warnings import SetuptoolsWarning

if TYPE_CHECKING:
from setuptools._importlib import metadata # noqa
Expand Down Expand Up @@ -90,12 +92,13 @@ def _apply_tool_table(dist: "Distribution", config: dict, filename: _Path):
for field, value in tool_table.items():
norm_key = json_compatible_key(field)

if norm_key in TOOL_TABLE_DEPRECATIONS:
suggestion, kwargs = TOOL_TABLE_DEPRECATIONS[norm_key]
msg = f"The parameter `{norm_key}` is deprecated, {suggestion}"
SetuptoolsDeprecationWarning.emit(
"Deprecated config", msg, **kwargs # type: ignore
)
if norm_key in TOOL_TABLE_REMOVALS:
suggestion = cleandoc(TOOL_TABLE_REMOVALS[norm_key])
msg = f"""
The parameter `tool.setuptools.{field}` was long deprecated
and has been removed from `pyproject.toml`.
"""
raise RemovedConfigError("\n".join([cleandoc(msg), suggestion]))

norm_key = TOOL_TABLE_RENAMES.get(norm_key, norm_key)
_set_config(dist, norm_key, value)
Expand All @@ -105,13 +108,13 @@ def _apply_tool_table(dist: "Distribution", config: dict, filename: _Path):

def _handle_missing_dynamic(dist: "Distribution", project_table: dict):
"""Be temporarily forgiving with ``dynamic`` fields not listed in ``dynamic``"""
# TODO: Set fields back to `None` once the feature stabilizes
dynamic = set(project_table.get("dynamic", []))
for field, getter in _PREVIOUSLY_DEFINED.items():
if not (field in project_table or field in dynamic):
value = getter(dist)
if value:
_WouldIgnoreField.emit(field=field, value=value)
_MissingDynamic.emit(field=field, value=value)
project_table[field] = _RESET_PREVIOUSLY_DEFINED.get(field)


def json_compatible_key(key: str) -> str:
Expand Down Expand Up @@ -226,14 +229,18 @@ def _unify_entry_points(project_table: dict):
renaming = {"scripts": "console_scripts", "gui_scripts": "gui_scripts"}
for key, value in list(project.items()): # eager to allow modifications
norm_key = json_compatible_key(key)
if norm_key in renaming and value:
if norm_key in renaming:
# Don't skip even if value is empty (reason: reset missing `dynamic`)
entry_points[renaming[norm_key]] = project.pop(key)

if entry_points:
project["entry-points"] = {
name: [f"{k} = {v}" for k, v in group.items()]
for name, group in entry_points.items()
if group # now we can skip empty groups
}
# Sometimes this will set `project["entry-points"] = {}`, and that is
# intentional (for reseting configurations that are missing `dynamic`).


def _copy_command_options(pyproject: dict, dist: "Distribution", filename: _Path):
Expand Down Expand Up @@ -353,11 +360,11 @@ def _acessor(obj):
}

TOOL_TABLE_RENAMES = {"script_files": "scripts"}
TOOL_TABLE_DEPRECATIONS = {
"namespace_packages": (
"consider using implicit namespaces instead (PEP 420).",
{"due_date": (2023, 10, 30)}, # warning introduced in May 2022
)
TOOL_TABLE_REMOVALS = {
"namespace_packages": """
Please migrate to implicit native namespaces instead.
See https://packaging.python.org/en/latest/guides/packaging-namespace-packages/.
""",
}

SETUPTOOLS_PATCHES = {
Expand Down Expand Up @@ -388,14 +395,27 @@ def _acessor(obj):
}


class _WouldIgnoreField(SetuptoolsDeprecationWarning):
_SUMMARY = "`{field}` defined outside of `pyproject.toml` would be ignored."
_RESET_PREVIOUSLY_DEFINED: dict = {
# Fix improper setting: given in `setup.py`, but not listed in `dynamic`
# dict: pyproject name => value to which reset
"license": {},
"authors": [],
"maintainers": [],
"keywords": [],
"classifiers": [],
"urls": {},
"entry-points": {},
"scripts": {},
"gui-scripts": {},
"dependencies": [],
"optional-dependencies": [],
}

_DETAILS = """
##########################################################################
# configuration would be ignored/result in error due to `pyproject.toml` #
##########################################################################

class _MissingDynamic(SetuptoolsWarning):
_SUMMARY = "`{field}` defined outside of `pyproject.toml` is ignored."

_DETAILS = """
The following seems to be defined outside of `pyproject.toml`:

`{field} = {value!r}`
Expand All @@ -405,12 +425,14 @@ class _WouldIgnoreField(SetuptoolsDeprecationWarning):

https://packaging.python.org/en/latest/specifications/declaring-project-metadata/

For the time being, `setuptools` will still consider the given value (as a
**transitional** measure), but please note that future releases of setuptools will
follow strictly the standard.

To prevent this warning, you can list `{field}` under `dynamic` or alternatively
To prevent this problem, you can list `{field}` under `dynamic` or alternatively
remove the `[project]` table from your file and rely entirely on other means of
configuration.
"""
_DUE_DATE = (2023, 10, 30) # Initially introduced in 27 May 2022
# TODO: Consider removing this check in the future?
# There is a trade-off here between improving "debug-ability" and the cost
# of running/testing/maintaining these unnecessary checks...

@classmethod
def details(cls, field: str, value: Any) -> str:
return cls._DETAILS.format(field=field, value=value)
Loading
Loading