From 845634da23761afb7993e7e3edf6aaf39d3658f0 Mon Sep 17 00:00:00 2001 From: Daniel Hollas Date: Mon, 5 Jan 2026 11:58:51 +0100 Subject: [PATCH 1/5] Avoid importing metadata module in autocompletion --- src/pip/_internal/cli/autocompletion.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pip/_internal/cli/autocompletion.py b/src/pip/_internal/cli/autocompletion.py index f22cd1159a2..8add031618e 100644 --- a/src/pip/_internal/cli/autocompletion.py +++ b/src/pip/_internal/cli/autocompletion.py @@ -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: @@ -51,6 +50,8 @@ def autocomplete() -> None: "uninstall", ] if should_list_installed: + from pip._internal.metadata import get_default_environment + env = get_default_environment() lc = current.lower() installed = [ From c2e2f7935cc494fbadc4c53a858b6bf12de151ce Mon Sep 17 00:00:00 2001 From: Daniel Hollas Date: Mon, 5 Jan 2026 12:03:52 +0100 Subject: [PATCH 2/5] Move get_runnable_pip --- src/pip/_internal/build_env.py | 19 +------------------ src/pip/_internal/cli/main_parser.py | 3 +-- src/pip/_internal/utils/misc.py | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/pip/_internal/build_env.py b/src/pip/_internal/build_env.py index f28d862f279..50f2badbeff 100644 --- a/src/pip/_internal/build_env.py +++ b/src/pip/_internal/build_env.py @@ -4,7 +4,6 @@ import logging import os -import pathlib import site import sys import textwrap @@ -15,12 +14,12 @@ from pip._vendor.packaging.version import Version -from pip import __file__ as pip_location from pip._internal.cli.spinners import open_spinner from pip._internal.locations import get_platlib, get_purelib, get_scheme from pip._internal.metadata import get_default_environment, get_environment from pip._internal.utils.deprecation import deprecated from pip._internal.utils.logging import VERBOSE +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 @@ -49,22 +48,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 diff --git a/src/pip/_internal/cli/main_parser.py b/src/pip/_internal/cli/main_parser.py index 5ce9f5a02d4..7c7fb1806e0 100644 --- a/src/pip/_internal/cli/main_parser.py +++ b/src/pip/_internal/cli/main_parser.py @@ -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"] diff --git a/src/pip/_internal/utils/misc.py b/src/pip/_internal/utils/misc.py index 66e521bbc1b..ac718a2383f 100644 --- a/src/pip/_internal/utils/misc.py +++ b/src/pip/_internal/utils/misc.py @@ -5,6 +5,7 @@ import hashlib import logging import os +import pathlib import posixpath import shutil import stat @@ -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 @@ -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 From f88e8666be39187520fe29607cd605c17a533bd9 Mon Sep 17 00:00:00 2001 From: Daniel Hollas Date: Mon, 5 Jan 2026 12:35:26 +0100 Subject: [PATCH 3/5] Avoid importing packaging.requirements module during tab autocompletion --- src/pip/_internal/exceptions.py | 5 ++++- src/pip/_internal/utils/misc.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/pip/_internal/exceptions.py b/src/pip/_internal/exceptions.py index 8bcb46212f8..1cc79faff47 100644 --- a/src/pip/_internal/exceptions.py +++ b/src/pip/_internal/exceptions.py @@ -18,7 +18,6 @@ from itertools import chain, groupby, repeat from typing import TYPE_CHECKING, Literal -from pip._vendor.packaging.requirements import InvalidRequirement from pip._vendor.packaging.version import InvalidVersion from pip._vendor.rich.console import Console, ConsoleOptions, RenderResult from pip._vendor.rich.markup import escape @@ -27,6 +26,7 @@ if TYPE_CHECKING: from hashlib import _Hash + from pip._vendor.packaging.requirements import InvalidRequirement from pip._vendor.requests.models import PreparedRequest, Request, Response from pip._internal.metadata import BaseDistribution @@ -809,6 +809,9 @@ def __init__( dist: BaseDistribution, invalid_exc: InvalidRequirement | InvalidVersion, ) -> None: + # packaging.requirements module is slow to import + from pip._vendor.packaging.requirements import InvalidRequirement + installed_location = dist.installed_location if isinstance(invalid_exc, InvalidRequirement): diff --git a/src/pip/_internal/utils/misc.py b/src/pip/_internal/utils/misc.py index ac718a2383f..327f288428f 100644 --- a/src/pip/_internal/utils/misc.py +++ b/src/pip/_internal/utils/misc.py @@ -20,6 +20,7 @@ from pathlib import Path from types import FunctionType, TracebackType from typing import ( + TYPE_CHECKING, Any, BinaryIO, Callable, @@ -29,7 +30,6 @@ cast, ) -from pip._vendor.packaging.requirements import Requirement from pip._vendor.pyproject_hooks import BuildBackendHookCaller from pip import __file__ as pip_location @@ -40,6 +40,9 @@ from pip._internal.utils.retry import retry from pip._internal.utils.virtualenv import running_under_virtualenv +if TYPE_CHECKING: + from pip._vendor.packaging.requirements import Requirement + __all__ = [ "rmtree", "display_path", From 612660ba85e9b04696ded2e11c60f6545c474dc5 Mon Sep 17 00:00:00 2001 From: Daniel Hollas Date: Mon, 5 Jan 2026 12:40:07 +0100 Subject: [PATCH 4/5] Add news entry --- news/4768.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/4768.feature.rst diff --git a/news/4768.feature.rst b/news/4768.feature.rst new file mode 100644 index 00000000000..a41818ad2f8 --- /dev/null +++ b/news/4768.feature.rst @@ -0,0 +1 @@ +Speedup tab autocompletion by lazy-importing certain modules. From d1011798f04ca86a928b7697afb268f7c5b0fa7e Mon Sep 17 00:00:00 2001 From: Daniel Hollas Date: Tue, 13 Jan 2026 18:11:09 +0000 Subject: [PATCH 5/5] Revert "Avoid importing packaging.requirements module during tab autocompletion" This reverts commit f88e8666be39187520fe29607cd605c17a533bd9. --- src/pip/_internal/exceptions.py | 5 +---- src/pip/_internal/utils/misc.py | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/pip/_internal/exceptions.py b/src/pip/_internal/exceptions.py index ab0584ac001..4d4e012cc8c 100644 --- a/src/pip/_internal/exceptions.py +++ b/src/pip/_internal/exceptions.py @@ -19,6 +19,7 @@ from itertools import chain, groupby, repeat from typing import TYPE_CHECKING, Literal +from pip._vendor.packaging.requirements import InvalidRequirement from pip._vendor.packaging.version import InvalidVersion from pip._vendor.rich.console import Console, ConsoleOptions, RenderResult from pip._vendor.rich.markup import escape @@ -27,7 +28,6 @@ if TYPE_CHECKING: from hashlib import _Hash - from pip._vendor.packaging.requirements import InvalidRequirement from pip._vendor.requests.models import PreparedRequest, Request, Response from pip._internal.metadata import BaseDistribution @@ -810,9 +810,6 @@ def __init__( dist: BaseDistribution, invalid_exc: InvalidRequirement | InvalidVersion, ) -> None: - # packaging.requirements module is slow to import - from pip._vendor.packaging.requirements import InvalidRequirement - installed_location = dist.installed_location if isinstance(invalid_exc, InvalidRequirement): diff --git a/src/pip/_internal/utils/misc.py b/src/pip/_internal/utils/misc.py index 327f288428f..ac718a2383f 100644 --- a/src/pip/_internal/utils/misc.py +++ b/src/pip/_internal/utils/misc.py @@ -20,7 +20,6 @@ from pathlib import Path from types import FunctionType, TracebackType from typing import ( - TYPE_CHECKING, Any, BinaryIO, Callable, @@ -30,6 +29,7 @@ cast, ) +from pip._vendor.packaging.requirements import Requirement from pip._vendor.pyproject_hooks import BuildBackendHookCaller from pip import __file__ as pip_location @@ -40,9 +40,6 @@ from pip._internal.utils.retry import retry from pip._internal.utils.virtualenv import running_under_virtualenv -if TYPE_CHECKING: - from pip._vendor.packaging.requirements import Requirement - __all__ = [ "rmtree", "display_path",