Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/6362.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix for PEP660 editable VCS dependencies not reinstalled correctly.
18 changes: 18 additions & 0 deletions pipenv/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,24 @@ def is_satisfied(self, req: InstallRequirement):
None,
)
if match is not None:
# For editable VCS dependencies, check if the source directory exists first
# This ensures we reinstall if the source checkout is missing
if req.editable and req.link and req.link.is_vcs:
# Use the environment's prefix to get the src directory
# (get_src_prefix() uses sys.prefix which is pipenv's venv, not the project's)
src_dir = os.path.join(str(self.prefix), "src")

# If the src directory doesn't exist, the requirement is not satisfied
if not os.path.exists(src_dir):
return False

# If we have a specific package directory, check that too
if req.name:
pkg_dir = os.path.join(src_dir, req.name)
if not os.path.exists(pkg_dir):
return False

return True
if req.specifier is not None:
return SpecifierSet(str(req.specifier)).contains(
match.version, prereleases=True
Expand Down
8 changes: 8 additions & 0 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,14 @@ def lockfile(self):
def lockfile_path(self):
return Path(os.sep.join([self.path, "Pipfile.lock"]))

@property
def virtualenv_location(self):
"""Get the virtualenv location for this instance."""
c = self.pipenv("--venv")
if c.returncode == 0:
return c.stdout.strip()
return None


if sys.version_info[:2] <= (3, 8):
# Windows python3.8 fails without this patch. Additional details: https://bugs.python.org/issue42796
Expand Down
66 changes: 66 additions & 0 deletions tests/integration/test_editable_vcs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import shutil
from pathlib import Path

import pytest


@pytest.mark.integration
@pytest.mark.install
@pytest.mark.editable
@pytest.mark.vcs
def test_editable_vcs_reinstall(pipenv_instance_private_pypi):
"""Test that editable VCS dependencies are reinstalled when the source checkout is missing."""
with pipenv_instance_private_pypi() as p:
# Create a Pipfile with an editable VCS dependency
with open(p.pipfile_path, "w") as f:
f.write("""
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
gunicorn = {git = "https://github.com/benoitc/gunicorn", ref = "23.0.0", editable = true}
""".strip())

# Install the dependency
c = p.pipenv("install")
assert c.returncode == 0, f"Failed to install: {c.stderr}"

# Verify the src directory was created
# The src directory could be in the project directory or in the virtualenv directory
src_dir_project = Path(p.path) / "src"
venv_location = p.virtualenv_location
src_dir_venv = Path(venv_location) / "src" if venv_location else None

# Check if either src directory exists
src_dir = src_dir_project if src_dir_project.exists() else src_dir_venv
assert src_dir.exists(), f"src directory was not created in either {src_dir_project} or {src_dir_venv}"
assert any(src_dir.iterdir()), "src directory is empty"

# Import the package to verify it's installed correctly
c = p.pipenv("run python -c 'import gunicorn'")
assert c.returncode == 0, f"Failed to import gunicorn: {c.stderr}"

# Remove the src directory to simulate the issue
shutil.rmtree(src_dir)
assert not src_dir.exists(), "Failed to remove src directory"

# Run pipenv install again to see if it reinstalls the dependency
c = p.pipenv("install")
assert c.returncode == 0, f"Failed to reinstall: {c.stderr}"

# Verify the src directory was recreated
# Check both possible locations again
src_dir_project = Path(p.path) / "src"
venv_location = p.virtualenv_location
src_dir_venv = Path(venv_location) / "src" if venv_location else None

# Check if either src directory exists
src_dir = src_dir_project if src_dir_project.exists() else src_dir_venv
assert src_dir.exists(), f"src directory was not recreated in either {src_dir_project} or {src_dir_venv}"
assert any(src_dir.iterdir()), "recreated src directory is empty"

# Import the package again to verify it's reinstalled correctly
c = p.pipenv("run python -c 'import gunicorn'")
assert c.returncode == 0, f"Failed to import gunicorn after reinstall: {c.stderr}"
Loading