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/4768.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Speedup tab autocompletion by lazy-importing certain modules.
19 changes: 1 addition & 18 deletions src/pip/_internal/build_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import logging
import os
import pathlib
import site
import sys
import textwrap
Expand All @@ -18,7 +17,6 @@

from pip._vendor.packaging.version import Version

from pip import __file__ as pip_location
from pip._internal.cli.spinners import open_rich_spinner, open_spinner
from pip._internal.exceptions import (
BuildDependencyInstallError,
Expand All @@ -30,6 +28,7 @@
from pip._internal.metadata import get_default_environment, get_environment
from pip._internal.utils.deprecation import deprecated
from pip._internal.utils.logging import VERBOSE, capture_logging
from pip._internal.utils.misc import get_runnable_pip
from pip._internal.utils.packaging import get_requirement
from pip._internal.utils.subprocess import call_subprocess
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
Expand Down Expand Up @@ -61,22 +60,6 @@ def __init__(self, path: str) -> None:
self.lib_dirs = _dedup(scheme.purelib, scheme.platlib)


def get_runnable_pip() -> str:
"""Get a file to pass to a Python executable, to run the currently-running pip.

This is used to run a pip subprocess, for installing requirements into the build
environment.
"""
source = pathlib.Path(pip_location).resolve().parent

if not source.is_dir():
# This would happen if someone is using pip from inside a zip file. In that
# case, we can use that directly.
return str(source)

return os.fsdecode(source / "__pip-runner__.py")


def _get_system_sitepackages() -> set[str]:
"""Get system site packages

Expand Down
3 changes: 2 additions & 1 deletion src/pip/_internal/cli/autocompletion.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

from pip._internal.cli.main_parser import create_main_parser
from pip._internal.commands import commands_dict, create_command
from pip._internal.metadata import get_default_environment


def autocomplete() -> None:
Expand Down Expand Up @@ -51,6 +50,8 @@ def autocomplete() -> None:
"uninstall",
]
if should_list_installed:
from pip._internal.metadata import get_default_environment
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's quite unfortunate to have this import in the middle of the function, but the speedup for autocompletion is quite significant so I think it's worth it. We could also extract the code in this branch into a standalone function, but it seemed like more work than it's worth.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This lazy import should be safe as it is only triggered during tab autocompletion.


env = get_default_environment()
lc = current.lower()
installed = [
Expand Down
3 changes: 1 addition & 2 deletions src/pip/_internal/cli/main_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@
import subprocess
import sys

from pip._internal.build_env import get_runnable_pip
from pip._internal.cli import cmdoptions
from pip._internal.cli.parser import ConfigOptionParser, UpdatingDefaultsHelpFormatter
from pip._internal.commands import commands_dict, get_similar_commands
from pip._internal.exceptions import CommandError
from pip._internal.utils.misc import get_pip_version, get_prog
from pip._internal.utils.misc import get_pip_version, get_prog, get_runnable_pip

__all__ = ["create_main_parser", "parse_command"]

Expand Down
18 changes: 18 additions & 0 deletions src/pip/_internal/utils/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import hashlib
import logging
import os
import pathlib
import posixpath
import shutil
import stat
Expand All @@ -31,6 +32,7 @@
from pip._vendor.packaging.requirements import Requirement
from pip._vendor.pyproject_hooks import BuildBackendHookCaller

from pip import __file__ as pip_location
from pip import __version__
from pip._internal.exceptions import CommandError, ExternallyManagedEnvironment
from pip._internal.locations import get_major_minor_version
Expand Down Expand Up @@ -74,6 +76,22 @@ def get_pip_version() -> str:
return f"pip {__version__} from {pip_pkg_dir} (python {get_major_minor_version()})"


def get_runnable_pip() -> str:
"""Get a file to pass to a Python executable, to run the currently-running pip.

This is used to run a pip subprocess, for installing requirements into the build
environment.
"""
source = pathlib.Path(pip_location).resolve().parent

if not source.is_dir():
# This would happen if someone is using pip from inside a zip file. In that
# case, we can use that directly.
return str(source)

return os.fsdecode(source / "__pip-runner__.py")


def normalize_version_info(py_version_info: tuple[int, ...]) -> tuple[int, int, int]:
"""
Convert a tuple of ints representing a Python version to one of length
Expand Down