diff --git a/news/10647.feature.rst b/news/10647.feature.rst new file mode 100644 index 00000000000..ad9d06fbb05 --- /dev/null +++ b/news/10647.feature.rst @@ -0,0 +1,3 @@ +Allow Python distributors to opt-out from or opt-in to the ``sysconfig`` +installation scheme backend by setting ``sysconfig._PIP_USE_SYSCONFIG`` +to ``True`` or ``False``. diff --git a/src/pip/_internal/locations/__init__.py b/src/pip/_internal/locations/__init__.py index dba182d7578..729c7548de7 100644 --- a/src/pip/_internal/locations/__init__.py +++ b/src/pip/_internal/locations/__init__.py @@ -45,7 +45,22 @@ _PLATLIBDIR: str = getattr(sys, "platlibdir", "lib") -_USE_SYSCONFIG = sys.version_info >= (3, 10) + +def _should_use_sysconfig() -> bool: + """ + This function determines the value of _USE_SYSCONFIG. + By default, pip uses sysconfig on Python 3.10+. + But Python distributors can override this decision by setting: + sysconfig._PIP_USE_SYSCONFIG = True / False + Rationale in https://github.com/pypa/pip/issues/10647 + """ + if hasattr(sysconfig, "_PIP_USE_SYSCONFIG"): + return bool(sysconfig._PIP_USE_SYSCONFIG) # type: ignore [attr-defined] + return sys.version_info >= (3, 10) + + +# This is a function for testability, but should be constant during any one run. +_USE_SYSCONFIG = _should_use_sysconfig() def _looks_like_bpo_44860() -> bool: diff --git a/tests/unit/test_locations.py b/tests/unit/test_locations.py index 640be7f0df8..48de5f86006 100644 --- a/tests/unit/test_locations.py +++ b/tests/unit/test_locations.py @@ -6,13 +6,14 @@ import os import shutil import sys +import sysconfig import tempfile from typing import Any, Dict from unittest.mock import Mock import pytest -from pip._internal.locations import SCHEME_KEYS, get_scheme +from pip._internal.locations import SCHEME_KEYS, _should_use_sysconfig, get_scheme from tests.lib.path import Path if sys.platform == "win32": @@ -82,6 +83,24 @@ def get_mock_getpwuid(self, uid: int) -> pwd.struct_passwd: result.pw_name = self.username return result + def test_default_should_use_sysconfig( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + monkeypatch.delattr(sysconfig, "_PIP_USE_SYSCONFIG", raising=False) + if sys.version_info[:2] >= (3, 10): + assert _should_use_sysconfig() is True + else: + assert _should_use_sysconfig() is False + + @pytest.mark.parametrize("vendor_value", [True, False, None, "", 0, 1]) + def test_vendor_overriden_should_use_sysconfig( + self, monkeypatch: pytest.MonkeyPatch, vendor_value: Any + ) -> None: + monkeypatch.setattr( + sysconfig, "_PIP_USE_SYSCONFIG", vendor_value, raising=False + ) + assert _should_use_sysconfig() is bool(vendor_value) + class TestDistutilsScheme: def test_root_modifies_appropriately(self) -> None: