Skip to content

bpo-26175: Fix SpooledTemporaryFile IOBase abstract #3249

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

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 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
15 changes: 15 additions & 0 deletions Lib/tempfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,9 @@ def __exit__(self, exc, value, tb):
def __iter__(self):
return self._file.__iter__()

def __del__(self):
Copy link

@pppery pppery Jun 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be added: deleting the SpooledTemporaryFile will null out the reference to the underline file, and therefor call its __del__

Copy link
Member

@vadmium vadmium Jun 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if you inherit the default IOBase.__del__ implementation, it will call close and defeat any ResourceWarning that might otherwise be emitted. Perhaps it is better to make __del__ do nothing, or set it to object.__del__. [Seems that last option doesn’t exist.]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree: the underlying file should not be explicitly deleted as this is not expected behaviour. I can imagine a situation where someone is deliberately holding a reference to the underlying file and they would not expect/want it to be closed until their own reference has fallen out of scope. I've changed the method to do nothing and added a docstring to reflect this.

Thanks for your feedback

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree: the underlying file should not be explicitly deleted as this is not expected behaviour

The doc says: """This function operates exactly as TemporaryFile() does, except [irrelevant differences]."""

And then, about TemporaryFile: """On completion of the context or destruction of the file object the temporary file will be removed from the filesystem."""

So it seems the underlying file should be deleted when the file object disappears.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The point is that IOBase implements a __del__ which has some side-effects. Those side effects are not desirable here. Any underlying buffers being wrapped, e.g. the TemporaryFile or the BytesIO, will be gc'd and handled as they should.

I think this implementation is correct.

return self._file.__del__()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, calling the __del__ method directly like that is not correct. __del__ is only to be called when all external references are gone. Instead I would suggest something like the following:

    def __del__(self):
        if not self.closed:
            import warnings
            warnings.warn('unclosed file %r' % (self,), ResourceWarning,
                          stacklevel=2, source=self)
            self.close()

(this is adapted from FileIO.__del__ in Lib/pyio.py)


def close(self):
self._file.close()

Expand Down Expand Up @@ -737,6 +740,12 @@ def newlines(self):
def read(self, *args):
return self._file.read(*args)

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

def readinto(self, b):
return self._file.readinto(b)

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

Expand All @@ -746,6 +755,9 @@ def readlines(self, *args):
def seek(self, *args):
self._file.seek(*args)

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

@property
def softspace(self):
return self._file.softspace
Expand All @@ -767,6 +779,9 @@ def write(self, s):
self._check(file)
return rv

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

def writelines(self, iterable):
file = self._file
rv = file.writelines(iterable)
Expand Down
29 changes: 29 additions & 0 deletions Lib/test/test_tempfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,35 @@ def test_basic(self):
f = self.do_create(max_size=100, pre="a", suf=".txt")
self.assertFalse(f._rolled)

def test_iobase_abstract(self):
# SpooledTemporaryFile should implement the IOBase abstract
iobase_abstract = (
'close',
'closed',
'fileno',
'flush',
'isatty',
'read',
'readable',
'readinto',
'readline',
'readlines',
'seek',
'seekable',
'tell',
'truncate',
'write',
'writable',
'writelines',
'__del__',
)
f = self.do_create()
for attribute in iobase_abstract:
self.assertTrue(
hasattr(f, attribute),
'{} attribute missing'.format(attribute)
)

def test_del_on_close(self):
# A SpooledTemporaryFile is deleted when closed
dir = tempfile.mkdtemp()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fully implement `io.IOBase` abstract for `tempfile.SpooledTemporaryFile`.
Patch by Gary Fernie