-
-
Notifications
You must be signed in to change notification settings - Fork 30.9k
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
bpo-26175: Fix SpooledTemporaryFile IOBase abstract #3249
Changes from 5 commits
83d6152
3b69baa
753b4fb
a70113e
fb28362
bc9d0a9
b588dec
ab49dd1
4251b21
19f4c08
4519b9c
554aad6
be094b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -626,7 +626,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. | ||
|
@@ -685,6 +685,9 @@ def __exit__(self, exc, value, tb): | |
def __iter__(self): | ||
return self._file.__iter__() | ||
|
||
def __del__(self): | ||
return self._file.__del__() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, calling the 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 |
||
|
||
def close(self): | ||
self._file.close() | ||
|
||
|
@@ -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) | ||
|
||
|
@@ -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 | ||
|
@@ -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) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -982,6 +982,22 @@ 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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Those tests are not very interesting. It would be better to test the methods operate properly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It literally delegates everything to the underlying file. The existing tests should cover the behaviors. The new behaviors are intended to flesh out the API so that |
||
# SpooledTemporaryFile should implement the io.IOBase interface. | ||
# IOBase does not declare read(), readinto(), or write(), but | ||
# they should be considered part of the interface. | ||
iobase_abstract = {'read', 'readinto', 'write'} | ||
spooledtempfile_abstract = set(dir(tempfile.SpooledTemporaryFile)) | ||
missing_attributes = iobase_abstract - spooledtempfile_abstract | ||
self.assertFalse( | ||
missing_attributes, | ||
'io.IOBase attributes missing from SpooledTemporaryFile' | ||
) | ||
|
||
def test_del_on_close(self): | ||
# A SpooledTemporaryFile is deleted when closed | ||
dir = tempfile.mkdtemp() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
Fully implement io.IOBase interface for tempfile.SpooledTemporaryFile. | ||
Patch by Gary Fernie. |
There was a problem hiding this comment.
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__
There was a problem hiding this comment.
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. [Seems that last option doesn’t exist.]object.__del__
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
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. theTemporaryFile
or theBytesIO
, will be gc'd and handled as they should.I think this implementation is correct.