Skip to content
Open
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
1 change: 1 addition & 0 deletions newsfragments/4523.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added a warning when ``tool.setuptools.dynamic`` defines a field that is not listed in ``project.dynamic``.
30 changes: 30 additions & 0 deletions setuptools/config/pyprojecttoml.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ def expand(self):
self._expand_packages()
self._canonic_package_data()
self._canonic_package_data("exclude-package-data")
self._warn_about_missing_dynamic()

# A distribution object is required for discovering the correct package_dir
dist = self._ensure_dist()
Expand All @@ -228,6 +229,19 @@ def expand(self):
dist._referenced_files.update(self._referenced_files)
return self.config

def _warn_about_missing_dynamic(self) -> None:
"""Warn when a directive cannot be used because ``project.dynamic`` omits it."""
for field in sorted(self.dynamic_cfg):
if not self._uses_dynamic_directive(field):
_MissingDynamicDirective.emit(field=field)

def _uses_dynamic_directive(self, field: str) -> bool:
if field in self.dynamic:
return True
return field == "entry-points" and any(
item in self.dynamic for item in ("scripts", "gui-scripts")
)

def _expand_packages(self):
packages = self.setuptools_cfg.get("packages")
if packages is None or isinstance(packages, (list, tuple)):
Expand Down Expand Up @@ -475,3 +489,19 @@ class _ToolsTypoInMetadata(SetuptoolsWarning):
_SUMMARY = (
"Ignoring [tools.setuptools] in pyproject.toml, did you mean [tool.setuptools]?"
)


class _MissingDynamicDirective(SetuptoolsWarning):
_SUMMARY = "`tool.setuptools.dynamic.{field}` is ignored."

_DETAILS = """
The following seems to be defined in `tool.setuptools.dynamic`:

`{field}`

According to the spec, setuptools cannot use this value unless `{field}` is
listed as `dynamic` in the `[project]` table.

To prevent this problem, you can list `{field}` under `project.dynamic` or
remove the corresponding `tool.setuptools.dynamic.{field}` configuration.
"""
18 changes: 18 additions & 0 deletions setuptools/tests/config/test_pyprojecttoml.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re
import warnings
from configparser import ConfigParser
from inspect import cleandoc

Expand Down Expand Up @@ -210,6 +211,23 @@ def test_scripts_not_listed_in_dynamic(self, tmp_path, missing_dynamic):
with pytest.raises(OptionError, match=re.compile(msg, re.DOTALL)):
expand_configuration(self.pyproject(dynamic), tmp_path)

def test_entry_points_file_used_for_scripts_without_warning(self, tmp_path):
entry_points = ConfigParser()
entry_points.read_dict({"console_scripts": {"a": "mod.a:func"}})
with open(tmp_path / "entry-points.txt", "w", encoding="utf-8") as f:
entry_points.write(f)

with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
expanded = expand_configuration(self.pyproject(["scripts"]), tmp_path)

assert expanded["project"]["scripts"]["a"] == "mod.a:func"
assert not [
warning
for warning in caught
if "tool.setuptools.dynamic.entry-points" in str(warning.message)
]


class TestClassifiers:
def test_dynamic(self, tmp_path):
Expand Down
25 changes: 25 additions & 0 deletions setuptools/tests/config/test_pyprojecttoml_dynamic_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,31 @@ def test_dynamic_dependencies(tmp_path):
assert dist.install_requires == ["six"]


def test_warns_when_dynamic_dependencies_not_listed(tmp_path):
files = {
"requirements.txt": "six\n",
"pyproject.toml": cleandoc(
"""
[project]
name = "myproj"
version = "1.0"

[tool.setuptools.dynamic.dependencies]
file = ["requirements.txt"]
"""
),
}
path.build(files, prefix=tmp_path)
dist = Distribution()
with pytest.warns(
SetuptoolsWarning,
match=r"(?s)tool\.setuptools\.dynamic\.dependencies.*project\.dynamic",
):
dist = apply_configuration(dist, tmp_path / "pyproject.toml")

assert not dist.install_requires


def test_dynamic_optional_dependencies(tmp_path):
files = {
"requirements-docs.txt": "sphinx\n # comment\n",
Expand Down