mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
gh-70363: Implement io.IOBase
interface for SpooledTemporaryFile
(GH-29560)
Since the underlying file-like objects (either `io.BytesIO`, or a true file object) all implement the `io.IOBase` interface, the `SpooledTemporaryFile` should as well. Additionally, since the underlying file object will either be an instance of an `io.BufferedIOBase` (for binary mode) or an `io.TextIOBase` (for text mode), methods for these classes were also implemented. In every case, the required methods and properties are simply delegated to the underlying file object. Co-authored-by: Gary Fernie <Gary.Fernie@skyscanner.net> Co-authored-by: Inada Naoki <songofacandy@gmail.com>
This commit is contained in:
parent
52dc9c3066
commit
78e70be331
5 changed files with 92 additions and 3 deletions
|
@ -123,6 +123,11 @@ The module defines the following user-callable items:
|
||||||
.. versionchanged:: 3.8
|
.. versionchanged:: 3.8
|
||||||
Added *errors* parameter.
|
Added *errors* parameter.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.11
|
||||||
|
Fully implements the :class:`io.BufferedIOBase` and
|
||||||
|
:class:`io.TextIOBase` abstract base classes (depending on whether binary
|
||||||
|
or text *mode* was specified).
|
||||||
|
|
||||||
|
|
||||||
.. class:: TemporaryDirectory(suffix=None, prefix=None, dir=None, ignore_cleanup_errors=False)
|
.. class:: TemporaryDirectory(suffix=None, prefix=None, dir=None, ignore_cleanup_errors=False)
|
||||||
|
|
||||||
|
|
|
@ -639,7 +639,7 @@ else:
|
||||||
_os.close(fd)
|
_os.close(fd)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
class SpooledTemporaryFile:
|
class SpooledTemporaryFile(_io.IOBase):
|
||||||
"""Temporary file wrapper, specialized to switch from BytesIO
|
"""Temporary file wrapper, specialized to switch from BytesIO
|
||||||
or StringIO to a real file when it exceeds a certain size or
|
or StringIO to a real file when it exceeds a certain size or
|
||||||
when a fileno is needed.
|
when a fileno is needed.
|
||||||
|
@ -704,6 +704,16 @@ class SpooledTemporaryFile:
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return self._file.__iter__()
|
return self._file.__iter__()
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
if not self.closed:
|
||||||
|
_warnings.warn(
|
||||||
|
"Unclosed file {!r}".format(self),
|
||||||
|
ResourceWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
source=self
|
||||||
|
)
|
||||||
|
self.close()
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self._file.close()
|
self._file.close()
|
||||||
|
|
||||||
|
@ -747,15 +757,30 @@ class SpooledTemporaryFile:
|
||||||
def newlines(self):
|
def newlines(self):
|
||||||
return self._file.newlines
|
return self._file.newlines
|
||||||
|
|
||||||
|
def readable(self):
|
||||||
|
return self._file.readable()
|
||||||
|
|
||||||
def read(self, *args):
|
def read(self, *args):
|
||||||
return self._file.read(*args)
|
return self._file.read(*args)
|
||||||
|
|
||||||
|
def read1(self, *args):
|
||||||
|
return self._file.read1(*args)
|
||||||
|
|
||||||
|
def readinto(self, b):
|
||||||
|
return self._file.readinto(b)
|
||||||
|
|
||||||
|
def readinto1(self, b):
|
||||||
|
return self._file.readinto1(b)
|
||||||
|
|
||||||
def readline(self, *args):
|
def readline(self, *args):
|
||||||
return self._file.readline(*args)
|
return self._file.readline(*args)
|
||||||
|
|
||||||
def readlines(self, *args):
|
def readlines(self, *args):
|
||||||
return self._file.readlines(*args)
|
return self._file.readlines(*args)
|
||||||
|
|
||||||
|
def seekable(self):
|
||||||
|
return self._file.seekable()
|
||||||
|
|
||||||
def seek(self, *args):
|
def seek(self, *args):
|
||||||
return self._file.seek(*args)
|
return self._file.seek(*args)
|
||||||
|
|
||||||
|
@ -764,11 +789,14 @@ class SpooledTemporaryFile:
|
||||||
|
|
||||||
def truncate(self, size=None):
|
def truncate(self, size=None):
|
||||||
if size is None:
|
if size is None:
|
||||||
self._file.truncate()
|
return self._file.truncate()
|
||||||
else:
|
else:
|
||||||
if size > self._max_size:
|
if size > self._max_size:
|
||||||
self.rollover()
|
self.rollover()
|
||||||
self._file.truncate(size)
|
return self._file.truncate(size)
|
||||||
|
|
||||||
|
def writable(self):
|
||||||
|
return self._file.writable()
|
||||||
|
|
||||||
def write(self, s):
|
def write(self, s):
|
||||||
file = self._file
|
file = self._file
|
||||||
|
@ -782,6 +810,9 @@ class SpooledTemporaryFile:
|
||||||
self._check(file)
|
self._check(file)
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
def detach(self):
|
||||||
|
return self._file.detach()
|
||||||
|
|
||||||
|
|
||||||
class TemporaryDirectory:
|
class TemporaryDirectory:
|
||||||
"""Create and return a temporary directory. This has the same
|
"""Create and return a temporary directory. This has the same
|
||||||
|
|
|
@ -1061,6 +1061,30 @@ class TestSpooledTemporaryFile(BaseTestCase):
|
||||||
f = self.do_create(max_size=100, pre="a", suf=".txt")
|
f = self.do_create(max_size=100, pre="a", suf=".txt")
|
||||||
self.assertFalse(f._rolled)
|
self.assertFalse(f._rolled)
|
||||||
|
|
||||||
|
def test_is_iobase(self):
|
||||||
|
# SpooledTemporaryFile should implement io.IOBase
|
||||||
|
self.assertIsInstance(self.do_create(), io.IOBase)
|
||||||
|
|
||||||
|
def test_iobase_interface(self):
|
||||||
|
# SpooledTemporaryFile should implement the io.IOBase interface.
|
||||||
|
# Ensure it has all the required methods and properties.
|
||||||
|
iobase_attrs = {
|
||||||
|
# From IOBase
|
||||||
|
'fileno', 'seek', 'truncate', 'close', 'closed', '__enter__',
|
||||||
|
'__exit__', 'flush', 'isatty', '__iter__', '__next__', 'readable',
|
||||||
|
'readline', 'readlines', 'seekable', 'tell', 'writable',
|
||||||
|
'writelines',
|
||||||
|
# From BufferedIOBase (binary mode) and TextIOBase (text mode)
|
||||||
|
'detach', 'read', 'read1', 'write', 'readinto', 'readinto1',
|
||||||
|
'encoding', 'errors', 'newlines',
|
||||||
|
}
|
||||||
|
spooledtempfile_attrs = set(dir(tempfile.SpooledTemporaryFile))
|
||||||
|
missing_attrs = iobase_attrs - spooledtempfile_attrs
|
||||||
|
self.assertFalse(
|
||||||
|
missing_attrs,
|
||||||
|
'SpooledTemporaryFile missing attributes from IOBase/BufferedIOBase/TextIOBase'
|
||||||
|
)
|
||||||
|
|
||||||
def test_del_on_close(self):
|
def test_del_on_close(self):
|
||||||
# A SpooledTemporaryFile is deleted when closed
|
# A SpooledTemporaryFile is deleted when closed
|
||||||
dir = tempfile.mkdtemp()
|
dir = tempfile.mkdtemp()
|
||||||
|
@ -1076,6 +1100,30 @@ class TestSpooledTemporaryFile(BaseTestCase):
|
||||||
finally:
|
finally:
|
||||||
os.rmdir(dir)
|
os.rmdir(dir)
|
||||||
|
|
||||||
|
def test_del_unrolled_file(self):
|
||||||
|
# The unrolled SpooledTemporaryFile should raise a ResourceWarning
|
||||||
|
# when deleted since the file was not explicitly closed.
|
||||||
|
f = self.do_create(max_size=10)
|
||||||
|
f.write(b'foo')
|
||||||
|
self.assertEqual(f.name, None) # Unrolled so no filename/fd
|
||||||
|
with self.assertWarns(ResourceWarning):
|
||||||
|
f.__del__()
|
||||||
|
|
||||||
|
def test_del_rolled_file(self):
|
||||||
|
# The rolled file should be deleted when the SpooledTemporaryFile
|
||||||
|
# object is deleted. This should raise a ResourceWarning since the file
|
||||||
|
# was not explicitly closed.
|
||||||
|
f = self.do_create(max_size=2)
|
||||||
|
f.write(b'foo')
|
||||||
|
name = f.name # This is a fd on posix+cygwin, a filename everywhere else
|
||||||
|
self.assertTrue(os.path.exists(name))
|
||||||
|
with self.assertWarns(ResourceWarning):
|
||||||
|
f.__del__()
|
||||||
|
self.assertFalse(
|
||||||
|
os.path.exists(name),
|
||||||
|
"Rolled SpooledTemporaryFile (name=%s) exists after delete" % name
|
||||||
|
)
|
||||||
|
|
||||||
def test_rewrite_small(self):
|
def test_rewrite_small(self):
|
||||||
# A SpooledTemporaryFile can be written to multiple within the max_size
|
# A SpooledTemporaryFile can be written to multiple within the max_size
|
||||||
f = self.do_create(max_size=30)
|
f = self.do_create(max_size=30)
|
||||||
|
|
|
@ -1172,6 +1172,7 @@ Dimitri Merejkowsky
|
||||||
Brian Merrell
|
Brian Merrell
|
||||||
Bruce Merry
|
Bruce Merry
|
||||||
Alexis Métaireau
|
Alexis Métaireau
|
||||||
|
Carey Metcalfe
|
||||||
Luke Mewburn
|
Luke Mewburn
|
||||||
Carl Meyer
|
Carl Meyer
|
||||||
Kyle Meyer
|
Kyle Meyer
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Fully implement the :class:`io.BufferedIOBase` or :class:`io.TextIOBase`
|
||||||
|
interface for :class:`tempfile.SpooledTemporaryFile` objects. This lets them
|
||||||
|
work correctly with higher-level layers (like compression modules). Patch by
|
||||||
|
Carey Metcalfe.
|
Loading…
Add table
Add a link
Reference in a new issue