Skip to content
17 changes: 16 additions & 1 deletion Lib/test/test_zipfile/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from test.support import (
findfile, requires_zlib, requires_bz2, requires_lzma,
requires_zstd, captured_stdout, captured_stderr, requires_subprocess,
cpython_only, gc_collect
cpython_only, gc_collect, catch_unraisable_exception
)
from test.support.os_helper import (
TESTFN, unlink, rmtree, temp_dir, temp_cwd, fd_count, FakePath,
Expand Down Expand Up @@ -4095,6 +4095,21 @@ def test_garbage_collection(self):
zf.close()
del zf

# ResourceWarning escalated to an error is reported as unraisable,
# and the archive is still closed.
buf = io.BytesIO()
zf = zipfile.ZipFile(buf, "w")
zf.writestr("f.txt", b"data")
with (warnings.catch_warnings(action="error", category=ResourceWarning),
catch_unraisable_exception() as cm):
del zf
gc_collect()
self.assertIs(cm.unraisable.exc_type, ResourceWarning)

with zipfile.ZipFile(buf, "r") as zf:
self.assertEqual(zf.namelist(), ["f.txt"])
self.assertEqual(zf.read("f.txt"), b"data")

def test_unsupported_version(self):
# File has an extract_version of 120
data = (b'PK\x03\x04x\x00\x00\x00\x00\x00!p\xa1@\x00\x00\x00\x00\x00\x00'
Expand Down
16 changes: 10 additions & 6 deletions Lib/zipfile/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import sys
import threading
import time
lazy import warnings
import warnings # must stay eager to support finalization
Comment thread
brittanyrey marked this conversation as resolved.

try:
import zlib # We may need its compression method
Expand Down Expand Up @@ -2646,14 +2646,18 @@ def mkdir(self, zinfo_or_directory_name, mode=511):
self.fp.write(zinfo.FileHeader(False))
self.start_dir = self.fp.tell()

def __del__(self):
def __del__(self, _warn=warnings.warn):
"""Call the "close()" method in case the user forgot."""
# gh-81954: Warn if writable ZipFile is implicitly closed.
# GC cleanup order is non-deterministic and can result in data loss.
if self.fp is not None and self.mode in ('w', 'x', 'a'):
warnings.warn(f"unclosed ZipFile {self!r}",
ResourceWarning, source=self, stacklevel=2)
self.close()
try:
# Wrap within a try to ensure the file is closed even if
# warnings raises.
if self.fp is not None and self.mode in ('w', 'x', 'a'):
_warn(f"unclosed ZipFile {self!r}",
ResourceWarning, source=self, stacklevel=2)
finally:
self.close()

def close(self):
"""Close the file, and for mode 'w', 'x' and 'a' write the ending
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix :class:`zipfile.ZipFile` not being closed at interpreter shutdown when
emitting its :exc:`ResourceWarning` failed with an :exc:`ImportError`.
Loading