]> granicus.if.org Git - python/commitdiff
- Issue #21539: Add a *exists_ok* argument to `Pathlib.mkdir()` to mimic
authorBarry Warsaw <barry@python.org>
Tue, 5 Aug 2014 15:28:12 +0000 (11:28 -0400)
committerBarry Warsaw <barry@python.org>
Tue, 5 Aug 2014 15:28:12 +0000 (11:28 -0400)
  `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.

Doc/library/pathlib.rst
Lib/pathlib.py
Lib/test/test_pathlib.py
Lib/test/test_platform.py
Misc/NEWS

index 0a2a4e3be0e276948f73b87b33441d5707221117..67ed914e9f8d106d2cbb009f17f37e0ca635f6c6 100644 (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)
 
index 428de39f964dd357be113f352f9184f9f1e43bf4..eff6ae3f0c9ff3f4485b9d074a5fcb5d9e528bed 100644 (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
index a45cf7e5887664652f0f6bbc292661891671798c..4f762176ba978d80d26a8fd5204e720d424263be 100644 (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')
index b3de43b7abb8a2d8099e13788c9b527b5b6159be..ededbdba421949ed2a85d1e45712942897fba4bb 100644 (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')
 
index f0687e45bb8da7e699c3255a6cbf3073e3ebe986..99a50a5ba71505146a431ab6365f3acd457e0ed7 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -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.