- Issue #21539: Add a *exists_ok* argument to `Pathlib.mkdir()` to mimic

`mkdir -p` and `os.makedirs()` functionality.  When true, ignore
  FileExistsErrors.  Patch by Berker Peksag.

(With minor cleanups, additional tests, doc tweaks, etc. by Barry)

Also:

* Remove some unused imports in test_pathlib.py reported by pyflakes.
This commit is contained in:
Barry Warsaw 2014-08-05 11:28:12 -04:00
parent 17fd1e1013
commit 7c549c4e64
5 changed files with 78 additions and 9 deletions

View File

@ -791,7 +791,7 @@ call fails (for example because the path doesn't exist):
the symbolic link's information rather than its target's.
.. method:: Path.mkdir(mode=0o777, parents=False)
.. method:: Path.mkdir(mode=0o777, parents=False, exist_ok=False)
Create a new directory at this given path. If *mode* is given, it is
combined with the process' ``umask`` value to determine the file mode
@ -805,6 +805,16 @@ call fails (for example because the path doesn't exist):
If *parents* is false (the default), a missing parent raises
:exc:`FileNotFoundError`.
If *exist_ok* is false (the default), an :exc:`FileExistsError` is
raised if the target directory already exists.
If *exist_ok* is true, :exc:`FileExistsError` exceptions will be
ignored (same behavior as the POSIX ``mkdir -p`` command), but only if the
last path component is not an existing non-directory file.
.. versionchanged:: 3.5
The *exist_ok* parameter was added.
.. method:: Path.open(mode='r', buffering=-1, encoding=None, errors=None, newline=None)

View File

@ -1106,14 +1106,21 @@ class Path(PurePath):
fd = self._raw_open(flags, mode)
os.close(fd)
def mkdir(self, mode=0o777, parents=False):
def mkdir(self, mode=0o777, parents=False, exist_ok=False):
if self._closed:
self._raise_closed()
if not parents:
self._accessor.mkdir(self, mode)
try:
self._accessor.mkdir(self, mode)
except FileExistsError:
if not exist_ok or not self.is_dir():
raise
else:
try:
self._accessor.mkdir(self, mode)
except FileExistsError:
if not exist_ok or not self.is_dir():
raise
except OSError as e:
if e.errno != ENOENT:
raise

View File

@ -4,13 +4,10 @@ import os
import errno
import pathlib
import pickle
import shutil
import socket
import stat
import sys
import tempfile
import unittest
from contextlib import contextmanager
from test import support
TESTFN = support.TESTFN
@ -743,7 +740,6 @@ class PureWindowsPathTest(_BasePurePathTest, unittest.TestCase):
self.assertEqual(P('//Some/SHARE/a/B'), P('//somE/share/A/b'))
def test_as_uri(self):
from urllib.parse import quote_from_bytes
P = self.cls
with self.assertRaises(ValueError):
P('/a/b').as_uri()
@ -1617,6 +1613,59 @@ class _BasePathTest(object):
# the parent's permissions follow the default process settings
self.assertEqual(stat.S_IMODE(p.parent.stat().st_mode), mode)
def test_mkdir_exist_ok(self):
p = self.cls(BASE, 'dirB')
st_ctime_first = p.stat().st_ctime
self.assertTrue(p.exists())
self.assertTrue(p.is_dir())
with self.assertRaises(FileExistsError) as cm:
p.mkdir()
self.assertEqual(cm.exception.errno, errno.EEXIST)
p.mkdir(exist_ok=True)
self.assertTrue(p.exists())
self.assertEqual(p.stat().st_ctime, st_ctime_first)
def test_mkdir_exist_ok_with_parent(self):
p = self.cls(BASE, 'dirC')
self.assertTrue(p.exists())
with self.assertRaises(FileExistsError) as cm:
p.mkdir()
self.assertEqual(cm.exception.errno, errno.EEXIST)
p = p / 'newdirC'
p.mkdir(parents=True)
st_ctime_first = p.stat().st_ctime
self.assertTrue(p.exists())
with self.assertRaises(FileExistsError) as cm:
p.mkdir(parents=True)
self.assertEqual(cm.exception.errno, errno.EEXIST)
p.mkdir(parents=True, exist_ok=True)
self.assertTrue(p.exists())
self.assertEqual(p.stat().st_ctime, st_ctime_first)
def test_mkdir_with_child_file(self):
p = self.cls(BASE, 'dirB', 'fileB')
self.assertTrue(p.exists())
# An exception is raised when the last path component is an existing
# regular file, regardless of whether exist_ok is true or not.
with self.assertRaises(FileExistsError) as cm:
p.mkdir(parents=True)
self.assertEqual(cm.exception.errno, errno.EEXIST)
with self.assertRaises(FileExistsError) as cm:
p.mkdir(parents=True, exist_ok=True)
self.assertEqual(cm.exception.errno, errno.EEXIST)
def test_mkdir_no_parents_file(self):
p = self.cls(BASE, 'fileA')
self.assertTrue(p.exists())
# An exception is raised when the last path component is an existing
# regular file, regardless of whether exist_ok is true or not.
with self.assertRaises(FileExistsError) as cm:
p.mkdir()
self.assertEqual(cm.exception.errno, errno.EEXIST)
with self.assertRaises(FileExistsError) as cm:
p.mkdir(exist_ok=True)
self.assertEqual(cm.exception.errno, errno.EEXIST)
@with_symlinks
def test_symlink_to(self):
P = self.cls(BASE)
@ -1852,7 +1901,6 @@ class PosixPathTest(_BasePathTest, unittest.TestCase):
@with_symlinks
def test_resolve_loop(self):
# Loop detection for broken symlinks under POSIX
P = self.cls
# Loops with relative symlinks
os.symlink('linkX/inside', join('linkX'))
self._check_symlink_loop(BASE, 'linkX')

View File

@ -307,7 +307,7 @@ class PlatformTest(unittest.TestCase):
with mock.patch('platform._UNIXCONFDIR', tempdir):
distname, version, distid = platform.linux_distribution()
self.assertEqual(distname, 'Fedora')
self.assertEqual(distname, 'Fedora')
self.assertEqual(version, '19')
self.assertEqual(distid, 'Schr\xf6dinger\u2019s Cat')

View File

@ -123,6 +123,10 @@ Core and Builtins
Library
-------
- Issue #21539: Add a *exists_ok* argument to `Pathlib.mkdir()` to mimic
`mkdir -p` and `os.makedirs()` functionality. When true, ignore
FileExistsErrors. Patch by Berker Peksag.
- Issue #21047: set the default value for the *convert_charrefs* argument
of HTMLParser to True. Patch by Berker Peksag.