nanochatt / venv /lib /python3.10 /site-packages /setuptools /tests /config /test_apply_pyprojecttoml.py
| """Make sure that applying the configuration from pyproject.toml is equivalent to | |
| applying a similar configuration from setup.cfg | |
| To run these tests offline, please have a look on ``./downloads/preload.py`` | |
| """ | |
| from __future__ import annotations | |
| import io | |
| import re | |
| import tarfile | |
| from inspect import cleandoc | |
| from pathlib import Path | |
| from unittest.mock import Mock | |
| import pytest | |
| from ini2toml.api import LiteTranslator | |
| from packaging.metadata import Metadata | |
| import setuptools # noqa: F401 # ensure monkey patch to metadata | |
| from setuptools._static import is_static | |
| from setuptools.command.egg_info import write_requirements | |
| from setuptools.config import expand, pyprojecttoml, setupcfg | |
| from setuptools.config._apply_pyprojecttoml import _MissingDynamic, _some_attrgetter | |
| from setuptools.dist import Distribution | |
| from setuptools.errors import InvalidConfigError, RemovedConfigError | |
| from setuptools.warnings import InformationOnly, SetuptoolsDeprecationWarning | |
| from .downloads import retrieve_file, urls_from_file | |
| HERE = Path(__file__).parent | |
| EXAMPLES_FILE = "setupcfg_examples.txt" | |
| def makedist(path, **attrs): | |
| return Distribution({"src_root": path, **attrs}) | |
| def _mock_expand_patterns(patterns, *_, **__): | |
| """ | |
| Allow comparing the given patterns for 2 dist objects. | |
| We need to strip special chars to avoid errors when validating. | |
| """ | |
| return [re.sub("[^a-z0-9]+", "", p, flags=re.I) or "empty" for p in patterns] | |
| def test_apply_pyproject_equivalent_to_setupcfg(url, monkeypatch, tmp_path): | |
| monkeypatch.setattr(expand, "read_attr", Mock(return_value="0.0.1")) | |
| monkeypatch.setattr( | |
| Distribution, "_expand_patterns", Mock(side_effect=_mock_expand_patterns) | |
| ) | |
| setupcfg_example = retrieve_file(url) | |
| pyproject_example = Path(tmp_path, "pyproject.toml") | |
| setupcfg_text = setupcfg_example.read_text(encoding="utf-8") | |
| toml_config = LiteTranslator().translate(setupcfg_text, "setup.cfg") | |
| pyproject_example.write_text(toml_config, encoding="utf-8") | |
| dist_toml = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject_example) | |
| dist_cfg = setupcfg.apply_configuration(makedist(tmp_path), setupcfg_example) | |
| pkg_info_toml = core_metadata(dist_toml) | |
| pkg_info_cfg = core_metadata(dist_cfg) | |
| assert pkg_info_toml == pkg_info_cfg | |
| if any(getattr(d, "license_files", None) for d in (dist_toml, dist_cfg)): | |
| assert set(dist_toml.license_files) == set(dist_cfg.license_files) | |
| if any(getattr(d, "entry_points", None) for d in (dist_toml, dist_cfg)): | |
| print(dist_cfg.entry_points) | |
| ep_toml = { | |
| (k, *sorted(i.replace(" ", "") for i in v)) | |
| for k, v in dist_toml.entry_points.items() | |
| } | |
| ep_cfg = { | |
| (k, *sorted(i.replace(" ", "") for i in v)) | |
| for k, v in dist_cfg.entry_points.items() | |
| } | |
| assert ep_toml == ep_cfg | |
| if any(getattr(d, "package_data", None) for d in (dist_toml, dist_cfg)): | |
| pkg_data_toml = {(k, *sorted(v)) for k, v in dist_toml.package_data.items()} | |
| pkg_data_cfg = {(k, *sorted(v)) for k, v in dist_cfg.package_data.items()} | |
| assert pkg_data_toml == pkg_data_cfg | |
| if any(getattr(d, "data_files", None) for d in (dist_toml, dist_cfg)): | |
| data_files_toml = {(k, *sorted(v)) for k, v in dist_toml.data_files} | |
| data_files_cfg = {(k, *sorted(v)) for k, v in dist_cfg.data_files} | |
| assert data_files_toml == data_files_cfg | |
| assert set(dist_toml.install_requires) == set(dist_cfg.install_requires) | |
| if any(getattr(d, "extras_require", None) for d in (dist_toml, dist_cfg)): | |
| extra_req_toml = {(k, *sorted(v)) for k, v in dist_toml.extras_require.items()} | |
| extra_req_cfg = {(k, *sorted(v)) for k, v in dist_cfg.extras_require.items()} | |
| assert extra_req_toml == extra_req_cfg | |
| PEP621_EXAMPLE = """\ | |
| [project] | |
| name = "spam" | |
| version = "2020.0.0" | |
| description = "Lovely Spam! Wonderful Spam!" | |
| readme = "README.rst" | |
| requires-python = ">=3.8" | |
| license-files = ["LICENSE.txt"] # Updated to be PEP 639 compliant | |
| keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"] | |
| authors = [ | |
| {email = "hi@pradyunsg.me"}, | |
| {name = "Tzu-Ping Chung"} | |
| ] | |
| maintainers = [ | |
| {name = "Brett Cannon", email = "brett@python.org"}, | |
| {name = "John X. Ãørçeč", email = "john@utf8.org"}, | |
| {name = "Γαμα קּ 東", email = "gama@utf8.org"}, | |
| ] | |
| classifiers = [ | |
| "Development Status :: 4 - Beta", | |
| "Programming Language :: Python" | |
| ] | |
| dependencies = [ | |
| "httpx", | |
| "gidgethub[httpx]>4.0.0", | |
| "django>2.1; os_name != 'nt'", | |
| "django>2.0; os_name == 'nt'" | |
| ] | |
| [project.optional-dependencies] | |
| test = [ | |
| "pytest < 5.0.0", | |
| "pytest-cov[all]" | |
| ] | |
| [project.urls] | |
| homepage = "http://example.com" | |
| documentation = "http://readthedocs.org" | |
| repository = "http://github.com" | |
| changelog = "http://github.com/me/spam/blob/master/CHANGELOG.md" | |
| [project.scripts] | |
| spam-cli = "spam:main_cli" | |
| [project.gui-scripts] | |
| spam-gui = "spam:main_gui" | |
| [project.entry-points."spam.magical"] | |
| tomatoes = "spam:main_tomatoes" | |
| """ | |
| PEP621_INTERNATIONAL_EMAIL_EXAMPLE = """\ | |
| [project] | |
| name = "spam" | |
| version = "2020.0.0" | |
| authors = [ | |
| {email = "hi@pradyunsg.me"}, | |
| {name = "Tzu-Ping Chung"} | |
| ] | |
| maintainers = [ | |
| {name = "Степан Бандера", email = "криївка@оун-упа.укр"}, | |
| ] | |
| """ | |
| PEP621_EXAMPLE_SCRIPT = """ | |
| def main_cli(): pass | |
| def main_gui(): pass | |
| def main_tomatoes(): pass | |
| """ | |
| PEP639_LICENSE_TEXT = """\ | |
| [project] | |
| name = "spam" | |
| version = "2020.0.0" | |
| authors = [ | |
| {email = "hi@pradyunsg.me"}, | |
| {name = "Tzu-Ping Chung"} | |
| ] | |
| license = {text = "MIT"} | |
| """ | |
| PEP639_LICENSE_EXPRESSION = """\ | |
| [project] | |
| name = "spam" | |
| version = "2020.0.0" | |
| authors = [ | |
| {email = "hi@pradyunsg.me"}, | |
| {name = "Tzu-Ping Chung"} | |
| ] | |
| license = "mit or apache-2.0" # should be normalized in metadata | |
| classifiers = [ | |
| "Development Status :: 5 - Production/Stable", | |
| "Programming Language :: Python", | |
| ] | |
| """ | |
| def _pep621_example_project( | |
| tmp_path, | |
| readme="README.rst", | |
| pyproject_text=PEP621_EXAMPLE, | |
| ): | |
| pyproject = tmp_path / "pyproject.toml" | |
| text = pyproject_text | |
| replacements = {'readme = "README.rst"': f'readme = "{readme}"'} | |
| for orig, subst in replacements.items(): | |
| text = text.replace(orig, subst) | |
| pyproject.write_text(text, encoding="utf-8") | |
| (tmp_path / readme).write_text("hello world", encoding="utf-8") | |
| (tmp_path / "LICENSE.txt").write_text("--- LICENSE stub ---", encoding="utf-8") | |
| (tmp_path / "spam.py").write_text(PEP621_EXAMPLE_SCRIPT, encoding="utf-8") | |
| return pyproject | |
| def test_pep621_example(tmp_path): | |
| """Make sure the example in PEP 621 works""" | |
| pyproject = _pep621_example_project(tmp_path) | |
| dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject) | |
| assert set(dist.metadata.license_files) == {"LICENSE.txt"} | |
| def test_readme_content_type(tmp_path, readme, ctype): | |
| pyproject = _pep621_example_project(tmp_path, readme) | |
| dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject) | |
| assert dist.metadata.long_description_content_type == ctype | |
| def test_undefined_content_type(tmp_path): | |
| pyproject = _pep621_example_project(tmp_path, "README.tex") | |
| with pytest.raises(ValueError, match="Undefined content type for README.tex"): | |
| pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject) | |
| def test_no_explicit_content_type_for_missing_extension(tmp_path): | |
| pyproject = _pep621_example_project(tmp_path, "README") | |
| dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject) | |
| assert dist.metadata.long_description_content_type is None | |
| def test_utf8_maintainer_in_metadata( # issue-3663 | |
| expected_maintainers_meta_value, | |
| pyproject_text, | |
| tmp_path, | |
| ): | |
| pyproject = _pep621_example_project( | |
| tmp_path, | |
| "README", | |
| pyproject_text=pyproject_text, | |
| ) | |
| dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject) | |
| assert dist.metadata.maintainer_email == expected_maintainers_meta_value | |
| pkg_file = tmp_path / "PKG-FILE" | |
| with open(pkg_file, "w", encoding="utf-8") as fh: | |
| dist.metadata.write_pkg_file(fh) | |
| content = pkg_file.read_text(encoding="utf-8") | |
| assert f"Maintainer-email: {expected_maintainers_meta_value}" in content | |
| def test_license_in_metadata( | |
| license, | |
| license_expression, | |
| content_str, | |
| not_content_str, | |
| pyproject_text, | |
| tmp_path, | |
| ): | |
| pyproject = _pep621_example_project( | |
| tmp_path, | |
| "README", | |
| pyproject_text=pyproject_text, | |
| ) | |
| dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject) | |
| assert dist.metadata.license == license | |
| assert dist.metadata.license_expression == license_expression | |
| pkg_file = tmp_path / "PKG-FILE" | |
| with open(pkg_file, "w", encoding="utf-8") as fh: | |
| dist.metadata.write_pkg_file(fh) | |
| content = pkg_file.read_text(encoding="utf-8") | |
| assert "Metadata-Version: 2.4" in content | |
| assert content_str in content | |
| assert not_content_str not in content | |
| def test_license_classifier_with_license_expression(tmp_path): | |
| text = PEP639_LICENSE_EXPRESSION.rsplit("\n", 2)[0] | |
| pyproject = _pep621_example_project( | |
| tmp_path, | |
| "README", | |
| f"{text}\n \"License :: OSI Approved :: MIT License\"\n]", | |
| ) | |
| msg = "License classifiers have been superseded by license expressions" | |
| with pytest.raises(InvalidConfigError, match=msg) as exc: | |
| pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject) | |
| assert "License :: OSI Approved :: MIT License" in str(exc.value) | |
| def test_license_classifier_without_license_expression(tmp_path): | |
| text = """\ | |
| [project] | |
| name = "spam" | |
| version = "2020.0.0" | |
| license = {text = "mit or apache-2.0"} | |
| classifiers = ["License :: OSI Approved :: MIT License"] | |
| """ | |
| pyproject = _pep621_example_project(tmp_path, "README", text) | |
| msg1 = "License classifiers are deprecated(?:.|\n)*MIT License" | |
| msg2 = ".project.license. as a TOML table is deprecated" | |
| with ( | |
| pytest.warns(SetuptoolsDeprecationWarning, match=msg1), | |
| pytest.warns(SetuptoolsDeprecationWarning, match=msg2), | |
| ): | |
| dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject) | |
| # Check license classifier is still included | |
| assert dist.metadata.get_classifiers() == ["License :: OSI Approved :: MIT License"] | |
| class TestLicenseFiles: | |
| def base_pyproject( | |
| self, | |
| tmp_path, | |
| additional_text="", | |
| license_toml='license = {file = "LICENSE.txt"}\n', | |
| ): | |
| text = PEP639_LICENSE_EXPRESSION | |
| # Sanity-check | |
| assert 'license = "mit or apache-2.0"' in text | |
| assert 'license-files' not in text | |
| assert "[tool.setuptools]" not in text | |
| text = re.sub( | |
| r"(license = .*)\n", | |
| license_toml, | |
| text, | |
| count=1, | |
| ) | |
| assert license_toml in text # sanity check | |
| text = f"{text}\n{additional_text}\n" | |
| pyproject = _pep621_example_project(tmp_path, "README", pyproject_text=text) | |
| return pyproject | |
| def base_pyproject_license_pep639(self, tmp_path, additional_text=""): | |
| return self.base_pyproject( | |
| tmp_path, | |
| additional_text=additional_text, | |
| license_toml='license = "licenseref-Proprietary"' | |
| '\nlicense-files = ["_FILE*"]\n', | |
| ) | |
| def test_both_license_and_license_files_defined(self, tmp_path): | |
| setuptools_config = '[tool.setuptools]\nlicense-files = ["_FILE*"]' | |
| pyproject = self.base_pyproject(tmp_path, setuptools_config) | |
| (tmp_path / "_FILE.txt").touch() | |
| (tmp_path / "_FILE.rst").touch() | |
| # Would normally match the `license_files` patterns, but we want to exclude it | |
| # by being explicit. On the other hand, contents should be added to `license` | |
| license = tmp_path / "LICENSE.txt" | |
| license.write_text("LicenseRef-Proprietary\n", encoding="utf-8") | |
| msg1 = "'tool.setuptools.license-files' is deprecated in favor of 'project.license-files'" | |
| msg2 = ".project.license. as a TOML table is deprecated" | |
| with ( | |
| pytest.warns(SetuptoolsDeprecationWarning, match=msg1), | |
| pytest.warns(SetuptoolsDeprecationWarning, match=msg2), | |
| ): | |
| dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject) | |
| assert set(dist.metadata.license_files) == {"_FILE.rst", "_FILE.txt"} | |
| assert dist.metadata.license == "LicenseRef-Proprietary\n" | |
| def test_both_license_and_license_files_defined_pep639(self, tmp_path): | |
| # Set license and license-files | |
| pyproject = self.base_pyproject_license_pep639(tmp_path) | |
| (tmp_path / "_FILE.txt").touch() | |
| (tmp_path / "_FILE.rst").touch() | |
| msg = "Normalizing.*LicenseRef" | |
| with pytest.warns(InformationOnly, match=msg): | |
| dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject) | |
| assert set(dist.metadata.license_files) == {"_FILE.rst", "_FILE.txt"} | |
| assert dist.metadata.license is None | |
| assert dist.metadata.license_expression == "LicenseRef-Proprietary" | |
| def test_license_files_defined_twice(self, tmp_path): | |
| # Set project.license-files and tools.setuptools.license-files | |
| setuptools_config = '[tool.setuptools]\nlicense-files = ["_FILE*"]' | |
| pyproject = self.base_pyproject_license_pep639(tmp_path, setuptools_config) | |
| msg = "'project.license-files' is defined already. Remove 'tool.setuptools.license-files'" | |
| with pytest.raises(InvalidConfigError, match=msg): | |
| pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject) | |
| def test_default_patterns(self, tmp_path): | |
| setuptools_config = '[tool.setuptools]\nzip-safe = false' | |
| # ^ used just to trigger section validation | |
| pyproject = self.base_pyproject(tmp_path, setuptools_config, license_toml="") | |
| license_files = "LICENCE-a.html COPYING-abc.txt AUTHORS-xyz NOTICE,def".split() | |
| for fname in license_files: | |
| (tmp_path / fname).write_text(f"{fname}\n", encoding="utf-8") | |
| dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject) | |
| assert (tmp_path / "LICENSE.txt").exists() # from base example | |
| assert set(dist.metadata.license_files) == {*license_files, "LICENSE.txt"} | |
| def test_missing_patterns(self, tmp_path): | |
| pyproject = self.base_pyproject_license_pep639(tmp_path) | |
| assert list(tmp_path.glob("_FILE*")) == [] # sanity check | |
| msg1 = "Cannot find any files for the given pattern.*" | |
| msg2 = "Normalizing 'licenseref-Proprietary' to 'LicenseRef-Proprietary'" | |
| with ( | |
| pytest.warns(SetuptoolsDeprecationWarning, match=msg1), | |
| pytest.warns(InformationOnly, match=msg2), | |
| ): | |
| pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject) | |
| def test_deprecated_file_expands_to_text(self, tmp_path): | |
| """Make sure the old example with ``license = {text = ...}`` works""" | |
| assert 'license-files = ["LICENSE.txt"]' in PEP621_EXAMPLE # sanity check | |
| text = PEP621_EXAMPLE.replace( | |
| 'license-files = ["LICENSE.txt"]', | |
| 'license = {file = "LICENSE.txt"}', | |
| ) | |
| pyproject = _pep621_example_project(tmp_path, pyproject_text=text) | |
| msg = ".project.license. as a TOML table is deprecated" | |
| with pytest.warns(SetuptoolsDeprecationWarning, match=msg): | |
| dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject) | |
| assert dist.metadata.license == "--- LICENSE stub ---" | |
| assert set(dist.metadata.license_files) == {"LICENSE.txt"} # auto-filled | |
| class TestPyModules: | |
| # https://github.com/pypa/setuptools/issues/4316 | |
| def dist(self, name): | |
| toml_config = f""" | |
| [project] | |
| name = "test" | |
| version = "42.0" | |
| [tool.setuptools] | |
| py-modules = [{name!r}] | |
| """ | |
| pyproject = Path("pyproject.toml") | |
| pyproject.write_text(cleandoc(toml_config), encoding="utf-8") | |
| return pyprojecttoml.apply_configuration(Distribution({}), pyproject) | |
| def test_valid_module_name(self, tmp_path, monkeypatch, module): | |
| monkeypatch.chdir(tmp_path) | |
| assert module in self.dist(module).py_modules | |
| def test_invalid_module_name(self, tmp_path, monkeypatch, module): | |
| monkeypatch.chdir(tmp_path) | |
| with pytest.raises(ValueError, match="py-modules"): | |
| self.dist(module).py_modules | |
| class TestExtModules: | |
| def test_pyproject_sets_attribute(self, tmp_path, monkeypatch): | |
| monkeypatch.chdir(tmp_path) | |
| pyproject = Path("pyproject.toml") | |
| toml_config = """ | |
| [project] | |
| name = "test" | |
| version = "42.0" | |
| [tool.setuptools] | |
| ext-modules = [ | |
| {name = "my.ext", sources = ["hello.c", "world.c"]} | |
| ] | |
| """ | |
| pyproject.write_text(cleandoc(toml_config), encoding="utf-8") | |
| with pytest.warns(pyprojecttoml._ExperimentalConfiguration): | |
| dist = pyprojecttoml.apply_configuration(Distribution({}), pyproject) | |
| assert len(dist.ext_modules) == 1 | |
| assert dist.ext_modules[0].name == "my.ext" | |
| assert set(dist.ext_modules[0].sources) == {"hello.c", "world.c"} | |
| class TestDeprecatedFields: | |
| def test_namespace_packages(self, tmp_path): | |
| pyproject = tmp_path / "pyproject.toml" | |
| config = """ | |
| [project] | |
| name = "myproj" | |
| version = "42" | |
| [tool.setuptools] | |
| namespace-packages = ["myproj.pkg"] | |
| """ | |
| pyproject.write_text(cleandoc(config), encoding="utf-8") | |
| with pytest.raises(RemovedConfigError, match="namespace-packages"): | |
| pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject) | |
| class TestPresetField: | |
| def pyproject(self, tmp_path, dynamic, extra_content=""): | |
| content = f"[project]\nname = 'proj'\ndynamic = {dynamic!r}\n" | |
| if "version" not in dynamic: | |
| content += "version = '42'\n" | |
| file = tmp_path / "pyproject.toml" | |
| file.write_text(content + extra_content, encoding="utf-8") | |
| return file | |
| def test_not_listed_in_dynamic(self, tmp_path, attr, field, value): | |
| """Setuptools cannot set a field if not listed in ``dynamic``""" | |
| pyproject = self.pyproject(tmp_path, []) | |
| dist = makedist(tmp_path, **{attr: value}) | |
| msg = re.compile(f"defined outside of `pyproject.toml`:.*{field}", re.S) | |
| with pytest.warns(_MissingDynamic, match=msg): | |
| dist = pyprojecttoml.apply_configuration(dist, pyproject) | |
| dist_value = _some_attrgetter(f"metadata.{attr}", attr)(dist) | |
| assert not dist_value | |
| def test_listed_in_dynamic(self, tmp_path, attr, field, value): | |
| pyproject = self.pyproject(tmp_path, [field]) | |
| dist = makedist(tmp_path, **{attr: value}) | |
| dist = pyprojecttoml.apply_configuration(dist, pyproject) | |
| dist_value = _some_attrgetter(f"metadata.{attr}", attr)(dist) | |
| assert dist_value == value | |
| def test_license_files_exempt_from_dynamic(self, monkeypatch, tmp_path): | |
| """ | |
| license-file is currently not considered in the context of dynamic. | |
| As per 2025-02-19, https://packaging.python.org/en/latest/specifications/pyproject-toml/#license-files | |
| allows setuptools to fill-in `license-files` the way it sees fit: | |
| > If the license-files key is not defined, tools can decide how to handle license files. | |
| > For example they can choose not to include any files or use their own | |
| > logic to discover the appropriate files in the distribution. | |
| Using license_files from setup.py to fill-in the value is in accordance | |
| with this rule. | |
| """ | |
| monkeypatch.chdir(tmp_path) | |
| pyproject = self.pyproject(tmp_path, []) | |
| dist = makedist(tmp_path, license_files=["LIC*"]) | |
| (tmp_path / "LIC1").write_text("42", encoding="utf-8") | |
| dist = pyprojecttoml.apply_configuration(dist, pyproject) | |
| assert dist.metadata.license_files == ["LIC1"] | |
| def test_warning_overwritten_dependencies(self, tmp_path): | |
| src = "[project]\nname='pkg'\nversion='0.1'\ndependencies=['click']\n" | |
| pyproject = tmp_path / "pyproject.toml" | |
| pyproject.write_text(src, encoding="utf-8") | |
| dist = makedist(tmp_path, install_requires=["wheel"]) | |
| with pytest.warns(match="`install_requires` overwritten"): | |
| dist = pyprojecttoml.apply_configuration(dist, pyproject) | |
| assert "wheel" not in dist.install_requires | |
| def test_optional_dependencies_dont_remove_env_markers(self, tmp_path): | |
| """ | |
| Internally setuptools converts dependencies with markers to "extras". | |
| If ``install_requires`` is given by ``setup.py``, we have to ensure that | |
| applying ``optional-dependencies`` does not overwrite the mandatory | |
| dependencies with markers (see #3204). | |
| """ | |
| # If setuptools replace its internal mechanism that uses `requires.txt` | |
| # this test has to be rewritten to adapt accordingly | |
| extra = "\n[project.optional-dependencies]\nfoo = ['bar>1']\n" | |
| pyproject = self.pyproject(tmp_path, ["dependencies"], extra) | |
| install_req = ['importlib-resources (>=3.0.0) ; python_version < "3.7"'] | |
| dist = makedist(tmp_path, install_requires=install_req) | |
| dist = pyprojecttoml.apply_configuration(dist, pyproject) | |
| assert "foo" in dist.extras_require | |
| egg_info = dist.get_command_obj("egg_info") | |
| write_requirements(egg_info, tmp_path, tmp_path / "requires.txt") | |
| reqs = (tmp_path / "requires.txt").read_text(encoding="utf-8") | |
| assert "importlib-resources" in reqs | |
| assert "bar" in reqs | |
| assert ':python_version < "3.7"' in reqs | |
| def test_scripts_dont_require_dynamic_entry_points(self, tmp_path, field, group): | |
| # Issue 3862 | |
| pyproject = self.pyproject(tmp_path, [field]) | |
| dist = makedist(tmp_path, entry_points={group: ["foobar=foobar:main"]}) | |
| dist = pyprojecttoml.apply_configuration(dist, pyproject) | |
| assert group in dist.entry_points | |
| class TestMeta: | |
| def test_example_file_in_sdist(self, setuptools_sdist): | |
| """Meta test to ensure tests can run from sdist""" | |
| with tarfile.open(setuptools_sdist) as tar: | |
| assert any(name.endswith(EXAMPLES_FILE) for name in tar.getnames()) | |
| class TestInteropCommandLineParsing: | |
| def test_version(self, tmp_path, monkeypatch, capsys): | |
| # See pypa/setuptools#4047 | |
| # This test can be removed once the CLI interface of setup.py is removed | |
| monkeypatch.chdir(tmp_path) | |
| toml_config = """ | |
| [project] | |
| name = "test" | |
| version = "42.0" | |
| """ | |
| pyproject = Path(tmp_path, "pyproject.toml") | |
| pyproject.write_text(cleandoc(toml_config), encoding="utf-8") | |
| opts = {"script_args": ["--version"]} | |
| dist = pyprojecttoml.apply_configuration(Distribution(opts), pyproject) | |
| dist.parse_command_line() # <-- there should be no exception here. | |
| captured = capsys.readouterr() | |
| assert "42.0" in captured.out | |
| class TestStaticConfig: | |
| def test_mark_static_fields(self, tmp_path, monkeypatch): | |
| monkeypatch.chdir(tmp_path) | |
| toml_config = """ | |
| [project] | |
| name = "test" | |
| version = "42.0" | |
| dependencies = ["hello"] | |
| keywords = ["world"] | |
| classifiers = ["private :: hello world"] | |
| [tool.setuptools] | |
| obsoletes = ["abcd"] | |
| provides = ["abcd"] | |
| platforms = ["abcd"] | |
| """ | |
| pyproject = Path(tmp_path, "pyproject.toml") | |
| pyproject.write_text(cleandoc(toml_config), encoding="utf-8") | |
| dist = pyprojecttoml.apply_configuration(Distribution({}), pyproject) | |
| assert is_static(dist.install_requires) | |
| assert is_static(dist.metadata.keywords) | |
| assert is_static(dist.metadata.classifiers) | |
| assert is_static(dist.metadata.obsoletes) | |
| assert is_static(dist.metadata.provides) | |
| assert is_static(dist.metadata.platforms) | |
| # --- Auxiliary Functions --- | |
| def core_metadata(dist) -> str: | |
| with io.StringIO() as buffer: | |
| dist.metadata.write_pkg_file(buffer) | |
| pkg_file_txt = buffer.getvalue() | |
| # Make sure core metadata is valid | |
| Metadata.from_email(pkg_file_txt, validate=True) # can raise exceptions | |
| skip_prefixes: tuple[str, ...] = () | |
| skip_lines = set() | |
| # ---- DIFF NORMALISATION ---- | |
| # PEP 621 is very particular about author/maintainer metadata conversion, so skip | |
| skip_prefixes += ("Author:", "Author-email:", "Maintainer:", "Maintainer-email:") | |
| # May be redundant with Home-page | |
| skip_prefixes += ("Project-URL: Homepage,", "Home-page:") | |
| # May be missing in original (relying on default) but backfilled in the TOML | |
| skip_prefixes += ("Description-Content-Type:",) | |
| # Remove empty lines | |
| skip_lines.add("") | |
| result = [] | |
| for line in pkg_file_txt.splitlines(): | |
| if line.startswith(skip_prefixes) or line in skip_lines: | |
| continue | |
| result.append(line + "\n") | |
| return "".join(result) | |