Add a way to get a simpler invocation of pip
This makes it possible to provide more terse suggestions that are still
correct, depending on the user's environment.
+import itertools
+import os
+import shutil
import sys
from typing import List, Optional
from pip._internal.cli.main import main
+from pip._internal.utils.compat import WINDOWS
+
+_EXECUTABLE_NAMES = [
+ "pip",
+ f"pip{sys.version_info.major}",
+ f"pip{sys.version_info.major}.{sys.version_info.minor}",
+]
+if WINDOWS:
+ _allowed_extensions = {"", ".exe", ".EXE"}
+ _EXECUTABLE_NAMES = [
+ "".join(parts)
+ for parts in itertools.product(_EXECUTABLE_NAMES, _allowed_extensions)
+ ]
def _wrapper(args: Optional[List[str]] = None) -> int:
@@ -25,3 +41,34 @@ def _wrapper(args: Optional[List[str]] = None) -> int:
"running pip directly.\n"
)
return main(args)
+
+
+def get_best_invocation_for_this_pip() -> str:
+ """Try to figure out the best way to invoke pip in the current environment."""
+ binary_directory = "Scripts" if WINDOWS else "bin"
+ binary_prefix = os.path.join(sys.prefix, binary_directory)
+
+ # Try to use pip[X[.Y]] names, if those executables for this environment are
+ # the first on PATH with that name.
+ exe_are_in_PATH = binary_prefix in os.environ.get("PATH", "").split(os.pathsep)
+ if exe_are_in_PATH:
+ for exe_name in _EXECUTABLE_NAMES:
+ if shutil.which(exe_name) == os.path.join(binary_prefix, exe_name):
+ return exe_name
+
+ # Use the `-m` invocation, if there's no "nice" invocation.
+ return f"{get_best_invocation_for_this_python()} -m pip"
+
+
+def get_best_invocation_for_this_python() -> str:
+ """Try to figure out the best way to invoke the current Python."""
+ exe = sys.executable
+ exe_name = os.path.basename(exe)
+
+ # Try to use the basename, if it's the first executable.
+ found_executable = shutil.which(exe_name)
+ if found_executable and os.path.samefile(found_executable, exe):
+ return exe_name
+
+ # Use the full executable name, because we couldn't find something simpler.
+ return exe
