Skip to content

Commit

Permalink
fix: Broken cache on Windows (python-poetry#4531)
Browse files Browse the repository at this point in the history
Closes python-poetry#4479

The previous implementation would fail to install packages on Windows
because it creates a `Path` starting with a slash. Such a `Path` is
invalid on Windows. Instead, use `Link` and `url_to_path`.
  • Loading branch information
serverwentdown authored and edvardm committed Nov 24, 2021
1 parent 90738dd commit 2f4e84c
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 8 deletions.
14 changes: 9 additions & 5 deletions poetry/installation/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from poetry.core.packages.file_dependency import FileDependency
from poetry.core.packages.package import Package
from poetry.core.packages.utils.link import Link
from poetry.core.packages.utils.utils import url_to_path
from poetry.core.pyproject.toml import PyProjectTOML
from poetry.utils._compat import decode
from poetry.utils.env import EnvCommandError
Expand Down Expand Up @@ -119,7 +120,7 @@ def verbose(self, verbose: bool = True) -> "Executor":
return self

def pip_install(
self, req: Union[Path, str], upgrade: bool = False, editable: bool = False
self, req: Union[Path, Link], upgrade: bool = False, editable: bool = False
) -> int:
func = pip_install
if editable:
Expand Down Expand Up @@ -504,7 +505,7 @@ def _install(self, operation: Union[Install, Update]) -> int:
)
)
self._write(operation, message)
return self.pip_install(str(archive), upgrade=operation.job_type == "update")
return self.pip_install(archive, upgrade=operation.job_type == "update")

def _update(self, operation: Union[Install, Update]) -> int:
return self._install(operation)
Expand Down Expand Up @@ -678,17 +679,20 @@ def _download_link(self, operation: Union[Install, Update], link: Link) -> Link:
return archive

@staticmethod
def _validate_archive_hash(archive: Path, package: Package) -> str:
def _validate_archive_hash(archive: Union[Path, Link], package: Package) -> str:
archive_path = (
url_to_path(archive.url) if isinstance(archive, Link) else archive
)
file_dep = FileDependency(
package.name,
Path(archive.path) if isinstance(archive, Link) else archive,
archive_path,
)
archive_hash = "sha256:" + file_dep.hash()
known_hashes = {f["hash"] for f in package.files}

if archive_hash not in known_hashes:
raise RuntimeError(
f"Hash for {package} from archive {archive.name} not found in known hashes (was: {archive_hash})"
f"Hash for {package} from archive {archive_path.name} not found in known hashes (was: {archive_hash})"
)

return archive_hash
Expand Down
10 changes: 7 additions & 3 deletions poetry/utils/pip.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@
from pathlib import Path
from typing import Union

from poetry.core.packages.utils.link import Link
from poetry.core.packages.utils.utils import url_to_path
from poetry.exceptions import PoetryException
from poetry.utils.env import Env
from poetry.utils.env import EnvCommandError
from poetry.utils.env import ephemeral_environment


def pip_install(
path: Union[Path, str],
path: Union[Path, Link],
environment: Env,
editable: bool = False,
deps: bool = False,
upgrade: bool = False,
) -> Union[int, str]:
path = Path(path) if isinstance(path, str) else path
path = url_to_path(path.url) if isinstance(path, Link) else path
is_wheel = path.suffix == ".whl"

# We disable version check here as we are already pinning to version available in either the
Expand Down Expand Up @@ -60,7 +62,9 @@ def pip_install(
raise PoetryException(f"Failed to install {path.as_posix()}") from e


def pip_editable_install(directory: Path, environment: Env) -> Union[int, str]:
def pip_editable_install(
directory: Union[Path, Link], environment: Env
) -> Union[int, str]:
return pip_install(
path=directory, environment=environment, editable=True, deps=False, upgrade=True
)
32 changes: 32 additions & 0 deletions tests/installation/test_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from poetry.config.config import Config
from poetry.core.packages.package import Package
from poetry.core.packages.utils.link import Link
from poetry.core.utils._compat import PY36
from poetry.installation.executor import Executor
from poetry.installation.operations import Install
Expand Down Expand Up @@ -462,3 +463,34 @@ def test_executor_should_write_pep610_url_references_for_git(
"url": package.source_url,
},
)


def test_executor_should_use_cached_link_and_hash(
tmp_venv, pool, config, io, mocker, fixture_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(
"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",
}
]

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
11 changes: 11 additions & 0 deletions tests/utils/test_pip.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import pytest

from poetry.core.packages.utils.link import Link
from poetry.core.packages.utils.utils import path_to_url
from poetry.utils.pip import pip_install


Expand All @@ -12,6 +14,15 @@ def test_pip_install_successful(tmp_dir, tmp_venv, fixture_dir):
assert "Successfully installed demo-0.1.0" in result


def test_pip_install_link(tmp_dir, tmp_venv, fixture_dir):
file_path = Link(
path_to_url(fixture_dir("distributions/demo-0.1.0-py2.py3-none-any.whl"))
)
result = pip_install(file_path, tmp_venv)

assert "Successfully installed demo-0.1.0" in result


def test_pip_install_with_keyboard_interrupt(tmp_dir, tmp_venv, fixture_dir, mocker):
file_path = fixture_dir("distributions/demo-0.1.0-py2.py3-none-any.whl")
mocker.patch("subprocess.run", side_effect=KeyboardInterrupt())
Expand Down

0 comments on commit 2f4e84c

Please sign in to comment.