From cf76dbf7adf44e0b5571b0dba3b11aba91a63917 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Thu, 20 Mar 2025 19:14:43 -0400 Subject: [PATCH 1/6] Fix issue where editable requirements are not reinstalled --- pipenv/environment.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pipenv/environment.py b/pipenv/environment.py index 58fbf1506..7279ce6eb 100644 --- a/pipenv/environment.py +++ b/pipenv/environment.py @@ -743,6 +743,20 @@ 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 + src_dir = os.path.join(os.getcwd(), "src") + 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 From ab609954843851859e14d330201ab4ec55c6e450 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Thu, 20 Mar 2025 19:15:16 -0400 Subject: [PATCH 2/6] Add new tests --- tests/integration/test_editable_vcs.py | 56 ++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 tests/integration/test_editable_vcs.py diff --git a/tests/integration/test_editable_vcs.py b/tests/integration/test_editable_vcs.py new file mode 100644 index 000000000..e51135671 --- /dev/null +++ b/tests/integration/test_editable_vcs.py @@ -0,0 +1,56 @@ +import os +import shutil +from pathlib import Path + +import pytest + +from pipenv.utils.processes import subprocess_run + + +@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 + src_dir = Path(p.path) / "src" + assert src_dir.exists(), "src directory was not created" + 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 + assert src_dir.exists(), "src directory was not recreated" + 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}" From 75fca1f8ac4114149374e6969eba945335ce8217 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Thu, 20 Mar 2025 19:31:48 -0400 Subject: [PATCH 3/6] Fix issue where editable requirements are not reinstalled --- pipenv/environment.py | 18 +++++++++++++++++- tests/integration/test_editable_vcs.py | 17 ++++++++++++++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/pipenv/environment.py b/pipenv/environment.py index 7279ce6eb..bc650de0d 100644 --- a/pipenv/environment.py +++ b/pipenv/environment.py @@ -747,7 +747,23 @@ def is_satisfied(self, req: InstallRequirement): 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 - src_dir = os.path.join(os.getcwd(), "src") + + # Check for src directory in both possible locations + src_dir_cwd = os.path.join(os.getcwd(), "src") + src_dir_venv = ( + os.path.join(self.prefix, "src") if hasattr(self, "prefix") else None + ) + + # Use the src directory that exists, or default to the current working directory + src_dir = src_dir_cwd + if ( + not os.path.exists(src_dir_cwd) + and src_dir_venv + and os.path.exists(src_dir_venv) + ): + src_dir = src_dir_venv + + # If neither src directory exists, the requirement is not satisfied if not os.path.exists(src_dir): return False diff --git a/tests/integration/test_editable_vcs.py b/tests/integration/test_editable_vcs.py index e51135671..6169acad3 100644 --- a/tests/integration/test_editable_vcs.py +++ b/tests/integration/test_editable_vcs.py @@ -31,8 +31,13 @@ def test_editable_vcs_reinstall(pipenv_instance_private_pypi): assert c.returncode == 0, f"Failed to install: {c.stderr}" # Verify the src directory was created - src_dir = Path(p.path) / "src" - assert src_dir.exists(), "src directory was not 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 @@ -48,7 +53,13 @@ def test_editable_vcs_reinstall(pipenv_instance_private_pypi): assert c.returncode == 0, f"Failed to reinstall: {c.stderr}" # Verify the src directory was recreated - assert src_dir.exists(), "src directory was not 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 From a76baf3354276ba17a4976c592810f079d731328 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Wed, 9 Apr 2025 02:11:27 -0400 Subject: [PATCH 4/6] PR feedback --- pipenv/environment.py | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/pipenv/environment.py b/pipenv/environment.py index 8cada26d9..2001e6348 100644 --- a/pipenv/environment.py +++ b/pipenv/environment.py @@ -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 @@ -749,23 +750,11 @@ def is_satisfied(self, req: InstallRequirement): 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() - # Check for src directory in both possible locations - src_dir_cwd = os.path.join(os.getcwd(), "src") - src_dir_venv = ( - os.path.join(self.prefix, "src") if hasattr(self, "prefix") else None - ) - - # Use the src directory that exists, or default to the current working directory - src_dir = src_dir_cwd - if ( - not os.path.exists(src_dir_cwd) - and src_dir_venv - and os.path.exists(src_dir_venv) - ): - src_dir = src_dir_venv - - # If neither src directory exists, the requirement is not satisfied + # If the src directory doesn't exist, the requirement is not satisfied if not os.path.exists(src_dir): return False From c7d11a6ca4f141bc43c4ec3c94d319170f54df7b Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Wed, 9 Apr 2025 02:12:23 -0400 Subject: [PATCH 5/6] fix ruff --- tests/integration/test_editable_vcs.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/integration/test_editable_vcs.py b/tests/integration/test_editable_vcs.py index 6169acad3..52f38b267 100644 --- a/tests/integration/test_editable_vcs.py +++ b/tests/integration/test_editable_vcs.py @@ -1,11 +1,8 @@ -import os import shutil from pathlib import Path import pytest -from pipenv.utils.processes import subprocess_run - @pytest.mark.integration @pytest.mark.install @@ -34,7 +31,7 @@ def test_editable_vcs_reinstall(pipenv_instance_private_pypi): # 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}" @@ -56,7 +53,7 @@ def test_editable_vcs_reinstall(pipenv_instance_private_pypi): # 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}" From b46da71183464ea30fff817a4f4beba538c0cc72 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Wed, 9 Apr 2025 02:13:07 -0400 Subject: [PATCH 6/6] add news fragment --- news/6362.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/6362.bugfix.rst diff --git a/news/6362.bugfix.rst b/news/6362.bugfix.rst new file mode 100644 index 000000000..2a4ff2472 --- /dev/null +++ b/news/6362.bugfix.rst @@ -0,0 +1 @@ +Fix for PEP660 editable VCS dependencies not reinstalled correctly.