예제 #1
0
    def _get_operations_from_lock(
        self, locked_repository: Repository
    ) -> Sequence[Operation]:
        installed_repo = self._installed_repository
        ops = []

        extra_packages = self._get_extra_packages(locked_repository)
        for locked in locked_repository.packages:
            is_installed = False
            for installed in installed_repo.packages:
                if locked.name == installed.name:
                    is_installed = True
                    if locked.optional and locked.name not in extra_packages:
                        # Installed but optional and not requested in extras
                        ops.append(Uninstall(locked))
                    elif locked.version != installed.version:
                        ops.append(Update(installed, locked))

            # If it's optional and not in required extras
            # we do not install
            if locked.optional and locked.name not in extra_packages:
                continue

            op = Install(locked)
            if is_installed:
                op.skip("Already installed")

            ops.append(op)

        return ops
예제 #2
0
def test_execute_executes_a_batch_of_operations(
    config, pool, io, tmp_dir, mock_file_downloads
):
    config = Config()
    config.merge({"cache-dir": tmp_dir})

    env = MockEnv(path=Path(tmp_dir))
    executor = Executor(env, pool, config, io)

    file_package = Package("demo", "0.1.0")
    file_package.source_type = "file"
    file_package.source_url = str(
        Path(__file__)
        .parent.parent.joinpath(
            "fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl"
        )
        .resolve()
    )

    directory_package = Package("simple-project", "1.2.3")
    directory_package.source_type = "directory"
    directory_package.source_url = str(
        Path(__file__).parent.parent.joinpath("fixtures/simple_project").resolve()
    )

    git_package = Package("demo", "0.1.0")
    git_package.source_type = "git"
    git_package.source_reference = "master"
    git_package.source_url = "https://github.com/demo/demo.git"

    assert 0 == executor.execute(
        [
            Install(Package("pytest", "3.5.2")),
            Uninstall(Package("attrs", "17.4.0")),
            Update(Package("requests", "2.18.3"), Package("requests", "2.18.4")),
            Uninstall(Package("clikit", "0.2.3")).skip("Not currently installed"),
            Install(file_package),
            Install(directory_package),
            Install(git_package),
        ]
    )

    expected = """
Package operations: 4 installs, 1 update, 1 removal

  • Installing pytest (3.5.2)
  • Removing attrs (17.4.0)
  • Updating requests (2.18.3 -> 2.18.4)
  • Installing demo (0.1.0 {})
  • Installing simple-project (1.2.3 {})
  • Installing demo (0.1.0 master)
""".format(
        file_package.source_url, directory_package.source_url
    )

    expected = set(expected.splitlines())
    output = set(io.fetch_output().splitlines())
    assert expected == output
    assert 5 == len(env.executed)
예제 #3
0
def test_executor_should_use_cached_link_and_hash(
    config, io, pool, mocker, fixture_dir, tmp_dir
):
    # Produce a file:/// URI that is a valid link
    link_cached = Link(
        fixture_dir("distributions")
        .joinpath("demo-0.1.0-py2.py3-none-any.whl")
        .as_uri()
    )
    mocker.patch.object(
        Chef, "get_cached_archive_for_link", side_effect=lambda _: link_cached
    )

    env = MockEnv(path=Path(tmp_dir))
    executor = Executor(env, pool, config, io)

    package = Package("demo", "0.1.0")
    package.files = [
        {
            "file": "demo-0.1.0-py2.py3-none-any.whl",
            "hash": "md5:15507846fd4299596661d0197bfb4f90",
        }
    ]

    archive = executor._download_link(
        Install(package), Link("https://example.com/demo-0.1.0-py2.py3-none-any.whl")
    )

    assert archive == link_cached
예제 #4
0
def test_executor_should_check_every_possible_hash_types(
    config, io, pool, mocker, fixture_dir, tmp_dir
):
    mocker.patch.object(
        Chef, "get_cached_archive_for_link", side_effect=lambda link: link,
    )
    mocker.patch.object(
        Executor,
        "_download_archive",
        return_value=fixture_dir("distributions").joinpath(
            "demo-0.1.0-py2.py3-none-any.whl"
        ),
    )

    env = MockEnv(path=Path(tmp_dir))
    executor = Executor(env, pool, config, io)

    package = Package("demo", "0.1.0")
    package.files = [
        {
            "file": "demo-0.1.0-py2.py3-none-any.whl",
            "hash": "md5:15507846fd4299596661d0197bfb4f90",
        }
    ]

    archive = executor._download_link(
        Install(package), Link("https://example.com/demo-0.1.0-py2.py3-none-any.whl")
    )

    assert archive == fixture_dir("distributions").joinpath(
        "demo-0.1.0-py2.py3-none-any.whl"
    )
예제 #5
0
def test_execute_should_gracefully_handle_io_error(config: Config, pool: Pool,
                                                   mocker: MockerFixture,
                                                   io: BufferedIO,
                                                   env: MockEnv):
    executor = Executor(env, pool, config, io)
    executor.verbose()

    original_write_line = executor._io.write_line

    def write_line(string: str, **kwargs: Any) -> None:
        # Simulate UnicodeEncodeError
        string.encode("ascii")
        original_write_line(string, **kwargs)

    mocker.patch.object(io, "write_line", side_effect=write_line)

    assert executor.execute([Install(Package("clikit", "0.2.3"))]) == 1

    expected = r"""
Package operations: 1 install, 0 updates, 0 removals


\s*Unicode\w+Error
"""

    assert re.match(expected, io.fetch_output())
예제 #6
0
def test_executor_should_delete_incomplete_downloads(config, io, tmp_dir,
                                                     mocker, pool,
                                                     mock_file_downloads):
    fixture = Path(__file__).parent.parent.joinpath(
        "fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl")
    destination_fixture = Path(tmp_dir) / "tomlkit-0.5.3-py2.py3-none-any.whl"
    shutil.copyfile(str(fixture), str(destination_fixture))
    mocker.patch(
        "poetry.installation.executor.Executor._download_archive",
        side_effect=Exception("Download error"),
    )
    mocker.patch(
        "poetry.installation.chef.Chef.get_cached_archive_for_link",
        side_effect=lambda link: link,
    )
    mocker.patch(
        "poetry.installation.chef.Chef.get_cache_directory_for_link",
        return_value=Path(tmp_dir),
    )

    config = Config()
    config.merge({"cache-dir": tmp_dir})

    env = MockEnv(path=Path(tmp_dir))
    executor = Executor(env, pool, config, io)

    with pytest.raises(Exception, match="Download error"):
        executor._download(Install(Package("tomlkit", "0.5.3")))

    assert not destination_fixture.exists()
예제 #7
0
def test_executor_should_use_cached_link_and_hash(
    tmp_venv: VirtualEnv,
    pool: Pool,
    config: Config,
    io: BufferedIO,
    mocker: MockerFixture,
    fixture_dir: FixtureDirGetter,
):
    # Produce a file:/// URI that is a valid link
    link_cached = Link(
        fixture_dir("distributions").joinpath(
            "demo-0.1.0-py2.py3-none-any.whl").as_uri())
    mocker.patch(
        "poetry.installation.chef.Chef.get_cached_archive_for_link",
        return_value=link_cached,
    )

    package = Package("demo", "0.1.0")
    # Set package.files so the executor will attempt to hash the package
    package.files = [{
        "file":
        "demo-0.1.0-py2.py3-none-any.whl",
        "hash":
        "sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a",  # noqa: E501
    }]

    executor = Executor(tmp_venv, pool, config, io)
    archive = executor._download_link(
        Install(package),
        Link("https://example.com/demo-0.1.0-py2.py3-none-any.whl"),
    )
    assert archive == link_cached
예제 #8
0
def test_executor_should_write_pep610_url_references_for_git(
        tmp_venv, pool, config, io, mock_file_downloads):
    package = Package(
        "demo",
        "0.1.2",
        source_type="git",
        source_reference="master",
        source_resolved_reference="123456",
        source_url="https://github.com/demo/demo.git",
    )

    executor = Executor(tmp_venv, pool, config, io)
    executor.execute([Install(package)])
    verify_installed_distribution(
        tmp_venv,
        package,
        {
            "vcs_info": {
                "vcs": "git",
                "requested_revision": "master",
                "commit_id": "123456",
            },
            "url": package.source_url,
        },
    )
예제 #9
0
def test_executor_should_write_pep610_url_references_for_editable_directories(
        tmp_venv, pool, config, io):
    url = Path(__file__).parent.parent.joinpath(
        "fixtures/simple_project").resolve()
    package = Package(
        "simple-project",
        "1.2.3",
        source_type="directory",
        source_url=url.as_posix(),
        develop=True,
    )

    executor = Executor(tmp_venv, pool, config, io)
    executor.execute([Install(package)])

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

    direct_url_file = dist_info.joinpath("direct_url.json")

    assert direct_url_file.exists()

    url_reference = json.loads(direct_url_file.read_text(encoding="utf-8"))

    assert url_reference == {
        "dir_info": {
            "editable": True
        },
        "url": url.as_uri()
    }
예제 #10
0
def test_executor_should_write_pep610_url_references_for_urls(
        tmp_venv, pool, config, io, mock_file_downloads):
    package = Package(
        "demo",
        "0.1.0",
        source_type="url",
        source_url=
        "https://files.pythonhosted.org/demo-0.1.0-py2.py3-none-any.whl",
    )

    executor = Executor(tmp_venv, pool, config, io)
    executor.execute([Install(package)])

    dist_info = tmp_venv.site_packages.path.joinpath("demo-0.1.0.dist-info")
    assert dist_info.exists()

    direct_url_file = dist_info.joinpath("direct_url.json")

    assert direct_url_file.exists()

    url_reference = json.loads(direct_url_file.read_text(encoding="utf-8"))

    assert url_reference == {
        "archive_info": {},
        "url":
        "https://files.pythonhosted.org/demo-0.1.0-py2.py3-none-any.whl",
    }
예제 #11
0
def test_executor_should_write_pep610_url_references_for_git(
        tmp_venv, pool, config, io, mock_file_downloads):
    package = Package(
        "demo",
        "0.1.2",
        source_type="git",
        source_reference="master",
        source_resolved_reference="123456",
        source_url="https://github.com/demo/demo.git",
    )

    executor = Executor(tmp_venv, pool, config, io)
    executor.execute([Install(package)])

    dist_info = tmp_venv.site_packages.path.joinpath("demo-0.1.2.dist-info")
    assert dist_info.exists()

    direct_url_file = dist_info.joinpath("direct_url.json")

    assert direct_url_file.exists()

    url_reference = json.loads(direct_url_file.read_text(encoding="utf-8"))

    assert url_reference == {
        "vcs_info": {
            "vcs": "git",
            "requested_revision": "master",
            "commit_id": "123456",
        },
        "url": "https://github.com/demo/demo.git",
    }
예제 #12
0
def test_execute_works_with_no_ansi_output(mocker, config, pool,
                                           io_not_decorated, tmp_dir,
                                           mock_file_downloads, env):
    config = Config()
    config.merge({"cache-dir": tmp_dir})

    executor = Executor(env, pool, config, io_not_decorated)

    install_output = (
        "some string that does not contain a keyb0ard !nterrupt or cance11ed by u$er"
    )
    mocker.patch.object(env, "_run", return_value=install_output)
    return_code = executor.execute([
        Install(Package("pytest", "3.5.2")),
    ])
    env._run.assert_called_once()

    expected = """
Package operations: 1 install, 0 updates, 0 removals

  • Installing pytest (3.5.2)
"""
    expected = set(expected.splitlines())
    output = set(io_not_decorated.fetch_output().splitlines())
    assert expected == output
    assert 0 == return_code
예제 #13
0
def test_execute_works_with_ansi_output(mocker, config, pool, io_decorated,
                                        tmp_dir, mock_file_downloads, env):
    config = Config()
    config.merge({"cache-dir": tmp_dir})

    executor = Executor(env, pool, config, io_decorated)

    install_output = (
        "some string that does not contain a keyb0ard !nterrupt or cance11ed by u$er"
    )
    mocker.patch.object(env, "_run", return_value=install_output)
    return_code = executor.execute([
        Install(Package("pytest", "3.5.2")),
    ])
    env._run.assert_called_once()

    expected = [
        "\x1b[39;1mPackage operations\x1b[39;22m: \x1b[34m1\x1b[39m install, \x1b[34m0\x1b[39m updates, \x1b[34m0\x1b[39m removals",
        "\x1b[34;1m•\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mpytest\x1b[39m\x1b[39m (\x1b[39m\x1b[39;1m3.5.2\x1b[39;22m\x1b[39m)\x1b[39m: \x1b[34mPending...\x1b[39m",
        "\x1b[34;1m•\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mpytest\x1b[39m\x1b[39m (\x1b[39m\x1b[39;1m3.5.2\x1b[39;22m\x1b[39m)\x1b[39m: \x1b[34mDownloading...\x1b[39m",
        "\x1b[34;1m•\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mpytest\x1b[39m\x1b[39m (\x1b[39m\x1b[39;1m3.5.2\x1b[39;22m\x1b[39m)\x1b[39m: \x1b[34mInstalling...\x1b[39m",
        "\x1b[32;1m•\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mpytest\x1b[39m\x1b[39m (\x1b[39m\x1b[32m3.5.2\x1b[39m\x1b[39m)\x1b[39m",  # finished
    ]
    output = io_decorated.fetch_output()
    # hint: use print(repr(output)) if you need to debug this

    for line in expected:
        assert line in output
    assert 0 == return_code
예제 #14
0
def test_executor_should_write_pep610_url_references_for_directories(
    tmp_venv, pool, config, io
):
    url = Path(__file__).parent.parent.joinpath("fixtures/simple_project").resolve()
    package = Package(
        "simple-project", "1.2.3", source_type="directory", source_url=url.as_posix()
    )

    executor = Executor(tmp_venv, pool, config, io)
    executor.execute([Install(package)])
    verify_installed_distribution(
        tmp_venv, package, {"dir_info": {}, "url": url.as_uri()}
    )
예제 #15
0
def test_executor_should_write_pep610_url_references_for_files(
        tmp_venv, pool, config, io):
    url = (Path(__file__).parent.parent.joinpath(
        "fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl").resolve())
    package = Package("demo",
                      "0.1.0",
                      source_type="file",
                      source_url=url.as_posix())

    executor = Executor(tmp_venv, pool, config, io)
    executor.execute([Install(package)])
    verify_installed_distribution(tmp_venv, package, {
        "archive_info": {},
        "url": url.as_uri()
    })
예제 #16
0
def test_executor_should_write_pep610_url_references_for_urls(
    tmp_venv, pool, config, io, mock_file_downloads
):
    package = Package(
        "demo",
        "0.1.0",
        source_type="url",
        source_url="https://files.pythonhosted.org/demo-0.1.0-py2.py3-none-any.whl",
    )

    executor = Executor(tmp_venv, pool, config, io)
    executor.execute([Install(package)])
    verify_installed_distribution(
        tmp_venv, package, {"archive_info": {}, "url": package.source_url}
    )
예제 #17
0
def test_executor_should_write_pep610_url_references_for_editable_directories(
    tmp_venv: "VirtualEnv", pool: Pool, config: "Config", io: BufferedIO
):
    url = Path(__file__).parent.parent.joinpath("fixtures/simple_project").resolve()
    package = Package(
        "simple-project",
        "1.2.3",
        source_type="directory",
        source_url=url.as_posix(),
        develop=True,
    )

    executor = Executor(tmp_venv, pool, config, io)
    executor.execute([Install(package)])
    verify_installed_distribution(
        tmp_venv, package, {"dir_info": {"editable": True}, "url": url.as_uri()}
    )
예제 #18
0
def test_execute_should_show_operation_as_cancelled_on_subprocess_keyboard_interrupt(
        config, mocker, io, env):
    executor = Executor(env, pool, config, io)
    executor.verbose()

    # A return code of -2 means KeyboardInterrupt in the pip subprocess
    mocker.patch.object(executor, "_install", return_value=-2)

    assert 1 == executor.execute([Install(Package("clikit", "0.2.3"))])

    expected = """
Package operations: 1 install, 0 updates, 0 removals

  • Installing clikit (0.2.3)
  • Installing clikit (0.2.3): Cancelled
"""

    assert expected == io.fetch_output()
예제 #19
0
def test_executor_should_not_write_pep610_url_references_for_cached_package(
    package: Package,
    mocker: MockerFixture,
    fixture_dir: FixtureDirGetter,
    tmp_venv: VirtualEnv,
    pool: Pool,
    config: Config,
    io: BufferedIO,
):
    link_cached = fixture_dir(
        "distributions") / "demo-0.1.0-py2.py3-none-any.whl"

    mocker.patch("poetry.installation.executor.Executor._download",
                 return_value=link_cached)

    executor = Executor(tmp_venv, pool, config, io)
    executor.execute([Install(package)])
    verify_installed_distribution(tmp_venv, package)
예제 #20
0
def test_execute_should_show_errors(config, mocker, io, env):
    executor = Executor(env, pool, config, io)
    executor.verbose()

    mocker.patch.object(executor, "_install", side_effect=Exception("It failed!"))

    assert 1 == executor.execute([Install(Package("clikit", "0.2.3"))])

    expected = """
Package operations: 1 install, 0 updates, 0 removals

  • Installing clikit (0.2.3)

  Exception

  It failed!
"""

    assert expected in io.fetch_output()
예제 #21
0
def test_executor_should_write_pep610_url_references_for_files(
        tmp_venv, pool, config, io):
    url = (Path(__file__).parent.parent.joinpath(
        "fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl").resolve())
    package = Package("demo",
                      "0.1.0",
                      source_type="file",
                      source_url=url.as_posix())

    executor = Executor(tmp_venv, pool, config, io)
    executor.execute([Install(package)])

    dist_info = tmp_venv.site_packages.path.joinpath("demo-0.1.0.dist-info")
    assert dist_info.exists()

    direct_url_file = dist_info.joinpath("direct_url.json")

    assert direct_url_file.exists()

    url_reference = json.loads(direct_url_file.read_text(encoding="utf-8"))

    assert url_reference == {"archive_info": {}, "url": url.as_uri()}
예제 #22
0
def test_executor_should_check_every_possible_hash_types_before_failing(
    config, io, pool, mocker, fixture_dir, tmp_dir
):
    mocker.patch.object(
        Chef, "get_cached_archive_for_link", side_effect=lambda link: link,
    )
    mocker.patch.object(
        Executor,
        "_download_archive",
        return_value=fixture_dir("distributions").joinpath(
            "demo-0.1.0-py2.py3-none-any.whl"
        ),
    )

    env = MockEnv(path=Path(tmp_dir))
    executor = Executor(env, pool, config, io)

    package = Package("demo", "0.1.0")
    package.files = [
        {"file": "demo-0.1.0-py2.py3-none-any.whl", "hash": "md5:123456"},
        {"file": "demo-0.1.0-py2.py3-none-any.whl", "hash": "sha256:123456"},
    ]

    expected_message = (
        "Invalid hashes "
        "("
        "md5:15507846fd4299596661d0197bfb4f90, "
        "sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a"
        ") "
        "for demo (0.1.0) using archive demo-0.1.0-py2.py3-none-any.whl. "
        "Expected one of md5:123456, sha256:123456."
    )

    with pytest.raises(RuntimeError, match=re.escape(expected_message)):
        executor._download_link(
            Install(package),
            Link("https://example.com/demo-0.1.0-py2.py3-none-any.whl"),
        )
예제 #23
0
def test_execute_should_gracefully_handle_io_error(config, mocker, io, env):
    executor = Executor(env, pool, config, io)
    executor.verbose()

    original_write_line = executor._io.write_line

    def write_line(string, flags=None):
        # Simulate UnicodeEncodeError
        string.encode("ascii")
        original_write_line(string, flags)

    mocker.patch.object(io, "write_line", side_effect=write_line)

    assert 1 == executor.execute([Install(Package("clikit", "0.2.3"))])

    expected = r"""
Package operations: 1 install, 0 updates, 0 removals


\s*Unicode\w+Error
"""

    assert re.match(expected, io.fetch_output())
예제 #24
0
    def calculate_operations(self,
                             with_uninstalls: bool = True,
                             synchronize: bool = False) -> list[Operation]:
        from poetry.installation.operations import Install
        from poetry.installation.operations import Uninstall
        from poetry.installation.operations import Update

        operations: list[Operation] = []

        for result_package, priority in self._result_packages:
            installed = False

            for installed_package in self._installed_packages:
                if result_package.name == installed_package.name:
                    installed = True

                    # We have to perform an update if the version or another
                    # attribute of the package has changed (source type, url, ref, ...).
                    if result_package.version != installed_package.version or (
                        (
                            # This has to be done because installed packages cannot
                            # have type "legacy". If a package with type "legacy"
                            # is installed, the installed package has no source_type.
                            # Thus, if installed_package has no source_type and
                            # the result_package has source_type "legacy" (negation of
                            # the following condition), update must not be performed.
                            # This quirk has the side effect that when switching
                            # from PyPI to legacy (or vice versa),
                            # no update is performed.
                            installed_package.source_type
                            or result_package.source_type != "legacy")
                            and not result_package.is_same_package_as(
                                installed_package)):
                        operations.append(
                            Update(installed_package,
                                   result_package,
                                   priority=priority))
                    else:
                        operations.append(
                            Install(result_package).skip("Already installed"))

                    break

            if not installed:
                operations.append(Install(result_package, priority=priority))

        if with_uninstalls:
            for current_package in self._current_packages:
                found = any(current_package.name == result_package.name
                            for result_package, _ in self._result_packages)

                if not found:
                    for installed_package in self._installed_packages:
                        if installed_package.name == current_package.name:
                            operations.append(Uninstall(current_package))

            if synchronize:
                current_package_names = {
                    current_package.name
                    for current_package in self._current_packages
                }
                # We preserve pip/setuptools/wheel when not managed by poetry, this is
                # done to avoid externally managed virtual environments causing
                # unnecessary removals.
                preserved_package_names = {
                    "pip",
                    "setuptools",
                    "wheel",
                } - current_package_names

                for installed_package in self._installed_packages:
                    if (self._root_package and installed_package.name
                            == self._root_package.name):
                        continue

                    if installed_package.name in preserved_package_names:
                        continue

                    if installed_package.name not in current_package_names:
                        operations.append(Uninstall(installed_package))

        return sorted(
            operations,
            key=lambda o: (
                -o.priority,
                o.package.name,
                o.package.version,
            ),
        )
예제 #25
0
def test_execute_executes_a_batch_of_operations(mocker, config, pool, io,
                                                tmp_dir, mock_file_downloads,
                                                env):
    pip_editable_install = mocker.patch(
        "poetry.installation.executor.pip_editable_install", unsafe=not PY36)

    config = Config()
    config.merge({"cache-dir": tmp_dir})

    executor = Executor(env, pool, config, io)

    file_package = Package(
        "demo",
        "0.1.0",
        source_type="file",
        source_url=Path(__file__).parent.parent.joinpath(
            "fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl").resolve(
            ).as_posix(),
    )

    directory_package = Package(
        "simple-project",
        "1.2.3",
        source_type="directory",
        source_url=Path(__file__).parent.parent.joinpath(
            "fixtures/simple_project").resolve().as_posix(),
    )

    git_package = Package(
        "demo",
        "0.1.0",
        source_type="git",
        source_reference="master",
        source_url="https://github.com/demo/demo.git",
        develop=True,
    )

    return_code = executor.execute([
        Install(Package("pytest", "3.5.2")),
        Uninstall(Package("attrs", "17.4.0")),
        Update(Package("requests", "2.18.3"), Package("requests", "2.18.4")),
        Uninstall(Package("clikit", "0.2.3")).skip("Not currently installed"),
        Install(file_package),
        Install(directory_package),
        Install(git_package),
    ])

    expected = """
Package operations: 4 installs, 1 update, 1 removal

  • Installing pytest (3.5.2)
  • Removing attrs (17.4.0)
  • Updating requests (2.18.3 -> 2.18.4)
  • Installing demo (0.1.0 {})
  • Installing simple-project (1.2.3 {})
  • Installing demo (0.1.0 master)
""".format(file_package.source_url, directory_package.source_url)

    expected = set(expected.splitlines())
    output = set(io.fetch_output().splitlines())
    assert expected == output
    assert 5 == len(env.executed)
    assert 0 == return_code
    pip_editable_install.assert_called_once()
예제 #26
0
    def calculate_operations(self,
                             with_uninstalls: bool = True,
                             synchronize: bool = False) -> list[Operation]:
        from poetry.installation.operations import Install
        from poetry.installation.operations import Uninstall
        from poetry.installation.operations import Update

        operations: list[Operation] = []

        for result_package, priority in self._result_packages:
            installed = False

            for installed_package in self._installed_packages:
                if result_package.name == installed_package.name:
                    installed = True

                    if result_package.version != installed_package.version or (
                        (installed_package.source_type
                         or result_package.source_type != "legacy")
                            and not result_package.is_same_package_as(
                                installed_package)):
                        operations.append(
                            Update(installed_package,
                                   result_package,
                                   priority=priority))
                    else:
                        operations.append(
                            Install(result_package).skip("Already installed"))

                    break

            if not installed:
                operations.append(Install(result_package, priority=priority))

        if with_uninstalls:
            for current_package in self._current_packages:
                found = any(current_package.name == result_package.name
                            for result_package, _ in self._result_packages)

                if not found:
                    for installed_package in self._installed_packages:
                        if installed_package.name == current_package.name:
                            operations.append(Uninstall(current_package))

            if synchronize:
                current_package_names = {
                    current_package.name
                    for current_package in self._current_packages
                }
                # We preserve pip/setuptools/wheel when not managed by poetry, this is
                # done to avoid externally managed virtual environments causing
                # unnecessary removals.
                preserved_package_names = {
                    "pip",
                    "setuptools",
                    "wheel",
                } - current_package_names

                for installed_package in self._installed_packages:
                    if (self._root_package and installed_package.name
                            == self._root_package.name):
                        continue

                    if installed_package.name in preserved_package_names:
                        continue

                    if installed_package.name not in current_package_names:
                        operations.append(Uninstall(installed_package))

        return sorted(
            operations,
            key=lambda o: (
                -o.priority,
                o.package.name,
                o.package.version,
            ),
        )
예제 #27
0
    def solve(self, use_latest=None):  # type: (...) -> List[Operation]
        with self._provider.progress():
            start = time.time()
            packages, depths = self._solve(use_latest=use_latest)
            end = time.time()

            if len(self._overrides) > 1:
                self._provider.debug(
                    "Complete version solving took {:.3f} seconds with {} overrides"
                    .format(end - start, len(self._overrides)))
                self._provider.debug("Resolved with overrides: {}".format(
                    ", ".join("({})".format(b) for b in self._overrides)))

        operations = []
        for i, package in enumerate(packages):
            installed = False
            for pkg in self._installed.packages:
                if package.name == pkg.name:
                    installed = True

                    if pkg.source_type == "git" and package.source_type == "git":
                        from poetry.core.vcs.git import Git

                        # Trying to find the currently installed version
                        pkg_source_url = Git.normalize_url(pkg.source_url)
                        package_source_url = Git.normalize_url(
                            package.source_url)
                        for locked in self._locked.packages:
                            if locked.name != pkg.name or locked.source_type != "git":
                                continue

                            locked_source_url = Git.normalize_url(
                                locked.source_url)
                            if (locked.name == pkg.name
                                    and locked.source_type == pkg.source_type
                                    and locked_source_url == pkg_source_url
                                    and locked.source_reference
                                    == pkg.source_reference):
                                pkg = Package(pkg.name, locked.version)
                                pkg.source_type = "git"
                                pkg.source_url = locked.source_url
                                pkg.source_reference = locked.source_reference
                                break

                        if pkg_source_url != package_source_url or (
                                pkg.source_reference !=
                                package.source_reference
                                and not pkg.source_reference.startswith(
                                    package.source_reference)):
                            operations.append(
                                Update(pkg, package, priority=depths[i]))
                        else:
                            operations.append(
                                Install(package).skip("Already installed"))
                    elif package.version != pkg.version:
                        # Checking version
                        operations.append(
                            Update(pkg, package, priority=depths[i]))
                    elif pkg.source_type and package.source_type != pkg.source_type:
                        operations.append(
                            Update(pkg, package, priority=depths[i]))
                    else:
                        operations.append(
                            Install(
                                package,
                                priority=depths[i]).skip("Already installed"))

                    break

            if not installed:
                operations.append(Install(package, priority=depths[i]))

        # Checking for removals
        for pkg in self._locked.packages:
            remove = True
            for package in packages:
                if pkg.name == package.name:
                    remove = False
                    break

            if remove:
                skip = True
                for installed in self._installed.packages:
                    if installed.name == pkg.name:
                        skip = False
                        break

                op = Uninstall(pkg)
                if skip:
                    op.skip("Not currently installed")

                operations.append(op)

        if self._remove_untracked:
            locked_names = {locked.name for locked in self._locked.packages}

            for installed in self._installed.packages:
                if installed.name == self._package.name:
                    continue
                if installed.name in Provider.UNSAFE_PACKAGES:
                    # Never remove pip, setuptools etc.
                    continue
                if installed.name not in locked_names:
                    operations.append(Uninstall(installed))

        return sorted(
            operations,
            key=lambda o: (
                -o.priority,
                o.package.name,
                o.package.version,
            ),
        )
예제 #28
0
def test_execute_executes_a_batch_of_operations(
    mocker: MockerFixture,
    config: Config,
    pool: Pool,
    io: BufferedIO,
    tmp_dir: str,
    mock_file_downloads: None,
    env: MockEnv,
):
    pip_install = mocker.patch("poetry.installation.executor.pip_install")

    config.merge({"cache-dir": tmp_dir})

    executor = Executor(env, pool, config, io)

    file_package = Package(
        "demo",
        "0.1.0",
        source_type="file",
        source_url=Path(__file__).parent.parent.joinpath(
            "fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl").resolve(
            ).as_posix(),
    )

    directory_package = Package(
        "simple-project",
        "1.2.3",
        source_type="directory",
        source_url=Path(__file__).parent.parent.joinpath(
            "fixtures/simple_project").resolve().as_posix(),
    )

    git_package = Package(
        "demo",
        "0.1.0",
        source_type="git",
        source_reference="master",
        source_url="https://github.com/demo/demo.git",
        develop=True,
    )

    return_code = executor.execute([
        Install(Package("pytest", "3.5.2")),
        Uninstall(Package("attrs", "17.4.0")),
        Update(Package("requests", "2.18.3"), Package("requests", "2.18.4")),
        Uninstall(Package("clikit", "0.2.3")).skip("Not currently installed"),
        Install(file_package),
        Install(directory_package),
        Install(git_package),
    ])

    expected = f"""
Package operations: 4 installs, 1 update, 1 removal

  • Installing pytest (3.5.2)
  • Removing attrs (17.4.0)
  • Updating requests (2.18.3 -> 2.18.4)
  • Installing demo (0.1.0 {file_package.source_url})
  • Installing simple-project (1.2.3 {directory_package.source_url})
  • Installing demo (0.1.0 master)
"""

    expected = set(expected.splitlines())
    output = set(io.fetch_output().splitlines())
    assert output == expected
    assert len(env.executed) == 1
    assert return_code == 0
    assert pip_install.call_count == 5
    assert pip_install.call_args.kwargs.get("upgrade", False)
    assert pip_install.call_args.kwargs.get("editable", False)