Repository: pypa/pipenv

Issue:
Using dependencies from a URL fails on Windows
### Issue description

Using dependencies from a URL is broken on Windows since ``pipenv==2023.9.7``. This is likely caused by treating the URL as filesystem path (which on Windows will change the slashes).

Might be caused by https://github.com/pypa/pipenv/commit/91743a602933dc817af4c9164985358bb9125bb4.

### Expected result

```
λ pipenv lock
Locking [packages] dependencies...
Building requirements...
Resolving dependencies...
Success!
Locking [dev-packages] dependencies...
Updated Pipfile.lock (ce05cad406b5493dd778394bb280d21c43453a535aa20df07333d0e22d99b03f)!
```

### Actual result
```
pipenv.patched.pip._internal.exceptions.InstallationError: Invalid requirement: '\\files.pythonhosted.org\\packages\\70\\8e\\0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44\\requests==2.31.0'
Hint: It looks like a path. File '\files.pythonhosted.org\packages\70\8e\0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44\requests==2.31.0' does not exist.
```

### Steps to replicate
```
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[packages]
requests = { file = "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl" }

[requires]
python_version = "3.10"
```



Discussion:

Resolving patch:
diff --git a/pipenv/utils/dependencies.py b/pipenv/utils/dependencies.py
--- a/pipenv/utils/dependencies.py
+++ b/pipenv/utils/dependencies.py
@@ -660,34 +660,43 @@ def find_package_name_from_directory(directory):
     return None
 
 
+def ensure_path_is_relative(file_path):
+    abs_path = Path(file_path).resolve()
+    current_dir = Path.cwd()
+
+    # Check if the paths are on different drives
+    if abs_path.drive != current_dir.drive:
+        # If on different drives, return the absolute path
+        return abs_path
+
+    try:
+        # Try to create a relative path
+        return abs_path.relative_to(current_dir)
+    except ValueError:
+        # If the direct relative_to fails, manually compute the relative path
+        common_parts = 0
+        for part_a, part_b in zip(abs_path.parts, current_dir.parts):
+            if part_a == part_b:
+                common_parts += 1
+            else:
+                break
+
+        # Number of ".." needed are the extra parts in the current directory
+        # beyond the common parts
+        up_levels = [".."] * (len(current_dir.parts) - common_parts)
+        # The relative path is constructed by going up as needed and then
+        # appending the non-common parts of the absolute path
+        rel_parts = up_levels + list(abs_path.parts[common_parts:])
+        relative_path = Path(*rel_parts)
+        return str(relative_path)
+
+
 def determine_path_specifier(package: InstallRequirement):
     if package.link:
         if package.link.scheme in ["http", "https"]:
             return package.link.url_without_fragment
         if package.link.scheme == "file":
-            abs_path = Path(package.link.file_path).resolve()
-            current_dir = Path.cwd()
-
-            try:
-                relative_path = abs_path.relative_to(current_dir)
-                return relative_path.as_posix()
-            except ValueError:
-                # If the direct relative_to fails, manually compute the relative path
-                common_parts = 0
-                for part_a, part_b in zip(abs_path.parts, current_dir.parts):
-                    if part_a == part_b:
-                        common_parts += 1
-                    else:
-                        break
-
-                # Number of ".." needed are the extra parts in the current directory
-                # beyond the common parts
-                up_levels = [".."] * (len(current_dir.parts) - common_parts)
-                # The relative path is constructed by going up as needed and then
-                # appending the non-common parts of the absolute path
-                rel_parts = up_levels + list(abs_path.parts[common_parts:])
-                relative_path = Path(*rel_parts)
-                return relative_path.as_posix()
+            return ensure_path_is_relative(package.link.file_path)
 
 
 def determine_vcs_specifier(package: InstallRequirement):
@@ -1003,19 +1012,16 @@ def expansive_install_req_from_line(
     return install_req, name
 
 
-def _file_path_from_pipfile(path_obj, pipfile_entry):
+def file_path_from_pipfile(path_str, pipfile_entry):
     """Creates an installable file path from a pipfile entry.
     Handles local and remote paths, files and directories;
     supports extras and editable specification.
     Outputs a pip installable line.
     """
-    parsed_url = urlparse(str(path_obj))
-    if parsed_url.scheme in ["http", "https", "ftp", "file"]:
-        req_str = str(path_obj)
-    elif path_obj.is_absolute():
-        req_str = str(path_obj.as_posix())
+    if path_str.startswith(("http:", "https:", "ftp:")):
+        req_str = path_str
     else:
-        req_str = f"./{str(path_obj.as_posix())}"
+        req_str = ensure_path_is_relative(path_str)
 
     if pipfile_entry.get("extras"):
         req_str = f"{req_str}[{','.join(pipfile_entry['extras'])}]"
@@ -1076,11 +1082,9 @@ def install_req_from_pipfile(name, pipfile):
         else:
             req_str = f"{name}{extras_str}@ {req_str}{subdirectory}"
     elif "path" in _pipfile:
-        path_obj = Path(_pipfile["path"])
-        req_str = _file_path_from_pipfile(path_obj, _pipfile)
+        req_str = file_path_from_pipfile(_pipfile["path"], _pipfile)
     elif "file" in _pipfile:
-        path_obj = Path(_pipfile["file"])
-        req_str = _file_path_from_pipfile(path_obj, _pipfile)
+        req_str = file_path_from_pipfile(_pipfile["file"], _pipfile)
     else:
         # We ensure version contains an operator. Default to equals (==)
         _pipfile["version"] = version = get_version(pipfile)


Test patch:
diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py
--- a/tests/integration/conftest.py
+++ b/tests/integration/conftest.py
@@ -166,7 +166,7 @@ def write(self):
 
     @classmethod
     def get_fixture_path(cls, path, fixtures="test_artifacts"):
-        return Path(__file__).resolve().parent.parent / fixtures / path
+        return Path(Path(__file__).resolve().parent.parent / fixtures / path)
 
 
 class _PipenvInstance:
diff --git a/tests/integration/test_install_twists.py b/tests/integration/test_install_twists.py
--- a/tests/integration/test_install_twists.py
+++ b/tests/integration/test_install_twists.py
@@ -182,7 +182,7 @@ def test_local_tar_gz_file(pipenv_instance_private_pypi, testsroot):
     file_name = "requests-2.19.1.tar.gz"
 
     with pipenv_instance_private_pypi() as p:
-        requests_path = p._pipfile.get_fixture_path(f"{file_name}").as_posix()
+        requests_path = p._pipfile.get_fixture_path(f"{file_name}")
 
         # This tests for a bug when installing a zipfile
         c = p.pipenv(f"install {requests_path}")
@@ -248,7 +248,7 @@ def test_multiple_editable_packages_should_not_race(pipenv_instance_private_pypi
         """
 
         for pkg_name in pkgs:
-            source_path = p._pipfile.get_fixture_path(f"git/{pkg_name}/").as_posix()
+            source_path = p._pipfile.get_fixture_path(f"git/{pkg_name}/")
             shutil.copytree(source_path, pkg_name)
 
             pipfile_string += f'"{pkg_name}" = {{path = "./{pkg_name}", editable = true}}\n'
diff --git a/tests/integration/test_install_uri.py b/tests/integration/test_install_uri.py
--- a/tests/integration/test_install_uri.py
+++ b/tests/integration/test_install_uri.py
@@ -53,9 +53,8 @@ def test_urls_work(pipenv_instance_pypi):
 @pytest.mark.files
 def test_file_urls_work(pipenv_instance_pypi):
     with pipenv_instance_pypi() as p:
-        whl = Path(__file__).parent.parent.joinpath(
-            "pypi", "six", "six-1.11.0-py2.py3-none-any.whl"
-        )
+        whl = Path(Path(__file__).resolve().parent.parent / "pypi" / "six" / "six-1.11.0-py2.py3-none-any.whl")
+
         try:
             whl = whl.resolve()
         except OSError:
@@ -172,7 +171,7 @@ def test_install_specifying_index_url(pipenv_instance_private_pypi):
 def test_install_local_vcs_not_in_lockfile(pipenv_instance_pypi):
     with pipenv_instance_pypi() as p:
         # six_path = os.path.join(p.path, "six")
-        six_path = p._pipfile.get_fixture_path("git/six/").as_posix()
+        six_path = p._pipfile.get_fixture_path("git/six/")
         c = subprocess_run(["git", "clone", six_path, "./six"])
         assert c.returncode == 0
         c = p.pipenv("install -e ./six")