Skip to content

Commit

Permalink
Fix issue-6011 direct file url path (#6012)
Browse files Browse the repository at this point in the history
* Refactor this path logic to file url bug and re-use relative pathing logic.
* Handle case where the drive letter is different and so relative path may not be possible
* Add news fragment
  • Loading branch information
matteius committed Nov 14, 2023
1 parent bc668e6 commit cd961a4
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 41 deletions.
1 change: 1 addition & 0 deletions news/6011.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix Using dependencies from a URL fails on Windows.
72 changes: 38 additions & 34 deletions pipenv/utils/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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'])}]"
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/test_install_twists.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
Expand Down Expand Up @@ -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'
Expand Down
7 changes: 3 additions & 4 deletions tests/integration/test_install_uri.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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")
Expand Down

0 comments on commit cd961a4

Please sign in to comment.