Skip to content
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

Migrate message_about_scripts_not_on_PATH to use pathlib #11903

Closed
wants to merge 8 commits into from
1 change: 1 addition & 0 deletions news/11719.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Normalize paths before checking if installed scripts are on PATH.
17 changes: 9 additions & 8 deletions src/pip/_internal/operations/install/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from base64 import urlsafe_b64encode
from email.message import Message
from itertools import chain, filterfalse, starmap
from pathlib import Path
from typing import (
IO,
TYPE_CHECKING,
Expand Down Expand Up @@ -135,24 +136,24 @@ def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]:
return None

# Group scripts by the path they were installed in
grouped_by_dir: Dict[str, Set[str]] = collections.defaultdict(set)
grouped_by_dir: Dict[Path, Set[str]] = collections.defaultdict(set)
for destfile in scripts:
parent_dir = os.path.dirname(destfile)
script_name = os.path.basename(destfile)
dest_path = Path(destfile)
parent_dir = dest_path.parent.resolve()
script_name = dest_path.name
grouped_by_dir[parent_dir].add(script_name)

# We don't want to warn for directories that are on PATH.
not_warn_dirs = [
os.path.normcase(i).rstrip(os.sep)
for i in os.environ.get("PATH", "").split(os.pathsep)
Path(i).resolve() for i in os.environ.get("PATH", "").split(os.pathsep)
]
# If an executable sits with sys.executable, we don't warn for it.
# This covers the case of venv invocations without activating the venv.
not_warn_dirs.append(os.path.normcase(os.path.dirname(sys.executable)))
warn_for: Dict[str, Set[str]] = {
not_warn_dirs.append(Path(sys.executable).parent.resolve())
warn_for: Dict[Path, Set[str]] = {
parent_dir: scripts
for parent_dir, scripts in grouped_by_dir.items()
if os.path.normcase(parent_dir) not in not_warn_dirs
if parent_dir not in not_warn_dirs
}
if not warn_for:
return None
Expand Down
30 changes: 19 additions & 11 deletions tests/unit/test_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ def test_single_script__single_dir_not_on_PATH(self) -> None:
retval = self._template(paths=["/a/b", "/c/d/bin"], scripts=["/c/d/foo"])
assert retval is not None
assert "--no-warn-script-location" in retval
assert "foo is installed in '/c/d'" in retval
assert f"foo is installed in '{Path('/c/d').resolve()}'" in retval
assert self.tilde_warning_msg not in retval

def test_two_script__single_dir_not_on_PATH(self) -> None:
Expand All @@ -545,7 +545,7 @@ def test_two_script__single_dir_not_on_PATH(self) -> None:
)
assert retval is not None
assert "--no-warn-script-location" in retval
assert "baz and foo are installed in '/c/d'" in retval
assert f"baz and foo are installed in '{Path('/c/d').resolve()}'" in retval
assert self.tilde_warning_msg not in retval

def test_multi_script__multi_dir_not_on_PATH(self) -> None:
Expand All @@ -555,8 +555,8 @@ def test_multi_script__multi_dir_not_on_PATH(self) -> None:
)
assert retval is not None
assert "--no-warn-script-location" in retval
assert "bar, baz and foo are installed in '/c/d'" in retval
assert "spam is installed in '/a/b/c'" in retval
assert f"bar, baz and foo are installed in '{Path('/c/d').resolve()}'" in retval
assert f"spam is installed in '{Path('/a/b/c').resolve()}'" in retval
assert self.tilde_warning_msg not in retval

def test_multi_script_all__multi_dir_not_on_PATH(self) -> None:
Expand All @@ -566,8 +566,8 @@ def test_multi_script_all__multi_dir_not_on_PATH(self) -> None:
)
assert retval is not None
assert "--no-warn-script-location" in retval
assert "bar, baz and foo are installed in '/c/d'" in retval
assert "eggs and spam are installed in '/a/b/c'" in retval
assert f"bar, baz and foo are installed in '{Path('/c/d').resolve()}'" in retval
assert f"eggs and spam are installed in '{Path('/a/b/c').resolve()}'" in retval
assert self.tilde_warning_msg not in retval

def test_two_script__single_dir_on_PATH(self) -> None:
Expand All @@ -589,6 +589,12 @@ def test_multi_script__single_dir_on_PATH(self) -> None:
)
assert retval is None

def test_PATH_check_path_normalization(self) -> None:
retval = self._template(
paths=["/a/./b/../b//c/", "/d/e/bin"], scripts=["/a/b/c/foo"]
)
assert retval is None

def test_single_script__single_dir_on_PATH(self) -> None:
retval = self._template(paths=["/a/b", "/c/d/bin"], scripts=["/a/b/foo"])
assert retval is None
Expand Down Expand Up @@ -638,9 +644,9 @@ def test_multi_script_all_tilde__multi_dir_not_on_PATH(self) -> None:
)
assert retval is not None
assert "--no-warn-script-location" in retval
assert "bar, baz and foo are installed in '/c/d'" in retval
assert "eggs and spam are installed in '/a/b/c'" in retval
assert "tilde is installed in '/e/f'" in retval
assert f"bar, baz and foo are installed in '{Path('/c/d').resolve()}'" in retval
assert f"eggs and spam are installed in '{Path('/a/b/c').resolve()}'" in retval
assert f"tilde is installed in '{Path('/e/f').resolve()}'" in retval
assert self.tilde_warning_msg in retval

def test_multi_script_all_tilde_not_at_start__multi_dir_not_on_PATH(self) -> None:
Expand All @@ -656,8 +662,10 @@ def test_multi_script_all_tilde_not_at_start__multi_dir_not_on_PATH(self) -> Non
)
assert retval is not None
assert "--no-warn-script-location" in retval
assert "bar, baz and foo are installed in '/c/d'" in retval
assert "eggs and spam are installed in '/e/f~f/c'" in retval
assert f"bar, baz and foo are installed in '{Path('/c/d').resolve()}'" in retval
assert (
f"eggs and spam are installed in '{Path('/e/f~f/c').resolve()}'" in retval
)
assert self.tilde_warning_msg not in retval


Expand Down