diff options
author | Antoine Pitrou <solipsis@pitrou.net> | 2013-12-21 21:16:19 (GMT) |
---|---|---|
committer | Antoine Pitrou <solipsis@pitrou.net> | 2013-12-21 21:16:19 (GMT) |
commit | 2b7f69851d641e4bf48c762c9a23df5b51982a1f (patch) | |
tree | 6bfd2dce3c4aa7bbec313e1001e32b6842621a66 /Lib | |
parent | c16dfe18376791b3118ef42d39a3afd25ac6e306 (diff) | |
parent | 17c93260a62ac895f336351158e6fdaa6b117bcb (diff) | |
download | cpython-2b7f69851d641e4bf48c762c9a23df5b51982a1f.zip cpython-2b7f69851d641e4bf48c762c9a23df5b51982a1f.tar.gz cpython-2b7f69851d641e4bf48c762c9a23df5b51982a1f.tar.bz2 |
Issue #18879: When a method is looked up on a temporary file, avoid closing the file before the method is possibly called.
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/tempfile.py | 96 | ||||
-rw-r--r-- | Lib/test/test_tempfile.py | 17 |
2 files changed, 81 insertions, 32 deletions
diff --git a/Lib/tempfile.py b/Lib/tempfile.py index 5d34621..eae528d 100644 --- a/Lib/tempfile.py +++ b/Lib/tempfile.py @@ -27,6 +27,7 @@ __all__ = [ # Imports. +import functools as _functools import warnings as _warnings import sys as _sys import io as _io @@ -329,6 +330,46 @@ def mktemp(suffix="", prefix=template, dir=None): "No usable temporary filename found") +class _TemporaryFileCloser: + """A separate object allowing proper closing of a temporary file's + underlying file object, without adding a __del__ method to the + temporary file.""" + + def __init__(self, file, name, delete=True): + self.file = file + self.name = name + self.close_called = False + self.delete = delete + + # NT provides delete-on-close as a primitive, so we don't need + # the wrapper to do anything special. We still use it so that + # file.name is useful (i.e. not "(fdopen)") with NamedTemporaryFile. + if _os.name != 'nt': + # Cache the unlinker so we don't get spurious errors at + # shutdown when the module-level "os" is None'd out. Note + # that this must be referenced as self.unlink, because the + # name TemporaryFileWrapper may also get None'd out before + # __del__ is called. + unlink = _os.unlink + + def close(self): + if not self.close_called: + self.close_called = True + self.file.close() + if self.delete: + self.unlink(self.name) + + # Need to ensure the file is deleted on __del__ + def __del__(self): + self.close() + + else: + def close(self): + if not self.close_called: + self.close_called = True + self.file.close() + + class _TemporaryFileWrapper: """Temporary file wrapper @@ -340,8 +381,8 @@ class _TemporaryFileWrapper: def __init__(self, file, name, delete=True): self.file = file self.name = name - self.close_called = False self.delete = delete + self._closer = _TemporaryFileCloser(file, name, delete) def __getattr__(self, name): # Attribute lookups are delegated to the underlying file @@ -349,6 +390,15 @@ class _TemporaryFileWrapper: # (i.e. methods are cached, closed and friends are not) file = self.__dict__['file'] a = getattr(file, name) + if hasattr(a, '__call__'): + func = a + @_functools.wraps(func) + def func_wrapper(*args, **kwargs): + return func(*args, **kwargs) + # Avoid closing the file as long as the wrapper is alive, + # see issue #18879. + func_wrapper._closer = self._closer + a = func_wrapper if not isinstance(a, int): setattr(self, name, a) return a @@ -359,41 +409,23 @@ class _TemporaryFileWrapper: self.file.__enter__() return self + # Need to trap __exit__ as well to ensure the file gets + # deleted when used in a with statement + def __exit__(self, exc, value, tb): + result = self.file.__exit__(exc, value, tb) + self.close() + return result + + def close(self): + """ + Close the temporary file, possibly deleting it. + """ + self._closer.close() + # iter() doesn't use __getattr__ to find the __iter__ method def __iter__(self): return iter(self.file) - # NT provides delete-on-close as a primitive, so we don't need - # the wrapper to do anything special. We still use it so that - # file.name is useful (i.e. not "(fdopen)") with NamedTemporaryFile. - if _os.name != 'nt': - # Cache the unlinker so we don't get spurious errors at - # shutdown when the module-level "os" is None'd out. Note - # that this must be referenced as self.unlink, because the - # name TemporaryFileWrapper may also get None'd out before - # __del__ is called. - unlink = _os.unlink - - def close(self): - if not self.close_called: - self.close_called = True - self.file.close() - if self.delete: - self.unlink(self.name) - - def __del__(self): - self.close() - - # Need to trap __exit__ as well to ensure the file gets - # deleted when used in a with statement - def __exit__(self, exc, value, tb): - result = self.file.__exit__(exc, value, tb) - self.close() - return result - else: - def __exit__(self, exc, value, tb): - self.file.__exit__(exc, value, tb) - def NamedTemporaryFile(mode='w+b', buffering=-1, encoding=None, newline=None, suffix="", prefix=template, diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py index 2e99013..351ef08 100644 --- a/Lib/test/test_tempfile.py +++ b/Lib/test/test_tempfile.py @@ -8,6 +8,7 @@ import sys import re import warnings import contextlib +import weakref import unittest from test import support @@ -689,6 +690,22 @@ class TestNamedTemporaryFile(BaseTestCase): self.do_create(pre="a", suf="b") self.do_create(pre="aa", suf=".txt") + def test_method_lookup(self): + # Issue #18879: Looking up a temporary file method should keep it + # alive long enough. + f = self.do_create() + wr = weakref.ref(f) + write = f.write + write2 = f.write + del f + write(b'foo') + del write + write2(b'bar') + del write2 + if support.check_impl_detail(cpython=True): + # No reference cycle was created. + self.assertIsNone(wr()) + def test_creates_named(self): # NamedTemporaryFile creates files with names f = tempfile.NamedTemporaryFile() |