Пример #1
0
def test_builder_installs_proper_files_when_packages_configured(
        project_with_include: Poetry, tmp_venv: VirtualEnv):
    builder = EditableBuilder(project_with_include, tmp_venv, NullIO())
    builder.build()

    pth_file = "with_include.pth"
    assert tmp_venv.site_packages.exists(pth_file)

    pth_file = tmp_venv.site_packages.find(pth_file)[0]

    paths = set()
    with pth_file.open() as f:
        for line in f.readlines():
            line = line.strip(os.linesep)
            if line:
                paths.add(line)

    project_root = project_with_include.file.parent.resolve()
    expected = {
        project_root.as_posix(),
        project_root.joinpath("src").as_posix()
    }

    assert paths.issubset(expected)
    assert len(paths) == len(expected)
Пример #2
0
def test_builder_setup_generation_runs_with_pip_editable(tmp_dir: str):
    # create an isolated copy of the project
    fixture = Path(
        __file__).parent.parent.parent / "fixtures" / "extended_project"
    extended_project = Path(tmp_dir) / "extended_project"

    shutil.copytree(fixture, extended_project)
    assert extended_project.exists()

    poetry = Factory().create_poetry(extended_project)

    # we need a venv with setuptools since we are verifying setup.py builds
    with ephemeral_environment(flags={"no-setuptools": False}) as venv:
        builder = EditableBuilder(poetry, venv, NullIO())
        builder.build()

        # is the package installed?
        repository = InstalledRepository.load(venv)
        assert repository.package("extended-project", "1.2.3")

        # check for the module built by build.py
        try:
            output = venv.run_python_script(
                "from extended_project import built; print(built.__file__)"
            ).strip()
        except EnvCommandError:
            pytest.fail("Unable to import built module")
        else:
            built_py = Path(output).resolve()

        expected = extended_project / "extended_project" / "built.py"

        # ensure the package was installed as editable
        assert built_py == expected.resolve()
Пример #3
0
def test_builder_should_execute_build_scripts(extended_without_setup_poetry):
    env = MockEnv(path=Path("/foo"))
    builder = EditableBuilder(extended_without_setup_poetry, env, NullIO())

    builder.build()

    assert [
        ["python", str(extended_without_setup_poetry.file.parent / "build.py")]
    ] == env.executed
Пример #4
0
def test_builder_falls_back_on_setup_and_pip_for_packages_with_build_scripts(
        mocker, extended_poetry, tmp_dir):
    pip_editable_install = mocker.patch(
        "poetry.masonry.builders.editable.pip_editable_install")
    env = MockEnv(path=Path(tmp_dir) / "foo")
    builder = EditableBuilder(extended_poetry, env, NullIO())

    builder.build()
    pip_editable_install.assert_called_once_with(
        extended_poetry.pyproject.file.path.parent, env)
    assert [] == env.executed
Пример #5
0
    def install_directory(self, package):
        from poetry.factory import Factory
        from poetry.utils.toml_file import TomlFile

        if package.root_dir:
            req = (package.root_dir / package.source_url).as_posix()
        else:
            req = os.path.realpath(package.source_url)

        args = ["install", "--no-deps", "-U"]

        pyproject = TomlFile(os.path.join(req, "pyproject.toml"))

        has_poetry = False
        has_build_system = False
        if pyproject.exists():
            pyproject_content = pyproject.read()
            has_poetry = (
                "tool" in pyproject_content and "poetry" in pyproject_content["tool"]
            )
            # Even if there is a build system specified
            # pip as of right now does not support it fully
            # TODO: Check for pip version when proper PEP-517 support lands
            # has_build_system = ("build-system" in pyproject_content)

        setup = os.path.join(req, "setup.py")
        has_setup = os.path.exists(setup)
        if has_poetry and (package.develop or not has_build_system):
            # We actually need to rely on creating a temporary setup.py
            # file since pip, as of this comment, does not support
            # build-system for editable packages
            # We also need it for non-PEP-517 packages
            from poetry.masonry.builders.editable import EditableBuilder

            builder = EditableBuilder(
                Factory().create_poetry(pyproject.parent), self._env, NullIO()
            )

            builder.build()

            return

        if package.develop:
            args.append("-e")

        args.append(req)

        try:
            return self.run(*args)
        finally:
            if not has_setup and os.path.exists(setup):
                os.remove(setup)
Пример #6
0
def test_builder_should_execute_build_scripts(
        mocker: MockerFixture, extended_without_setup_poetry: Poetry,
        tmp_dir: str):
    env = MockEnv(path=Path(tmp_dir) / "foo")
    mocker.patch("poetry.masonry.builders.editable.build_environment"
                 ).return_value.__enter__.return_value = env

    builder = EditableBuilder(extended_without_setup_poetry, env, NullIO())

    builder.build()

    assert [[
        "python",
        str(extended_without_setup_poetry.file.parent / "build.py")
    ]] == env.executed
Пример #7
0
def test_builder_falls_back_on_setup_and_pip_for_packages_with_build_scripts(
        extended_poetry, tmp_dir):
    env = MockEnv(path=Path(tmp_dir) / "foo")
    builder = EditableBuilder(extended_poetry, env, NullIO())

    builder.build()

    assert [[
        "python",
        "-m",
        "pip",
        "install",
        "-e",
        str(extended_poetry.file.parent),
        "--no-deps",
    ]] == env.executed
Пример #8
0
    def _install_directory(self, operation: Install | Update) -> int:
        from poetry.factory import Factory

        package = operation.package
        operation_message = self.get_operation_message(operation)

        message = (
            f"  <fg=blue;options=bold>•</> {operation_message}:"
            " <info>Building...</info>"
        )
        self._write(operation, message)

        assert package.source_url is not None
        if package.root_dir:
            req = package.root_dir / package.source_url
        else:
            req = Path(package.source_url).resolve(strict=False)

        pyproject = PyProjectTOML(os.path.join(req, "pyproject.toml"))

        if pyproject.is_poetry_project():
            # Even if there is a build system specified
            # some versions of pip (< 19.0.0) don't understand it
            # so we need to check the version of pip to know
            # if we can rely on the build system
            legacy_pip = (
                self._env.pip_version
                < self._env.pip_version.__class__.from_parts(19, 0, 0)
            )
            package_poetry = Factory().create_poetry(pyproject.file.path.parent)

            builder: Builder
            if package.develop and not package_poetry.package.build_script:
                from poetry.masonry.builders.editable import EditableBuilder

                # This is a Poetry package in editable mode
                # we can use the EditableBuilder without going through pip
                # to install it, unless it has a build script.
                builder = EditableBuilder(package_poetry, self._env, NullIO())
                builder.build()

                return 0
            elif legacy_pip or package_poetry.package.build_script:
                from poetry.core.masonry.builders.sdist import SdistBuilder

                # We need to rely on creating a temporary setup.py
                # file since the version of pip does not support
                # build-systems
                # We also need it for non-PEP-517 packages
                builder = SdistBuilder(package_poetry)

                with builder.setup_py():
                    if package.develop:
                        return self.pip_install(req, upgrade=True, editable=True)
                    return self.pip_install(req, upgrade=True)

        if package.develop:
            return self.pip_install(req, upgrade=True, editable=True)

        return self.pip_install(req, upgrade=True)
Пример #9
0
    def install_directory(self, package: "Package") -> Union[str, int]:
        from cleo.io.null_io import NullIO

        from poetry.factory import Factory

        req: Path

        if package.root_dir:
            req = (package.root_dir / package.source_url).as_posix()
        else:
            req = Path(package.source_url).resolve(strict=False)

        pyproject = PyProjectTOML(os.path.join(req, "pyproject.toml"))

        if pyproject.is_poetry_project():
            # Even if there is a build system specified
            # some versions of pip (< 19.0.0) don't understand it
            # so we need to check the version of pip to know
            # if we can rely on the build system
            legacy_pip = self._env.pip_version < self._env.pip_version.__class__(
                19, 0, 0)
            package_poetry = Factory().create_poetry(
                pyproject.file.path.parent)

            if package.develop and not package_poetry.package.build_script:
                from poetry.masonry.builders.editable import EditableBuilder

                # This is a Poetry package in editable mode
                # we can use the EditableBuilder without going through pip
                # to install it, unless it has a build script.
                builder = EditableBuilder(package_poetry, self._env, NullIO())
                builder.build()

                return 0
            elif legacy_pip or package_poetry.package.build_script:
                from poetry.core.masonry.builders.sdist import SdistBuilder

                # We need to rely on creating a temporary setup.py
                # file since the version of pip does not support
                # build-systems
                # We also need it for non-PEP-517 packages
                builder = SdistBuilder(package_poetry)

                with builder.setup_py():
                    if package.develop:
                        return pip_editable_install(directory=req,
                                                    environment=self._env)
                    return pip_install(path=req,
                                       environment=self._env,
                                       deps=False,
                                       upgrade=True)

        if package.develop:
            return pip_editable_install(directory=req, environment=self._env)
        return pip_install(path=req,
                           environment=self._env,
                           deps=False,
                           upgrade=True)
Пример #10
0
    def install_directory(self, package):
        from poetry.factory import Factory
        from poetry.io.null_io import NullIO

        if package.root_dir:
            req = (package.root_dir / package.source_url).as_posix()
        else:
            req = os.path.realpath(package.source_url)

        args = ["install", "--no-deps", "-U"]

        pyproject = PyProjectTOML(os.path.join(req, "pyproject.toml"))

        if pyproject.is_poetry_project():
            # Even if there is a build system specified
            # some versions of pip (< 19.0.0) don't understand it
            # so we need to check the version of pip to know
            # if we can rely on the build system
            legacy_pip = self._env.pip_version < self._env.pip_version.__class__(
                19, 0, 0)
            package_poetry = Factory().create_poetry(
                pyproject.file.path.parent)

            if package.develop and not package_poetry.package.build_script:
                from poetry.masonry.builders.editable import EditableBuilder

                # This is a Poetry package in editable mode
                # we can use the EditableBuilder without going through pip
                # to install it, unless it has a build script.
                builder = EditableBuilder(package_poetry, self._env, NullIO())
                builder.build()

                return 0
            elif legacy_pip or package_poetry.package.build_script:
                from poetry.core.masonry.builders.sdist import SdistBuilder

                # We need to rely on creating a temporary setup.py
                # file since the version of pip does not support
                # build-systems
                # We also need it for non-PEP-517 packages
                builder = SdistBuilder(package_poetry)

                with builder.setup_py():
                    if package.develop:
                        args.append("-e")

                    args.append(req)

                    return self.run_pip(*args)

        if package.develop:
            args.append("-e")

        args.append(req)

        return self.run(*args)
Пример #11
0
def test_builder_installs_proper_files_for_standard_packages(
        simple_poetry, tmp_venv):
    builder = EditableBuilder(simple_poetry, tmp_venv, NullIO())

    builder.build()

    assert tmp_venv._bin_dir.joinpath("foo").exists()
    assert tmp_venv.site_packages.path.joinpath("simple_project.pth").exists()
    assert simple_poetry.file.parent.resolve().as_posix(
    ) == tmp_venv.site_packages.path.joinpath(
        "simple_project.pth").read_text().strip(os.linesep)

    dist_info = tmp_venv.site_packages.path.joinpath(
        "simple_project-1.2.3.dist-info")
    assert dist_info.exists()
    assert dist_info.joinpath("INSTALLER").exists()
    assert dist_info.joinpath("METADATA").exists()
    assert dist_info.joinpath("RECORD").exists()
    assert dist_info.joinpath("entry_points.txt").exists()

    assert "poetry" == dist_info.joinpath("INSTALLER").read_text()
    assert (
        "[console_scripts]\nbaz=bar:baz.boom.bim\nfoo=foo:bar\nfox=fuz.foo:bar.baz\n\n"
        == dist_info.joinpath("entry_points.txt").read_text())

    metadata = """\
Metadata-Version: 2.1
Name: simple-project
Version: 1.2.3
Summary: Some description.
Home-page: https://python-poetry.org
License: MIT
Keywords: packaging,dependency,poetry
Author: Sébastien Eustace
Author-email: [email protected]
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Project-URL: Documentation, https://python-poetry.org/docs
Project-URL: Repository, https://github.com/python-poetry/poetry
Description-Content-Type: text/x-rst

My Package
==========

"""
    assert metadata == dist_info.joinpath("METADATA").read_text(
        encoding="utf-8")

    records = dist_info.joinpath("RECORD").read_text()
    assert str(
        tmp_venv.site_packages.path.joinpath("simple_project.pth")) in records
    assert str(tmp_venv._bin_dir.joinpath("foo")) in records
    assert str(tmp_venv._bin_dir.joinpath("baz")) in records
    assert str(dist_info.joinpath("METADATA")) in records
    assert str(dist_info.joinpath("INSTALLER")) in records
    assert str(dist_info.joinpath("entry_points.txt")) in records
    assert str(dist_info.joinpath("RECORD")) in records

    baz_script = """\
#!{python}
from bar import baz

if __name__ == '__main__':
    baz.boom.bim()
""".format(python=tmp_venv._bin("python"))

    assert baz_script == tmp_venv._bin_dir.joinpath("baz").read_text()

    foo_script = """\
#!{python}
from foo import bar

if __name__ == '__main__':
    bar()
""".format(python=tmp_venv._bin("python"))

    assert foo_script == tmp_venv._bin_dir.joinpath("foo").read_text()

    fox_script = """\
#!{python}
from fuz.foo import bar

if __name__ == '__main__':
    bar.baz()
""".format(python=tmp_venv._bin("python"))

    assert fox_script == tmp_venv._bin_dir.joinpath("fox").read_text()
Пример #12
0
    def _install_directory(self, operation):
        from poetry.factory import Factory
        from poetry.utils.toml_file import TomlFile

        package = operation.package
        operation_message = self.get_operation_message(operation)

        message = "  <fg=blue;options=bold>•</> {message}: <info>Building...</info>".format(
            message=operation_message,
        )
        self._write(operation, message)

        if package.root_dir:
            req = os.path.join(str(package.root_dir), package.source_url)
        else:
            req = os.path.realpath(package.source_url)

        args = ["install", "--no-deps", "-U"]

        pyproject = TomlFile(os.path.join(req, "pyproject.toml"))

        has_poetry = False
        has_build_system = False
        if pyproject.exists():
            pyproject_content = pyproject.read()
            has_poetry = (
                "tool" in pyproject_content and "poetry" in pyproject_content["tool"]
            )
            # Even if there is a build system specified
            # some versions of pip (< 19.0.0) don't understand it
            # so we need to check the version of pip to know
            # if we can rely on the build system
            pip_version = self._env.pip_version
            pip_version_with_build_system_support = pip_version.__class__(19, 0, 0)
            has_build_system = (
                "build-system" in pyproject_content
                and pip_version >= pip_version_with_build_system_support
            )

        if has_poetry:
            package_poetry = Factory().create_poetry(pyproject.parent)
            if package.develop and not package_poetry.package.build_script:
                from poetry.masonry.builders.editable import EditableBuilder

                # This is a Poetry package in editable mode
                # we can use the EditableBuilder without going through pip
                # to install it, unless it has a build script.
                builder = EditableBuilder(package_poetry, self._env, NullIO())
                builder.build()

                return 0
            elif not has_build_system or package_poetry.package.build_script:
                from poetry.core.masonry.builders.sdist import SdistBuilder

                # We need to rely on creating a temporary setup.py
                # file since the version of pip does not support
                # build-systems
                # We also need it for non-PEP-517 packages
                builder = SdistBuilder(package_poetry)

                with builder.setup_py():
                    if package.develop:
                        args.append("-e")

                    args.append(req)

                    return self.run_pip(*args)

        if package.develop:
            args.append("-e")

        args.append(req)

        return self.run_pip(*args)
Пример #13
0
    def _install_directory(self, operation: Union[Install, Update]) -> int:
        from poetry.factory import Factory

        package = operation.package
        operation_message = self.get_operation_message(operation)

        message = (
            "  <fg=blue;options=bold>•</> {message}: <info>Building...</info>".
            format(message=operation_message, ))
        self._write(operation, message)

        if package.root_dir:
            req = os.path.join(str(package.root_dir), package.source_url)
        else:
            req = os.path.realpath(package.source_url)

        args = ["install", "--no-deps", "-U"]

        pyproject = PyProjectTOML(os.path.join(req, "pyproject.toml"))

        if pyproject.is_poetry_project():
            # Even if there is a build system specified
            # some versions of pip (< 19.0.0) don't understand it
            # so we need to check the version of pip to know
            # if we can rely on the build system
            legacy_pip = self._env.pip_version < self._env.pip_version.__class__(
                19, 0, 0)
            package_poetry = Factory().create_poetry(
                pyproject.file.path.parent)

            if package.develop and not package_poetry.package.build_script:
                from poetry.masonry.builders.editable import EditableBuilder

                # This is a Poetry package in editable mode
                # we can use the EditableBuilder without going through pip
                # to install it, unless it has a build script.
                builder = EditableBuilder(package_poetry, self._env, NullIO())
                builder.build()

                return 0
            elif legacy_pip or package_poetry.package.build_script:
                from poetry.core.masonry.builders.sdist import SdistBuilder

                # We need to rely on creating a temporary setup.py
                # file since the version of pip does not support
                # build-systems
                # We also need it for non-PEP-517 packages
                builder = SdistBuilder(package_poetry)

                with builder.setup_py():
                    if package.develop:
                        args.append("-e")

                    args.append(req)

                    return self.run_pip(*args)

        if package.develop:
            args.append("-e")

        args.append(req)

        return self.run_pip(*args)
Пример #14
0
    def handle(self) -> int:
        from poetry.core.masonry.utils.module import ModuleOrPackageNotFound

        from poetry.masonry.builders.editable import EditableBuilder

        self._installer.use_executor(
            self.poetry.config.get("experimental.new-installer", False))

        if self.option("extras") and self.option("all-extras"):
            self.line_error(
                "<error>You cannot specify explicit"
                " `<fg=yellow;options=bold>--extras</>` while installing"
                " using `<fg=yellow;options=bold>--all-extras</>`.</error>")
            return 1

        if self.option("only-root") and any(
                self.option(key) for key in {"with", "without", "only"}):
            self.line_error(
                "<error>The `<fg=yellow;options=bold>--with</>`,"
                " `<fg=yellow;options=bold>--without</>` and"
                " `<fg=yellow;options=bold>--only</>` options cannot be used with"
                " the `<fg=yellow;options=bold>--only-root</>`"
                " option.</error>")
            return 1

        if self.option("only-root") and self.option("no-root"):
            self.line_error(
                "<error>You cannot specify `<fg=yellow;options=bold>--no-root</>`"
                " when using `<fg=yellow;options=bold>--only-root</>`.</error>"
            )
            return 1

        if self.option("all-extras"):
            extras = list(self.poetry.package.extras.keys())
        else:
            extras = []
            for extra in self.option("extras", []):
                if " " in extra:
                    extras += [e.strip() for e in extra.split(" ")]
                else:
                    extras.append(extra)

        self._installer.extras(extras)

        with_synchronization = self.option("sync")
        if self.option("remove-untracked"):
            self.line_error(
                "<warning>The `<fg=yellow;options=bold>--remove-untracked</>` option is"
                " deprecated, use the `<fg=yellow;options=bold>--sync</>` option"
                " instead.</warning>")

            with_synchronization = True

        self._installer.only_groups(self.activated_groups)
        self._installer.dry_run(self.option("dry-run"))
        self._installer.requires_synchronization(with_synchronization)
        self._installer.verbose(self.io.is_verbose())

        return_code = self._installer.run()

        if return_code != 0:
            return return_code

        if self.option("no-root"):
            return 0

        try:
            builder = EditableBuilder(self.poetry, self._env, self.io)
        except ModuleOrPackageNotFound:
            # This is likely due to the fact that the project is an application
            # not following the structure expected by Poetry
            # If this is a true error it will be picked up later by build anyway.
            return 0

        log_install = ("<b>Installing</> the current project:"
                       f" <c1>{self.poetry.package.pretty_name}</c1>"
                       f" (<{{tag}}>{self.poetry.package.pretty_version}</>)")
        overwrite = self.io.output.is_decorated() and not self.io.is_debug()
        self.line("")
        self.write(log_install.format(tag="c2"))
        if not overwrite:
            self.line("")

        if self.option("dry-run"):
            self.line("")
            return 0

        builder.build()

        if overwrite:
            self.overwrite(log_install.format(tag="success"))
            self.line("")

        return 0
Пример #15
0
def test_builder_installs_proper_files_for_standard_packages(
        simple_poetry: Poetry, tmp_venv: VirtualEnv):
    builder = EditableBuilder(simple_poetry, tmp_venv, NullIO())

    builder.build()

    assert tmp_venv._bin_dir.joinpath("foo").exists()
    pth_file = "simple_project.pth"
    assert tmp_venv.site_packages.exists(pth_file)
    assert (simple_poetry.file.parent.resolve().as_posix() ==
            tmp_venv.site_packages.find(pth_file)[0].read_text().strip(
                os.linesep))

    dist_info = "simple_project-1.2.3.dist-info"
    assert tmp_venv.site_packages.exists(dist_info)

    dist_info = tmp_venv.site_packages.find(dist_info)[0]

    assert dist_info.joinpath("INSTALLER").exists()
    assert dist_info.joinpath("METADATA").exists()
    assert dist_info.joinpath("RECORD").exists()
    assert dist_info.joinpath("entry_points.txt").exists()
    assert dist_info.joinpath("direct_url.json").exists()

    assert not DeepDiff(
        {
            "dir_info": {
                "editable": True
            },
            "url": simple_poetry.file.path.parent.as_uri(),
        },
        json.loads(dist_info.joinpath("direct_url.json").read_text()),
    )

    assert dist_info.joinpath("INSTALLER").read_text() == "poetry"
    assert (dist_info.joinpath("entry_points.txt").read_text() ==
            "[console_scripts]\nbaz=bar:baz.boom.bim\nfoo=foo:bar\n"
            "fox=fuz.foo:bar.baz\n\n")

    metadata = """\
Metadata-Version: 2.1
Name: simple-project
Version: 1.2.3
Summary: Some description.
Home-page: https://python-poetry.org
License: MIT
Keywords: packaging,dependency,poetry
Author: Sébastien Eustace
Author-email: [email protected]
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Project-URL: Documentation, https://python-poetry.org/docs
Project-URL: Repository, https://github.com/python-poetry/poetry
Description-Content-Type: text/x-rst

My Package
==========

"""
    assert metadata == dist_info.joinpath("METADATA").read_text(
        encoding="utf-8")

    with open(dist_info.joinpath("RECORD"), encoding="utf-8", newline="") as f:
        reader = csv.reader(f)
        records = list(reader)

    assert all(len(row) == 3 for row in records)
    record_entries = {row[0] for row in records}
    pth_file = "simple_project.pth"
    assert tmp_venv.site_packages.exists(pth_file)
    assert str(tmp_venv.site_packages.find(pth_file)[0]) in record_entries
    assert str(tmp_venv._bin_dir.joinpath("foo")) in record_entries
    assert str(tmp_venv._bin_dir.joinpath("baz")) in record_entries
    assert str(dist_info.joinpath("METADATA")) in record_entries
    assert str(dist_info.joinpath("INSTALLER")) in record_entries
    assert str(dist_info.joinpath("entry_points.txt")) in record_entries
    assert str(dist_info.joinpath("RECORD")) in record_entries
    assert str(dist_info.joinpath("direct_url.json")) in record_entries

    baz_script = f"""\
#!{tmp_venv.python}
import sys
from bar import baz

if __name__ == '__main__':
    sys.exit(baz.boom.bim())
"""

    assert baz_script == tmp_venv._bin_dir.joinpath("baz").read_text()

    foo_script = f"""\
#!{tmp_venv.python}
import sys
from foo import bar

if __name__ == '__main__':
    sys.exit(bar())
"""

    assert foo_script == tmp_venv._bin_dir.joinpath("foo").read_text()

    fox_script = f"""\
#!{tmp_venv.python}
import sys
from fuz.foo import bar

if __name__ == '__main__':
    sys.exit(bar.baz())
"""

    assert fox_script == tmp_venv._bin_dir.joinpath("fox").read_text()