fix(env): prefer project .venv over inherited VIRTUAL_ENV with -C#10780
fix(env): prefer project .venv over inherited VIRTUAL_ENV with -C#10780SergioChan wants to merge 2 commits intopython-poetry:mainfrom
Conversation
Reviewer's GuideAdjusts EnvManager.get() to prefer a target project’s in-project virtualenv over an inherited VIRTUAL_ENV when Poetry is invoked from outside that project (e.g., via -C), while adding regression tests to cover both the new behavior and the preserved behavior when running inside the project directory. Class diagram for EnvManager and virtualenv preference logicclassDiagram
class EnvManager {
- Poetry _poetry
- Path in_project_venv
Env get(reload: bool)
bool in_project_venv_exists()
}
class Env {
<<abstract>>
}
class VirtualEnv {
Path path
+VirtualEnv(path: Path)
}
class Poetry {
File file
}
class File {
Path path
}
class Path
EnvManager --> Poetry : uses
Poetry --> File : has
File --> Path : has
Env <|-- VirtualEnv : extends
EnvManager --> Env : returns
EnvManager --> VirtualEnv : constructs
note for EnvManager "Updated get logic:<br/>- Detects in_venv via env_prefix and conda_env_name<br/>- Computes project_path from _poetry.file.path.parent<br/>- Computes cwd from Path.cwd<br/>- If in_venv && env is None && invoked_outside_project && in_project_venv_exists:<br/> returns VirtualEnv(in_project_venv)<br/>- Otherwise retains existing behavior, including respecting<br/> explicitly activated VIRTUAL_ENV when running inside project"
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- In the tests, consider using
monkeypatch.setenvinstead of writing toos.environdirectly so environment variables are automatically restored and tests remain isolated. - The use of
Path.is_relative_toassumes a minimum Python version of 3.9; if Poetry still supports older versions, you may need to replace this with a compatibility helper (e.g., try/except or manual path comparison).
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In the tests, consider using `monkeypatch.setenv` instead of writing to `os.environ` directly so environment variables are automatically restored and tests remain isolated.
- The use of `Path.is_relative_to` assumes a minimum Python version of 3.9; if Poetry still supports older versions, you may need to replace this with a compatibility helper (e.g., try/except or manual path comparison).
## Individual Comments
### Comment 1
<location path="src/poetry/utils/env/env_manager.py" line_range="221-222" />
<code_context>
+ project_path = self._poetry.file.path.parent.resolve()
+ cwd = Path.cwd().resolve()
+ invoked_outside_project = not (
+ cwd == project_path or cwd.is_relative_to(project_path)
+ )
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Using Path.is_relative_to requires Python 3.9+; ensure this aligns with the supported runtime or add a compatibility fallback.
If any supported runtime (including bootstrap/runtime tooling, not just managed envs) can be <3.9, this will raise AttributeError at runtime. If 3.9+ is guaranteed everywhere, no change needed; otherwise, add a small compatibility helper (e.g., try `cwd.is_relative_to` and fall back to a `str(cwd).startswith(str(project_path) + os.sep)` check) or centralize this logic in a utility so the version-specific handling lives in one place.
</issue_to_address>
### Comment 2
<location path="tests/utils/env/test_env_manager.py" line_range="580-589" />
<code_context>
assert env.base == Path(sys.base_prefix)
+def test_get_prefers_in_project_venv_when_running_outside_project(
+ tmp_path: Path,
+ manager: EnvManager,
+ in_project_venv_dir: Path,
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ os.environ["VIRTUAL_ENV"] = "/environment/prefix"
+ outside_cwd = tmp_path / "outside"
+ outside_cwd.mkdir()
+ monkeypatch.chdir(outside_cwd)
+
+ env = manager.get()
</code_context>
<issue_to_address>
**suggestion (testing):** Use `monkeypatch.setenv` instead of mutating `os.environ` directly to avoid cross-test leakage
This test (and the one below) assigns to `os.environ["VIRTUAL_ENV"]`, which can leak into later tests if cleanup is skipped. Since `monkeypatch` is already in use, please switch to `monkeypatch.setenv("VIRTUAL_ENV", "/environment/prefix")` in both tests so the environment is automatically restored.
Suggested implementation:
```python
def test_get_prefers_in_project_venv_when_running_outside_project(
tmp_path: Path,
manager: EnvManager,
in_project_venv_dir: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setenv("VIRTUAL_ENV", "/environment/prefix")
outside_cwd = tmp_path / "outside"
```
In the same file (`tests/utils/env/test_env_manager.py`), inside the `test_get_keeps_active_virtualenv_when_running_inside_project` function, replace any direct assignment like:
```python
os.environ["VIRTUAL_ENV"] = "/environment/prefix"
```
(or similar) with:
```python
monkeypatch.setenv("VIRTUAL_ENV", "/environment/prefix")
```
This ensures the environment variable is automatically restored after the test and prevents cross-test leakage.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| invoked_outside_project = not ( | ||
| cwd == project_path or cwd.is_relative_to(project_path) |
There was a problem hiding this comment.
issue (bug_risk): Using Path.is_relative_to requires Python 3.9+; ensure this aligns with the supported runtime or add a compatibility fallback.
If any supported runtime (including bootstrap/runtime tooling, not just managed envs) can be <3.9, this will raise AttributeError at runtime. If 3.9+ is guaranteed everywhere, no change needed; otherwise, add a small compatibility helper (e.g., try cwd.is_relative_to and fall back to a str(cwd).startswith(str(project_path) + os.sep) check) or centralize this logic in a utility so the version-specific handling lives in one place.
| def test_get_prefers_in_project_venv_when_running_outside_project( | ||
| tmp_path: Path, | ||
| manager: EnvManager, | ||
| in_project_venv_dir: Path, | ||
| monkeypatch: pytest.MonkeyPatch, | ||
| ) -> None: | ||
| os.environ["VIRTUAL_ENV"] = "/environment/prefix" | ||
| outside_cwd = tmp_path / "outside" | ||
| outside_cwd.mkdir() | ||
| monkeypatch.chdir(outside_cwd) |
There was a problem hiding this comment.
suggestion (testing): Use monkeypatch.setenv instead of mutating os.environ directly to avoid cross-test leakage
This test (and the one below) assigns to os.environ["VIRTUAL_ENV"], which can leak into later tests if cleanup is skipped. Since monkeypatch is already in use, please switch to monkeypatch.setenv("VIRTUAL_ENV", "/environment/prefix") in both tests so the environment is automatically restored.
Suggested implementation:
def test_get_prefers_in_project_venv_when_running_outside_project(
tmp_path: Path,
manager: EnvManager,
in_project_venv_dir: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setenv("VIRTUAL_ENV", "/environment/prefix")
outside_cwd = tmp_path / "outside"In the same file (tests/utils/env/test_env_manager.py), inside the test_get_keeps_active_virtualenv_when_running_inside_project function, replace any direct assignment like:
os.environ["VIRTUAL_ENV"] = "/environment/prefix"(or similar) with:
monkeypatch.setenv("VIRTUAL_ENV", "/environment/prefix")This ensures the environment variable is automatically restored after the test and prevents cross-test leakage.
|
Related Documentation 3 document(s) may need updating based on files changed in this PR: Python Poetry basic-usage
|
|
Pushed a follow-up commit to address CI/review feedback:
Validation run locally:
|
|
cf #10689 - I imagine this one should be rejected for the same reasons as that one. Plus the additional reason that behaving differently depending on whether the invocation was from "inside" or "outside" the project will be confusing. |
|
Thanks for the candid feedback — that’s fair. The intended difference vs #10689 was to narrow behavior to That said, I agree the inside/outside split can feel surprising from a UX perspective. If maintainers prefer to keep behavior aligned with the direction in #10689, I’m happy to close this PR and rework toward a less surprising approach (for example, surfacing a clear warning/error instead of auto-switching envs). |
Pull Request Check List
Resolves: #10773
Summary
VIRTUAL_ENVis already set (for example viapoetry -C <project>from another environment).VIRTUAL_ENVis still respected.tests/utils/env/test_env_manager.py.Testing
python -m pytest tests/utils/env/test_env_manager.py -k "prefers_in_project_venv_when_running_outside_project or keeps_active_virtualenv_when_running_inside_project or prefers_explicitly_activated_virtualenvs_over_env_var" -qSummary by Sourcery
Adjust environment selection to prefer a target project's in-project virtualenv when invoked from outside the project while already inside another virtualenv.
Bug Fixes:
Tests: