diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index cd4a2b9c291..1a5b9820fa2 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -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 @@ -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: @@ -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) @@ -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 diff --git a/poetry/utils/pip.py b/poetry/utils/pip.py index 416c56b97ae..3c56a295d02 100644 --- a/poetry/utils/pip.py +++ b/poetry/utils/pip.py @@ -4,6 +4,8 @@ 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 @@ -11,13 +13,13 @@ 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 @@ -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 ) diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index de3945661fd..5da4ef2d492 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -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 @@ -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 diff --git a/tests/utils/test_pip.py b/tests/utils/test_pip.py index d13d68992e8..11c38b841fc 100644 --- a/tests/utils/test_pip.py +++ b/tests/utils/test_pip.py @@ -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 @@ -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())