From 56d326913fdcb43dab45b080df603615320bad20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sun, 13 Oct 2024 12:24:44 +0200 Subject: [PATCH] Never use the pkg_resources backend on python 3.14+ --- news/13010.removal.rst | 2 ++ src/pip/_internal/metadata/__init__.py | 24 +++++++++++++------ src/pip/_internal/metadata/importlib/_envs.py | 7 +++--- tests/functional/test_uninstall.py | 4 ++++ 4 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 news/13010.removal.rst diff --git a/news/13010.removal.rst b/news/13010.removal.rst new file mode 100644 index 00000000000..1b665cc20bf --- /dev/null +++ b/news/13010.removal.rst @@ -0,0 +1,2 @@ +On python 3.14+, the ``pkg_resources`` metadata backend is not used anymore, +and pip does not attempt to detect installed ``.egg`` distributions. diff --git a/src/pip/_internal/metadata/__init__.py b/src/pip/_internal/metadata/__init__.py index 3535e5699bc..df03c47d97e 100644 --- a/src/pip/_internal/metadata/__init__.py +++ b/src/pip/_internal/metadata/__init__.py @@ -25,20 +25,30 @@ def _should_use_importlib_metadata() -> bool: """Whether to use the ``importlib.metadata`` or ``pkg_resources`` backend. By default, pip uses ``importlib.metadata`` on Python 3.11+, and - ``pkg_resources`` otherwise. This can be overridden by a couple of ways: + ``pkg_resources`` otherwise. Up to Python 3.13, This can be + overridden by a couple of ways: * If environment variable ``_PIP_USE_IMPORTLIB_METADATA`` is set, it - dictates whether ``importlib.metadata`` is used, regardless of Python - version. - * On Python 3.11+, Python distributors can patch ``importlib.metadata`` - to add a global constant ``_PIP_USE_IMPORTLIB_METADATA = False``. This - makes pip use ``pkg_resources`` (unless the user set the aforementioned - environment variable to *True*). + dictates whether ``importlib.metadata`` is used, for Python <3.14. + * On Python 3.11, 3.12 and 3.13, Python distributors can patch + ``importlib.metadata`` to add a global constant + ``_PIP_USE_IMPORTLIB_METADATA = False``. This makes pip use + ``pkg_resources`` (unless the user set the aforementioned environment + variable to *True*). + + On Python 3.14+, the ``pkg_resources`` backend cannot be used. """ + if sys.version_info >= (3, 14): + # On Python >=3.14 we only support importlib.metadata. + return True with contextlib.suppress(KeyError, ValueError): + # On Python <3.14, if the environment variable is set, we obey what it says. return bool(strtobool(os.environ["_PIP_USE_IMPORTLIB_METADATA"])) if sys.version_info < (3, 11): + # On Python <3.11, we always use pkg_resources, unless the environment + # variable was set. return False + # On Python 3.11, 3.12 and 3.13, we check if the global constant is set. import importlib.metadata return bool(getattr(importlib.metadata, "_PIP_USE_IMPORTLIB_METADATA", True)) diff --git a/src/pip/_internal/metadata/importlib/_envs.py b/src/pip/_internal/metadata/importlib/_envs.py index 681fd241fdc..c7b09d215e3 100644 --- a/src/pip/_internal/metadata/importlib/_envs.py +++ b/src/pip/_internal/metadata/importlib/_envs.py @@ -179,9 +179,10 @@ def _iter_distributions(self) -> Iterator[BaseDistribution]: finder = _DistributionFinder() for location in self._paths: yield from finder.find(location) - for dist in finder.find_eggs(location): - _emit_egg_deprecation(dist.location) - yield dist + if sys.version_info < (3, 14): + for dist in finder.find_eggs(location): + _emit_egg_deprecation(dist.location) + yield dist # This must go last because that's how pkg_resources tie-breaks. yield from finder.find_linked(location) diff --git a/tests/functional/test_uninstall.py b/tests/functional/test_uninstall.py index d86ba172002..db2d25f2233 100644 --- a/tests/functional/test_uninstall.py +++ b/tests/functional/test_uninstall.py @@ -628,6 +628,10 @@ def test_uninstall_with_symlink( assert symlink_target.stat().st_mode == st_mode +@pytest.mark.skipif( + "sys.version_info >= (3, 14)", + reason="Uninstall of .egg distributions not supported in Python 3.14+", +) def test_uninstall_setuptools_develop_install( script: PipTestEnvironment, data: TestData ) -> None: