From fd7831b466bb7993b9a3489475a456ec91cb0f2b Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Wed, 15 Oct 2025 05:48:16 +0200 Subject: [PATCH 1/5] Fix the typing of pip._internal.utils.* --- src/pip/_internal/utils/misc.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pip/_internal/utils/misc.py b/src/pip/_internal/utils/misc.py index 3a28e8449de..48c9efd4ecf 100644 --- a/src/pip/_internal/utils/misc.py +++ b/src/pip/_internal/utils/misc.py @@ -542,10 +542,9 @@ def __str__(self) -> str: return self.redacted # This is useful for testing. - def __eq__(self, other: Any) -> bool: - if type(self) is not type(other): + def __eq__(self, other: object) -> bool: + if type(self) is not type(other) or not isinstance(other, HiddenText): return False - # The string being used for redaction doesn't also have to match, # just the raw, original string. return self.secret == other.secret From 43c668aaca5eb1a0b63ecedb7b4533d3bfe8fa33 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Wed, 15 Oct 2025 06:50:02 +0200 Subject: [PATCH 2/5] Additional minor typing issues in _internal.[exceptions|locations|self_outdated_check] --- src/pip/_internal/exceptions.py | 4 ++-- src/pip/_internal/locations/__init__.py | 3 +-- src/pip/_internal/locations/_sysconfig.py | 5 ++++- src/pip/_internal/self_outdated_check.py | 4 ++-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/pip/_internal/exceptions.py b/src/pip/_internal/exceptions.py index d6e9095fb55..8bcb46212f8 100644 --- a/src/pip/_internal/exceptions.py +++ b/src/pip/_internal/exceptions.py @@ -27,7 +27,7 @@ if TYPE_CHECKING: from hashlib import _Hash - from pip._vendor.requests.models import Request, Response + from pip._vendor.requests.models import PreparedRequest, Request, Response from pip._internal.metadata import BaseDistribution from pip._internal.network.download import _FileDownload @@ -314,7 +314,7 @@ def __init__( self, error_msg: str, response: Response | None = None, - request: Request | None = None, + request: Request | PreparedRequest | None = None, ) -> None: """ Initialize NetworkConnectionError with `request` and `response` diff --git a/src/pip/_internal/locations/__init__.py b/src/pip/_internal/locations/__init__.py index 9f2c4fe316c..38808c6ad60 100644 --- a/src/pip/_internal/locations/__init__.py +++ b/src/pip/_internal/locations/__init__.py @@ -6,7 +6,6 @@ import pathlib import sys import sysconfig -from typing import Any from pip._internal.models.scheme import SCHEME_KEYS, Scheme from pip._internal.utils.compat import WINDOWS @@ -134,7 +133,7 @@ def _looks_like_red_hat_scheme() -> bool: from distutils.command.install import install from distutils.dist import Distribution - cmd: Any = install(Distribution()) + cmd: install = install(Distribution()) cmd.finalize_options() return ( cmd.exec_prefix == f"{os.path.normpath(sys.exec_prefix)}/local" diff --git a/src/pip/_internal/locations/_sysconfig.py b/src/pip/_internal/locations/_sysconfig.py index d4a448ece52..0d0233bb880 100644 --- a/src/pip/_internal/locations/_sysconfig.py +++ b/src/pip/_internal/locations/_sysconfig.py @@ -4,6 +4,7 @@ import os import sys import sysconfig +from typing import Callable from pip._internal.exceptions import InvalidSchemeCombination, UserInstallationInvalid from pip._internal.models.scheme import SCHEME_KEYS, Scheme @@ -24,7 +25,9 @@ _AVAILABLE_SCHEMES = set(sysconfig.get_scheme_names()) -_PREFERRED_SCHEME_API = getattr(sysconfig, "get_preferred_scheme", None) +_PREFERRED_SCHEME_API: Callable[[str], str] | None = getattr( + sysconfig, "get_preferred_scheme", None +) def _should_use_osx_framework_prefix() -> bool: diff --git a/src/pip/_internal/self_outdated_check.py b/src/pip/_internal/self_outdated_check.py index 5999ddb3737..db015b9df2b 100644 --- a/src/pip/_internal/self_outdated_check.py +++ b/src/pip/_internal/self_outdated_check.py @@ -9,7 +9,7 @@ import os.path import sys from dataclasses import dataclass -from typing import Any, Callable +from typing import Callable from pip._vendor.packaging.version import Version from pip._vendor.packaging.version import parse as parse_version @@ -61,7 +61,7 @@ def _convert_date(isodate: str) -> datetime.datetime: class SelfCheckState: def __init__(self, cache_dir: str) -> None: - self._state: dict[str, Any] = {} + self._state: dict[str, str] = {} self._statefile_path = None # Try to load the existing state From 72554d7016f87e61d93415d4eeb0a72e94d76a81 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Thu, 30 Oct 2025 05:07:03 +0100 Subject: [PATCH 3/5] Refine the HiddenText.__eq__ implementation for clarity --- src/pip/_internal/utils/misc.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/pip/_internal/utils/misc.py b/src/pip/_internal/utils/misc.py index 48c9efd4ecf..be1949fa41b 100644 --- a/src/pip/_internal/utils/misc.py +++ b/src/pip/_internal/utils/misc.py @@ -541,13 +541,14 @@ def __repr__(self) -> str: def __str__(self) -> str: return self.redacted - # This is useful for testing. def __eq__(self, other: object) -> bool: - if type(self) is not type(other) or not isinstance(other, HiddenText): - return False - # The string being used for redaction doesn't also have to match, - # just the raw, original string. - return self.secret == other.secret + # Equality is particularly useful for testing. + + if type(self) is type(other): + # The string being used for redaction doesn't also have to match, + # just the raw, original string. + return self.secret == cast(HiddenText, other).secret + return NotImplemented def hide_value(value: str) -> HiddenText: From b572e96fe00373ed8fa211491d697d55869600fe Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Thu, 30 Oct 2025 05:22:43 +0100 Subject: [PATCH 4/5] Disable hashing on HiddenText, since we have a custom __eq__. --- src/pip/_internal/utils/misc.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pip/_internal/utils/misc.py b/src/pip/_internal/utils/misc.py index be1949fa41b..9ddf81d084f 100644 --- a/src/pip/_internal/utils/misc.py +++ b/src/pip/_internal/utils/misc.py @@ -543,13 +543,17 @@ def __str__(self) -> str: def __eq__(self, other: object) -> bool: # Equality is particularly useful for testing. - if type(self) is type(other): # The string being used for redaction doesn't also have to match, # just the raw, original string. return self.secret == cast(HiddenText, other).secret return NotImplemented + # Disable hashing, since we have a custom __eq__ and don't need hash-ability + # (yet). The only required property of hashing is that objects which compare + # equal have the same hash value. + __hash__ = None # type: ignore[assignment] + def hide_value(value: str) -> HiddenText: return HiddenText(value, redacted="****") From 9389796c06603bd267322fe4ace0001f647a009d Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Thu, 30 Oct 2025 05:31:36 +0100 Subject: [PATCH 5/5] Remove explicit typing of install command, and let it be inferred automatically --- src/pip/_internal/locations/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pip/_internal/locations/__init__.py b/src/pip/_internal/locations/__init__.py index 38808c6ad60..c0b63205fe0 100644 --- a/src/pip/_internal/locations/__init__.py +++ b/src/pip/_internal/locations/__init__.py @@ -133,7 +133,7 @@ def _looks_like_red_hat_scheme() -> bool: from distutils.command.install import install from distutils.dist import Distribution - cmd: install = install(Distribution()) + cmd = install(Distribution()) cmd.finalize_options() return ( cmd.exec_prefix == f"{os.path.normpath(sys.exec_prefix)}/local"