Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
test_pyprojecttoml.py422 linesDownload Raw Back to config
1import re2from configparser import ConfigParser3from inspect import cleandoc4 5import jaraco.path6import pytest7import tomli_w8from path import Path9 10import setuptools  # noqa: F401 # force distutils.core to be patched11from setuptools.config.pyprojecttoml import (12    _ToolsTypoInMetadata,13    apply_configuration,14    expand_configuration,15    read_configuration,16    validate,17)18from setuptools.dist import Distribution19from setuptools.errors import OptionError20 21import distutils.core22 23EXAMPLE = """24[project]25name = "myproj"26keywords = ["some", "key", "words"]27dynamic = ["version", "readme"]28requires-python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"29dependencies = [30    'importlib-metadata>=0.12;python_version<"3.8"',31    'importlib-resources>=1.0;python_version<"3.7"',32    'pathlib2>=2.3.3,<3;python_version < "3.4" and sys.platform != "win32"',33]34 35[project.optional-dependencies]36docs = [37    "sphinx>=3",38    "sphinx-argparse>=0.2.5",39    "sphinx-rtd-theme>=0.4.3",40]41testing = [42    "pytest>=1",43    "coverage>=3,<5",44]45 46[project.scripts]47exec = "pkg.__main__:exec"48 49[build-system]50requires = ["setuptools", "wheel"]51build-backend = "setuptools.build_meta"52 53[tool.setuptools]54package-dir = {"" = "src"}55zip-safe = true56platforms = ["any"]57 58[tool.setuptools.packages.find]59where = ["src"]60 61[tool.setuptools.cmdclass]62sdist = "pkg.mod.CustomSdist"63 64[tool.setuptools.dynamic.version]65attr = "pkg.__version__.VERSION"66 67[tool.setuptools.dynamic.readme]68file = ["README.md"]69content-type = "text/markdown"70 71[tool.setuptools.package-data]72"*" = ["*.txt"]73 74[tool.setuptools.data-files]75"data" = ["_files/*.txt"]76 77[tool.distutils.sdist]78formats = "gztar"79 80[tool.distutils.bdist_wheel]81universal = true82"""83 84 85def create_example(path, pkg_root):86    files = {87        "pyproject.toml": EXAMPLE,88        "README.md": "hello world",89        "_files": {90            "file.txt": "",91        },92    }93    packages = {94        "pkg": {95            "__init__.py": "",96            "mod.py": "class CustomSdist: pass",97            "__version__.py": "VERSION = (3, 10)",98            "__main__.py": "def exec(): print('hello')",99        },100    }101 102    assert pkg_root  # Meta-test: cannot be empty string.103 104    if pkg_root == ".":105        files = {**files, **packages}106        # skip other files: flat-layout will raise error for multi-package dist107    else:108        # Use this opportunity to ensure namespaces are discovered109        files[pkg_root] = {**packages, "other": {"nested": {"__init__.py": ""}}}110 111    jaraco.path.build(files, prefix=path)112 113 114def verify_example(config, path, pkg_root):115    pyproject = path / "pyproject.toml"116    pyproject.write_text(tomli_w.dumps(config), encoding="utf-8")117    expanded = expand_configuration(config, path)118    expanded_project = expanded["project"]119    assert read_configuration(pyproject, expand=True) == expanded120    assert expanded_project["version"] == "3.10"121    assert expanded_project["readme"]["text"] == "hello world"122    assert "packages" in expanded["tool"]["setuptools"]123    if pkg_root == ".":124        # Auto-discovery will raise error for multi-package dist125        assert set(expanded["tool"]["setuptools"]["packages"]) == {"pkg"}126    else:127        assert set(expanded["tool"]["setuptools"]["packages"]) == {128            "pkg",129            "other",130            "other.nested",131        }132    assert expanded["tool"]["setuptools"]["include-package-data"] is True133    assert "" in expanded["tool"]["setuptools"]["package-data"]134    assert "*" not in expanded["tool"]["setuptools"]["package-data"]135    assert expanded["tool"]["setuptools"]["data-files"] == [136        ("data", ["_files/file.txt"])137    ]138 139 140def test_read_configuration(tmp_path):141    create_example(tmp_path, "src")142    pyproject = tmp_path / "pyproject.toml"143 144    config = read_configuration(pyproject, expand=False)145    assert config["project"].get("version") is None146    assert config["project"].get("readme") is None147 148    verify_example(config, tmp_path, "src")149 150 151@pytest.mark.parametrize(152    ("pkg_root", "opts"),153    [154        (".", {}),155        ("src", {}),156        ("lib", {"packages": {"find": {"where": ["lib"]}}}),157    ],158)159def test_discovered_package_dir_with_attr_directive_in_config(tmp_path, pkg_root, opts):160    create_example(tmp_path, pkg_root)161 162    pyproject = tmp_path / "pyproject.toml"163 164    config = read_configuration(pyproject, expand=False)165    assert config["project"].get("version") is None166    assert config["project"].get("readme") is None167    config["tool"]["setuptools"].pop("packages", None)168    config["tool"]["setuptools"].pop("package-dir", None)169 170    config["tool"]["setuptools"].update(opts)171    verify_example(config, tmp_path, pkg_root)172 173 174ENTRY_POINTS = {175    "console_scripts": {"a": "mod.a:func"},176    "gui_scripts": {"b": "mod.b:func"},177    "other": {"c": "mod.c:func [extra]"},178}179 180 181class TestEntryPoints:182    def write_entry_points(self, tmp_path):183        entry_points = ConfigParser()184        entry_points.read_dict(ENTRY_POINTS)185        with open(tmp_path / "entry-points.txt", "w", encoding="utf-8") as f:186            entry_points.write(f)187 188    def pyproject(self, dynamic=None):189        project = {"dynamic": dynamic or ["scripts", "gui-scripts", "entry-points"]}190        tool = {"dynamic": {"entry-points": {"file": "entry-points.txt"}}}191        return {"project": project, "tool": {"setuptools": tool}}192 193    def test_all_listed_in_dynamic(self, tmp_path):194        self.write_entry_points(tmp_path)195        expanded = expand_configuration(self.pyproject(), tmp_path)196        expanded_project = expanded["project"]197        assert len(expanded_project["scripts"]) == 1198        assert expanded_project["scripts"]["a"] == "mod.a:func"199        assert len(expanded_project["gui-scripts"]) == 1200        assert expanded_project["gui-scripts"]["b"] == "mod.b:func"201        assert len(expanded_project["entry-points"]) == 1202        assert expanded_project["entry-points"]["other"]["c"] == "mod.c:func [extra]"203 204    @pytest.mark.parametrize("missing_dynamic", ("scripts", "gui-scripts"))205    def test_scripts_not_listed_in_dynamic(self, tmp_path, missing_dynamic):206        self.write_entry_points(tmp_path)207        dynamic = {"scripts", "gui-scripts", "entry-points"} - {missing_dynamic}208 209        msg = f"defined outside of `pyproject.toml`:.*{missing_dynamic}"210        with pytest.raises(OptionError, match=re.compile(msg, re.DOTALL)):211            expand_configuration(self.pyproject(dynamic), tmp_path)212 213 214class TestClassifiers:215    def test_dynamic(self, tmp_path):216        # Let's create a project example that has dynamic classifiers217        # coming from a txt file.218        create_example(tmp_path, "src")219        classifiers = cleandoc(220            """221            Framework :: Flask222            Programming Language :: Haskell223            """224        )225        (tmp_path / "classifiers.txt").write_text(classifiers, encoding="utf-8")226 227        pyproject = tmp_path / "pyproject.toml"228        config = read_configuration(pyproject, expand=False)229        dynamic = config["project"]["dynamic"]230        config["project"]["dynamic"] = list({*dynamic, "classifiers"})231        dynamic_config = config["tool"]["setuptools"]["dynamic"]232        dynamic_config["classifiers"] = {"file": "classifiers.txt"}233 234        # When the configuration is expanded,235        # each line of the file should be an different classifier.236        validate(config, pyproject)237        expanded = expand_configuration(config, tmp_path)238 239        assert set(expanded["project"]["classifiers"]) == {240            "Framework :: Flask",241            "Programming Language :: Haskell",242        }243 244    def test_dynamic_without_config(self, tmp_path):245        config = """246        [project]247        name = "myproj"248        version = '42'249        dynamic = ["classifiers"]250        """251 252        pyproject = tmp_path / "pyproject.toml"253        pyproject.write_text(cleandoc(config), encoding="utf-8")254        with pytest.raises(OptionError, match="No configuration .* .classifiers."):255            read_configuration(pyproject)256 257    def test_dynamic_readme_from_setup_script_args(self, tmp_path):258        config = """259        [project]260        name = "myproj"261        version = '42'262        dynamic = ["readme"]263        """264        pyproject = tmp_path / "pyproject.toml"265        pyproject.write_text(cleandoc(config), encoding="utf-8")266        dist = Distribution(attrs={"long_description": "42"})267        # No error should occur because of missing `readme`268        dist = apply_configuration(dist, pyproject)269        assert dist.metadata.long_description == "42"270 271    def test_dynamic_without_file(self, tmp_path):272        config = """273        [project]274        name = "myproj"275        version = '42'276        dynamic = ["classifiers"]277 278        [tool.setuptools.dynamic]279        classifiers = {file = ["classifiers.txt"]}280        """281 282        pyproject = tmp_path / "pyproject.toml"283        pyproject.write_text(cleandoc(config), encoding="utf-8")284        with pytest.warns(UserWarning, match="File .*classifiers.txt. cannot be found"):285            expanded = read_configuration(pyproject)286        assert "classifiers" not in expanded["project"]287 288 289class TestImportNames:290    EXAMPLES = [291        'import-names = ["hello", "world"]',292        'import-namespaces = ["hello", "world"]',293        'dynamic = ["import-names"]',294        'dynamic = ["import-namespaces"]',295    ]296 297    @pytest.mark.parametrize("example", EXAMPLES)298    def test_not_implemented(self, monkeypatch, tmp_path, example):299        monkeypatch.chdir(tmp_path)300        pyproject = Path("pyproject.toml")301        toml_config = f"""302        [project]303        name = 'proj'304        version = '42'305        {example}306        """307        pyproject.write_text(cleandoc(toml_config), encoding="utf-8")308        with pytest.raises(NotImplementedError, match='import-names'):309            apply_configuration(Distribution({}), pyproject)310 311 312@pytest.mark.parametrize(313    "example",314    (315        """316        [project]317        name = "myproj"318        version = "1.2"319 320        [my-tool.that-disrespect.pep518]321        value = 42322        """,323    ),324)325def test_ignore_unrelated_config(tmp_path, example):326    pyproject = tmp_path / "pyproject.toml"327    pyproject.write_text(cleandoc(example), encoding="utf-8")328 329    # Make sure no error is raised due to 3rd party configs in pyproject.toml330    assert read_configuration(pyproject) is not None331 332 333@pytest.mark.parametrize(334    ("example", "error_msg"),335    [336        (337            """338            [project]339            name = "myproj"340            version = "1.2"341            requires = ['pywin32; platform_system=="Windows"' ]342            """,343            "configuration error: .project. must not contain ..requires.. properties",344        ),345    ],346)347def test_invalid_example(tmp_path, example, error_msg):348    pyproject = tmp_path / "pyproject.toml"349    pyproject.write_text(cleandoc(example), encoding="utf-8")350 351    pattern = re.compile(352        f"invalid pyproject.toml.*{error_msg}.*", re.MULTILINE | re.DOTALL353    )354    with pytest.raises(ValueError, match=pattern):355        read_configuration(pyproject)356 357 358@pytest.mark.parametrize("config", ("", "[tool.something]\nvalue = 42"))359def test_empty(tmp_path, config):360    pyproject = tmp_path / "pyproject.toml"361    pyproject.write_text(config, encoding="utf-8")362 363    # Make sure no error is raised364    assert read_configuration(pyproject) == {}365 366 367@pytest.mark.parametrize("config", ("[project]\nname = 'myproj'\nversion='42'\n",))368def test_include_package_data_by_default(tmp_path, config):369    """Builds with ``pyproject.toml`` should consider ``include-package-data=True`` as370    default.371    """372    pyproject = tmp_path / "pyproject.toml"373    pyproject.write_text(config, encoding="utf-8")374 375    config = read_configuration(pyproject)376    assert config["tool"]["setuptools"]["include-package-data"] is True377 378 379def test_include_package_data_in_setuppy(tmp_path):380    """Builds with ``pyproject.toml`` should consider ``include_package_data`` set in381    ``setup.py``.382 383    See https://github.com/pypa/setuptools/issues/3197#issuecomment-1079023889384    """385    files = {386        "pyproject.toml": "[project]\nname = 'myproj'\nversion='42'\n",387        "setup.py": "__import__('setuptools').setup(include_package_data=False)",388    }389    jaraco.path.build(files, prefix=tmp_path)390 391    with Path(tmp_path):392        dist = distutils.core.run_setup("setup.py", {}, stop_after="config")393 394    assert dist.get_name() == "myproj"395    assert dist.get_version() == "42"396    assert dist.include_package_data is False397 398 399def test_warn_tools_typo(tmp_path):400    """Test that the common ``tools.setuptools`` typo in ``pyproject.toml`` issues a warning401 402    See https://github.com/pypa/setuptools/issues/4150403    """404    config = """405    [build-system]406    requires = ["setuptools"]407    build-backend = "setuptools.build_meta"408 409    [project]410    name = "myproj"411    version = '42'412 413    [tools.setuptools]414    packages = ["package"]415    """416 417    pyproject = tmp_path / "pyproject.toml"418    pyproject.write_text(cleandoc(config), encoding="utf-8")419 420    with pytest.warns(_ToolsTypoInMetadata):421        read_configuration(pyproject)422 
codekingpro/portable-devtools · Team Ai