From e2b028d2d77cc73a8039324b60e3fa4dedee32a2 Mon Sep 17 00:00:00 2001 From: Azure Linux Security Servicing Account Date: Mon, 4 May 2026 03:34:57 +0000 Subject: [PATCH 1/3] Patch python-pip for CVE-2026-6357 --- SPECS/python-pip/CVE-2026-6357.patch | 371 +++++++++++++++++++++++++++ SPECS/python-pip/python-pip.spec | 6 +- 2 files changed, 376 insertions(+), 1 deletion(-) create mode 100644 SPECS/python-pip/CVE-2026-6357.patch diff --git a/SPECS/python-pip/CVE-2026-6357.patch b/SPECS/python-pip/CVE-2026-6357.patch new file mode 100644 index 00000000000..cc097f0d25f --- /dev/null +++ b/SPECS/python-pip/CVE-2026-6357.patch @@ -0,0 +1,371 @@ +From 4d3bacc72e85d03e3dd8c9fdb6e960774192c53c Mon Sep 17 00:00:00 2001 +From: AllSpark +Date: Mon, 4 May 2026 03:26:56 +0000 +Subject: [PATCH] Backport split pip self-version check into fetch(before) and + emit(after) phases; adjust context managers and tests; compute prompt based + on cached/remote and installer; skip check in install when pip is target + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: AI Backport of https://github.com/pypa/pip/pull/13923.patch +--- + src/pip/_internal/cli/base_command.py | 11 ++-- + src/pip/_internal/cli/index_command.py | 35 +++++++++--- + src/pip/_internal/commands/install.py | 6 ++ + src/pip/_internal/commands/list.py | 13 +++-- + src/pip/_internal/self_outdated_check.py | 73 ++++++++++++------------ + tests/unit/test_base_command.py | 8 +-- + tests/unit/test_commands.py | 9 ++- + 7 files changed, 93 insertions(+), 62 deletions(-) + +diff --git a/src/pip/_internal/cli/base_command.py b/src/pip/_internal/cli/base_command.py +index bc1ab65..4d81124 100644 +--- a/src/pip/_internal/cli/base_command.py ++++ b/src/pip/_internal/cli/base_command.py +@@ -6,8 +6,9 @@ import optparse + import os + import sys + import traceback ++import contextlib + from optparse import Values +-from typing import List, Optional, Tuple ++from typing import List, Optional, Tuple, Iterator + + from pip._vendor.rich import reconfigure + from pip._vendor.rich import traceback as rich_traceback +@@ -78,7 +79,8 @@ class Command(CommandContextMixIn): + def add_options(self) -> None: + pass + +- def handle_pip_version_check(self, options: Values) -> None: ++ @contextlib.contextmanager ++ def pip_version_check(self, options: Values, args: List[str]) -> Iterator[None]: + """ + This is a no-op so that commands by default do not do the pip version + check. +@@ -86,16 +88,15 @@ class Command(CommandContextMixIn): + # Make sure we do the pip version check if the index_group options + # are present. + assert not hasattr(options, "no_index") ++ yield + + def run(self, options: Values, args: List[str]) -> int: + raise NotImplementedError + + def _run_wrapper(self, level_number: int, options: Values, args: List[str]) -> int: + def _inner_run() -> int: +- try: ++ with self.pip_version_check(options, args): + return self.run(options, args) +- finally: +- self.handle_pip_version_check(options) + + if options.debug_mode: + rich_traceback.install(show_locals=True) +diff --git a/src/pip/_internal/cli/index_command.py b/src/pip/_internal/cli/index_command.py +index 226f8da..930f23a 100644 +--- a/src/pip/_internal/cli/index_command.py ++++ b/src/pip/_internal/cli/index_command.py +@@ -9,8 +9,12 @@ so commands which don't always hit the network (e.g. list w/o --outdated or + import logging + import os + import sys ++import contextlib + from optparse import Values +-from typing import TYPE_CHECKING, List, Optional ++from typing import TYPE_CHECKING, List, Optional, Iterator ++ ++if TYPE_CHECKING: ++ from pip._internal.self_outdated_check import UpgradePrompt + + from pip._vendor import certifi + +@@ -131,10 +135,16 @@ class SessionCommandMixin(CommandContextMixIn): + return session + + +-def _pip_self_version_check(session: "PipSession", options: Values) -> None: +- from pip._internal.self_outdated_check import pip_self_version_check as check ++def _pip_self_version_check_fetch(session: "PipSession", options: Values) -> Optional["UpgradePrompt"]: ++ from pip._internal.self_outdated_check import pip_self_version_check_fetch ++ ++ return pip_self_version_check_fetch(session, options) ++ + +- check(session, options) ++def _pip_self_version_check_emit(upgrade_prompt: Optional["UpgradePrompt"]) -> None: ++ from pip._internal.self_outdated_check import pip_self_version_check_emit ++ ++ pip_self_version_check_emit(upgrade_prompt) + + + class IndexGroupCommand(Command, SessionCommandMixin): +@@ -144,7 +154,8 @@ class IndexGroupCommand(Command, SessionCommandMixin): + This also corresponds to the commands that permit the pip version check. + """ + +- def handle_pip_version_check(self, options: Values) -> None: ++ @contextlib.contextmanager ++ def pip_version_check(self, options: Values, args: List[str]) -> Iterator[None]: + """ + Do the pip version check if not disabled. + +@@ -154,17 +165,27 @@ class IndexGroupCommand(Command, SessionCommandMixin): + assert hasattr(options, "no_index") + + if options.disable_pip_version_check or options.no_index: ++ yield + return + ++ upgrade_prompt: Optional["UpgradePrompt"] = None + try: +- # Otherwise, check if we're using the latest version of pip available. + session = self._build_session( + options, + retries=0, + timeout=min(5, options.timeout), + ) + with session: +- _pip_self_version_check(session, options) ++ upgrade_prompt = _pip_self_version_check_fetch(session, options) + except Exception: + logger.warning("There was an error checking the latest version of pip.") + logger.debug("See below for error", exc_info=True) ++ ++ try: ++ yield ++ finally: ++ try: ++ _pip_self_version_check_emit(upgrade_prompt) ++ except Exception: ++ logger.warning("There was an error checking the latest version of pip.") ++ logger.debug("See below for error", exc_info=True) +diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py +index ad45a2f..298b92c 100644 +--- a/src/pip/_internal/commands/install.py ++++ b/src/pip/_internal/commands/install.py +@@ -1,4 +1,10 @@ + import errno ++from __future__ import annotations ++ ++import contextlib ++from collections.abc import Iterator ++from pip._vendor.packaging.requirements import InvalidRequirement, Requirement ++ + import json + import operator + import os +diff --git a/src/pip/_internal/commands/list.py b/src/pip/_internal/commands/list.py +index 82fc46a..ba00f82 100644 +--- a/src/pip/_internal/commands/list.py ++++ b/src/pip/_internal/commands/list.py +@@ -1,7 +1,8 @@ + import json + import logging ++import contextlib + from optparse import Values +-from typing import TYPE_CHECKING, Generator, List, Optional, Sequence, Tuple, cast ++from typing import TYPE_CHECKING, Generator, List, Optional, Sequence, Tuple, cast, Iterator + + from pip._vendor.packaging.utils import canonicalize_name + from pip._vendor.packaging.version import Version +@@ -134,9 +135,13 @@ class ListCommand(IndexGroupCommand): + self.parser.insert_option_group(0, index_opts) + self.parser.insert_option_group(0, self.cmd_opts) + +- def handle_pip_version_check(self, options: Values) -> None: +- if options.outdated or options.uptodate: +- super().handle_pip_version_check(options) ++ @contextlib.contextmanager ++ def pip_version_check(self, options: Values, args: List[str]) -> Iterator[None]: ++ if not (options.outdated or options.uptodate): ++ yield ++ return ++ with super().pip_version_check(options, args): ++ yield + + def _build_package_finder( + self, options: Values, session: "PipSession" +diff --git a/src/pip/_internal/self_outdated_check.py b/src/pip/_internal/self_outdated_check.py +index f9a91af..fe0e0d2 100644 +--- a/src/pip/_internal/self_outdated_check.py ++++ b/src/pip/_internal/self_outdated_check.py +@@ -1,5 +1,4 @@ + import datetime +-import functools + import hashlib + import json + import logging +@@ -7,7 +6,7 @@ import optparse + import os.path + import sys + from dataclasses import dataclass +-from typing import Any, Callable, Dict, Optional ++from typing import Any, Dict, Optional + + from pip._vendor.packaging.version import Version + from pip._vendor.packaging.version import parse as parse_version +@@ -26,7 +25,8 @@ from pip._internal.utils.entrypoints import ( + get_best_invocation_for_this_python, + ) + from pip._internal.utils.filesystem import adjacent_tmp_file, check_path_owner, replace +-from pip._internal.utils.misc import ensure_dir ++from pip._internal.utils.misc import ensure_dir, check_externally_managed ++from pip._internal.exceptions import ExternallyManagedEnvironment + + _WEEK = datetime.timedelta(days=7) + +@@ -149,14 +149,6 @@ class UpgradePrompt: + ) + + +-def was_installed_by_pip(pkg: str) -> bool: +- """Checks whether pkg was installed by pip +- +- This is used not to display the upgrade message when pip is in fact +- installed by system package manager, such as dnf on Fedora. +- """ +- dist = get_default_environment().get_distribution(pkg) +- return dist is not None and "pip" == dist.installer + + + def _get_current_remote_pip_version( +@@ -187,28 +179,15 @@ def _get_current_remote_pip_version( + return str(best_candidate.version) + + +-def _self_version_check_logic( +- *, +- state: SelfCheckState, +- current_time: datetime.datetime, +- local_version: Version, +- get_remote_version: Callable[[], Optional[str]], ++def _compute_upgrade_prompt( ++ local_version: Version, remote_version_str: str, installed_by_pip: bool + ) -> Optional[UpgradePrompt]: +- remote_version_str = state.get(current_time) +- if remote_version_str is None: +- remote_version_str = get_remote_version() +- if remote_version_str is None: +- logger.debug("No remote pip version found") +- return None +- state.set(remote_version_str, current_time) +- + remote_version = parse_version(remote_version_str) + logger.debug("Remote version of pip: %s", remote_version) + logger.debug("Local version of pip: %s", local_version) ++ logger.debug("Was pip installed by pip? %s", installed_by_pip) + +- pip_installed_by_pip = was_installed_by_pip("pip") +- logger.debug("Was pip installed by pip? %s", pip_installed_by_pip) +- if not pip_installed_by_pip: ++ if not installed_by_pip: + return None # Only suggest upgrade if pip is installed by pip. + + local_version_is_older = ( +@@ -221,24 +200,44 @@ def _self_version_check_logic( + return None + + +-def pip_self_version_check(session: PipSession, options: optparse.Values) -> None: +- """Check for an update for pip. ++def pip_self_version_check_fetch( ++ session: PipSession, options: optparse.Values ++) -> Optional[UpgradePrompt]: ++ """Compute the pip upgrade prompt, if any, before the command runs. + + Limit the frequency of checks to once per week. State is stored either in + the active virtualenv or in the user's USER_CACHE_DIR keyed off the prefix + of the pip script path. ++ ++ Pair with :func:`pip_self_version_check_emit`, which displays the prompt ++ after the command body runs. + """ + installed_dist = get_default_environment().get_distribution("pip") + if not installed_dist: +- return ++ return None ++ try: ++ check_externally_managed() ++ except ExternallyManagedEnvironment: ++ return None + +- upgrade_prompt = _self_version_check_logic( +- state=SelfCheckState(cache_dir=options.cache_dir), +- current_time=datetime.datetime.now(datetime.timezone.utc), ++ state = SelfCheckState(cache_dir=options.cache_dir) ++ current_time = datetime.datetime.now(datetime.timezone.utc) ++ remote_version_str = state.get(current_time) ++ if remote_version_str is None: ++ remote_version_str = _get_current_remote_pip_version(session, options) ++ if remote_version_str is None: ++ logger.debug("No remote pip version found") ++ return None ++ state.set(remote_version_str, current_time) ++ ++ return _compute_upgrade_prompt( + local_version=installed_dist.version, +- get_remote_version=functools.partial( +- _get_current_remote_pip_version, session, options +- ), ++ remote_version_str=remote_version_str, ++ installed_by_pip=installed_dist.installer == "pip", + ) ++ ++ ++def pip_self_version_check_emit(upgrade_prompt: Optional[UpgradePrompt]) -> None: ++ """Emit the upgrade prompt captured by :func:`pip_self_version_check_fetch`.""" + if upgrade_prompt is not None: + logger.warning("%s", upgrade_prompt, extra={"rich": True}) +diff --git a/tests/unit/test_base_command.py b/tests/unit/test_base_command.py +index f9fae65..5fdf94a 100644 +--- a/tests/unit/test_base_command.py ++++ b/tests/unit/test_base_command.py +@@ -97,14 +97,14 @@ class TestCommand: + assert "Traceback (most recent call last):" in stderr + + +-@patch("pip._internal.cli.index_command.Command.handle_pip_version_check") +-def test_handle_pip_version_check_called(mock_handle_version_check: Mock) -> None: ++@patch("pip._internal.cli.index_command.Command.pip_version_check") ++def test_pip_version_check_called(mock_version_check: Mock) -> None: + """ +- Check that Command.handle_pip_version_check() is called. ++ Check that ``Command.pip_version_check()`` wraps the command body. + """ + cmd = FakeCommand() + cmd.main([]) +- mock_handle_version_check.assert_called_once() ++ mock_version_check.assert_called_once() + + + def test_log_command_success(fixed_time: None, tmpdir: Path) -> None: +diff --git a/tests/unit/test_commands.py b/tests/unit/test_commands.py +index 9d5aefe..7e944ce 100644 +--- a/tests/unit/test_commands.py ++++ b/tests/unit/test_commands.py +@@ -87,8 +87,8 @@ def test_index_group_commands() -> None: + (True, True, False), + ], + ) +-@mock.patch("pip._internal.cli.index_command._pip_self_version_check") +-def test_index_group_handle_pip_version_check( ++@mock.patch("pip._internal.cli.index_command._pip_self_version_check_fetch") ++def test_index_group_pip_version_check( + mock_version_check: mock.Mock, + command_name: str, + disable_pip_version_check: bool, +@@ -96,9 +96,8 @@ def test_index_group_handle_pip_version_check( + expected_called: bool, + ) -> None: + """ +- Test whether pip_self_version_check() is called when +- handle_pip_version_check() is called, for each of the +- IndexGroupCommand classes. ++ Test whether self-version check is performed when ``pip_version_check()`` ++ is called, for each of the IndexGroupCommand classes. + """ + command = create_command(command_name) + options = command.parser.get_default_values() +-- +2.45.4 + diff --git a/SPECS/python-pip/python-pip.spec b/SPECS/python-pip/python-pip.spec index a50fc18f729..e2049bc7f37 100644 --- a/SPECS/python-pip/python-pip.spec +++ b/SPECS/python-pip/python-pip.spec @@ -5,7 +5,7 @@ A tool for installing and managing Python packages} Summary: A tool for installing and managing Python packages Name: python-pip Version: 24.2 -Release: 7%{?dist} +Release: 8%{?dist} License: MIT AND Python-2.0.1 AND Apache-2.0 AND BSD-2-Clause AND BSD-3-Clause AND ISC AND LGPL-2.1-only AND MPL-2.0 AND (Apache-2.0 OR BSD-2-Clause) Vendor: Microsoft Corporation Distribution: Azure Linux @@ -17,6 +17,7 @@ Patch1: CVE-2025-8869.patch Patch2: CVE-2025-50181.patch Patch3: CVE-2026-1703.patch Patch4: CVE-2026-3219.patch +Patch5: CVE-2026-6357.patch BuildArch: noarch @@ -60,6 +61,9 @@ BuildRequires: python3-wheel %{python3_sitelib}/pip* %changelog +* Fri May 08 2026 Azure Linux Security Servicing Account - 24.2-8 +- Patch for CVE-2026-6357 + * Wed Apr 22 2026 Azure Linux Security Servicing Account - 24.2-7 - Patch for CVE-2026-3219 From 9bcc62e7a15179c97ff310741c2feb223d4e16ba Mon Sep 17 00:00:00 2001 From: BinduSri-6522866 Date: Tue, 5 May 2026 09:14:06 +0000 Subject: [PATCH 2/3] manually applied patch for CVE-2026-6357 --- SPECS/python-pip/CVE-2026-6357.patch | 480 ++++++++++++++++++++++----- 1 file changed, 400 insertions(+), 80 deletions(-) diff --git a/SPECS/python-pip/CVE-2026-6357.patch b/SPECS/python-pip/CVE-2026-6357.patch index cc097f0d25f..b6846532b16 100644 --- a/SPECS/python-pip/CVE-2026-6357.patch +++ b/SPECS/python-pip/CVE-2026-6357.patch @@ -1,48 +1,59 @@ -From 4d3bacc72e85d03e3dd8c9fdb6e960774192c53c Mon Sep 17 00:00:00 2001 -From: AllSpark -Date: Mon, 4 May 2026 03:26:56 +0000 -Subject: [PATCH] Backport split pip self-version check into fetch(before) and - emit(after) phases; adjust context managers and tests; compute prompt based - on cached/remote and installer; skip check in install when pip is target +From 8786d084768e0b42afa093083c8c05ee8e525574 Mon Sep 17 00:00:00 2001 +From: Damian Shaw +Date: Fri, 17 Apr 2026 09:52:38 -0400 +Subject: [PATCH 1/3] Split the pip self-version check to get info before run -Signed-off-by: Azure Linux Security Servicing Account -Upstream-reference: AI Backport of https://github.com/pypa/pip/pull/13923.patch +Upstream Patch reference: https://patch-diff.githubusercontent.com/raw/pypa/pip/pull/13923.patch --- - src/pip/_internal/cli/base_command.py | 11 ++-- - src/pip/_internal/cli/index_command.py | 35 +++++++++--- - src/pip/_internal/commands/install.py | 6 ++ - src/pip/_internal/commands/list.py | 13 +++-- - src/pip/_internal/self_outdated_check.py | 73 ++++++++++++------------ - tests/unit/test_base_command.py | 8 +-- - tests/unit/test_commands.py | 9 ++- - 7 files changed, 93 insertions(+), 62 deletions(-) + news/13923.trivial.rst | 2 + + src/pip/_internal/cli/base_command.py | 10 +- + src/pip/_internal/cli/index_command.py | 34 +++++- + src/pip/_internal/commands/install.py | 22 ++++ + src/pip/_internal/commands/list.py | 12 +- + src/pip/_internal/self_outdated_check.py | 75 ++++++------- + tests/unit/test_base_command.py | 8 +- + tests/unit/test_commands.py | 44 ++++++-- + tests/unit/test_self_check_outdated.py | 133 ++++++++++++++++++----- + 9 files changed, 250 insertions(+), 90 deletions(-) + create mode 100644 news/13923.trivial.rst +diff --git a/news/13923.trivial.rst b/news/13923.trivial.rst +new file mode 100644 +index 0000000..cb3b0ff +--- /dev/null ++++ b/news/13923.trivial.rst +@@ -0,0 +1,2 @@ ++Split the pip self-version check into a fetch phase before the command ++body and an emit phase afterwards. diff --git a/src/pip/_internal/cli/base_command.py b/src/pip/_internal/cli/base_command.py -index bc1ab65..4d81124 100644 +index bc1ab65..5da10c0 100644 --- a/src/pip/_internal/cli/base_command.py +++ b/src/pip/_internal/cli/base_command.py -@@ -6,8 +6,9 @@ import optparse +@@ -1,11 +1,13 @@ + """Base Command class, and related routines""" + ++import contextlib + import logging + import logging.config + import optparse import os import sys import traceback -+import contextlib ++from collections.abc import Iterator from optparse import Values --from typing import List, Optional, Tuple -+from typing import List, Optional, Tuple, Iterator + from typing import List, Optional, Tuple - from pip._vendor.rich import reconfigure - from pip._vendor.rich import traceback as rich_traceback -@@ -78,7 +79,8 @@ class Command(CommandContextMixIn): +@@ -78,7 +80,8 @@ class Command(CommandContextMixIn): def add_options(self) -> None: pass - def handle_pip_version_check(self, options: Values) -> None: + @contextlib.contextmanager -+ def pip_version_check(self, options: Values, args: List[str]) -> Iterator[None]: ++ def pip_version_check(self, options: Values, args: list[str]) -> Iterator[None]: """ This is a no-op so that commands by default do not do the pip version check. -@@ -86,16 +88,15 @@ class Command(CommandContextMixIn): +@@ -86,16 +89,15 @@ class Command(CommandContextMixIn): # Make sure we do the pip version check if the index_group options # are present. assert not hasattr(options, "no_index") @@ -62,61 +73,68 @@ index bc1ab65..4d81124 100644 if options.debug_mode: rich_traceback.install(show_locals=True) diff --git a/src/pip/_internal/cli/index_command.py b/src/pip/_internal/cli/index_command.py -index 226f8da..930f23a 100644 +index 226f8da..635b9ad 100644 --- a/src/pip/_internal/cli/index_command.py +++ b/src/pip/_internal/cli/index_command.py -@@ -9,8 +9,12 @@ so commands which don't always hit the network (e.g. list w/o --outdated or +@@ -6,8 +6,10 @@ so commands which don't always hit the network (e.g. list w/o --outdated or + --uptodate) don't need waste time importing PipSession and friends. + """ + ++import contextlib import logging import os ++from collections.abc import Iterator import sys -+import contextlib from optparse import Values --from typing import TYPE_CHECKING, List, Optional -+from typing import TYPE_CHECKING, List, Optional, Iterator -+ -+if TYPE_CHECKING: + from typing import TYPE_CHECKING, List, Optional +@@ -21,6 +23,7 @@ if TYPE_CHECKING: + from ssl import SSLContext + + from pip._internal.network.session import PipSession + from pip._internal.self_outdated_check import UpgradePrompt - from pip._vendor import certifi + logger = logging.getLogger(__name__) -@@ -131,10 +135,16 @@ class SessionCommandMixin(CommandContextMixIn): +@@ -131,10 +134,18 @@ class SessionCommandMixin(CommandContextMixIn): return session -def _pip_self_version_check(session: "PipSession", options: Values) -> None: - from pip._internal.self_outdated_check import pip_self_version_check as check -+def _pip_self_version_check_fetch(session: "PipSession", options: Values) -> Optional["UpgradePrompt"]: ++def _pip_self_version_check_fetch( ++ session: PipSession, options: Values ++) -> UpgradePrompt | None: + from pip._internal.self_outdated_check import pip_self_version_check_fetch -+ -+ return pip_self_version_check_fetch(session, options) -+ - check(session, options) -+def _pip_self_version_check_emit(upgrade_prompt: Optional["UpgradePrompt"]) -> None: ++ return pip_self_version_check_fetch(session, options) ++ ++ ++def _pip_self_version_check_emit(upgrade_prompt: UpgradePrompt | None) -> None: + from pip._internal.self_outdated_check import pip_self_version_check_emit + + pip_self_version_check_emit(upgrade_prompt) class IndexGroupCommand(Command, SessionCommandMixin): -@@ -144,7 +154,8 @@ class IndexGroupCommand(Command, SessionCommandMixin): +@@ -144,7 +155,8 @@ class IndexGroupCommand(Command, SessionCommandMixin): This also corresponds to the commands that permit the pip version check. """ - def handle_pip_version_check(self, options: Values) -> None: + @contextlib.contextmanager -+ def pip_version_check(self, options: Values, args: List[str]) -> Iterator[None]: ++ def pip_version_check(self, options: Values, args: list[str]) -> Iterator[None]: """ Do the pip version check if not disabled. -@@ -154,17 +165,27 @@ class IndexGroupCommand(Command, SessionCommandMixin): +@@ -154,17 +166,27 @@ class IndexGroupCommand(Command, SessionCommandMixin): assert hasattr(options, "no_index") if options.disable_pip_version_check or options.no_index: + yield return -+ upgrade_prompt: Optional["UpgradePrompt"] = None ++ upgrade_prompt: UpgradePrompt | None = None try: - # Otherwise, check if we're using the latest version of pip available. session = self._build_session( @@ -140,35 +158,71 @@ index 226f8da..930f23a 100644 + logger.warning("There was an error checking the latest version of pip.") + logger.debug("See below for error", exc_info=True) diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py -index ad45a2f..298b92c 100644 +index ad45a2f..02bc885 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py -@@ -1,4 +1,10 @@ - import errno -+from __future__ import annotations -+ +@@ -1,12 +1,15 @@ +import contextlib -+from collections.abc import Iterator -+from pip._vendor.packaging.requirements import InvalidRequirement, Requirement -+ + import errno import json import operator import os + import shutil + import site ++from collections.abc import Iterator + from optparse import SUPPRESS_HELP, Values + from typing import List, Optional + ++from pip._vendor.packaging.requirements import InvalidRequirement, Requirement + from pip._vendor.packaging.utils import canonicalize_name + from pip._vendor.rich import print_json + +@@ -50,6 +53,14 @@ from pip._internal.wheel_builder import build, should_build_for_install_command + logger = getLogger(__name__) + + ++def _arg_refers_to_pip(arg: str) -> bool: ++ try: ++ req = Requirement(arg) ++ except InvalidRequirement: ++ return False ++ return canonicalize_name(req.name) == "pip" ++ ++ + class InstallCommand(RequirementCommand): + """ + Install packages from: +@@ -263,6 +274,17 @@ class InstallCommand(RequirementCommand): + ), + ) + ++ @contextlib.contextmanager ++ def pip_version_check(self, options: Values, args: list[str]) -> Iterator[None]: ++ # Skip the self-version check when pip itself is a requirement. The ++ # running pip may be replaced mid-command, and the upgrade prompt ++ # is redundant. ++ if any(_arg_refers_to_pip(arg) for arg in args): ++ yield ++ return ++ with super().pip_version_check(options, args): ++ yield ++ + @with_cleanup + def run(self, options: Values, args: List[str]) -> int: + if options.use_user_site and options.target_dir is not None: diff --git a/src/pip/_internal/commands/list.py b/src/pip/_internal/commands/list.py -index 82fc46a..ba00f82 100644 +index 82fc46a..b850e32 100644 --- a/src/pip/_internal/commands/list.py +++ b/src/pip/_internal/commands/list.py -@@ -1,7 +1,8 @@ +@@ -1,5 +1,7 @@ ++import contextlib import json import logging -+import contextlib ++from collections.abc import Iterator from optparse import Values --from typing import TYPE_CHECKING, Generator, List, Optional, Sequence, Tuple, cast -+from typing import TYPE_CHECKING, Generator, List, Optional, Sequence, Tuple, cast, Iterator + from typing import TYPE_CHECKING, Generator, List, Optional, Sequence, Tuple, cast - from pip._vendor.packaging.utils import canonicalize_name - from pip._vendor.packaging.version import Version -@@ -134,9 +135,13 @@ class ListCommand(IndexGroupCommand): +@@ -134,9 +136,13 @@ class ListCommand(IndexGroupCommand): self.parser.insert_option_group(0, index_opts) self.parser.insert_option_group(0, self.cmd_opts) @@ -176,7 +230,7 @@ index 82fc46a..ba00f82 100644 - if options.outdated or options.uptodate: - super().handle_pip_version_check(options) + @contextlib.contextmanager -+ def pip_version_check(self, options: Values, args: List[str]) -> Iterator[None]: ++ def pip_version_check(self, options: Values, args: list[str]) -> Iterator[None]: + if not (options.outdated or options.uptodate): + yield + return @@ -186,7 +240,7 @@ index 82fc46a..ba00f82 100644 def _build_package_finder( self, options: Values, session: "PipSession" diff --git a/src/pip/_internal/self_outdated_check.py b/src/pip/_internal/self_outdated_check.py -index f9a91af..fe0e0d2 100644 +index f9a91af..5ec9df1 100644 --- a/src/pip/_internal/self_outdated_check.py +++ b/src/pip/_internal/self_outdated_check.py @@ -1,5 +1,4 @@ @@ -214,7 +268,7 @@ index f9a91af..fe0e0d2 100644 _WEEK = datetime.timedelta(days=7) -@@ -149,14 +149,6 @@ class UpgradePrompt: +@@ -149,16 +149,6 @@ class UpgradePrompt: ) @@ -226,10 +280,12 @@ index f9a91af..fe0e0d2 100644 - """ - dist = get_default_environment().get_distribution(pkg) - return dist is not None and "pip" == dist.installer - - +- +- def _get_current_remote_pip_version( -@@ -187,28 +179,15 @@ def _get_current_remote_pip_version( + session: PipSession, options: optparse.Values + ) -> Optional[str]: +@@ -187,28 +177,15 @@ def _get_current_remote_pip_version( return str(best_candidate.version) @@ -262,7 +318,7 @@ index f9a91af..fe0e0d2 100644 return None # Only suggest upgrade if pip is installed by pip. local_version_is_older = ( -@@ -221,24 +200,44 @@ def _self_version_check_logic( +@@ -221,24 +198,44 @@ def _self_version_check_logic( return None @@ -270,7 +326,7 @@ index f9a91af..fe0e0d2 100644 - """Check for an update for pip. +def pip_self_version_check_fetch( + session: PipSession, options: optparse.Values -+) -> Optional[UpgradePrompt]: ++) -> UpgradePrompt | None: + """Compute the pip upgrade prompt, if any, before the command runs. Limit the frequency of checks to once per week. State is stored either in @@ -288,10 +344,7 @@ index f9a91af..fe0e0d2 100644 + check_externally_managed() + except ExternallyManagedEnvironment: + return None - -- upgrade_prompt = _self_version_check_logic( -- state=SelfCheckState(cache_dir=options.cache_dir), -- current_time=datetime.datetime.now(datetime.timezone.utc), ++ + state = SelfCheckState(cache_dir=options.cache_dir) + current_time = datetime.datetime.now(datetime.timezone.utc) + remote_version_str = state.get(current_time) @@ -301,7 +354,10 @@ index f9a91af..fe0e0d2 100644 + logger.debug("No remote pip version found") + return None + state.set(remote_version_str, current_time) -+ + +- upgrade_prompt = _self_version_check_logic( +- state=SelfCheckState(cache_dir=options.cache_dir), +- current_time=datetime.datetime.now(datetime.timezone.utc), + return _compute_upgrade_prompt( local_version=installed_dist.version, - get_remote_version=functools.partial( @@ -312,7 +368,7 @@ index f9a91af..fe0e0d2 100644 ) + + -+def pip_self_version_check_emit(upgrade_prompt: Optional[UpgradePrompt]) -> None: ++def pip_self_version_check_emit(upgrade_prompt: UpgradePrompt | None) -> None: + """Emit the upgrade prompt captured by :func:`pip_self_version_check_fetch`.""" if upgrade_prompt is not None: logger.warning("%s", upgrade_prompt, extra={"rich": True}) @@ -340,10 +396,20 @@ index f9fae65..5fdf94a 100644 def test_log_command_success(fixed_time: None, tmpdir: Path) -> None: diff --git a/tests/unit/test_commands.py b/tests/unit/test_commands.py -index 9d5aefe..7e944ce 100644 +index 9d5aefe..095faf4 100644 --- a/tests/unit/test_commands.py +++ b/tests/unit/test_commands.py -@@ -87,8 +87,8 @@ def test_index_group_commands() -> None: +@@ -79,16 +79,16 @@ def test_index_group_commands() -> None: + @pytest.mark.parametrize( + "disable_pip_version_check, no_index, expected_called", + [ +- # pip_self_version_check() is only called when both +- # disable_pip_version_check and no_index are False. ++ # The fetch phase only runs when both disable_pip_version_check ++ # and no_index are False. + (False, False, True), + (False, True, False), + (True, False, False), (True, True, False), ], ) @@ -354,18 +420,272 @@ index 9d5aefe..7e944ce 100644 mock_version_check: mock.Mock, command_name: str, disable_pip_version_check: bool, -@@ -96,9 +96,8 @@ def test_index_group_handle_pip_version_check( +@@ -96,26 +96,52 @@ def test_index_group_handle_pip_version_check( expected_called: bool, ) -> None: """ - Test whether pip_self_version_check() is called when - handle_pip_version_check() is called, for each of the - IndexGroupCommand classes. -+ Test whether self-version check is performed when ``pip_version_check()`` -+ is called, for each of the IndexGroupCommand classes. ++ Test whether the pre-body fetch runs when ``pip_version_check()`` is ++ entered, for each of the IndexGroupCommand classes. """ command = create_command(command_name) options = command.parser.get_default_values() + options.disable_pip_version_check = disable_pip_version_check + options.no_index = no_index ++ # Return None so the emit branch is a no-op. ++ mock_version_check.return_value = None + + # See test test_list_pip_version_check() below. + if command_name == "list": + expected_called = False + +- command.handle_pip_version_check(options) ++ with command.pip_version_check(options, []): ++ pass + if expected_called: + mock_version_check.assert_called_once() + else: + mock_version_check.assert_not_called() + + ++@mock.patch("pip._internal.cli.index_command._pip_self_version_check_fetch") ++def test_install_pip_version_check_skipped_when_pip_is_a_requirement( ++ mock_version_check: mock.Mock, ++) -> None: ++ """``pip install pip`` must skip the self-version check: the running pip ++ may be replaced before emit.""" ++ command = create_command("install") ++ options = command.parser.get_default_values() ++ options.disable_pip_version_check = False ++ options.no_index = False ++ ++ with command.pip_version_check(options, ["pip"]): ++ pass ++ mock_version_check.assert_not_called() ++ ++ with command.pip_version_check(options, ["pip==25.0"]): ++ pass ++ mock_version_check.assert_not_called() ++ ++ with command.pip_version_check(options, ["some-other-pkg"]): ++ pass ++ mock_version_check.assert_called_once() ++ ++ + def test_requirement_commands() -> None: + """ + Test which commands inherit from RequirementCommand. +@@ -128,7 +154,7 @@ def test_requirement_commands() -> None: + + + @pytest.mark.parametrize("flag", ["", "--outdated", "--uptodate"]) +-@mock.patch("pip._internal.cli.index_command._pip_self_version_check") ++@mock.patch("pip._internal.cli.index_command._pip_self_version_check_fetch") + @mock.patch.dict(os.environ, {"PIP_DISABLE_PIP_VERSION_CHECK": "no"}) + def test_list_pip_version_check(version_check_mock: mock.Mock, flag: str) -> None: + """ +diff --git a/tests/unit/test_self_check_outdated.py b/tests/unit/test_self_check_outdated.py +index 99a556e..b62da75 100644 +--- a/tests/unit/test_self_check_outdated.py ++++ b/tests/unit/test_self_check_outdated.py +@@ -6,13 +6,26 @@ import sys + from optparse import Values + from pathlib import Path + from typing import Optional +-from unittest.mock import ANY, Mock, patch ++from unittest.mock import Mock, patch + + import pytest + from freezegun import freeze_time + from pip._vendor.packaging.version import Version + + from pip._internal import self_outdated_check ++from pip._internal.self_outdated_check import ( ++ UpgradePrompt, ++ pip_self_version_check_emit, ++ pip_self_version_check_fetch, ++) ++ ++ ++def _make_installed_dist(version: str, installer: str = "pip") -> Mock: ++ """Build a stand-in for the installed pip distribution.""" ++ installed_dist = Mock() ++ installed_dist.version = Version(version) ++ installed_dist.installer = installer ++ return installed_dist + + + @pytest.mark.parametrize( +@@ -33,31 +46,59 @@ def test_get_statefile_name_known_values(key: str, expected: str) -> None: + + + @freeze_time("1970-01-02T11:00:00Z") +-@patch("pip._internal.self_outdated_check._self_version_check_logic") ++@patch("pip._internal.self_outdated_check._get_current_remote_pip_version") + @patch("pip._internal.self_outdated_check.SelfCheckState") +-def test_pip_self_version_check_calls_underlying_implementation( +- mocked_state: Mock, mocked_function: Mock, tmpdir: Path ++@patch("pip._internal.self_outdated_check.get_default_environment") ++def test_pip_self_version_check_fetch_calls_underlying_implementation( ++ mocked_env: Mock, mocked_state: Mock, mocked_get_remote: Mock, tmpdir: Path + ) -> None: + # GIVEN + mock_session = Mock() + fake_options = Values({"cache_dir": str(tmpdir)}) +- mocked_function.return_value = None ++ mocked_env.return_value.get_distribution.return_value = _make_installed_dist("1.0") ++ mocked_state.return_value.get.return_value = None ++ mocked_get_remote.return_value = "5.0" + + # WHEN +- self_outdated_check.pip_self_version_check(mock_session, fake_options) ++ result = pip_self_version_check_fetch(mock_session, fake_options) + + # THEN ++ assert result == UpgradePrompt(old="1.0", new="5.0") + mocked_state.assert_called_once_with(cache_dir=str(tmpdir)) +- mocked_function.assert_called_once_with( +- state=mocked_state(cache_dir=str(tmpdir)), +- current_time=datetime.datetime( +- 1970, 1, 2, 11, 0, 0, tzinfo=datetime.timezone.utc +- ), +- local_version=ANY, +- get_remote_version=ANY, ++ mocked_get_remote.assert_called_once_with(mock_session, fake_options) ++ mocked_state.return_value.set.assert_called_once_with( ++ "5.0", ++ datetime.datetime(1970, 1, 2, 11, 0, 0, tzinfo=datetime.timezone.utc), + ) + + ++def test_pip_self_version_check_emit_logs_prompt( ++ caplog: pytest.LogCaptureFixture, ++) -> None: ++ # GIVEN ++ prompt = UpgradePrompt(old="1.0", new="2.0") ++ ++ # WHEN ++ with caplog.at_level(logging.WARNING): ++ pip_self_version_check_emit(prompt) ++ ++ # THEN ++ assert len(caplog.records) == 1 ++ assert caplog.records[0].levelno == logging.WARNING ++ ++ ++def test_pip_self_version_check_emit_no_prompt_is_silent( ++ caplog: pytest.LogCaptureFixture, ++) -> None: ++ # WHEN ++ with caplog.at_level(logging.WARNING): ++ pip_self_version_check_emit(None) ++ ++ # THEN ++ assert caplog.records == [] ++ ++ ++@freeze_time("2000-01-01T00:00:00Z") + @pytest.mark.parametrize( + [ + "installed_version", +@@ -89,27 +130,41 @@ def test_core_logic( + should_show_prompt: bool, + caplog: pytest.LogCaptureFixture, + monkeypatch: pytest.MonkeyPatch, ++ tmpdir: Path, + ) -> None: + # GIVEN ++ installed_dist = _make_installed_dist( ++ installed_version, installer="pip" if installed_by_pip else "apt" ++ ) ++ monkeypatch.setattr( ++ self_outdated_check, ++ "get_default_environment", ++ lambda: Mock(get_distribution=Mock(return_value=installed_dist)), ++ ) ++ monkeypatch.setattr(self_outdated_check, "check_externally_managed", lambda: None) ++ monkeypatch.setattr( ++ self_outdated_check, ++ "_get_current_remote_pip_version", ++ lambda session, options: remote_version, ++ ) ++ mock_state_instance = Mock() ++ mock_state_instance.get.return_value = stored_version + monkeypatch.setattr( +- self_outdated_check, "was_installed_by_pip", lambda _: installed_by_pip ++ self_outdated_check, ++ "SelfCheckState", ++ Mock(return_value=mock_state_instance), + ) +- mock_state = Mock() +- mock_state.get.return_value = stored_version +- fake_time = datetime.datetime(2000, 1, 1, 0, 0, 0) ++ fake_time = datetime.datetime(2000, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc) + version_that_should_be_checked = stored_version or remote_version + + # WHEN + with caplog.at_level(logging.DEBUG): +- return_value = self_outdated_check._self_version_check_logic( +- state=mock_state, +- current_time=fake_time, +- local_version=Version(installed_version), +- get_remote_version=lambda: remote_version, ++ return_value = pip_self_version_check_fetch( ++ session=Mock(), options=Values({"cache_dir": str(tmpdir)}) + ) + + # THEN +- mock_state.get.assert_called_once_with(fake_time) ++ mock_state_instance.get.assert_called_once_with(fake_time) + assert caplog.messages == [ + f"Remote version of pip: {version_that_should_be_checked}", + f"Local version of pip: {installed_version}", +@@ -117,9 +172,9 @@ def test_core_logic( + ] + + if stored_version: +- mock_state.set.assert_not_called() ++ mock_state_instance.set.assert_not_called() + else: +- mock_state.set.assert_called_once_with( ++ mock_state_instance.set.assert_called_once_with( + version_that_should_be_checked, fake_time + ) + +@@ -184,3 +239,31 @@ class TestSelfCheckState: + "last_check": "2000-01-01T00:00:00+00:00", + "pypi_version": "1.0.0", + } ++ ++ ++@patch("pip._internal.self_outdated_check._get_current_remote_pip_version") ++@patch("pip._internal.self_outdated_check.get_default_environment") ++def test_fetch_suppressed_by_externally_managed( ++ mocked_env: Mock, mocked_get_remote: Mock, tmpdir: Path ++) -> None: ++ mocked_env.return_value.get_distribution.return_value = _make_installed_dist("1.0") ++ fake_options = Values({"cache_dir": str(tmpdir)}) ++ with patch( ++ "pip._internal.self_outdated_check.check_externally_managed", ++ side_effect=ExternallyManagedEnvironment("nope"), ++ ): ++ result = pip_self_version_check_fetch(session=Mock(), options=fake_options) ++ assert result is None ++ mocked_get_remote.assert_not_called() ++ ++ ++@patch("pip._internal.self_outdated_check._get_current_remote_pip_version") ++@patch("pip._internal.self_outdated_check.get_default_environment") ++def test_fetch_skipped_when_pip_not_installed( ++ mocked_env: Mock, mocked_get_remote: Mock, tmpdir: Path ++) -> None: ++ mocked_env.return_value.get_distribution.return_value = None ++ fake_options = Values({"cache_dir": str(tmpdir)}) ++ result = pip_self_version_check_fetch(session=Mock(), options=fake_options) ++ assert result is None ++ mocked_get_remote.assert_not_called() -- -2.45.4 +2.43.0 From 6aeaf9d4af76f67d5a8f1563783b18170569452d Mon Sep 17 00:00:00 2001 From: BinduSri-6522866 Date: Fri, 8 May 2026 04:50:17 +0000 Subject: [PATCH 3/3] update toolchain manifest files --- toolkit/resources/manifests/package/toolchain_aarch64.txt | 2 +- toolkit/resources/manifests/package/toolchain_x86_64.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/toolkit/resources/manifests/package/toolchain_aarch64.txt b/toolkit/resources/manifests/package/toolchain_aarch64.txt index 75e56ccf6f2..74109d3d8fa 100644 --- a/toolkit/resources/manifests/package/toolchain_aarch64.txt +++ b/toolkit/resources/manifests/package/toolchain_aarch64.txt @@ -550,7 +550,7 @@ python3-magic-5.45-1.azl3.noarch.rpm python3-markupsafe-2.1.3-1.azl3.aarch64.rpm python3-newt-0.52.23-1.azl3.aarch64.rpm python3-packaging-23.2-3.azl3.noarch.rpm -python3-pip-24.2-7.azl3.noarch.rpm +python3-pip-24.2-8.azl3.noarch.rpm python3-pygments-2.7.4-2.azl3.noarch.rpm python3-rpm-4.18.2-1.azl3.aarch64.rpm python3-rpm-generators-14-11.azl3.noarch.rpm diff --git a/toolkit/resources/manifests/package/toolchain_x86_64.txt b/toolkit/resources/manifests/package/toolchain_x86_64.txt index bff83e56322..be9ed184f9e 100644 --- a/toolkit/resources/manifests/package/toolchain_x86_64.txt +++ b/toolkit/resources/manifests/package/toolchain_x86_64.txt @@ -558,7 +558,7 @@ python3-magic-5.45-1.azl3.noarch.rpm python3-markupsafe-2.1.3-1.azl3.x86_64.rpm python3-newt-0.52.23-1.azl3.x86_64.rpm python3-packaging-23.2-3.azl3.noarch.rpm -python3-pip-24.2-7.azl3.noarch.rpm +python3-pip-24.2-8.azl3.noarch.rpm python3-pygments-2.7.4-2.azl3.noarch.rpm python3-rpm-4.18.2-1.azl3.x86_64.rpm python3-rpm-generators-14-11.azl3.noarch.rpm