Thanks to visit codestin.com
Credit goes to github.com

Skip to content

bpo-26175: Implement io.IOBase interface for SpooledTemporaryFile #29560

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Doc/library/tempfile.rst
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ The module defines the following user-callable items:
.. versionchanged:: 3.8
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)

Expand Down
37 changes: 34 additions & 3 deletions Lib/tempfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,7 @@ def TemporaryFile(mode='w+b', buffering=-1, encoding=None,
_os.close(fd)
raise

class SpooledTemporaryFile:
class SpooledTemporaryFile(_io.IOBase):
"""Temporary file wrapper, specialized to switch from BytesIO
or StringIO to a real file when it exceeds a certain size or
when a fileno is needed.
Expand Down Expand Up @@ -704,6 +704,16 @@ def __exit__(self, exc, value, tb):
def __iter__(self):
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):
self._file.close()

Expand Down Expand Up @@ -747,15 +757,30 @@ def name(self):
def newlines(self):
return self._file.newlines

def readable(self):
return self._file.readable()

def read(self, *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):
return self._file.readline(*args)

def readlines(self, *args):
return self._file.readlines(*args)

def seekable(self):
return self._file.seekable()

def seek(self, *args):
return self._file.seek(*args)

Expand All @@ -764,11 +789,14 @@ def tell(self):

def truncate(self, size=None):
if size is None:
self._file.truncate()
return self._file.truncate()
else:
if size > self._max_size:
self.rollover()
self._file.truncate(size)
return self._file.truncate(size)

def writable(self):
return self._file.writable()

def write(self, s):
file = self._file
Expand All @@ -782,6 +810,9 @@ def writelines(self, iterable):
self._check(file)
return rv

def detach(self):
return self._file.detach()


class TemporaryDirectory:
"""Create and return a temporary directory. This has the same
Expand Down
48 changes: 48 additions & 0 deletions Lib/test/test_tempfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,30 @@ def test_basic(self):
f = self.do_create(max_size=100, pre="a", suf=".txt")
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):
# A SpooledTemporaryFile is deleted when closed
dir = tempfile.mkdtemp()
Expand All @@ -1076,6 +1100,30 @@ def test_del_on_close(self):
finally:
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):
# A SpooledTemporaryFile can be written to multiple within the max_size
f = self.do_create(max_size=30)
Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -1172,6 +1172,7 @@ Dimitri Merejkowsky
Brian Merrell
Bruce Merry
Alexis Métaireau
Carey Metcalfe
Luke Mewburn
Carl Meyer
Kyle Meyer
Expand Down
Original file line number Diff line number Diff line change
@@ -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.