From 82996b109d91fa26ea2d550b1d3de4a4b4b2c9a8 Mon Sep 17 00:00:00 2001 From: lipengyu Date: Thu, 10 Sep 2026 16:06:38 +0800 Subject: [PATCH] gh-149760: Improve exception tracebacks from TarFile.next() Keep the original traceback when TarFile.next() reraises non-zlib exceptions, including when zlib is unavailable. Preserve the existing conversion of zlib.error to ReadError. --- Lib/tarfile.py | 8 +++--- Lib/test/test_tarfile.py | 27 +++++++++++++++++++ ...-09-10-15-51-43.gh-issue-149760.Pwme-m.rst | 2 ++ 3 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-09-10-15-51-43.gh-issue-149760.Pwme-m.rst diff --git a/Lib/tarfile.py b/Lib/tarfile.py index f46e938fd314ddb..c03ccdd92d6ea53 100644 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -2951,12 +2951,12 @@ def next(self): except Exception as e: try: import zlib + except ImportError: + pass + else: if isinstance(e, zlib.error): raise ReadError(f'zlib error: {e}') from None - else: - raise e - except ImportError: - raise e + raise break if tarinfo is not None: diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 10106c3ada9ba52..dd24dba861fc10d 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -877,6 +877,33 @@ def test_zlib_error_does_not_leak(self): with self.assertRaises(tarfile.ReadError): tarfile.open(self.tarname) + def test_next_preserves_exception_traceback(self): + class FailingFile(io.BytesIO): + def read(self, *args): + raise error + + for error_type in (OSError, ImportError): + for missing_zlib in (False, True): + with self.subTest(error_type=error_type, + missing_zlib=missing_zlib): + error = error_type("read failed") + modules = {"zlib": None} if missing_zlib else {} + with unittest.mock.patch.dict(sys.modules, modules): + try: + tarfile.open(fileobj=FailingFile(), mode="r:") + except error_type as exc: + self.assertIs(exc, error) + next_frames = 0 + tb = exc.__traceback__ + while tb is not None: + code = tb.tb_frame.f_code + if code is tarfile.TarFile.next.__code__: + next_frames += 1 + tb = tb.tb_next + self.assertEqual(next_frames, 1) + else: + self.fail(f"{error_type.__name__} not raised") + def test_next_on_empty_tarfile(self): fd = io.BytesIO() tf = tarfile.open(fileobj=fd, mode="w") diff --git a/Misc/NEWS.d/next/Library/2026-09-10-15-51-43.gh-issue-149760.Pwme-m.rst b/Misc/NEWS.d/next/Library/2026-09-10-15-51-43.gh-issue-149760.Pwme-m.rst new file mode 100644 index 000000000000000..382ea96692ab4a2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-10-15-51-43.gh-issue-149760.Pwme-m.rst @@ -0,0 +1,2 @@ +Preserve the original traceback when :meth:`tarfile.TarFile.next` +reraises an exception.