Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
test_editable_install.py1262 linesDownload Raw Back to tests
1from __future__ import annotations2 3import os4import platform5import stat6import subprocess7import sys8from copy import deepcopy9from importlib import import_module10from importlib.machinery import EXTENSION_SUFFIXES11from pathlib import Path12from textwrap import dedent13from typing import Any14from unittest.mock import Mock15from uuid import uuid416 17import jaraco.envs18import jaraco.path19import pytest20from path import Path as _Path21 22from setuptools._importlib import resources as importlib_resources23from setuptools.command.editable_wheel import (24    _encode_pth,25    _find_namespaces,26    _find_package_roots,27    _find_virtual_namespaces,28    _finder_template,29    _LinkTree,30    _TopLevelFinder,31    editable_wheel,32)33from setuptools.dist import Distribution34from setuptools.extension import Extension35from setuptools.warnings import SetuptoolsDeprecationWarning36 37from . import contexts, namespaces38 39from distutils.core import run_setup40 41 42@pytest.fixture(params=["strict", "lenient"])43def editable_opts(request):44    if request.param == "strict":45        return ["--config-settings", "editable-mode=strict"]46    return []47 48 49EXAMPLE = {50    'pyproject.toml': dedent(51        """\52        [build-system]53        requires = ["setuptools"]54        build-backend = "setuptools.build_meta"55 56        [project]57        name = "mypkg"58        version = "3.14159"59        license = {text = "MIT"}60        description = "This is a Python package"61        dynamic = ["readme"]62        classifiers = [63            "Development Status :: 5 - Production/Stable",64            "Intended Audience :: Developers"65        ]66        urls = {Homepage = "https://github.com"}67 68        [tool.setuptools]69        package-dir = {"" = "src"}70        packages = {find = {where = ["src"]}}71        license-files = ["LICENSE*"]72 73        [tool.setuptools.dynamic]74        readme = {file = "README.rst"}75 76        [tool.distutils.egg_info]77        tag-build = ".post0"78        """79    ),80    "MANIFEST.in": dedent(81        """\82        global-include *.py *.txt83        global-exclude *.py[cod]84        prune dist85        prune build86        """87    ).strip(),88    "README.rst": "This is a ``README``",89    "LICENSE.txt": "---- placeholder MIT license ----",90    "src": {91        "mypkg": {92            "__init__.py": dedent(93                """\94                import sys95                from importlib.metadata import PackageNotFoundError, version96 97                try:98                    __version__ = version(__name__)99                except PackageNotFoundError:100                    __version__ = "unknown"101                """102            ),103            "__main__.py": dedent(104                """\105                from importlib.resources import read_text106                from . import __version__, __name__ as parent107                from .mod import x108 109                data = read_text(parent, "data.txt")110                print(__version__, data, x)111                """112            ),113            "mod.py": "x = ''",114            "data.txt": "Hello World",115        }116    },117}118 119 120SETUP_SCRIPT_STUB = "__import__('setuptools').setup()"121 122 123@pytest.mark.xfail(sys.platform == "darwin", reason="pypa/setuptools#4328")124@pytest.mark.parametrize(125    "files",126    [127        {**EXAMPLE, "setup.py": SETUP_SCRIPT_STUB},128        EXAMPLE,  # No setup.py script129    ],130)131def test_editable_with_pyproject(tmp_path, venv, files, editable_opts):132    project = tmp_path / "mypkg"133    project.mkdir()134    jaraco.path.build(files, prefix=project)135 136    cmd = [137        "python",138        "-m",139        "pip",140        "install",141        "--no-build-isolation",  # required to force current version of setuptools142        "-e",143        str(project),144        *editable_opts,145    ]146    print(venv.run(cmd))147 148    cmd = ["python", "-m", "mypkg"]149    assert venv.run(cmd).strip() == "3.14159.post0 Hello World"150 151    (project / "src/mypkg/data.txt").write_text("foobar", encoding="utf-8")152    (project / "src/mypkg/mod.py").write_text("x = 42", encoding="utf-8")153    assert venv.run(cmd).strip() == "3.14159.post0 foobar 42"154 155 156def test_editable_with_flat_layout(tmp_path, venv, editable_opts):157    files = {158        "mypkg": {159            "pyproject.toml": dedent(160                """\161                [build-system]162                requires = ["setuptools", "wheel"]163                build-backend = "setuptools.build_meta"164 165                [project]166                name = "mypkg"167                version = "3.14159"168 169                [tool.setuptools]170                packages = ["pkg"]171                py-modules = ["mod"]172                """173            ),174            "pkg": {"__init__.py": "a = 4"},175            "mod.py": "b = 2",176        },177    }178    jaraco.path.build(files, prefix=tmp_path)179    project = tmp_path / "mypkg"180 181    cmd = [182        "python",183        "-m",184        "pip",185        "install",186        "--no-build-isolation",  # required to force current version of setuptools187        "-e",188        str(project),189        *editable_opts,190    ]191    print(venv.run(cmd))192    cmd = ["python", "-c", "import pkg, mod; print(pkg.a, mod.b)"]193    assert venv.run(cmd).strip() == "4 2"194 195 196def test_editable_with_single_module(tmp_path, venv, editable_opts):197    files = {198        "mypkg": {199            "pyproject.toml": dedent(200                """\201                [build-system]202                requires = ["setuptools", "wheel"]203                build-backend = "setuptools.build_meta"204 205                [project]206                name = "mod"207                version = "3.14159"208 209                [tool.setuptools]210                py-modules = ["mod"]211                """212            ),213            "mod.py": "b = 2",214        },215    }216    jaraco.path.build(files, prefix=tmp_path)217    project = tmp_path / "mypkg"218 219    cmd = [220        "python",221        "-m",222        "pip",223        "install",224        "--no-build-isolation",  # required to force current version of setuptools225        "-e",226        str(project),227        *editable_opts,228    ]229    print(venv.run(cmd))230    cmd = ["python", "-c", "import mod; print(mod.b)"]231    assert venv.run(cmd).strip() == "2"232 233 234class TestLegacyNamespaces:235    # legacy => pkg_resources.declare_namespace(...) + setup(namespace_packages=...)236 237    def test_nspkg_file_is_unique(self, tmp_path, monkeypatch):238        deprecation = pytest.warns(239            SetuptoolsDeprecationWarning, match=".*namespace_packages parameter.*"240        )241        installation_dir = tmp_path / ".installation_dir"242        installation_dir.mkdir()243        examples = (244            "myns.pkgA",245            "myns.pkgB",246            "myns.n.pkgA",247            "myns.n.pkgB",248        )249 250        for name in examples:251            pkg = namespaces.build_namespace_package(tmp_path, name, version="42")252            with deprecation, monkeypatch.context() as ctx:253                ctx.chdir(pkg)254                dist = run_setup("setup.py", stop_after="config")255                cmd = editable_wheel(dist)256                cmd.finalize_options()257                editable_name = cmd.get_finalized_command("dist_info").name258                cmd._install_namespaces(installation_dir, editable_name)259 260        files = list(installation_dir.glob("*-nspkg.pth"))261        assert len(files) == len(examples)262 263    @pytest.mark.parametrize(264        "impl",265        (266            "pkg_resources",267            #  "pkgutil",  => does not work268        ),269    )270    @pytest.mark.parametrize("ns", ("myns.n",))271    def test_namespace_package_importable(272        self, venv, tmp_path, ns, impl, editable_opts273    ):274        """275        Installing two packages sharing the same namespace, one installed276        naturally using pip or `--single-version-externally-managed`277        and the other installed in editable mode should leave the namespace278        intact and both packages reachable by import.279        (Ported from test_develop).280        """281        build_system = """\282        [build-system]283        requires = ["setuptools"]284        build-backend = "setuptools.build_meta"285        """286        pkg_A = namespaces.build_namespace_package(tmp_path, f"{ns}.pkgA", impl=impl)287        pkg_B = namespaces.build_namespace_package(tmp_path, f"{ns}.pkgB", impl=impl)288        (pkg_A / "pyproject.toml").write_text(build_system, encoding="utf-8")289        (pkg_B / "pyproject.toml").write_text(build_system, encoding="utf-8")290        # use pip to install to the target directory291        opts = editable_opts[:]292        opts.append("--no-build-isolation")  # force current version of setuptools293        venv.run(["python", "-m", "pip", "install", str(pkg_A), *opts])294        venv.run(["python", "-m", "pip", "install", "-e", str(pkg_B), *opts])295        venv.run(["python", "-c", f"import {ns}.pkgA; import {ns}.pkgB"])296 297 298class TestPep420Namespaces:299    def test_namespace_package_importable(self, venv, tmp_path, editable_opts):300        """301        Installing two packages sharing the same namespace, one installed302        normally using pip and the other installed in editable mode303        should allow importing both packages.304        """305        pkg_A = namespaces.build_pep420_namespace_package(tmp_path, 'myns.n.pkgA')306        pkg_B = namespaces.build_pep420_namespace_package(tmp_path, 'myns.n.pkgB')307        # use pip to install to the target directory308        opts = editable_opts[:]309        opts.append("--no-build-isolation")  # force current version of setuptools310        venv.run(["python", "-m", "pip", "install", str(pkg_A), *opts])311        venv.run(["python", "-m", "pip", "install", "-e", str(pkg_B), *opts])312        venv.run(["python", "-c", "import myns.n.pkgA; import myns.n.pkgB"])313 314    def test_namespace_created_via_package_dir(self, venv, tmp_path, editable_opts):315        """Currently users can create a namespace by tweaking `package_dir`"""316        files = {317            "pkgA": {318                "pyproject.toml": dedent(319                    """\320                    [build-system]321                    requires = ["setuptools", "wheel"]322                    build-backend = "setuptools.build_meta"323 324                    [project]325                    name = "pkgA"326                    version = "3.14159"327 328                    [tool.setuptools]329                    package-dir = {"myns.n.pkgA" = "src"}330                    """331                ),332                "src": {"__init__.py": "a = 1"},333            },334        }335        jaraco.path.build(files, prefix=tmp_path)336        pkg_A = tmp_path / "pkgA"337        pkg_B = namespaces.build_pep420_namespace_package(tmp_path, 'myns.n.pkgB')338        pkg_C = namespaces.build_pep420_namespace_package(tmp_path, 'myns.n.pkgC')339 340        # use pip to install to the target directory341        opts = editable_opts[:]342        opts.append("--no-build-isolation")  # force current version of setuptools343        venv.run(["python", "-m", "pip", "install", str(pkg_A), *opts])344        venv.run(["python", "-m", "pip", "install", "-e", str(pkg_B), *opts])345        venv.run(["python", "-m", "pip", "install", "-e", str(pkg_C), *opts])346        venv.run(["python", "-c", "from myns.n import pkgA, pkgB, pkgC"])347 348    def test_namespace_accidental_config_in_lenient_mode(self, venv, tmp_path):349        """Sometimes users might specify an ``include`` pattern that ignores parent350        packages. In a normal installation this would ignore all modules inside the351        parent packages, and make them namespaces (reported in issue #3504),352        so the editable mode should preserve this behaviour.353        """354        files = {355            "pkgA": {356                "pyproject.toml": dedent(357                    """\358                    [build-system]359                    requires = ["setuptools", "wheel"]360                    build-backend = "setuptools.build_meta"361 362                    [project]363                    name = "pkgA"364                    version = "3.14159"365 366                    [tool.setuptools]367                    packages.find.include = ["mypkg.*"]368                    """369                ),370                "mypkg": {371                    "__init__.py": "",372                    "other.py": "b = 1",373                    "n": {374                        "__init__.py": "",375                        "pkgA.py": "a = 1",376                    },377                },378                "MANIFEST.in": EXAMPLE["MANIFEST.in"],379            },380        }381        jaraco.path.build(files, prefix=tmp_path)382        pkg_A = tmp_path / "pkgA"383 384        # use pip to install to the target directory385        opts = ["--no-build-isolation"]  # force current version of setuptools386        venv.run(["python", "-m", "pip", "-v", "install", "-e", str(pkg_A), *opts])387        out = venv.run(["python", "-c", "from mypkg.n import pkgA; print(pkgA.a)"])388        assert out.strip() == "1"389        cmd = """\390        try:391            import mypkg.other392        except ImportError:393            print("mypkg.other not defined")394        """395        out = venv.run(["python", "-c", dedent(cmd)])396        assert "mypkg.other not defined" in out397 398 399def test_editable_with_prefix(tmp_path, sample_project, editable_opts):400    """401    Editable install to a prefix should be discoverable.402    """403    prefix = tmp_path / 'prefix'404 405    # figure out where pip will likely install the package406    site_packages_all = [407        prefix / Path(path).relative_to(sys.prefix)408        for path in sys.path409        if 'site-packages' in path and path.startswith(sys.prefix)410    ]411 412    for sp in site_packages_all:413        sp.mkdir(parents=True)414 415    # install workaround416    _addsitedirs(site_packages_all)417 418    env = dict(os.environ, PYTHONPATH=os.pathsep.join(map(str, site_packages_all)))419    cmd = [420        sys.executable,421        '-m',422        'pip',423        'install',424        '--editable',425        str(sample_project),426        '--prefix',427        str(prefix),428        '--no-build-isolation',429        *editable_opts,430    ]431    subprocess.check_call(cmd, env=env)432 433    # now run 'sample' with the prefix on the PYTHONPATH434    bin = 'Scripts' if platform.system() == 'Windows' else 'bin'435    exe = prefix / bin / 'sample'436    subprocess.check_call([exe], env=env)437 438 439class TestFinderTemplate:440    """This test focus in getting a particular implementation detail right.441    If at some point in time the implementation is changed for something different,442    this test can be modified or even excluded.443    """444 445    def install_finder(self, finder):446        loc = {}447        exec(finder, loc, loc)448        loc["install"]()449 450    def test_packages(self, tmp_path):451        files = {452            "src1": {453                "pkg1": {454                    "__init__.py": "",455                    "subpkg": {"mod1.py": "a = 42"},456                },457            },458            "src2": {"mod2.py": "a = 43"},459        }460        jaraco.path.build(files, prefix=tmp_path)461 462        mapping = {463            "pkg1": str(tmp_path / "src1/pkg1"),464            "mod2": str(tmp_path / "src2/mod2"),465        }466        template = _finder_template(str(uuid4()), mapping, {})467 468        with contexts.save_paths(), contexts.save_sys_modules():469            for mod in ("pkg1", "pkg1.subpkg", "pkg1.subpkg.mod1", "mod2"):470                sys.modules.pop(mod, None)471 472            self.install_finder(template)473            mod1 = import_module("pkg1.subpkg.mod1")474            mod2 = import_module("mod2")475            subpkg = import_module("pkg1.subpkg")476 477            assert mod1.a == 42478            assert mod2.a == 43479            expected = str((tmp_path / "src1/pkg1/subpkg").resolve())480            assert_path(subpkg, expected)481 482    def test_namespace(self, tmp_path):483        files = {"pkg": {"__init__.py": "a = 13", "text.txt": "abc"}}484        jaraco.path.build(files, prefix=tmp_path)485 486        mapping = {"ns.othername": str(tmp_path / "pkg")}487        namespaces = {"ns": []}488 489        template = _finder_template(str(uuid4()), mapping, namespaces)490        with contexts.save_paths(), contexts.save_sys_modules():491            for mod in ("ns", "ns.othername"):492                sys.modules.pop(mod, None)493 494            self.install_finder(template)495            pkg = import_module("ns.othername")496            text = importlib_resources.files(pkg) / "text.txt"497 498            expected = str((tmp_path / "pkg").resolve())499            assert_path(pkg, expected)500            assert pkg.a == 13501 502            # Make sure resources can also be found503            assert text.read_text(encoding="utf-8") == "abc"504 505    def test_combine_namespaces(self, tmp_path):506        files = {507            "src1": {"ns": {"pkg1": {"__init__.py": "a = 13"}}},508            "src2": {"ns": {"mod2.py": "b = 37"}},509        }510        jaraco.path.build(files, prefix=tmp_path)511 512        mapping = {513            "ns.pkgA": str(tmp_path / "src1/ns/pkg1"),514            "ns": str(tmp_path / "src2/ns"),515        }516        namespaces_ = {"ns": [str(tmp_path / "src1"), str(tmp_path / "src2")]}517        template = _finder_template(str(uuid4()), mapping, namespaces_)518 519        with contexts.save_paths(), contexts.save_sys_modules():520            for mod in ("ns", "ns.pkgA", "ns.mod2"):521                sys.modules.pop(mod, None)522 523            self.install_finder(template)524            pkgA = import_module("ns.pkgA")525            mod2 = import_module("ns.mod2")526 527            expected = str((tmp_path / "src1/ns/pkg1").resolve())528            assert_path(pkgA, expected)529            assert pkgA.a == 13530            assert mod2.b == 37531 532    def test_combine_namespaces_nested(self, tmp_path):533        """534        Users may attempt to combine namespace packages in a nested way via535        ``package_dir`` as shown in pypa/setuptools#4248.536        """537 538        files = {539            "src": {"my_package": {"my_module.py": "a = 13"}},540            "src2": {"my_package2": {"my_module2.py": "b = 37"}},541        }542 543        stack = jaraco.path.DirectoryStack()544        with stack.context(tmp_path):545            jaraco.path.build(files)546            attrs = {547                "script_name": "%PEP 517%",548                "package_dir": {549                    "different_name": "src/my_package",550                    "different_name.subpkg": "src2/my_package2",551                },552                "packages": ["different_name", "different_name.subpkg"],553            }554            dist = Distribution(attrs)555            finder = _TopLevelFinder(dist, str(uuid4()))556            code = next(v for k, v in finder.get_implementation() if k.endswith(".py"))557 558        with contexts.save_paths(), contexts.save_sys_modules():559            for mod in attrs["packages"]:560                sys.modules.pop(mod, None)561 562            self.install_finder(code)563            mod1 = import_module("different_name.my_module")564            mod2 = import_module("different_name.subpkg.my_module2")565 566            expected = str((tmp_path / "src/my_package/my_module.py").resolve())567            assert str(Path(mod1.__file__).resolve()) == expected568 569            expected = str((tmp_path / "src2/my_package2/my_module2.py").resolve())570            assert str(Path(mod2.__file__).resolve()) == expected571 572            assert mod1.a == 13573            assert mod2.b == 37574 575    def test_dynamic_path_computation(self, tmp_path):576        # Follows the example in PEP 420577        files = {578            "project1": {"parent": {"child": {"one.py": "x = 1"}}},579            "project2": {"parent": {"child": {"two.py": "x = 2"}}},580            "project3": {"parent": {"child": {"three.py": "x = 3"}}},581        }582        jaraco.path.build(files, prefix=tmp_path)583        mapping = {}584        namespaces_ = {"parent": [str(tmp_path / "project1/parent")]}585        template = _finder_template(str(uuid4()), mapping, namespaces_)586 587        mods = (f"parent.child.{name}" for name in ("one", "two", "three"))588        with contexts.save_paths(), contexts.save_sys_modules():589            for mod in ("parent", "parent.child", "parent.child", *mods):590                sys.modules.pop(mod, None)591 592            self.install_finder(template)593 594            one = import_module("parent.child.one")595            assert one.x == 1596 597            with pytest.raises(ImportError):598                import_module("parent.child.two")599 600            sys.path.append(str(tmp_path / "project2"))601            two = import_module("parent.child.two")602            assert two.x == 2603 604            with pytest.raises(ImportError):605                import_module("parent.child.three")606 607            sys.path.append(str(tmp_path / "project3"))608            three = import_module("parent.child.three")609            assert three.x == 3610 611    def test_no_recursion(self, tmp_path):612        # See issue #3550613        files = {614            "pkg": {615                "__init__.py": "from . import pkg",616            },617        }618        jaraco.path.build(files, prefix=tmp_path)619 620        mapping = {621            "pkg": str(tmp_path / "pkg"),622        }623        template = _finder_template(str(uuid4()), mapping, {})624 625        with contexts.save_paths(), contexts.save_sys_modules():626            sys.modules.pop("pkg", None)627 628            self.install_finder(template)629            with pytest.raises(ImportError, match="pkg"):630                import_module("pkg")631 632    def test_similar_name(self, tmp_path):633        files = {634            "foo": {635                "__init__.py": "",636                "bar": {637                    "__init__.py": "",638                },639            },640        }641        jaraco.path.build(files, prefix=tmp_path)642 643        mapping = {644            "foo": str(tmp_path / "foo"),645        }646        template = _finder_template(str(uuid4()), mapping, {})647 648        with contexts.save_paths(), contexts.save_sys_modules():649            sys.modules.pop("foo", None)650            sys.modules.pop("foo.bar", None)651 652            self.install_finder(template)653            with pytest.raises(ImportError, match="foobar"):654                import_module("foobar")655 656    def test_case_sensitivity(self, tmp_path):657        files = {658            "foo": {659                "__init__.py": "",660                "lowercase.py": "x = 1",661                "bar": {662                    "__init__.py": "",663                    "lowercase.py": "x = 2",664                },665            },666        }667        jaraco.path.build(files, prefix=tmp_path)668        mapping = {669            "foo": str(tmp_path / "foo"),670        }671        template = _finder_template(str(uuid4()), mapping, {})672        with contexts.save_paths(), contexts.save_sys_modules():673            sys.modules.pop("foo", None)674 675            self.install_finder(template)676            with pytest.raises(ImportError, match="'FOO'"):677                import_module("FOO")678 679            with pytest.raises(ImportError, match="'foo\\.LOWERCASE'"):680                import_module("foo.LOWERCASE")681 682            with pytest.raises(ImportError, match="'foo\\.bar\\.Lowercase'"):683                import_module("foo.bar.Lowercase")684 685            with pytest.raises(ImportError, match="'foo\\.BAR'"):686                import_module("foo.BAR.lowercase")687 688            with pytest.raises(ImportError, match="'FOO'"):689                import_module("FOO.bar.lowercase")690 691            mod = import_module("foo.lowercase")692            assert mod.x == 1693 694            mod = import_module("foo.bar.lowercase")695            assert mod.x == 2696 697    def test_namespace_case_sensitivity(self, tmp_path):698        files = {699            "pkg": {700                "__init__.py": "a = 13",701                "foo": {702                    "__init__.py": "b = 37",703                    "bar.py": "c = 42",704                },705            },706        }707        jaraco.path.build(files, prefix=tmp_path)708 709        mapping = {"ns.othername": str(tmp_path / "pkg")}710        namespaces = {"ns": []}711 712        template = _finder_template(str(uuid4()), mapping, namespaces)713        with contexts.save_paths(), contexts.save_sys_modules():714            for mod in ("ns", "ns.othername"):715                sys.modules.pop(mod, None)716 717            self.install_finder(template)718            pkg = import_module("ns.othername")719            expected = str((tmp_path / "pkg").resolve())720            assert_path(pkg, expected)721            assert pkg.a == 13722 723            foo = import_module("ns.othername.foo")724            assert foo.b == 37725 726            bar = import_module("ns.othername.foo.bar")727            assert bar.c == 42728 729            with pytest.raises(ImportError, match="'NS'"):730                import_module("NS.othername.foo")731 732            with pytest.raises(ImportError, match="'ns\\.othername\\.FOO\\'"):733                import_module("ns.othername.FOO")734 735            with pytest.raises(ImportError, match="'ns\\.othername\\.foo\\.BAR\\'"):736                import_module("ns.othername.foo.BAR")737 738    def test_intermediate_packages(self, tmp_path):739        """740        The finder should not import ``fullname`` if the intermediate segments741        don't exist (see pypa/setuptools#4019).742        """743        files = {744            "src": {745                "mypkg": {746                    "__init__.py": "",747                    "config.py": "a = 13",748                    "helloworld.py": "b = 13",749                    "components": {750                        "config.py": "a = 37",751                    },752                },753            }754        }755        jaraco.path.build(files, prefix=tmp_path)756 757        mapping = {"mypkg": str(tmp_path / "src/mypkg")}758        template = _finder_template(str(uuid4()), mapping, {})759 760        with contexts.save_paths(), contexts.save_sys_modules():761            for mod in (762                "mypkg",763                "mypkg.config",764                "mypkg.helloworld",765                "mypkg.components",766                "mypkg.components.config",767                "mypkg.components.helloworld",768            ):769                sys.modules.pop(mod, None)770 771            self.install_finder(template)772 773            config = import_module("mypkg.components.config")774            assert config.a == 37775 776            helloworld = import_module("mypkg.helloworld")777            assert helloworld.b == 13778 779            with pytest.raises(ImportError):780                import_module("mypkg.components.helloworld")781 782 783def test_pkg_roots(tmp_path):784    """This test focus in getting a particular implementation detail right.785    If at some point in time the implementation is changed for something different,786    this test can be modified or even excluded.787    """788    files = {789        "a": {"b": {"__init__.py": "ab = 1"}, "__init__.py": "a = 1"},790        "d": {"__init__.py": "d = 1", "e": {"__init__.py": "de = 1"}},791        "f": {"g": {"h": {"__init__.py": "fgh = 1"}}},792        "other": {"__init__.py": "abc = 1"},793        "another": {"__init__.py": "abcxyz = 1"},794        "yet_another": {"__init__.py": "mnopq = 1"},795    }796    jaraco.path.build(files, prefix=tmp_path)797    package_dir = {798        "a.b.c": "other",799        "a.b.c.x.y.z": "another",800        "m.n.o.p.q": "yet_another",801    }802    packages = [803        "a",804        "a.b",805        "a.b.c",806        "a.b.c.x.y",807        "a.b.c.x.y.z",808        "d",809        "d.e",810        "f",811        "f.g",812        "f.g.h",813        "m.n.o.p.q",814    ]815    roots = _find_package_roots(packages, package_dir, tmp_path)816    assert roots == {817        "a": str(tmp_path / "a"),818        "a.b.c": str(tmp_path / "other"),819        "a.b.c.x.y.z": str(tmp_path / "another"),820        "d": str(tmp_path / "d"),821        "f": str(tmp_path / "f"),822        "m.n.o.p.q": str(tmp_path / "yet_another"),823    }824 825    ns = set(dict(_find_namespaces(packages, roots)))826    assert ns == {"f", "f.g"}827 828    ns = set(_find_virtual_namespaces(roots))829    assert ns == {"a.b", "a.b.c.x", "a.b.c.x.y", "m", "m.n", "m.n.o", "m.n.o.p"}830 831 832class TestOverallBehaviour:833    PYPROJECT = """\834        [build-system]835        requires = ["setuptools"]836        build-backend = "setuptools.build_meta"837 838        [project]839        name = "mypkg"840        version = "3.14159"841        """842 843    # Any: Would need a TypedDict. Keep it simple for tests844    FLAT_LAYOUT: dict[str, Any] = {845        "pyproject.toml": dedent(PYPROJECT),846        "MANIFEST.in": EXAMPLE["MANIFEST.in"],847        "otherfile.py": "",848        "mypkg": {849            "__init__.py": "",850            "mod1.py": "var = 42",851            "subpackage": {852                "__init__.py": "",853                "mod2.py": "var = 13",854                "resource_file.txt": "resource 39",855            },856        },857    }858 859    EXAMPLES = {860        "flat-layout": FLAT_LAYOUT,861        "src-layout": {862            "pyproject.toml": dedent(PYPROJECT),863            "MANIFEST.in": EXAMPLE["MANIFEST.in"],864            "otherfile.py": "",865            "src": {"mypkg": FLAT_LAYOUT["mypkg"]},866        },867        "custom-layout": {868            "pyproject.toml": dedent(PYPROJECT)869            + dedent(870                """\871                [tool.setuptools]872                packages = ["mypkg", "mypkg.subpackage"]873 874                [tool.setuptools.package-dir]875                "mypkg.subpackage" = "other"876                """877            ),878            "MANIFEST.in": EXAMPLE["MANIFEST.in"],879            "otherfile.py": "",880            "mypkg": {881                "__init__.py": "",882                "mod1.py": FLAT_LAYOUT["mypkg"]["mod1.py"],883            },884            "other": FLAT_LAYOUT["mypkg"]["subpackage"],885        },886        "namespace": {887            "pyproject.toml": dedent(PYPROJECT),888            "MANIFEST.in": EXAMPLE["MANIFEST.in"],889            "otherfile.py": "",890            "src": {891                "mypkg": {892                    "mod1.py": FLAT_LAYOUT["mypkg"]["mod1.py"],893                    "subpackage": FLAT_LAYOUT["mypkg"]["subpackage"],894                },895            },896        },897    }898 899    @pytest.mark.xfail(sys.platform == "darwin", reason="pypa/setuptools#4328")900    @pytest.mark.parametrize("layout", EXAMPLES.keys())901    def test_editable_install(self, tmp_path, venv, layout, editable_opts):902        project, _ = install_project(903            "mypkg", venv, tmp_path, self.EXAMPLES[layout], *editable_opts904        )905 906        # Ensure stray files are not importable907        cmd_import_error = """\908        try:909            import otherfile910        except ImportError as ex:911            print(ex)912        """913        out = venv.run(["python", "-c", dedent(cmd_import_error)])914        assert "No module named 'otherfile'" in out915 916        # Ensure the modules are importable917        cmd_get_vars = """\918        import mypkg, mypkg.mod1, mypkg.subpackage.mod2919        print(mypkg.mod1.var, mypkg.subpackage.mod2.var)920        """921        out = venv.run(["python", "-c", dedent(cmd_get_vars)])922        assert "42 13" in out923 924        # Ensure resources are reachable925        cmd_get_resource = """\926        import mypkg.subpackage927        from setuptools._importlib import resources as importlib_resources928        text = importlib_resources.files(mypkg.subpackage) / "resource_file.txt"929        print(text.read_text(encoding="utf-8"))930        """931        out = venv.run(["python", "-c", dedent(cmd_get_resource)])932        assert "resource 39" in out933 934        # Ensure files are editable935        mod1 = next(project.glob("**/mod1.py"))936        mod2 = next(project.glob("**/mod2.py"))937        resource_file = next(project.glob("**/resource_file.txt"))938 939        mod1.write_text("var = 17", encoding="utf-8")940        mod2.write_text("var = 781", encoding="utf-8")941        resource_file.write_text("resource 374", encoding="utf-8")942 943        out = venv.run(["python", "-c", dedent(cmd_get_vars)])944        assert "42 13" not in out945        assert "17 781" in out946 947        out = venv.run(["python", "-c", dedent(cmd_get_resource)])948        assert "resource 39" not in out949        assert "resource 374" in out950 951 952class TestLinkTree:953    FILES = deepcopy(TestOverallBehaviour.EXAMPLES["src-layout"])954    FILES["pyproject.toml"] += dedent(955        """\956        [tool.setuptools]957        # Temporary workaround: both `include-package-data` and `package-data` configs958        # can be removed after #3260 is fixed.959        include-package-data = false960        package-data = {"*" = ["*.txt"]}961 962        [tool.setuptools.packages.find]963        where = ["src"]964        exclude = ["*.subpackage*"]965        """966    )967    FILES["src"]["mypkg"]["resource.not_in_manifest"] = "abc"968 969    def test_generated_tree(self, tmp_path):970        jaraco.path.build(self.FILES, prefix=tmp_path)971 972        with _Path(tmp_path):973            name = "mypkg-3.14159"974            dist = Distribution({"script_name": "%PEP 517%"})975            dist.parse_config_files()976 977            wheel = Mock()978            aux = tmp_path / ".aux"979            build = tmp_path / ".build"980            aux.mkdir()981            build.mkdir()982 983            build_py = dist.get_command_obj("build_py")984            build_py.editable_mode = True985            build_py.build_lib = str(build)986            build_py.ensure_finalized()987            outputs = build_py.get_outputs()988            output_mapping = build_py.get_output_mapping()989 990            make_tree = _LinkTree(dist, name, aux, build)991            make_tree(wheel, outputs, output_mapping)992 993            mod1 = next(aux.glob("**/mod1.py"))994            expected = tmp_path / "src/mypkg/mod1.py"995            assert_link_to(mod1, expected)996 997            assert next(aux.glob("**/subpackage"), None) is None998            assert next(aux.glob("**/mod2.py"), None) is None999            assert next(aux.glob("**/resource_file.txt"), None) is None1000 1001            assert next(aux.glob("**/resource.not_in_manifest"), None) is None1002 1003    def test_strict_install(self, tmp_path, venv):1004        opts = ["--config-settings", "editable-mode=strict"]1005        install_project("mypkg", venv, tmp_path, self.FILES, *opts)1006 1007        out = venv.run(["python", "-c", "import mypkg.mod1; print(mypkg.mod1.var)"])1008        assert "42" in out1009 1010        # Ensure packages excluded from distribution are not importable1011        cmd_import_error = """\1012        try:1013            from mypkg import subpackage1014        except ImportError as ex:1015            print(ex)1016        """1017        out = venv.run(["python", "-c", dedent(cmd_import_error)])1018        assert "cannot import name 'subpackage'" in out1019 1020        # Ensure resource files excluded from distribution are not reachable1021        cmd_get_resource = """\1022        import mypkg1023        from setuptools._importlib import resources as importlib_resources1024        try:1025            text = importlib_resources.files(mypkg) / "resource.not_in_manifest"1026            print(text.read_text(encoding="utf-8"))1027        except FileNotFoundError as ex:1028            print(ex)1029        """1030        out = venv.run(["python", "-c", dedent(cmd_get_resource)])1031        assert "No such file or directory" in out1032        assert "resource.not_in_manifest" in out1033 1034 1035@pytest.mark.filterwarnings("ignore:.*compat.*:setuptools.SetuptoolsDeprecationWarning")1036def test_compat_install(tmp_path, venv):1037    # TODO: Remove `compat` after Dec/2022.1038    opts = ["--config-settings", "editable-mode=compat"]1039    files = TestOverallBehaviour.EXAMPLES["custom-layout"]1040    install_project("mypkg", venv, tmp_path, files, *opts)1041 1042    out = venv.run(["python", "-c", "import mypkg.mod1; print(mypkg.mod1.var)"])1043    assert "42" in out1044 1045    expected_path = comparable_path(str(tmp_path))1046 1047    # Compatible behaviour will make spurious modules and excluded1048    # files importable directly from the original path1049    for cmd in (1050        "import otherfile; print(otherfile)",1051        "import other; print(other)",1052        "import mypkg; print(mypkg)",1053    ):1054        out = comparable_path(venv.run(["python", "-c", cmd]))1055        assert expected_path in out1056 1057    # Compatible behaviour will not consider custom mappings1058    cmd = """\1059    try:1060        from mypkg import subpackage;1061    except ImportError as ex:1062        print(ex)1063    """1064    out = venv.run(["python", "-c", dedent(cmd)])1065    assert "cannot import name 'subpackage'" in out1066 1067 1068@pytest.mark.uses_network1069def test_pbr_integration(pbr_package, venv, editable_opts):1070    """Ensure editable installs work with pbr, issue #3500"""1071    cmd = [1072        'python',1073        '-m',1074        'pip',1075        '-v',1076        'install',1077        '--editable',1078        pbr_package,1079        *editable_opts,1080    ]1081    venv.run(cmd, stderr=subprocess.STDOUT)1082    out = venv.run(["python", "-c", "import mypkg.hello"])1083    assert "Hello world!" in out1084 1085 1086class TestCustomBuildPy:1087    """1088    Issue #3501 indicates that some plugins/customizations might rely on:1089 1090    1. ``build_py`` not running1091    2. ``build_py`` always copying files to ``build_lib``1092 1093    During the transition period setuptools should prevent potential errors from1094    happening due to those assumptions.1095    """1096 1097    # TODO: Remove tests after _run_build_steps is removed.1098 1099    FILES = {1100        **TestOverallBehaviour.EXAMPLES["flat-layout"],1101        "setup.py": dedent(1102            """\1103            import pathlib1104            from setuptools import setup1105            from setuptools.command.build_py import build_py as orig1106 1107            class my_build_py(orig):1108                def run(self):1109                    super().run()1110                    raise ValueError("TEST_RAISE")1111 1112            setup(cmdclass={"build_py": my_build_py})1113            """1114        ),1115    }1116 1117    def test_safeguarded_from_errors(self, tmp_path, venv):1118        """Ensure that errors in custom build_py are reported as warnings"""1119        # Warnings should show up1120        _, out = install_project("mypkg", venv, tmp_path, self.FILES)1121        assert "SetuptoolsDeprecationWarning" in out1122        assert "ValueError: TEST_RAISE" in out1123        # but installation should be successful1124        out = venv.run(["python", "-c", "import mypkg.mod1; print(mypkg.mod1.var)"])1125        assert "42" in out1126 1127 1128class TestCustomBuildWheel:1129    def install_custom_build_wheel(self, dist):1130        bdist_wheel_cls = dist.get_command_class("bdist_wheel")1131 1132        class MyBdistWheel(bdist_wheel_cls):1133            def get_tag(self):1134                # In issue #3513, we can see that some extensions may try to access1135                # the `plat_name` property in bdist_wheel1136                if self.plat_name.startswith("macosx-"):1137                    _ = "macOS platform"1138                return super().get_tag()1139 1140        dist.cmdclass["bdist_wheel"] = MyBdistWheel1141 1142    def test_access_plat_name(self, tmpdir_cwd):1143        # Even when a custom bdist_wheel tries to access plat_name the build should1144        # be successful1145        jaraco.path.build({"module.py": "x = 42"})1146        dist = Distribution()1147        dist.script_name = "setup.py"1148        dist.set_defaults()1149        self.install_custom_build_wheel(dist)1150        cmd = editable_wheel(dist)1151        cmd.ensure_finalized()1152        cmd.run()1153        wheel_file = str(next(Path().glob('dist/*.whl')))1154        assert "editable" in wheel_file1155 1156 1157class TestCustomBuildExt:1158    def install_custom_build_ext_distutils(self, dist):1159        from distutils.command.build_ext import build_ext as build_ext_cls1160 1161        class MyBuildExt(build_ext_cls):1162            pass1163 1164        dist.cmdclass["build_ext"] = MyBuildExt1165 1166    @pytest.mark.skipif(1167        sys.platform != "linux", reason="compilers may fail without correct setup"1168    )1169    def test_distutils_leave_inplace_files(self, tmpdir_cwd):1170        jaraco.path.build({"module.c": ""})1171        attrs = {1172            "ext_modules": [Extension("module", ["module.c"])],1173        }1174        dist = Distribution(attrs)1175        dist.script_name = "setup.py"1176        dist.set_defaults()1177        self.install_custom_build_ext_distutils(dist)1178        cmd = editable_wheel(dist)1179        cmd.ensure_finalized()1180        cmd.run()1181        wheel_file = str(next(Path().glob('dist/*.whl')))1182        assert "editable" in wheel_file1183        files = [p for p in Path().glob("module.*") if p.suffix != ".c"]1184        assert len(files) == 11185        name = files[0].name1186        assert any(name.endswith(ext) for ext in EXTENSION_SUFFIXES)1187 1188 1189def test_debugging_tips(tmpdir_cwd, monkeypatch):1190    """Make sure to display useful debugging tips to the user."""1191    jaraco.path.build({"module.py": "x = 42"})1192    dist = Distribution()1193    dist.script_name = "setup.py"1194    dist.set_defaults()1195    cmd = editable_wheel(dist)1196    cmd.ensure_finalized()1197 1198    SimulatedErr = type("SimulatedErr", (Exception,), {})1199    simulated_failure = Mock(side_effect=SimulatedErr())1200    monkeypatch.setattr(cmd, "get_finalized_command", simulated_failure)

Showing the first 1,200 of 1262 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai