diff options
| author | Antoine Pitrou <solipsis@pitrou.net> | 2013-12-21 22:16:19 +0100 | 
|---|---|---|
| committer | Antoine Pitrou <solipsis@pitrou.net> | 2013-12-21 22:16:19 +0100 | 
| commit | 2b7f69851d641e4bf48c762c9a23df5b51982a1f (patch) | |
| tree | 6bfd2dce3c4aa7bbec313e1001e32b6842621a66 /Lib/tempfile.py | |
| parent | c16dfe18376791b3118ef42d39a3afd25ac6e306 (diff) | |
| parent | 17c93260a62ac895f336351158e6fdaa6b117bcb (diff) | |
| download | cpython-git-2b7f69851d641e4bf48c762c9a23df5b51982a1f.tar.gz | |
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/tempfile.py')
| -rw-r--r-- | Lib/tempfile.py | 96 | 
1 files changed, 64 insertions, 32 deletions
| diff --git a/Lib/tempfile.py b/Lib/tempfile.py index 5d3462102f..eae528da83 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, | 
