Skip to content

PEP660 editable VCS dependencies not reinstalled correctly #6362

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
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.
19 changes: 19 additions & 0 deletions pipenv/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import pipenv
from pipenv.patched.pip._internal.commands.install import InstallCommand
from pipenv.patched.pip._internal.index.package_finder import PackageFinder
from pipenv.patched.pip._internal.locations.base import get_src_prefix
from pipenv.patched.pip._internal.req.req_install import InstallRequirement
from pipenv.patched.pip._vendor.packaging.specifiers import SpecifierSet
from pipenv.patched.pip._vendor.packaging.utils import canonicalize_name
Expand Down Expand Up @@ -745,6 +746,24 @@ def is_satisfied(self, req: InstallRequirement):
match.version, prereleases=True
)
if req.link is None:
return True
elif req.editable and req.link.is_vcs:
# For editable VCS dependencies, check if the source directory exists
# This ensures we reinstall if the source checkout is missing
# Use get_src_prefix() to get the appropriate src directory
# This handles both virtualenv and non-virtualenv cases
src_dir = get_src_prefix()

# 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
elif req.editable and req.link.is_file:
requested_path = req.link.file_path
Expand Down
64 changes: 64 additions & 0 deletions tests/integration/test_editable_vcs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
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"
src_dir_venv = Path(p.virtualenv_location) / "src"

# 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"
src_dir_venv = Path(p.virtualenv_location) / "src"

# 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