~sschwarzer/ftputil

a2843e387f3c3d6e17a8ef2fcf3a25df1006d761 — Stefan Schwarzer 2 years ago 1b10ae6
Have `FTPHost.makedirs` handle `exist_ok`

If `exist_ok` is `False`, which is the default, raise an exception if
the leaf directory exists. Before, the exception wrongly wasn't
raised.

ticket: 117
3 files changed, 87 insertions(+), 5 deletions(-)

M ftputil/host.py
M test/test_host.py
M test/test_real_ftp.py
M ftputil/host.py => ftputil/host.py +21 -2
@@ 664,8 664,13 @@ class FTPHost:
    def makedirs(self, path, mode=None, exist_ok=False):
        """
        Make the directory `path`, but also make not yet existing intermediate
        directories, like `os.makedirs`. The value of `mode` is only accepted
        for compatibility with `os.makedirs` but otherwise ignored.
        directories, like `os.makedirs`.

        The value of `mode` is only accepted for compatibility with
        `os.makedirs` but otherwise ignored.

        If `exist_ok` is `False` (the default) and the leaf directory exists,
        raise a `PermanentError` with `errno` 17.
        """
        path = ftputil.tool.as_str_path(path)
        path = self.path.abspath(path)


@@ 683,6 688,7 @@ class FTPHost:
                try:
                    self.chdir(next_directory)
                except ftputil.error.PermanentError:
                    # Directory presumably doesn't exist.
                    try:
                        self.mkdir(next_directory)
                    except ftputil.error.PermanentError:


@@ 692,6 698,19 @@ class FTPHost:
                        # regular file with the name of the directory.
                        if not self.path.isdir(next_directory):
                            raise
                else:
                    # Directory exists. If we are at the last directory
                    # component and `exist_ok` is `False`, this is an error.
                    if (index == len(directories) - 1) and (not exist_ok):
                        # Before PEP 3151, if `exist_ok` is `False`, trying to
                        # create an existing directory in the local file system
                        # results in an `OSError` with `errno` 17, so emulate
                        # this also for FTP.
                        ftp_os_error = ftputil.error.PermanentError(
                            "path {!r} exists".format(path)
                        )
                        ftp_os_error.errno = 17
                        raise ftp_os_error
        finally:
            self.chdir(old_dir)


M test/test_host.py => test/test_host.py +64 -1
@@ 688,6 688,61 @@ class TestUploadAndDownload:
        assert flag is False


class TestMakedirs:
    def test_exist_ok_false(self):
        """
        If `exist_ok` is `False` or not specified, an existing leaf directory
        should lead to a `PermanentError` with `errno` set to 17.
        """
        # No `exist_ok` specified
        script = [
            Call("__init__"),
            Call("pwd", result="/"),
            Call("cwd", args=("/part1",)),
            Call("cwd", args=("/part1/part2",)),
            Call("cwd", args=("/",)),
            Call("close"),
        ]
        multisession_factory = scripted_session.factory(script)
        with test_base.ftp_host_factory(session_factory=multisession_factory) as host:
            with pytest.raises(ftputil.error.PermanentError) as exc_info:
                host.makedirs("/part1/part2")
            assert isinstance(exc_info.value, ftputil.error.PermanentError)
            assert exc_info.value.errno == 17
        # `exist_ok` explicitly set to `False`
        script = [
            Call("__init__"),
            Call("pwd", result="/"),
            Call("cwd", args=("/part1",)),
            Call("cwd", args=("/part1/part2",)),
            Call("cwd", args=("/",)),
            Call("close"),
        ]
        multisession_factory = scripted_session.factory(script)
        with test_base.ftp_host_factory(session_factory=multisession_factory) as host:
            with pytest.raises(ftputil.error.PermanentError) as exc_info:
                host.makedirs("/part1/part2", exist_ok=False)
            assert isinstance(exc_info.value, ftputil.error.PermanentError)
            assert exc_info.value.errno == 17

    def test_exist_ok_true(self):
        """
        If `exist_ok` is `True`, an existing leaf directory should _not_ lead
        to an exception.
        """
        script = [
            Call("__init__"),
            Call("pwd", result="/"),
            Call("cwd", args=("/part1",)),
            Call("cwd", args=("/part1/part2",)),
            Call("cwd", args=("/",)),
            Call("close"),
        ]
        multisession_factory = scripted_session.factory(script)
        with test_base.ftp_host_factory(session_factory=multisession_factory) as host:
            host.makedirs("/part1/part2", exist_ok=True)


class TestAcceptEitherUnicodeOrBytes:
    """
    Test whether certain `FTPHost` methods accept either unicode


@@ 880,7 935,15 @@ class TestAcceptEitherUnicodeOrBytes:
            # don't see an `mkd` calls here despite originally having a
            # `makedirs` call.
            Call("cwd", args=("/ä",)),
            Call("cwd", args=("/ä/ö",)),
            # If `exist_ok` is `False` (which is the default), the leaf
            # directory to make must not exist. In other words, the `chdir`
            # call is `makedirs` must fail with a permanent error.
            Call("cwd", args=("/ä/ö",), result=ftplib.error_perm),
            Call("cwd", args=("/ä",)),
            Call("cwd", args=("/ä",)),
            Call("mkd", args=("ö",)),
            # From `isdir` call
            Call("cwd", args=("/ä",)),
            Call("cwd", args=("/",)),
            Call("close"),
        ]

M test/test_real_ftp.py => test/test_real_ftp.py +2 -2
@@ 206,7 206,7 @@ class TestMkdir(RealFTPTest):
    def test_makedirs_of_existing_directory(self):
        host = self.host
        # The (chrooted) login directory
        host.makedirs("/")
        host.makedirs("/", exist_ok=True)

    def test_makedirs_with_file_in_the_way(self):
        host = self.host


@@ 239,7 239,7 @@ class TestMkdir(RealFTPTest):
        self.cleaner.add_dir("rootdir2/dir2")
        # Preparation: `rootdir2` exists but is only writable by root. `dir2`
        # is writable by regular ftp users. Both directories below should work.
        host.makedirs("rootdir2/dir2")
        host.makedirs("rootdir2/dir2", exist_ok=True)
        host.makedirs("rootdir2/dir2/dir3")