diff --git a/Lib/tarfile.py b/Lib/tarfile.py index 87500c726ce9a8..149d807ae0a776 100644 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -2083,7 +2083,7 @@ def zstopen(cls, name, mode="r", fileobj=None, level=None, options=None, if mode == 'r': raise ReadError("not a zstd file") from e raise - except Exception: + except: fileobj.close() raise t._extfileobj = False diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 02fd9620bcf33d..cf659d4370ec17 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -1065,6 +1065,21 @@ class LzmaDetectReadTest(LzmaTest, DetectReadTest): class ZstdDetectReadTest(ZstdTest, DetectReadTest): pass +@support.requires_zstd() +class ZstdOpenTest(unittest.TestCase): + """ + See: https://github.com/python/cpython/issues/150077 + """ + def test_zstopen_closes_fileobj_on_base_exception(self): + fileobj = unittest.mock.Mock() + with unittest.mock.patch("compression.zstd.ZstdFile", + return_value=fileobj), \ + unittest.mock.patch.object(tarfile.TarFile, "taropen", + side_effect=KeyboardInterrupt): + with self.assertRaises(KeyboardInterrupt): + tarfile.TarFile.zstopen("foo.tar.zst") + fileobj.close.assert_called_once() + class GzipBrokenHeaderCorrectException(GzipTest, unittest.TestCase): """ See: https://github.com/python/cpython/issues/107396 diff --git a/Misc/NEWS.d/next/Library/2026-05-19-18-25-00.gh-issue-150077.zstopen-close.rst b/Misc/NEWS.d/next/Library/2026-05-19-18-25-00.gh-issue-150077.zstopen-close.rst new file mode 100644 index 00000000000000..ffcc6db30ebd78 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-05-19-18-25-00.gh-issue-150077.zstopen-close.rst @@ -0,0 +1,4 @@ +Fix ``tarfile.TarFile.zstopen`` to close the underlying zstd file object +when opening the tar archive is interrupted by a :exc:`BaseException` +subclass such as :exc:`KeyboardInterrupt`. +