gh-127371 Avoid unbounded growth SpooledTempfile.writelines (GH-127372)

This commit is contained in:
Bert Peters 2025-03-04 23:06:07 +01:00 committed by GitHub
parent 691354ccb0
commit cb67b44ca9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 39 additions and 4 deletions

View file

@ -868,10 +868,14 @@ class SpooledTemporaryFile(_io.IOBase):
return rv
def writelines(self, iterable):
file = self._file
rv = file.writelines(iterable)
self._check(file)
return rv
if self._max_size == 0 or self._rolled:
return self._file.writelines(iterable)
it = iter(iterable)
for line in it:
self.write(line)
if self._rolled:
return self._file.writelines(it)
def detach(self):
return self._file.detach()

View file

@ -1284,6 +1284,34 @@ class TestSpooledTemporaryFile(BaseTestCase):
buf = f.read()
self.assertEqual(buf, b'xyz')
def test_writelines_rollover(self):
# Verify writelines rolls over before exhausting the iterator
f = self.do_create(max_size=2)
def it():
yield b'xy'
self.assertFalse(f._rolled)
yield b'z'
self.assertTrue(f._rolled)
f.writelines(it())
pos = f.seek(0)
self.assertEqual(pos, 0)
buf = f.read()
self.assertEqual(buf, b'xyz')
def test_writelines_fast_path(self):
f = self.do_create(max_size=2)
f.write(b'abc')
self.assertTrue(f._rolled)
f.writelines([b'd', b'e', b'f'])
pos = f.seek(0)
self.assertEqual(pos, 0)
buf = f.read()
self.assertEqual(buf, b'abcdef')
def test_writelines_sequential(self):
# A SpooledTemporaryFile should hold exactly max_size bytes, and roll
# over afterward

View file

@ -0,0 +1,3 @@
Avoid unbounded buffering for :meth:`!tempfile.SpooledTemporaryFile.writelines`.
Previously, disk spillover was only checked after the lines iterator had been
exhausted. This is now done after each line is written.