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

Add warning for deprecated metadata fields #4811

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 14 additions & 4 deletions docs/references/keywords.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,21 @@ extensions).
``url``
A string specifying the URL for the package homepage.

.. warning::
``url`` is deprecated as it generates the ``Home-page`` metadata.
Please use ``project_urls`` instead, with the relevant labels.
See :pep:`753`.

.. _keyword/download_url:

``download_url``
A string specifying the URL to download the package.

.. warning::
``download_url`` is deprecated as it generates the ``Download-URL`` metadata.
Please use ``project_urls`` instead, with the relevant labels.
See :pep:`753`.

.. _keyword/packages:

``packages``
Expand Down Expand Up @@ -257,14 +267,14 @@ extensions).

``requires``
.. warning::
``requires`` is superseded by ``install_requires`` and should not be used
anymore.
``requires`` is deprecated and superseded by ``install_requires``.
It should not be used anymore.

.. _keyword/obsoletes:

``obsoletes``
.. warning::
``obsoletes`` is currently ignored by ``pip``.
``obsoletes`` is deprecated and currently ignored by ``pip``.

List of strings describing packages which this package renders obsolete,
meaning that the two projects should not be installed at the same time.
Expand All @@ -283,7 +293,7 @@ extensions).

``provides``
.. warning::
``provides`` is currently ignored by ``pip``.
``provides`` is currently considered deprecated and is ignored by ``pip``.

List of strings describing package- and virtual package names contained
within this package.
Expand Down
29 changes: 25 additions & 4 deletions setuptools/config/setupcfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

from .. import _static
from .._path import StrPath
from ..errors import FileError, OptionError
from ..errors import FileError, OptionError, RemovedConfigError
from ..warnings import SetuptoolsDeprecationWarning
from . import expand

Expand Down Expand Up @@ -516,6 +516,25 @@ def config_handler(*args, **kwargs):

return config_handler

def _deprecated(self, field, func):
anchor = f"keyword-{field.replace('_', '-')}"
return self._deprecated_config_handler(
func,
f"Deprecated usage of `{field}` in `setup.cfg`.",
see_docs=f"references/keywords.html#{anchor}",
due_date=(2027, 1, 25), # introduced in 20 Jan 2025
)

def _removed(self, field, **kwargs):
def config_handler(*args, **kwargs):
raise RemovedConfigError(
f"Invalid use of `{field}` in `setup.cfg`.\nSee: "
"https://setuptools.pypa.io/en/latest/"
f"references/keywords.html#keyword-{field.replace('_', '-')}"
)

return config_handler


class ConfigMetadataHandler(ConfigHandler["DistributionMetadata"]):
section_prefix = 'metadata'
Expand Down Expand Up @@ -561,16 +580,18 @@ def parsers(self):
'maintainer_email': _static.Str,
'platforms': parse_list_static,
'keywords': parse_list_static,
'provides': parse_list_static,
'obsoletes': parse_list_static,
'provides': self._deprecated('provides', parse_list_static),
'obsoletes': self._deprecated('obsoletes', parse_list_static),
'requires': self._removed('requires'), # 2023-Nov-20
'classifiers': self._get_parser_compound(parse_file, parse_list_static),
'license': exclude_files_parser('license'),
'license_files': parse_list_static,
'description': parse_file,
'long_description': parse_file,
'long_description_content_type': _static.Str,
'version': self._parse_version, # Cannot be marked as dynamic
'url': _static.Str,
'url': self._deprecated('url', _static.Str),
'download_url': self._deprecated('download_url', _static.Str),
'project_urls': parse_dict_static,
}

Expand Down
25 changes: 22 additions & 3 deletions setuptools/dist.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,14 @@ class Distribution(_Distribution):
'extras_require': dict,
}

_DEPRECATED_FIELDS = (
"url",
"download_url",
"requires",
"provides",
"obsoletes",
)

# Used by build_py, editable_wheel and install_lib commands for legacy namespaces
namespace_packages: list[str] #: :meta private: DEPRECATED

Expand Down Expand Up @@ -326,9 +334,9 @@ def __init__(self, attrs: MutableMapping[str, Any] | None = None) -> None:
self.set_defaults = ConfigDiscovery(self)

self._set_metadata_defaults(attrs)

self.metadata.version = self._normalize_version(self.metadata.version)
self._finalize_requires()
self._check_deprecated_metadata_fields()

def _validate_metadata(self):
required = {"name"}
Expand All @@ -343,6 +351,16 @@ def _validate_metadata(self):
msg = f"Required package metadata is missing: {missing}"
raise DistutilsSetupError(msg)

def _check_deprecated_metadata_fields(self) -> None:
for attr in self._DEPRECATED_FIELDS:
if getattr(self.metadata, attr, None):
anchor = f"keyword-{attr.replace('_', '-')}"
SetuptoolsDeprecationWarning.emit(
f"Deprecated usage of `{attr}` in setuptools configuration.",
see_docs=f"references/keywords.html#{anchor}",
due_date=(2027, 1, 25), # introduced in 20 Jan 2025
)

def _set_metadata_defaults(self, attrs):
"""
Fill-in missing metadata fields not supported by distutils.
Expand Down Expand Up @@ -993,8 +1011,9 @@ def handle_display_options(self, option_order):

def run_command(self, command) -> None:
self.set_defaults()
# Postpone defaults until all explicit configuration is considered
# (setup() args, config files, command line and plugins)
self._check_deprecated_metadata_fields()
# Postpone defaults and validations until all explicit configuration is
# considered (setup() args, config files, command line and plugins)

super().run_command(command)

Expand Down
80 changes: 58 additions & 22 deletions setuptools/tests/config/test_setupcfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from setuptools.config.setupcfg import ConfigHandler, Target, read_configuration
from setuptools.dist import Distribution, _Distribution
from setuptools.errors import RemovedConfigError
from setuptools.warnings import SetuptoolsDeprecationWarning

from ..textwrap import DALS
Expand Down Expand Up @@ -136,19 +137,20 @@ def test_basic(self, tmpdir):
'license': 'BSD 3-Clause License',
}

with get_dist(tmpdir, meta_initial) as dist:
metadata = dist.metadata
with pytest.warns(SetuptoolsDeprecationWarning, match="Deprecated config"):
with get_dist(tmpdir, meta_initial) as dist:
metadata = dist.metadata

assert metadata.version == '10.1.1'
assert metadata.description == 'Some description'
assert metadata.long_description_content_type == 'text/something'
assert metadata.long_description == 'readme contents\nline2'
assert metadata.provides == ['package', 'package.sub']
assert metadata.license == 'BSD 3-Clause License'
assert metadata.name == 'fake_name'
assert metadata.keywords == ['one', 'two']
assert metadata.download_url == 'http://test.test.com/test/'
assert metadata.maintainer_email == '[email protected]'
assert metadata.version == '10.1.1'
assert metadata.description == 'Some description'
assert metadata.long_description_content_type == 'text/something'
assert metadata.long_description == 'readme contents\nline2'
assert metadata.provides == ['package', 'package.sub']
assert metadata.license == 'BSD 3-Clause License'
assert metadata.name == 'fake_name'
assert metadata.keywords == ['one', 'two']
assert metadata.download_url == 'http://test.test.com/test/'
assert metadata.maintainer_email == '[email protected]'

def test_license_cfg(self, tmpdir):
fake_env(
Expand Down Expand Up @@ -207,16 +209,17 @@ def test_aliases(self, tmpdir):
' Programming Language :: Python :: 3.5\n',
)

with get_dist(tmpdir) as dist:
metadata = dist.metadata
assert metadata.author_email == '[email protected]'
assert metadata.url == 'http://test.test.com/test/'
assert metadata.description == 'Short summary'
assert metadata.platforms == ['a', 'b']
assert metadata.classifiers == [
'Framework :: Django',
'Programming Language :: Python :: 3.5',
]
with pytest.warns(match='Deprecated usage of .url.'):
with get_dist(tmpdir) as dist:
metadata = dist.metadata
assert metadata.author_email == '[email protected]'
assert metadata.url == 'http://test.test.com/test/'
assert metadata.description == 'Short summary'
assert metadata.platforms == ['a', 'b']
assert metadata.classifiers == [
'Framework :: Django',
'Programming Language :: Python :: 3.5',
]

def test_multiline(self, tmpdir):
fake_env(
Expand Down Expand Up @@ -449,6 +452,39 @@ def test_make_option_lowercase(self, tmpdir):
assert metadata.name == 'foo'
assert metadata.description == 'Some description'

@pytest.mark.parametrize(
("field", "value"),
[
("provides", "setuptools"),
("obsoletes", "setuptools"),
("url", "www.setuptools.com.br"),
("download_url", "www.setuptools.com.br/42"),
],
)
def test_deprecated(self, tmpdir, field, value):
fake_env(
tmpdir,
f'[metadata]\nname = foo\ndescription = Desc\n{field} = {value}',
)
match = f"Deprecated usage of `{field}`"
with pytest.warns(SetuptoolsDeprecationWarning, match=match):
get_dist(tmpdir).__enter__()

@pytest.mark.parametrize(
("field", "value"),
[
("requires", "setuptools"),
],
)
def test_removed(self, tmpdir, field, value):
fake_env(
tmpdir,
f'[metadata]\nname = foo\ndescription = Desc\n{field} = {value}',
)
match = f"Invalid use of `{field}`"
with pytest.raises(RemovedConfigError, match=match):
get_dist(tmpdir).__enter__()


class TestOptions:
def test_basic(self, tmpdir):
Expand Down
2 changes: 1 addition & 1 deletion setuptools/tests/test_bdist_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
long_description="Another testing distribution \N{SNOWMAN}",
author="Illustrious Author",
author_email="[email protected]",
url="http://example.org/exemplary",
project_urls={"homepage": "http://example.org/exemplary"},
packages=["complexdist"],
setup_requires=["setuptools"],
install_requires=["quux", "splort"],
Expand Down
8 changes: 5 additions & 3 deletions setuptools/tests/test_core_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,11 @@ def __read_test_cases():
],
),
),
(
pytest.param(
'Metadata version 1.1: Download URL',
params(
download_url='https://example.com',
params(download_url='https://example.com'),
marks=pytest.mark.filterwarnings(
"ignore:Deprecated usage of .download_url"
),
),
(
Expand Down Expand Up @@ -380,6 +381,7 @@ def dist(self, request, monkeypatch, tmp_path):
yield setupcfg.apply_configuration(Distribution({}), config)

@pytest.mark.uses_network
@pytest.mark.filterwarnings('ignore:Deprecated config')
def test_equivalent_output(self, tmp_path, dist):
"""Ensure output from setuptools is equivalent to the one from `pypa/wheel`"""
# Generate a METADATA file using pypa/wheel for comparison
Expand Down
21 changes: 20 additions & 1 deletion setuptools/tests/test_dist.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import pytest

from setuptools import Distribution
from setuptools import Distribution, SetuptoolsDeprecationWarning
from setuptools.dist import check_package_data, check_specifier

from .test_easy_install import make_nspkg_sdist
Expand Down Expand Up @@ -276,3 +276,22 @@ def test_dist_default_name(tmp_path, dist_name, package_dir, package_files):
dist.set_defaults()
assert dist.py_modules or dist.packages
assert dist.get_name() == dist_name


@pytest.mark.parametrize(
("field", "value"),
[
("requires", ["setuptools"]),
("provides", ["setuptools"]),
("obsoletes", ["setuptools"]),
("url", "www.setuptools.com.br"),
("download_url", "www.setuptools.com.br/42"),
],
)
def test_deprecated_fields(tmpdir_cwd, field, value):
"""See discussion in https://github.com/pypa/setuptools/issues/4797"""
attrs = {"name": "test", "version": "0.42", field: value}
match = f"Deprecated usage of `{field}`"
with pytest.warns(SetuptoolsDeprecationWarning, match=match):
dist = Distribution(attrs)
assert getattr(dist.metadata, field, None) == value
Loading