~sschwarzer/ftputil

aa30554c49ad6719fed1b6f5bcd690690f8fd37c — Stefan Schwarzer 6 years ago 55ad515
Add `rest` argument to `FTPHost.open`

Allow a `rest` argument in `FTPHost.open`. Pass this on to
`FTPFile._open` and from there to `self._session.transfercmd`.

So far there's no error handling.
3 files changed, 44 insertions(+), 4 deletions(-)

M ftputil/file.py
M ftputil/host.py
M test/test_real_ftp.py
M ftputil/file.py => ftputil/file.py +2 -2
@@ 41,7 41,7 @@ class FTPFile(object):
        self._fobj = None

    def _open(self, path, mode, buffering=None, encoding=None, errors=None,
              newline=None):
              newline=None, rest=None):
        """
        Open the remote file with given path name and mode.



@@ 89,7 89,7 @@ class FTPFile(object):
            makefile_mode += "b"
        # Get connection and file object.
        with ftputil.error.ftplib_error_to_ftp_io_error:
            self._conn = self._session.transfercmd(command)
            self._conn = self._session.transfercmd(command, rest)
        # The file object. Under Python 3, this will already be a
        # `BufferedReader` or `BufferedWriter` object.
        fobj = self._conn.makefile(makefile_mode)

M ftputil/host.py => ftputil/host.py +10 -2
@@ 174,11 174,18 @@ class FTPHost(object):
        return None

    def open(self, path, mode="r", buffering=None, encoding=None, errors=None,
             newline=None):
             newline=None, rest=None):
        """
        Return an open file(-like) object which is associated with
        this `FTPHost` object.

        The arguments `path`, `mode`, `buffering`, `encoding`, `errors`
        and `newlines` have the same meaning as for `io.open`. If `rest`
        is given as an integer,

        - reading will start at the byte (zero-based) `rest`
        - writing will overwrite the remote file from byte `rest`

        This method tries to reuse a child but will generate a new one
        if none is available.
        """


@@ 208,7 215,8 @@ class FTPHost(object):
                    "exist or has insufficient access rights".
                    format(effective_dir))
        host._file._open(effective_file, mode=mode, buffering=buffering,
                         encoding=encoding, errors=errors, newline=newline)
                         encoding=encoding, errors=errors, newline=newline,
                         rest=rest)
        if "w" in mode:
            # Invalidate cache entry because size and timestamps will change.
            self.stat_cache.invalidate(effective_path)

M test/test_real_ftp.py => test/test_real_ftp.py +32 -0
@@ 771,6 771,38 @@ class TestChmod(RealFTPTest):
        self.assert_mode(file_name, 0o646)


class TestRestArgument(RealFTPTest):

    TEST_FILE_NAME = "rest_test"

    def setUp(self):
        super(TestRestArgument, self).setUp()
        # Write test file.
        with self.host.open(self.TEST_FILE_NAME, "wb") as fobj:
            fobj.write(b"abcdefghijkl")
        self.cleaner.add_file(self.TEST_FILE_NAME)

    def test_for_reading(self):
        """
        If a `rest` argument is passed to `open`, the following read
        operation should start at the byte given by `rest`.
        """
        with self.host.open(self.TEST_FILE_NAME, "rb", rest=3) as fobj:
            data = fobj.read()
        self.assertEqual(data, b"defghijkl")

    def test_for_writing(self):
        """
        If a `rest` argument is passed to `open`, the following write
        operation should start writing at the byte given by `rest`.
        """
        with self.host.open(self.TEST_FILE_NAME, "wb", rest=3) as fobj:
            fobj.write(b"123")
        with self.host.open(self.TEST_FILE_NAME, "rb") as fobj:
            data = fobj.read()
        self.assertEqual(data, b"abc123")


class TestOther(RealFTPTest):

    def test_open_for_reading(self):