Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7c549c4

Browse files
committed
- 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.
1 parent 17fd1e1 commit 7c549c4

5 files changed

Lines changed: 78 additions & 9 deletions

File tree

Doc/library/pathlib.rst

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -791,7 +791,7 @@ call fails (for example because the path doesn't exist):
791791
the symbolic link's information rather than its target's.
792792

793793

794-
.. method:: Path.mkdir(mode=0o777, parents=False)
794+
.. method:: Path.mkdir(mode=0o777, parents=False, exist_ok=False)
795795

796796
Create a new directory at this given path. If *mode* is given, it is
797797
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):
805805
If *parents* is false (the default), a missing parent raises
806806
:exc:`FileNotFoundError`.
807807

808+
If *exist_ok* is false (the default), an :exc:`FileExistsError` is
809+
raised if the target directory already exists.
810+
811+
If *exist_ok* is true, :exc:`FileExistsError` exceptions will be
812+
ignored (same behavior as the POSIX ``mkdir -p`` command), but only if the
813+
last path component is not an existing non-directory file.
814+
815+
.. versionchanged:: 3.5
816+
The *exist_ok* parameter was added.
817+
808818

809819
.. method:: Path.open(mode='r', buffering=-1, encoding=None, errors=None, newline=None)
810820

Lib/pathlib.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,14 +1106,21 @@ def touch(self, mode=0o666, exist_ok=True):
11061106
fd = self._raw_open(flags, mode)
11071107
os.close(fd)
11081108

1109-
def mkdir(self, mode=0o777, parents=False):
1109+
def mkdir(self, mode=0o777, parents=False, exist_ok=False):
11101110
if self._closed:
11111111
self._raise_closed()
11121112
if not parents:
1113-
self._accessor.mkdir(self, mode)
1113+
try:
1114+
self._accessor.mkdir(self, mode)
1115+
except FileExistsError:
1116+
if not exist_ok or not self.is_dir():
1117+
raise
11141118
else:
11151119
try:
11161120
self._accessor.mkdir(self, mode)
1121+
except FileExistsError:
1122+
if not exist_ok or not self.is_dir():
1123+
raise
11171124
except OSError as e:
11181125
if e.errno != ENOENT:
11191126
raise

Lib/test/test_pathlib.py

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,10 @@
44
import errno
55
import pathlib
66
import pickle
7-
import shutil
87
import socket
98
import stat
10-
import sys
119
import tempfile
1210
import unittest
13-
from contextlib import contextmanager
1411

1512
from test import support
1613
TESTFN = support.TESTFN
@@ -743,7 +740,6 @@ def test_eq(self):
743740
self.assertEqual(P('//Some/SHARE/a/B'), P('//somE/share/A/b'))
744741

745742
def test_as_uri(self):
746-
from urllib.parse import quote_from_bytes
747743
P = self.cls
748744
with self.assertRaises(ValueError):
749745
P('/a/b').as_uri()
@@ -1617,6 +1613,59 @@ def test_mkdir_parents(self):
16171613
# the parent's permissions follow the default process settings
16181614
self.assertEqual(stat.S_IMODE(p.parent.stat().st_mode), mode)
16191615

1616+
def test_mkdir_exist_ok(self):
1617+
p = self.cls(BASE, 'dirB')
1618+
st_ctime_first = p.stat().st_ctime
1619+
self.assertTrue(p.exists())
1620+
self.assertTrue(p.is_dir())
1621+
with self.assertRaises(FileExistsError) as cm:
1622+
p.mkdir()
1623+
self.assertEqual(cm.exception.errno, errno.EEXIST)
1624+
p.mkdir(exist_ok=True)
1625+
self.assertTrue(p.exists())
1626+
self.assertEqual(p.stat().st_ctime, st_ctime_first)
1627+
1628+
def test_mkdir_exist_ok_with_parent(self):
1629+
p = self.cls(BASE, 'dirC')
1630+
self.assertTrue(p.exists())
1631+
with self.assertRaises(FileExistsError) as cm:
1632+
p.mkdir()
1633+
self.assertEqual(cm.exception.errno, errno.EEXIST)
1634+
p = p / 'newdirC'
1635+
p.mkdir(parents=True)
1636+
st_ctime_first = p.stat().st_ctime
1637+
self.assertTrue(p.exists())
1638+
with self.assertRaises(FileExistsError) as cm:
1639+
p.mkdir(parents=True)
1640+
self.assertEqual(cm.exception.errno, errno.EEXIST)
1641+
p.mkdir(parents=True, exist_ok=True)
1642+
self.assertTrue(p.exists())
1643+
self.assertEqual(p.stat().st_ctime, st_ctime_first)
1644+
1645+
def test_mkdir_with_child_file(self):
1646+
p = self.cls(BASE, 'dirB', 'fileB')
1647+
self.assertTrue(p.exists())
1648+
# An exception is raised when the last path component is an existing
1649+
# regular file, regardless of whether exist_ok is true or not.
1650+
with self.assertRaises(FileExistsError) as cm:
1651+
p.mkdir(parents=True)
1652+
self.assertEqual(cm.exception.errno, errno.EEXIST)
1653+
with self.assertRaises(FileExistsError) as cm:
1654+
p.mkdir(parents=True, exist_ok=True)
1655+
self.assertEqual(cm.exception.errno, errno.EEXIST)
1656+
1657+
def test_mkdir_no_parents_file(self):
1658+
p = self.cls(BASE, 'fileA')
1659+
self.assertTrue(p.exists())
1660+
# An exception is raised when the last path component is an existing
1661+
# regular file, regardless of whether exist_ok is true or not.
1662+
with self.assertRaises(FileExistsError) as cm:
1663+
p.mkdir()
1664+
self.assertEqual(cm.exception.errno, errno.EEXIST)
1665+
with self.assertRaises(FileExistsError) as cm:
1666+
p.mkdir(exist_ok=True)
1667+
self.assertEqual(cm.exception.errno, errno.EEXIST)
1668+
16201669
@with_symlinks
16211670
def test_symlink_to(self):
16221671
P = self.cls(BASE)
@@ -1852,7 +1901,6 @@ def test_touch_mode(self):
18521901
@with_symlinks
18531902
def test_resolve_loop(self):
18541903
# Loop detection for broken symlinks under POSIX
1855-
P = self.cls
18561904
# Loops with relative symlinks
18571905
os.symlink('linkX/inside', join('linkX'))
18581906
self._check_symlink_loop(BASE, 'linkX')

Lib/test/test_platform.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ def test_linux_distribution_encoding(self):
307307
with mock.patch('platform._UNIXCONFDIR', tempdir):
308308
distname, version, distid = platform.linux_distribution()
309309

310-
self.assertEqual(distname, 'Fedora')
310+
self.assertEqual(distname, 'Fedora')
311311
self.assertEqual(version, '19')
312312
self.assertEqual(distid, 'Schr\xf6dinger\u2019s Cat')
313313

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ Core and Builtins
123123
Library
124124
-------
125125

126+
- Issue #21539: Add a *exists_ok* argument to `Pathlib.mkdir()` to mimic
127+
`mkdir -p` and `os.makedirs()` functionality. When true, ignore
128+
FileExistsErrors. Patch by Berker Peksag.
129+
126130
- Issue #21047: set the default value for the *convert_charrefs* argument
127131
of HTMLParser to True. Patch by Berker Peksag.
128132

0 commit comments

Comments
 (0)