Skip to content

Commit

Permalink
Towards pathlib.Path.mkdir compatibility (#410)
Browse files Browse the repository at this point in the history
* Implement mkdir args: parents, exist_ok

* Add tests for mkdir (local + remote)
  • Loading branch information
Shahriar Heidrich authored and henryiii committed Oct 16, 2018
1 parent ba95ca9 commit 361db26
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 11 deletions.
6 changes: 4 additions & 2 deletions plumbum/machines/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,8 +380,10 @@ def _path_move(self, src, dst):
def _path_copy(self, src, dst):
self._session.run("cp -r %s %s" % (shquote(src), shquote(dst)))

def _path_mkdir(self, fn):
self._session.run("mkdir -p %s" % (shquote(fn), ))
def _path_mkdir(self, fn, mode=None, minus_p=True):
p_str = "-p " if minus_p else ""
cmd = "mkdir %s%s" % (p_str, shquote(fn))
self._session.run(cmd)

def _path_chmod(self, mode, fn):
self._session.run("chmod %o %s" % (mode, shquote(fn)))
Expand Down
17 changes: 15 additions & 2 deletions plumbum/path/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,21 @@ def copy(self, dst, override=False):
dst exists and override is False."""

@abstractmethod
def mkdir(self):
"""Creates a directory at this path; if the directory already exists, silently ignore"""
def mkdir(self, mode=0o777, parents=True, exist_ok=True):
"""
Creates a directory at this path.
:param mode: **Not implemented yet,** just here to keep the order of arguments
backwards-compatible once it is.
:param parents: If this is true (the default), the directory's parents will also be created if
necessary.
:param exist_ok: If this is true (the default), no exception will be raised if the directory
already exists (otherwise ``OSError``).
Note that the defaults for ``parents`` and ``exist_ok`` are the opposite of what they are in
Python's own ``pathlib`` - this is to maintain backwards-compatibility with Plumbum's behaviour
from before they were implemented.
"""

@abstractmethod
def open(self, mode="r"):
Expand Down
11 changes: 7 additions & 4 deletions plumbum/path/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,14 +210,17 @@ def copy(self, dst, override=False, overwrite=True):
return dst

@_setdoc(Path)
def mkdir(self):
if not self.exists():
def mkdir(self, mode=0o777, parents=True, exist_ok=True):
if not self.exists() or not exist_ok:
try:
os.makedirs(str(self))
if parents:
os.makedirs(str(self))
else:
os.mkdir(str(self))
except OSError: # pragma: no cover
# directory might already exist (a race with other threads/processes)
_, ex, _ = sys.exc_info()
if ex.errno != errno.EEXIST:
if ex.errno != errno.EEXIST or not exist_ok:
raise

@_setdoc(Path)
Expand Down
22 changes: 19 additions & 3 deletions plumbum/path/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
from contextlib import contextmanager
from plumbum.path.base import Path, FSUser
from plumbum.lib import _setdoc, six
from plumbum.commands import shquote
from plumbum.commands import shquote, ProcessExecutionError
import sys

try: # Py3
import urllib.request as urllib
Expand Down Expand Up @@ -232,8 +233,23 @@ def copy(self, dst, override=False):
self.remote._path_copy(self, dst)

@_setdoc(Path)
def mkdir(self):
self.remote._path_mkdir(self)
def mkdir(self, mode=None, parents=True, exist_ok=True):
if parents and exist_ok:
self.remote._path_mkdir(self, mode=mode, minus_p=True)
else:
if parents and len(self.parts) > 1:
self.remote._path_mkdir(self.parent, mode=mode, minus_p=True)
try:
self.remote._path_mkdir(self, mode=mode, minus_p=False)
except ProcessExecutionError:
_, ex, _ = sys.exc_info()
if "File exists" in ex.stderr:
if not exist_ok:
raise OSError(
errno.EEXIST, "File exists (on remote end)",
str(self))
else:
raise

@_setdoc(Path)
def read(self, encoding=None):
Expand Down
17 changes: 17 additions & 0 deletions tests/test_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,23 @@ def test_getpath(self):
def test_path_dir(self):
assert local.path(__file__).dirname == SDIR

def test_mkdir(self):
# (identical to test_remote.TestRemotePath.test_mkdir)
with local.tempdir() as tmp:
(tmp / "a").mkdir(exist_ok=False, parents=False)
assert (tmp / "a").exists()
assert (tmp / "a").is_dir()
(tmp / "a").mkdir(exist_ok=True, parents=False)
(tmp / "a").mkdir(exist_ok=True, parents=True)
with pytest.raises(OSError):
(tmp / "a").mkdir(exist_ok=False, parents=False)
with pytest.raises(OSError):
(tmp / "a").mkdir(exist_ok=False, parents=True)
(tmp / "b" / "bb").mkdir(exist_ok=False, parents=True)
assert (tmp / "b" / "bb").exists()
assert (tmp / "b" / "bb").is_dir()
assert not tmp.exists()


@pytest.mark.usefixtures("testdir")
class TestLocalMachine:
Expand Down
18 changes: 18 additions & 0 deletions tests/test_remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,24 @@ def test_parent(self):
p2 = p1.parent
assert str(p2) == "/some/long/path/to"

def test_mkdir(self):
# (identical to test_local.TestLocalPath.test_mkdir)
with self._connect() as rem:
with rem.tempdir() as tmp:
(tmp / "a").mkdir(exist_ok=False, parents=False)
assert (tmp / "a").exists()
assert (tmp / "a").is_dir()
(tmp / "a").mkdir(exist_ok=True, parents=False)
(tmp / "a").mkdir(exist_ok=True, parents=True)
with pytest.raises(OSError):
(tmp / "a").mkdir(exist_ok=False, parents=False)
with pytest.raises(OSError):
(tmp / "a").mkdir(exist_ok=False, parents=True)
(tmp / "b" / "bb").mkdir(exist_ok=False, parents=True)
assert (tmp / "b" / "bb").exists()
assert (tmp / "b" / "bb").is_dir()
assert not tmp.exists()

class BaseRemoteMachineTest(object):
TUNNEL_PROG = r"""import sys, socket
s = socket.socket()
Expand Down

0 comments on commit 361db26

Please sign in to comment.