Repository: python-poetry/poetry

Issue:
Poetry on Windows fails with File does not exists error
<!--
  Hi there! Thank you for discovering and submitting an issue.

  Before you submit this; let's make sure of a few things.
  Please make sure the following boxes are ticked if they are correct.
  If not, please try and fulfill these first.
-->

<!-- Checked checkbox should look like this: [x] -->
- [x] I am on the [latest](https://github.com/python-poetry/poetry/releases/latest) Poetry version.
- [x] I have searched the [issues](https://github.com/python-poetry/poetry/issues) of this repo and believe that this is not a duplicate.
- [x] If an exception occurs when executing a command, I executed it again in debug mode (`-vvv` option).

<!--
  Once those are done, if you're able to fill in the following list with your information,
  it'd be very helpful to whoever handles the issue.
-->

- **OS version and name**: Windows 10
- **Poetry version**: 1.2.0a2
- **Link of a [Gist](https://gist.github.com/) with the contents of your pyproject.toml file**: Will publish if necessary for this issue, don't think it is.

## Issue
<!-- Now feel free to write your issue, but please be descriptive! Thanks again 🙌 ❤️ -->
When trying to `poetry add` a dependency or do `poetry install` or `poetry update`, the command fails with an error saying that the file does not exist. The file refers to the weel of any dependencies that need to be installed. However, the file actually does exist at the correct location, but is somehow not found by poetry. I think this is due to a leading `\` in the path. I haven't looked at the code, but I use bash bundled with git-for-windows in Windows Terminal. I assume that has something to do with the problem.

This is the error message, you can see the leading backslash in the path which I think is the culprit:

```
  ValueError

  File \C:\Users\<user>\AppData\Local\pypoetry\Cache\artifacts\ac\59\ac\9fc6b3c55594c6a7395665b20538ee05fb2ade328e723d2761b44c70ca\typed_ast-1.4.3-cp37-cp37m-win_amd64.whl does not exist

  at c:\users\<user>\appdata\roaming\pypoetry\venv\lib\site-packages\poetry\core\packages\file_dependency.py:41 in __init__
       37│             except FileNotFoundError:
       38│                 raise ValueError("Directory {} does not exist".format(self._path))
       39│
       40│         if not self._full_path.exists():
    →  41│             raise ValueError("File {} does not exist".format(self._path))
       42│
       43│         if self._full_path.is_dir():
       44│             raise ValueError("{} is a directory, expected a file".format(self._path))
       45│
```

However, the file does exist:

```
❯ file "C:\Users\<user>\AppData\Local\pypoetry\Cache\artifacts\ac\59\ac\9fc6b3c55594c6a7395665b20538ee05fb2ade328e723d2761b44c70ca\typed_ast-1.4.3-cp37-cp37m-win_amd64.whl"
C:\Users\<user>\AppData\Local\pypoetry\Cache\artifacts\ac\59\ac\9fc6b3c55594c6a7395665b20538ee05fb2ade328e723d2761b44c70ca\typed_ast-1.4.3-cp37-cp37m-win_amd64.whl: Zip archive data, at least v2.0 to extract, compression method=deflate
```

**Edit**:

I get the same error using powershell, so it seems like the issue is not related to using bash.


Discussion:
As of today I have the exact same problem. (Havent updated any package in a while, so might have been there earlier).

So far I've tried:

* updating poetry
* clearing the poetry cache
* removing all virtual envs
* reinstalling all packages
* reinstalling poetry

I am on the latest version of `poetry`.
The cause of the issue is in executor.py and core/packages/utils/link.py.

`Link`s (`file:///C:/Users...`) are converted into absolute paths (`/C:/Users...`) without accounting for Windows.

This issue only surfaces when using the cached local packages. 

Resolving patch:
diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py
--- 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
--- 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
     )


Test patch:
diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py
--- 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
--- 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())