diff --git a/newsfragments/4696.bugfix.rst b/newsfragments/4696.bugfix.rst new file mode 100644 index 0000000000..77ebf87c48 --- /dev/null +++ b/newsfragments/4696.bugfix.rst @@ -0,0 +1,4 @@ +Fix clashes for ``optional-dependencies`` in ``pyproject.toml`` and +``extra_requires`` in ``setup.cfg/setup.py``. +As per PEP 621, ``optional-dependencies`` has to be honoured and dynamic +behaviour is not allowed. diff --git a/setuptools/config/_apply_pyprojecttoml.py b/setuptools/config/_apply_pyprojecttoml.py index 23179f3548..16fe753b58 100644 --- a/setuptools/config/_apply_pyprojecttoml.py +++ b/setuptools/config/_apply_pyprojecttoml.py @@ -217,8 +217,10 @@ def _dependencies(dist: Distribution, val: list, _root_dir): def _optional_dependencies(dist: Distribution, val: dict, _root_dir): - existing = getattr(dist, "extras_require", None) or {} - dist.extras_require = {**existing, **val} + if getattr(dist, "extras_require", None): + msg = "`extras_require` overwritten in `pyproject.toml` (optional-dependencies)" + SetuptoolsWarning.emit(msg) + dist.extras_require = val def _ext_modules(dist: Distribution, val: list[dict]) -> list[Extension]: diff --git a/setuptools/tests/config/test_pyprojecttoml_dynamic_deps.py b/setuptools/tests/config/test_pyprojecttoml_dynamic_deps.py index 37e5234a45..e42f28ffaa 100644 --- a/setuptools/tests/config/test_pyprojecttoml_dynamic_deps.py +++ b/setuptools/tests/config/test_pyprojecttoml_dynamic_deps.py @@ -5,6 +5,7 @@ from setuptools.config.pyprojecttoml import apply_configuration from setuptools.dist import Distribution +from setuptools.warnings import SetuptoolsWarning def test_dynamic_dependencies(tmp_path): @@ -77,23 +78,32 @@ def test_mixed_dynamic_optional_dependencies(tmp_path): [tool.setuptools.dynamic.optional-dependencies.images] file = ["requirements-images.txt"] - - [build-system] - requires = ["setuptools", "wheel"] - build-backend = "setuptools.build_meta" """ ), } path.build(files, prefix=tmp_path) - - # Test that the mix-and-match doesn't currently validate. pyproject = tmp_path / "pyproject.toml" with pytest.raises(ValueError, match="project.optional-dependencies"): apply_configuration(Distribution(), pyproject) - # Explicitly disable the validation and try again, to see that the mix-and-match - # result would be correct. - dist = Distribution() - dist = apply_configuration(dist, pyproject, ignore_option_errors=True) - assert dist.extras_require == {"docs": ["sphinx"], "images": ["pillow~=42.0"]} + +def test_mixed_extras_require_optional_dependencies(tmp_path): + files = { + "pyproject.toml": cleandoc( + """ + [project] + name = "myproj" + version = "1.0" + optional-dependencies.docs = ["sphinx"] + """ + ), + } + + path.build(files, prefix=tmp_path) + pyproject = tmp_path / "pyproject.toml" + + with pytest.warns(SetuptoolsWarning, match=".extras_require. overwritten"): + dist = Distribution({"extras_require": {"hello": ["world"]}}) + dist = apply_configuration(dist, pyproject) + assert dist.extras_require == {"docs": ["sphinx"]}