Kaydet (Commit) 7c549c4e authored tarafından Barry Warsaw's avatar Barry Warsaw

- 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.
üst 17fd1e10
...@@ -791,7 +791,7 @@ call fails (for example because the path doesn't exist): ...@@ -791,7 +791,7 @@ call fails (for example because the path doesn't exist):
the symbolic link's information rather than its target's. 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 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 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): ...@@ -805,6 +805,16 @@ call fails (for example because the path doesn't exist):
If *parents* is false (the default), a missing parent raises If *parents* is false (the default), a missing parent raises
:exc:`FileNotFoundError`. :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) .. method:: Path.open(mode='r', buffering=-1, encoding=None, errors=None, newline=None)
......
...@@ -1106,14 +1106,21 @@ class Path(PurePath): ...@@ -1106,14 +1106,21 @@ class Path(PurePath):
fd = self._raw_open(flags, mode) fd = self._raw_open(flags, mode)
os.close(fd) os.close(fd)
def mkdir(self, mode=0o777, parents=False): def mkdir(self, mode=0o777, parents=False, exist_ok=False):
if self._closed: if self._closed:
self._raise_closed() self._raise_closed()
if not parents: 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: else:
try: try:
self._accessor.mkdir(self, mode) self._accessor.mkdir(self, mode)
except FileExistsError:
if not exist_ok or not self.is_dir():
raise
except OSError as e: except OSError as e:
if e.errno != ENOENT: if e.errno != ENOENT:
raise raise
......
...@@ -4,13 +4,10 @@ import os ...@@ -4,13 +4,10 @@ import os
import errno import errno
import pathlib import pathlib
import pickle import pickle
import shutil
import socket import socket
import stat import stat
import sys
import tempfile import tempfile
import unittest import unittest
from contextlib import contextmanager
from test import support from test import support
TESTFN = support.TESTFN TESTFN = support.TESTFN
...@@ -743,7 +740,6 @@ class PureWindowsPathTest(_BasePurePathTest, unittest.TestCase): ...@@ -743,7 +740,6 @@ class PureWindowsPathTest(_BasePurePathTest, unittest.TestCase):
self.assertEqual(P('//Some/SHARE/a/B'), P('//somE/share/A/b')) self.assertEqual(P('//Some/SHARE/a/B'), P('//somE/share/A/b'))
def test_as_uri(self): def test_as_uri(self):
from urllib.parse import quote_from_bytes
P = self.cls P = self.cls
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
P('/a/b').as_uri() P('/a/b').as_uri()
...@@ -1617,6 +1613,59 @@ class _BasePathTest(object): ...@@ -1617,6 +1613,59 @@ class _BasePathTest(object):
# the parent's permissions follow the default process settings # the parent's permissions follow the default process settings
self.assertEqual(stat.S_IMODE(p.parent.stat().st_mode), mode) 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 @with_symlinks
def test_symlink_to(self): def test_symlink_to(self):
P = self.cls(BASE) P = self.cls(BASE)
...@@ -1852,7 +1901,6 @@ class PosixPathTest(_BasePathTest, unittest.TestCase): ...@@ -1852,7 +1901,6 @@ class PosixPathTest(_BasePathTest, unittest.TestCase):
@with_symlinks @with_symlinks
def test_resolve_loop(self): def test_resolve_loop(self):
# Loop detection for broken symlinks under POSIX # Loop detection for broken symlinks under POSIX
P = self.cls
# Loops with relative symlinks # Loops with relative symlinks
os.symlink('linkX/inside', join('linkX')) os.symlink('linkX/inside', join('linkX'))
self._check_symlink_loop(BASE, 'linkX') self._check_symlink_loop(BASE, 'linkX')
......
...@@ -307,7 +307,7 @@ class PlatformTest(unittest.TestCase): ...@@ -307,7 +307,7 @@ class PlatformTest(unittest.TestCase):
with mock.patch('platform._UNIXCONFDIR', tempdir): with mock.patch('platform._UNIXCONFDIR', tempdir):
distname, version, distid = platform.linux_distribution() distname, version, distid = platform.linux_distribution()
self.assertEqual(distname, 'Fedora') self.assertEqual(distname, 'Fedora')
self.assertEqual(version, '19') self.assertEqual(version, '19')
self.assertEqual(distid, 'Schr\xf6dinger\u2019s Cat') self.assertEqual(distid, 'Schr\xf6dinger\u2019s Cat')
......
...@@ -123,6 +123,10 @@ Core and Builtins ...@@ -123,6 +123,10 @@ Core and Builtins
Library 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 - Issue #21047: set the default value for the *convert_charrefs* argument
of HTMLParser to True. Patch by Berker Peksag. of HTMLParser to True. Patch by Berker Peksag.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment