Skip to content
Open
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/3743.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Clear build directory cache in pytest fixture to avoid stale references.
1 change: 1 addition & 0 deletions news/3744.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Handle `Version._key` assignment for packaging compatibility.
1 change: 1 addition & 0 deletions news/3746.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Don't inherit `PYTHONPATH` environment variable from parent process when running `pdm run`.
1 change: 1 addition & 0 deletions src/pdm/cli/commands/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ def _run_process(
check_project_file(project)
project_env = project.environment
this_path = project_env.get_paths()["scripts"]
os.environ.pop("PYTHONPATH", None) # Don't inherit PYTHONPATH from the parent process
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid clearing PYTHONPATH for non-local environments

This unconditional PYTHONPATH removal changes pdm run behavior for virtualenv/system projects: PythonEnvironment.process_env only adds PATH/venv vars and does not restore PYTHONPATH (src/pdm/environments/python.py), so shell-provided paths are now silently discarded. That breaks common workflows like PYTHONPATH=src pdm run pytest (or scripts that expand ${PYTHONPATH}) where imports relied on inherited paths, even though the original bug only concerned PEP 582 leakage.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

@frostming frostming Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cgranade Do you think this is a breaking change? I assume there are use cases that rely on PYTHONPATH to run a script correctly, although I personally don't recommend manipulating PYTHONPATH manually.

os.environ.update(project_env.process_env)
if env_file is not None:
if isinstance(env_file, str):
Expand Down
8 changes: 4 additions & 4 deletions src/pdm/environments/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@ def process_env(self) -> dict[str, str]:
from pdm.cli.utils import get_pep582_path

env = super().process_env
pythonpath = os.getenv("PYTHONPATH", "").split(os.pathsep)
pythonpath = [get_pep582_path(self.project)] + [p for p in pythonpath if "/pep582" not in p.replace("\\", "/")]
env["PYTHONPATH"] = os.pathsep.join(pythonpath)
env["PEP582_PACKAGES"] = str(self.packages_path)
env.update(
PYTHONPATH=get_pep582_path(self.project),
PEP582_PACKAGES=str(self.packages_path),
)
return env

@cached_property
Expand Down
31 changes: 14 additions & 17 deletions tests/cli/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -546,26 +546,23 @@ def test_run_with_another_project_root(project, pdm, capfd, explicit_python):
assert out.strip() == "1"


def test_import_another_sitecustomize(project, pdm, capfd):
project.pyproject.metadata["requires-python"] = ">=2.7"
project.pyproject.write()
# a script for checking another sitecustomize is imported
project.root.joinpath("foo.py").write_text("import os;print(os.getenv('FOO'))")
# ensure there have at least one sitecustomize can be imported
# there may have more than one sitecustomize.py in sys.path
project.root.joinpath("sitecustomize.py").write_text("import os;os.environ['FOO'] = 'foo'")
env = os.environ.copy()
paths = env.get("PYTHONPATH")
this_path = str(project.root)
new_paths = [this_path] if not paths else [this_path, paths]
env["PYTHONPATH"] = os.pathsep.join(new_paths)
project._environment = None
def test_run_does_not_inherit_parent_pythonpath(project, pdm, capfd):
inherited_path = project.root / "inherited-pythonpath"
inherited_path.mkdir()
inherited_path.joinpath("sitecustomize.py").write_text("import os;os.environ['PDM_RUN_PYTHONPATH_MARKER'] = '1'")
check_cmd = "import os;print(os.getenv('PDM_RUN_PYTHONPATH_MARKER'));print(os.getenv('PYTHONPATH'))"
capfd.readouterr()
with cd(project.root):
result = pdm(["run", "python", "foo.py"], env=env)
result = pdm(
["run", "python", "-c", check_cmd],
obj=project,
env={"PYTHONPATH": str(inherited_path)},
)
assert result.exit_code == 0, result.stderr
out, _ = capfd.readouterr()
assert out.strip() == "foo"
marker, pythonpath = out.strip().splitlines()
assert marker == "None"
assert pythonpath == get_pep582_path(project)
assert str(inherited_path) not in pythonpath


def test_run_with_patched_sysconfig(project, pdm, capfd):
Expand Down
Loading