Skip to content

Commit d58c500

Browse files
authored
Update gzip from 3.13.5 (#5912)
1 parent 5c8b027 commit d58c500

File tree

2 files changed

+86
-67
lines changed

2 files changed

+86
-67
lines changed

Lib/gzip.py

Lines changed: 73 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@
55

66
# based on Andrew Kuchling's minigzip.py distributed with the zlib module
77

8-
import struct, sys, time, os
9-
import zlib
8+
import _compression
109
import builtins
1110
import io
12-
import _compression
11+
import os
12+
import struct
13+
import sys
14+
import time
15+
import weakref
16+
import zlib
1317

1418
__all__ = ["BadGzipFile", "GzipFile", "open", "compress", "decompress"]
1519

@@ -125,10 +129,13 @@ class BadGzipFile(OSError):
125129
class _WriteBufferStream(io.RawIOBase):
126130
"""Minimal object to pass WriteBuffer flushes into GzipFile"""
127131
def __init__(self, gzip_file):
128-
self.gzip_file = gzip_file
132+
self.gzip_file = weakref.ref(gzip_file)
129133

130134
def write(self, data):
131-
return self.gzip_file._write_raw(data)
135+
gzip_file = self.gzip_file()
136+
if gzip_file is None:
137+
raise RuntimeError("lost gzip_file")
138+
return gzip_file._write_raw(data)
132139

133140
def seekable(self):
134141
return False
@@ -190,51 +197,58 @@ def __init__(self, filename=None, mode=None,
190197
raise ValueError("Invalid mode: {!r}".format(mode))
191198
if mode and 'b' not in mode:
192199
mode += 'b'
193-
if fileobj is None:
194-
fileobj = self.myfileobj = builtins.open(filename, mode or 'rb')
195-
if filename is None:
196-
filename = getattr(fileobj, 'name', '')
197-
if not isinstance(filename, (str, bytes)):
198-
filename = ''
199-
else:
200-
filename = os.fspath(filename)
201-
origmode = mode
202-
if mode is None:
203-
mode = getattr(fileobj, 'mode', 'rb')
204-
205-
206-
if mode.startswith('r'):
207-
self.mode = READ
208-
raw = _GzipReader(fileobj)
209-
self._buffer = io.BufferedReader(raw)
210-
self.name = filename
211-
212-
elif mode.startswith(('w', 'a', 'x')):
213-
if origmode is None:
214-
import warnings
215-
warnings.warn(
216-
"GzipFile was opened for writing, but this will "
217-
"change in future Python releases. "
218-
"Specify the mode argument for opening it for writing.",
219-
FutureWarning, 2)
220-
self.mode = WRITE
221-
self._init_write(filename)
222-
self.compress = zlib.compressobj(compresslevel,
223-
zlib.DEFLATED,
224-
-zlib.MAX_WBITS,
225-
zlib.DEF_MEM_LEVEL,
226-
0)
227-
self._write_mtime = mtime
228-
self._buffer_size = _WRITE_BUFFER_SIZE
229-
self._buffer = io.BufferedWriter(_WriteBufferStream(self),
230-
buffer_size=self._buffer_size)
231-
else:
232-
raise ValueError("Invalid mode: {!r}".format(mode))
233200

234-
self.fileobj = fileobj
201+
try:
202+
if fileobj is None:
203+
fileobj = self.myfileobj = builtins.open(filename, mode or 'rb')
204+
if filename is None:
205+
filename = getattr(fileobj, 'name', '')
206+
if not isinstance(filename, (str, bytes)):
207+
filename = ''
208+
else:
209+
filename = os.fspath(filename)
210+
origmode = mode
211+
if mode is None:
212+
mode = getattr(fileobj, 'mode', 'rb')
213+
214+
215+
if mode.startswith('r'):
216+
self.mode = READ
217+
raw = _GzipReader(fileobj)
218+
self._buffer = io.BufferedReader(raw)
219+
self.name = filename
220+
221+
elif mode.startswith(('w', 'a', 'x')):
222+
if origmode is None:
223+
import warnings
224+
warnings.warn(
225+
"GzipFile was opened for writing, but this will "
226+
"change in future Python releases. "
227+
"Specify the mode argument for opening it for writing.",
228+
FutureWarning, 2)
229+
self.mode = WRITE
230+
self._init_write(filename)
231+
self.compress = zlib.compressobj(compresslevel,
232+
zlib.DEFLATED,
233+
-zlib.MAX_WBITS,
234+
zlib.DEF_MEM_LEVEL,
235+
0)
236+
self._write_mtime = mtime
237+
self._buffer_size = _WRITE_BUFFER_SIZE
238+
self._buffer = io.BufferedWriter(_WriteBufferStream(self),
239+
buffer_size=self._buffer_size)
240+
else:
241+
raise ValueError("Invalid mode: {!r}".format(mode))
235242

236-
if self.mode == WRITE:
237-
self._write_gzip_header(compresslevel)
243+
self.fileobj = fileobj
244+
245+
if self.mode == WRITE:
246+
self._write_gzip_header(compresslevel)
247+
except:
248+
# Avoid a ResourceWarning if the write fails,
249+
# eg read-only file or KeyboardInterrupt
250+
self._close()
251+
raise
238252

239253
@property
240254
def mtime(self):
@@ -363,11 +377,14 @@ def close(self):
363377
elif self.mode == READ:
364378
self._buffer.close()
365379
finally:
366-
self.fileobj = None
367-
myfileobj = self.myfileobj
368-
if myfileobj:
369-
self.myfileobj = None
370-
myfileobj.close()
380+
self._close()
381+
382+
def _close(self):
383+
self.fileobj = None
384+
myfileobj = self.myfileobj
385+
if myfileobj is not None:
386+
self.myfileobj = None
387+
myfileobj.close()
371388

372389
def flush(self,zlib_mode=zlib.Z_SYNC_FLUSH):
373390
self._check_not_closed()
@@ -580,12 +597,12 @@ def _rewind(self):
580597
self._new_member = True
581598

582599

583-
def compress(data, compresslevel=_COMPRESS_LEVEL_BEST, *, mtime=0):
600+
def compress(data, compresslevel=_COMPRESS_LEVEL_BEST, *, mtime=None):
584601
"""Compress data in one shot and return the compressed string.
585602
586603
compresslevel sets the compression level in range of 0-9.
587-
mtime can be used to set the modification time.
588-
The modification time is set to 0 by default, for reproducibility.
604+
mtime can be used to set the modification time. The modification time is
605+
set to the current time by default.
589606
"""
590607
# Wbits=31 automatically includes a gzip header and trailer.
591608
gzip_data = zlib.compress(data, level=compresslevel, wbits=31)

Lib/test/test_gzip.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33

44
import array
55
import functools
6+
import gc
67
import io
78
import os
89
import struct
910
import sys
1011
import unittest
1112
from subprocess import PIPE, Popen
13+
from test.support import catch_unraisable_exception
1214
from test.support import import_helper
1315
from test.support import os_helper
1416
from test.support import _4G, bigmemtest, requires_subprocess
@@ -713,17 +715,6 @@ def test_compress_mtime(self):
713715
f.read(1) # to set mtime attribute
714716
self.assertEqual(f.mtime, mtime)
715717

716-
def test_compress_mtime_default(self):
717-
# test for gh-125260
718-
datac = gzip.compress(data1, mtime=0)
719-
datac2 = gzip.compress(data1)
720-
self.assertEqual(datac, datac2)
721-
datac3 = gzip.compress(data1, mtime=None)
722-
self.assertNotEqual(datac, datac3)
723-
with gzip.GzipFile(fileobj=io.BytesIO(datac3), mode="rb") as f:
724-
f.read(1) # to set mtime attribute
725-
self.assertGreater(f.mtime, 1)
726-
727718
def test_compress_correct_level(self):
728719
for mtime in (0, 42):
729720
with self.subTest(mtime=mtime):
@@ -859,6 +850,17 @@ def test_write_seek_write(self):
859850
self.assertEqual(gzip.decompress(data), message * 2)
860851

861852

853+
def test_refloop_unraisable(self):
854+
# Ensure a GzipFile referring to a temporary fileobj deletes cleanly.
855+
# Previously an unraisable exception would occur on close because the
856+
# fileobj would be closed before the GzipFile as the result of a
857+
# reference loop. See issue gh-129726
858+
with catch_unraisable_exception() as cm:
859+
gzip.GzipFile(fileobj=io.BytesIO(), mode="w")
860+
gc.collect()
861+
self.assertIsNone(cm.unraisable)
862+
863+
862864
class TestOpen(BaseTest):
863865
def test_binary_modes(self):
864866
uncompressed = data1 * 50

0 commit comments

Comments
 (0)