Issue #22107: tempfile.gettempdir() and tempfile.mkdtemp() now try again

when a directory with the chosen name already exists on Windows as well as
on Unix.  tempfile.mkstemp() now fails early if parent directory is not
valid (not exists or is a file) on Windows.
This commit is contained in:
Serhiy Storchaka 2015-05-20 00:14:00 +03:00
commit 492f027793
3 changed files with 63 additions and 7 deletions

View File

@ -166,6 +166,13 @@ def _get_default_tempdir():
return dir return dir
except FileExistsError: except FileExistsError:
pass 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: except OSError:
break # no point trying more names in this directory break # no point trying more names in this directory
raise FileNotFoundError(_errno.ENOENT, raise FileNotFoundError(_errno.ENOENT,
@ -204,7 +211,8 @@ def _mkstemp_inner(dir, pre, suf, flags):
except PermissionError: except PermissionError:
# This exception is thrown when a directory with the chosen name # This exception is thrown when a directory with the chosen name
# already exists on windows. # 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 continue
else: else:
raise raise
@ -296,6 +304,14 @@ def mkdtemp(suffix="", prefix=template, dir=None):
return file return file
except FileExistsError: except FileExistsError:
continue # try again 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, raise FileExistsError(_errno.EEXIST,
"No usable temporary directory name found") "No usable temporary directory name found")

View File

@ -275,7 +275,39 @@ def _mock_candidate_names(*names):
lambda: iter(names)) lambda: iter(names))
class TestMkstempInner(BaseTestCase): class TestBadTempdir:
def test_read_only_directory(self):
with _inside_empty_temp_dir():
oldmode = mode = os.stat(tempfile.tempdir).st_mode
mode &= ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
os.chmod(tempfile.tempdir, mode)
try:
if os.access(tempfile.tempdir, os.W_OK):
self.skipTest("can't set the directory read-only")
with self.assertRaises(PermissionError):
self.make_temp()
self.assertEqual(os.listdir(tempfile.tempdir), [])
finally:
os.chmod(tempfile.tempdir, oldmode)
def test_nonexisting_directory(self):
with _inside_empty_temp_dir():
tempdir = os.path.join(tempfile.tempdir, 'nonexistent')
with support.swap_attr(tempfile, 'tempdir', tempdir):
with self.assertRaises(FileNotFoundError):
self.make_temp()
def test_non_directory(self):
with _inside_empty_temp_dir():
tempdir = os.path.join(tempfile.tempdir, 'file')
open(tempdir, 'wb').close()
with support.swap_attr(tempfile, 'tempdir', tempdir):
with self.assertRaises((NotADirectoryError, FileNotFoundError)):
self.make_temp()
class TestMkstempInner(TestBadTempdir, BaseTestCase):
"""Test the internal function _mkstemp_inner.""" """Test the internal function _mkstemp_inner."""
class mkstemped: class mkstemped:
@ -390,7 +422,7 @@ class TestMkstempInner(BaseTestCase):
os.lseek(f.fd, 0, os.SEEK_SET) os.lseek(f.fd, 0, os.SEEK_SET)
self.assertEqual(os.read(f.fd, 20), b"blat") self.assertEqual(os.read(f.fd, 20), b"blat")
def default_mkstemp_inner(self): def make_temp(self):
return tempfile._mkstemp_inner(tempfile.gettempdir(), return tempfile._mkstemp_inner(tempfile.gettempdir(),
tempfile.template, tempfile.template,
'', '',
@ -401,11 +433,11 @@ class TestMkstempInner(BaseTestCase):
# the chosen name already exists # the chosen name already exists
with _inside_empty_temp_dir(), \ with _inside_empty_temp_dir(), \
_mock_candidate_names('aaa', 'aaa', 'bbb'): _mock_candidate_names('aaa', 'aaa', 'bbb'):
(fd1, name1) = self.default_mkstemp_inner() (fd1, name1) = self.make_temp()
os.close(fd1) os.close(fd1)
self.assertTrue(name1.endswith('aaa')) self.assertTrue(name1.endswith('aaa'))
(fd2, name2) = self.default_mkstemp_inner() (fd2, name2) = self.make_temp()
os.close(fd2) os.close(fd2)
self.assertTrue(name2.endswith('bbb')) self.assertTrue(name2.endswith('bbb'))
@ -417,7 +449,7 @@ class TestMkstempInner(BaseTestCase):
dir = tempfile.mkdtemp() dir = tempfile.mkdtemp()
self.assertTrue(dir.endswith('aaa')) self.assertTrue(dir.endswith('aaa'))
(fd, name) = self.default_mkstemp_inner() (fd, name) = self.make_temp()
os.close(fd) os.close(fd)
self.assertTrue(name.endswith('bbb')) self.assertTrue(name.endswith('bbb'))
@ -529,9 +561,12 @@ class TestMkstemp(BaseTestCase):
os.rmdir(dir) os.rmdir(dir)
class TestMkdtemp(BaseTestCase): class TestMkdtemp(TestBadTempdir, BaseTestCase):
"""Test mkdtemp().""" """Test mkdtemp()."""
def make_temp(self):
return tempfile.mkdtemp()
def do_create(self, dir=None, pre="", suf=""): def do_create(self, dir=None, pre="", suf=""):
if dir is None: if dir is None:
dir = tempfile.gettempdir() dir = tempfile.gettempdir()

View File

@ -52,6 +52,11 @@ Core and Builtins
Library Library
------- -------
- Issue #22107: tempfile.gettempdir() and tempfile.mkdtemp() now try again
when a directory with the chosen name already exists on Windows as well as
on Unix. tempfile.mkstemp() now fails early if parent directory is not
valid (not exists or is a file) on Windows.
- Issue #23780: Improved error message in os.path.join() with single argument. - Issue #23780: Improved error message in os.path.join() with single argument.
- Issue #6598: Increased time precision and random number range in - Issue #6598: Increased time precision and random number range in