Edit: Originally this bug was expressed through shutil's support detection. shutil now directly imports from _bz2 which works around this issue.
Bug description:
Under lazy_imports=all, modules imported in a try/except block are still imported eagerly. However, imports that occur within that module are still imported lazily.
This may be intentional, but this bug is the result of that behaviour.
The try/except check is on bz2, which succeeds because the import to _bz2 which would fail is converted to a lazy import and is not used at top level so the import which would fail isn't triggered.
shutil could directly check _bz2 in this particular case, but I expect this pattern isn't uncommon. I looked into this after I ran into a similar issue with pip and rich with an OS-specific import that should have failed in a similar manner.
Related: #149640 as this also causes test_shutil to fail under lazy_imports=all if _bz2 isn't present.
_ZLIB_SUPPORTED, _LZMA_SUPPORTED and _ZSTD_SUPPORTED are correct since import zlib, import lzma and from compression import zstd fail immediately with ModuleNotFound even with PYTHON_LAZY_IMPORTS=all.
_BZ2_SUPPORTED is incorrect because import bz2 completes successfully even if import _bz2 will fail. It behaves differently since _bz2 symbols are only accessed when bz2 functions are called, not at import time.
lzma is working somewhat by luck as it uses a * import on the _lzma module and * imports can't be made lazy. zstd uses _zstd at module level so similarly escapes this issue. I say by luck as these aren't done with the intention of preventing the lazy import, it's just a side-effect.
I believe zlib itself is the module that would fail so its check isn't affected but I haven't confirmed this.
The simplest solution for _bz2 specifically is probably to add import _bz2; _bz2 to bz2.py to force the resolution. lzma and zstd could also do the same, but I'm not sure it's necessary as long as there are tests. (I'm not actually sure how you'd write the test for this case given it requires missing modules and the lazy import flag.)
We could instead check the internal modules directly in shutil but that makes shutil dependant on internals of the compression libraries which is probably undesirable?
Perhaps compression should have some kind of function or attribute that provides a list or set of available compression types?
I think there it would be fine to only check the internal modules and shutil could then use this directly rather than checking all of the modules itself.
I was having a look at writing a test for this and noticed that the test.support.requires_<format> functions have the same logic and probably the same issue.
So I believe the specific case of failure in shutil has been resolved as a side effect of #154908 but the logical issue still exists in other places that do check the modules directly like test.support. Not sure what to do here.
changed the title [-]`shutil._BZ2_SUPPORTED` is always `True` under `lazy_imports=all` even if it is not supported[/-][+]Importing `bz2` will succeed under `lazy_imports=all` even if `bz2` is not supported[/+]on Sep 8, 2026
changed the title [-]Importing `bz2` will succeed under `lazy_imports=all` even if `bz2` is not supported[/-][+]Importing `bz2` will succeed under `lazy_imports=all` even if bz2 is not supported[/+]on Sep 8, 2026
Activity
vstinner commented on May 21, 2026
I can reproduce the issue on Linux using:
_ZLIB_SUPPORTED, _LZMA_SUPPORTED and _ZSTD_SUPPORTED are correct since import zlib, import lzma and from compression import zstd fail immediately with ModuleNotFound even with PYTHON_LAZY_IMPORTS=all.
_BZ2_SUPPORTED is incorrect because import bz2 completes successfully even if import _bz2 will fail. It behaves differently since _bz2 symbols are only accessed when bz2 functions are called, not at import time.
DavidCEllis commented on May 22, 2026
lzma is working somewhat by luck as it uses a * import on the _lzma module and * imports can't be made lazy. zstd uses _zstd at module level so similarly escapes this issue. I say by luck as these aren't done with the intention of preventing the lazy import, it's just a side-effect.
I believe zlib itself is the module that would fail so its check isn't affected but I haven't confirmed this.
The simplest solution for _bz2 specifically is probably to add import _bz2; _bz2 to bz2.py to force the resolution. lzma and zstd could also do the same, but I'm not sure it's necessary as long as there are tests. (I'm not actually sure how you'd write the test for this case given it requires missing modules and the lazy import flag.)
We could instead check the internal modules directly in shutil but that makes shutil dependant on internals of the compression libraries which is probably undesirable?
pythongh-150167: Make shutil compression probes eager
DavidCEllis commented on Jun 2, 2026
Perhaps compression should have some kind of function or attribute that provides a list or set of available compression types?
I think there it would be fine to only check the internal modules and shutil could then use this directly rather than checking all of the modules itself.
DavidCEllis commented on Jul 27, 2026
I was having a look at writing a test for this and noticed that the test.support.requires_<format> functions have the same logic and probably the same issue.
cpython/Lib/test/support/__init__.py
Lines 548 to 581 in 993a0c6
DavidCEllis commented on Sep 8, 2026
So I believe the specific case of failure in shutil has been resolved as a side effect of #154908 but the logical issue still exists in other places that do check the modules directly like test.support. Not sure what to do here.