Repository: pdm-project/pdm
Pull request #1666 (2023-02-03 08:02:10)

fix: allow force re-creating venv in project
## Pull Request Checklist

- [x] A news fragment is added in `news/` describing what is new.
- [ ] Test cases added for changed code.

## Describe what you have changed in this PR.



Patch:
diff --git a/news/1666.feature.md b/news/1666.feature.md
new file mode 100644
index 00000000..5e2ab3bb
--- /dev/null
+++ b/news/1666.feature.md
@@ -0,0 +1,1 @@
+Allow creating venv in project forcely if it already exists.
diff --git a/src/pdm/cli/commands/venv/backends.py b/src/pdm/cli/commands/venv/backends.py
index f0158cb7..02033c4b 100644
--- a/src/pdm/cli/commands/venv/backends.py
+++ b/src/pdm/cli/commands/venv/backends.py
@@ -78,11 +78,17 @@ class Backend(abc.ABC):
         if not location.exists():
             return
         if not force:
-            raise VirtualenvCreateError(f"The location {location} is not empty")
-        self.project.core.ui.echo(
-            f"Cleaning existing target directory {location}", err=True
-        )
-        shutil.rmtree(location)
+            raise VirtualenvCreateError(
+                f"The location {location} is not empty, add --force to overwrite it."
+            )
+        if location.is_file():
+            self.project.core.ui.echo(f"Removing existing file {location}", err=True)
+            location.unlink()
+        else:
+            self.project.core.ui.echo(
+                f"Cleaning existing target directory {location}", err=True
+            )
+            shutil.rmtree(location)
 
     def get_location(self, name: str | None) -> Path:
         venv_parent = Path(self.project.config["venv.location"])
diff --git a/src/pdm/cli/commands/venv/create.py b/src/pdm/cli/commands/venv/create.py
index aab4fb65..eaa2070e 100644
--- a/src/pdm/cli/commands/venv/create.py
+++ b/src/pdm/cli/commands/venv/create.py
@@ -45,11 +45,7 @@ class CreateCommand(BaseCommand):
         )
 
     def handle(self, project: Project, options: argparse.Namespace) -> None:
-        in_project = (
-            project.config["venv.in_project"]
-            and not options.name
-            and not project.root.joinpath(".venv").exists()
-        )
+        in_project = project.config["venv.in_project"] and not options.name
         prompt = project.config["venv.prompt"]
         backend: str = options.backend or project.config["venv.backend"]
         venv_backend = BACKENDS[backend](project, options.python)


Test patch:
diff --git a/tests/cli/test_venv.py b/tests/cli/test_venv.py
index ac22b6fb..9440b291 100644
--- a/tests/cli/test_venv.py
+++ b/tests/cli/test_venv.py
@@ -42,10 +42,11 @@ def test_venv_create(invoke, project):
 def test_venv_create_in_project(invoke, project):
     project.project_config["venv.in_project"] = True
     invoke(["venv", "create"], obj=project, strict=True)
-    invoke(["venv", "create"], obj=project, strict=True)
     venv_path = project.root / ".venv"
     assert venv_path.exists()
-    assert len(os.listdir(project.root / "venvs")) == 1
+    result = invoke(["venv", "create"], obj=project)
+    assert result.exit_code == 1
+    assert "is not empty" in result.stderr
 
 
 @pytest.mark.usefixtures("fake_create")