diff --git a/Lib/shutil.py b/Lib/shutil.py index cad9b570241f2c..ed281072157f1f 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -10,34 +10,29 @@ import fnmatch import collections import errno +import importlib -try: - import zlib - del zlib - _ZLIB_SUPPORTED = True -except ImportError: - _ZLIB_SUPPORTED = False - -try: - import bz2 - del bz2 - _BZ2_SUPPORTED = True -except ImportError: - _BZ2_SUPPORTED = False - -try: - import lzma - del lzma - _LZMA_SUPPORTED = True -except ImportError: - _LZMA_SUPPORTED = False - -try: - from compression import zstd - del zstd - _ZSTD_SUPPORTED = True -except ImportError: - _ZSTD_SUPPORTED = False +def _compression_module_supported(module_name): + set_lazy_imports = getattr(sys, "set_lazy_imports", None) + try: + if set_lazy_imports is None: + importlib.import_module(module_name) + return True + + lazy_imports_mode = sys.get_lazy_imports() + set_lazy_imports("normal") + importlib.import_module(module_name) + except ImportError: + return False + finally: + if set_lazy_imports is not None: + set_lazy_imports(lazy_imports_mode) + return True + +_ZLIB_SUPPORTED = _compression_module_supported("zlib") +_BZ2_SUPPORTED = _compression_module_supported("bz2") +_LZMA_SUPPORTED = _compression_module_supported("lzma") +_ZSTD_SUPPORTED = _compression_module_supported("compression.zstd") _WINDOWS = os.name == 'nt' posix = nt = None diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 59cb319b0a95b5..784ea5dcd42797 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -31,6 +31,7 @@ from test import support from test.support import os_helper from test.support.os_helper import TESTFN, FakePath +from test.support.script_helper import assert_python_ok TESTFN2 = TESTFN + "2" TESTFN_SRC = TESTFN + "_SRC" @@ -2177,6 +2178,27 @@ def test_unpack_archive_gztar(self): def test_unpack_archive_bztar(self): self.check_unpack_tarball('bztar') + @support.requires_subprocess() + def test_bz2_support_probe_is_eager_under_lazy_imports(self): + module_dir = self.mkdtemp() + create_file( + (module_dir, 'bz2.py'), + 'import _bz2\n' + 'SENTINEL = True\n', + ) + create_file( + (module_dir, '_bz2.py'), + 'raise ImportError("missing _bz2 backend")\n', + ) + code = f""" +import sys +sys.path.insert(0, {module_dir!r}) +import shutil +print(shutil._BZ2_SUPPORTED) +""" + proc = assert_python_ok('-X', 'lazy_imports=all', '-S', '-c', code) + self.assertEqual(proc.out.strip(), b'False') + @support.requires_zstd() def test_unpack_archive_zstdtar(self): self.check_unpack_tarball('zstdtar') diff --git a/Misc/NEWS.d/next/Library/2026-05-22-18-40-00.gh-issue-150167.5L9x2h.rst b/Misc/NEWS.d/next/Library/2026-05-22-18-40-00.gh-issue-150167.5L9x2h.rst new file mode 100644 index 00000000000000..44c9d9476fb2c4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-05-22-18-40-00.gh-issue-150167.5L9x2h.rst @@ -0,0 +1,4 @@ +Make :mod:`shutil` probe compression-module support eagerly even when Python is +started with ``-X lazy_imports=all``. This fixes cases where +``shutil._BZ2_SUPPORTED`` could be reported as ``True`` even though the +underlying ``_bz2`` extension is unavailable.