summaryrefslogtreecommitdiff
path: root/Lib/tempfile.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/tempfile.py')
-rw-r--r--Lib/tempfile.py150
1 files changed, 59 insertions, 91 deletions
diff --git a/Lib/tempfile.py b/Lib/tempfile.py
index 6bc842f6d8..0537228ba5 100644
--- a/Lib/tempfile.py
+++ b/Lib/tempfile.py
@@ -1,10 +1,10 @@
"""Temporary files.
This module provides generic, low- and high-level interfaces for
-creating temporary files and directories. The interfaces listed
-as "safe" just below can be used without fear of race conditions.
-Those listed as "unsafe" cannot, and are provided for backward
-compatibility only.
+creating temporary files and directories. All of the interfaces
+provided by this module can be used without fear of race conditions
+except for 'mktemp'. 'mktemp' is subject to race conditions and
+should not be used; it is provided for backward compatibility only.
This module also provides some data items to the user:
@@ -27,7 +27,6 @@ __all__ = [
# Imports.
-import atexit as _atexit
import functools as _functools
import warnings as _warnings
import io as _io
@@ -35,23 +34,7 @@ import os as _os
import shutil as _shutil
import errno as _errno
from random import Random as _Random
-
-try:
- import fcntl as _fcntl
-except ImportError:
- def _set_cloexec(fd):
- pass
-else:
- def _set_cloexec(fd):
- try:
- flags = _fcntl.fcntl(fd, _fcntl.F_GETFD, 0)
- except OSError:
- pass
- else:
- # flags read successfully, modify
- flags |= _fcntl.FD_CLOEXEC
- _fcntl.fcntl(fd, _fcntl.F_SETFD, flags)
-
+import weakref as _weakref
try:
import _thread
@@ -60,8 +43,6 @@ except ImportError:
_allocate_lock = _thread.allocate_lock
_text_openflags = _os.O_RDWR | _os.O_CREAT | _os.O_EXCL
-if hasattr(_os, 'O_NOINHERIT'):
- _text_openflags |= _os.O_NOINHERIT
if hasattr(_os, 'O_NOFOLLOW'):
_text_openflags |= _os.O_NOFOLLOW
@@ -90,8 +71,8 @@ else:
# Fallback. All we need is something that raises OSError if the
# file doesn't exist.
def _stat(fn):
- f = open(fn)
- f.close()
+ fd = _os.open(fn, _os.O_RDONLY)
+ _os.close(fd)
def _exists(fn):
try:
@@ -125,7 +106,7 @@ class _RandomNameSequence:
def __next__(self):
c = self.characters
choose = self.rng.choice
- letters = [choose(c) for dummy in "123456"]
+ letters = [choose(c) for dummy in range(8)]
return ''.join(letters)
def _candidate_tempdir_list():
@@ -167,7 +148,7 @@ def _get_default_tempdir():
for dir in dirlist:
if dir != _os.curdir:
- dir = _os.path.normcase(_os.path.abspath(dir))
+ dir = _os.path.abspath(dir)
# Try only a few names per directory.
for seq in range(100):
name = next(namer)
@@ -185,6 +166,13 @@ def _get_default_tempdir():
return dir
except FileExistsError:
pass
+ except PermissionError:
+ # This exception is thrown when a directory with the chosen name
+ # already exists on windows.
+ if (_os.name == 'nt' and _os.path.isdir(dir) and
+ _os.access(dir, _os.W_OK)):
+ continue
+ break # no point trying more names in this directory
except OSError:
break # no point trying more names in this directory
raise FileNotFoundError(_errno.ENOENT,
@@ -217,14 +205,14 @@ def _mkstemp_inner(dir, pre, suf, flags):
file = _os.path.join(dir, pre + name + suf)
try:
fd = _os.open(file, flags, 0o600)
- _set_cloexec(fd)
return (fd, _os.path.abspath(file))
except FileExistsError:
continue # try again
except PermissionError:
# This exception is thrown when a directory with the chosen name
# already exists on windows.
- if _os.name == 'nt':
+ if (_os.name == 'nt' and _os.path.isdir(dir) and
+ _os.access(dir, _os.W_OK)):
continue
else:
raise
@@ -316,6 +304,14 @@ def mkdtemp(suffix="", prefix=template, dir=None):
return file
except FileExistsError:
continue # try again
+ except PermissionError:
+ # This exception is thrown when a directory with the chosen name
+ # already exists on windows.
+ if (_os.name == 'nt' and _os.path.isdir(dir) and
+ _os.access(dir, _os.W_OK)):
+ continue
+ else:
+ raise
raise FileExistsError(_errno.EEXIST,
"No usable temporary directory name found")
@@ -356,8 +352,7 @@ class _TemporaryFileCloser:
underlying file object, without adding a __del__ method to the
temporary file."""
- # Set here since __del__ checks it
- file = None
+ file = None # Set here since __del__ checks it
close_called = False
def __init__(self, file, name, delete=True):
@@ -378,9 +373,11 @@ class _TemporaryFileCloser:
def close(self, unlink=_os.unlink):
if not self.close_called and self.file is not None:
self.close_called = True
- self.file.close()
- if self.delete:
- unlink(self.name)
+ try:
+ self.file.close()
+ finally:
+ if self.delete:
+ unlink(self.name)
# Need to ensure the file is deleted on __del__
def __del__(self):
@@ -447,7 +444,13 @@ class _TemporaryFileWrapper:
# iter() doesn't use __getattr__ to find the __iter__ method
def __iter__(self):
- return iter(self.file)
+ # Don't return iter(self.file), but yield from it to avoid closing
+ # file as long as it's being used as iterator (see issue #23700). We
+ # can't use 'yield from' here because iter(file) returns the file
+ # object itself, which has a close method, and thus the file would get
+ # closed when the generator is finalized, due to PEP380 semantics.
+ for line in self.file:
+ yield line
def NamedTemporaryFile(mode='w+b', buffering=-1, encoding=None,
@@ -479,10 +482,14 @@ def NamedTemporaryFile(mode='w+b', buffering=-1, encoding=None,
flags |= _os.O_TEMPORARY
(fd, name) = _mkstemp_inner(dir, prefix, suffix, flags)
- file = _io.open(fd, mode, buffering=buffering,
- newline=newline, encoding=encoding)
+ try:
+ file = _io.open(fd, mode, buffering=buffering,
+ newline=newline, encoding=encoding)
- return _TemporaryFileWrapper(file, name, delete)
+ return _TemporaryFileWrapper(file, name, delete)
+ except Exception:
+ _os.close(fd)
+ raise
if _os.name != 'posix' or _os.sys.platform == 'cygwin':
# On non-POSIX and Cygwin systems, assume that we cannot unlink a file
@@ -535,7 +542,7 @@ class SpooledTemporaryFile:
else:
# Setting newline="\n" avoids newline translation;
# this is important because otherwise on Windows we'd
- # hget double newline translation upon rollover().
+ # get double newline translation upon rollover().
self._file = _io.StringIO(newline="\n")
self._max_size = max_size
self._rolled = False
@@ -680,12 +687,17 @@ class TemporaryDirectory(object):
in it are removed.
"""
- # Handle mkdtemp raising an exception
- name = None
- _closed = False
-
def __init__(self, suffix="", prefix=template, dir=None):
self.name = mkdtemp(suffix, prefix, dir)
+ self._finalizer = _weakref.finalize(
+ self, self._cleanup, self.name,
+ warn_message="Implicitly cleaning up {!r}".format(self))
+
+ @classmethod
+ def _cleanup(cls, name, warn_message):
+ _shutil.rmtree(name)
+ _warnings.warn(warn_message, ResourceWarning)
+
def __repr__(self):
return "<{} {!r}>".format(self.__class__.__name__, self.name)
@@ -693,53 +705,9 @@ class TemporaryDirectory(object):
def __enter__(self):
return self.name
- def cleanup(self, _warn=False, _warnings=_warnings):
- if self.name and not self._closed:
- try:
- _shutil.rmtree(self.name)
- except (TypeError, AttributeError) as ex:
- if "None" not in '%s' % (ex,):
- raise
- self._rmtree(self.name)
- self._closed = True
- if _warn and _warnings.warn:
- try:
- _warnings.warn("Implicitly cleaning up {!r}".format(self),
- ResourceWarning)
- except:
- if _is_running:
- raise
- # Don't raise an exception if modules needed for emitting
- # a warning are already cleaned in shutdown process.
-
def __exit__(self, exc, value, tb):
self.cleanup()
- def __del__(self):
- # Issue a ResourceWarning if implicit cleanup needed
- self.cleanup(_warn=True)
-
- def _rmtree(self, path, _OSError=OSError, _sep=_os.path.sep,
- _listdir=_os.listdir, _remove=_os.remove, _rmdir=_os.rmdir):
- # Essentially a stripped down version of shutil.rmtree. We can't
- # use globals because they may be None'ed out at shutdown.
- if not isinstance(path, str):
- _sep = _sep.encode()
- try:
- for name in _listdir(path):
- fullname = path + _sep + name
- try:
- _remove(fullname)
- except _OSError:
- self._rmtree(fullname)
- _rmdir(path)
- except _OSError:
- pass
-
-_is_running = True
-
-def _on_shutdown():
- global _is_running
- _is_running = False
-
-_atexit.register(_on_shutdown)
+ def cleanup(self):
+ if self._finalizer.detach():
+ _shutil.rmtree(self.name)