diff options
Diffstat (limited to 'paramiko/sftp_client.py')
-rw-r--r-- | paramiko/sftp_client.py | 533 |
1 files changed, 247 insertions, 286 deletions
diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py index cf94582..1caaf16 100644 --- a/paramiko/sftp_client.py +++ b/paramiko/sftp_client.py @@ -1,6 +1,6 @@ # Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com> # -# This file is part of paramiko. +# This file is part of Paramiko. # # Paramiko is free software; you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free @@ -16,9 +16,6 @@ # along with Paramiko; if not, write to the Free Software Foundation, Inc., # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. -""" -Client-mode SFTP support. -""" from binascii import hexlify import errno @@ -27,8 +24,18 @@ import stat import threading import time import weakref +from paramiko import util +from paramiko.channel import Channel +from paramiko.message import Message +from paramiko.common import INFO, DEBUG, o777 +from paramiko.py3compat import bytestring, b, u, long, string_types, bytes_types +from paramiko.sftp import BaseSFTP, CMD_OPENDIR, CMD_HANDLE, SFTPError, CMD_READDIR, \ + CMD_NAME, CMD_CLOSE, SFTP_FLAG_READ, SFTP_FLAG_WRITE, SFTP_FLAG_CREATE, \ + SFTP_FLAG_TRUNC, SFTP_FLAG_APPEND, SFTP_FLAG_EXCL, CMD_OPEN, CMD_REMOVE, \ + CMD_RENAME, CMD_MKDIR, CMD_RMDIR, CMD_STAT, CMD_ATTRS, CMD_LSTAT, \ + CMD_SYMLINK, CMD_SETSTAT, CMD_READLINK, CMD_REALPATH, CMD_STATUS, SFTP_OK, \ + SFTP_EOF, SFTP_NO_SUCH_FILE, SFTP_PERMISSION_DENIED -from paramiko.sftp import * from paramiko.sftp_attr import SFTPAttributes from paramiko.ssh_exception import SSHException from paramiko.sftp_file import SFTPFile @@ -42,31 +49,33 @@ def _to_unicode(s): """ try: return s.encode('ascii') - except UnicodeError: + except (UnicodeError, AttributeError): try: return s.decode('utf-8') except UnicodeError: return s +b_slash = b'/' -class SFTPClient (BaseSFTP): + +class SFTPClient(BaseSFTP): """ - SFTP client object. C{SFTPClient} is used to open an sftp session across - an open ssh L{Transport} and do remote file operations. + SFTP client object. + + Used to open an SFTP session across an open SSH `.Transport` and perform + remote file operations. """ - def __init__(self, sock): """ - Create an SFTP client from an existing L{Channel}. The channel - should already have requested the C{"sftp"} subsystem. + Create an SFTP client from an existing `.Channel`. The channel + should already have requested the ``"sftp"`` subsystem. An alternate way to create an SFTP client context is by using - L{from_transport}. + `from_transport`. - @param sock: an open L{Channel} using the C{"sftp"} subsystem - @type sock: L{Channel} + :param .Channel sock: an open `.Channel` using the ``"sftp"`` subsystem - @raise SSHException: if there's an exception while negotiating + :raises SSHException: if there's an exception while negotiating sftp """ BaseSFTP.__init__(self) @@ -85,19 +94,18 @@ class SFTPClient (BaseSFTP): self.ultra_debug = transport.get_hexdump() try: server_version = self._send_version() - except EOFError, x: + except EOFError: raise SSHException('EOF during negotiation') self._log(INFO, 'Opened sftp connection (server version %d)' % server_version) def from_transport(cls, t): """ - Create an SFTP client channel from an open L{Transport}. + Create an SFTP client channel from an open `.Transport`. - @param t: an open L{Transport} which is already authenticated - @type t: L{Transport} - @return: a new L{SFTPClient} object, referring to an sftp session - (channel) across the transport - @rtype: L{SFTPClient} + :param .Transport t: an open `.Transport` which is already authenticated + :return: + a new `.SFTPClient` object, referring to an sftp session (channel) + across the transport """ chan = t.open_session() if chan is None: @@ -109,84 +117,79 @@ class SFTPClient (BaseSFTP): def _log(self, level, msg, *args): if isinstance(msg, list): for m in msg: - super(SFTPClient, self)._log(level, "[chan %s] " + m, *([ self.sock.get_name() ] + list(args))) + self._log(level, m, *args) else: - super(SFTPClient, self)._log(level, "[chan %s] " + msg, *([ self.sock.get_name() ] + list(args))) + # escape '%' in msg (they could come from file or directory names) before logging + msg = msg.replace('%','%%') + super(SFTPClient, self)._log(level, "[chan %s] " + msg, *([self.sock.get_name()] + list(args))) def close(self): """ Close the SFTP session and its underlying channel. - @since: 1.4 + .. versionadded:: 1.4 """ self._log(INFO, 'sftp session closed.') self.sock.close() def get_channel(self): """ - Return the underlying L{Channel} object for this SFTP session. This + Return the underlying `.Channel` object for this SFTP session. This might be useful for doing things like setting a timeout on the channel. - @return: the SSH channel - @rtype: L{Channel} - - @since: 1.7.1 + .. versionadded:: 1.7.1 """ return self.sock def listdir(self, path='.'): """ - Return a list containing the names of the entries in the given C{path}. + Return a list containing the names of the entries in the given ``path``. + The list is in arbitrary order. It does not include the special - entries C{'.'} and C{'..'} even if they are present in the folder. - This method is meant to mirror C{os.listdir} as closely as possible. - For a list of full L{SFTPAttributes} objects, see L{listdir_attr}. + entries ``'.'`` and ``'..'`` even if they are present in the folder. + This method is meant to mirror ``os.listdir`` as closely as possible. + For a list of full `.SFTPAttributes` objects, see `listdir_attr`. - @param path: path to list (defaults to C{'.'}) - @type path: str - @return: list of filenames - @rtype: list of str + :param str path: path to list (defaults to ``'.'``) """ return [f.filename for f in self.listdir_attr(path)] def listdir_attr(self, path='.'): """ - Return a list containing L{SFTPAttributes} objects corresponding to - files in the given C{path}. The list is in arbitrary order. It does - not include the special entries C{'.'} and C{'..'} even if they are + Return a list containing `.SFTPAttributes` objects corresponding to + files in the given ``path``. The list is in arbitrary order. It does + not include the special entries ``'.'`` and ``'..'`` even if they are present in the folder. - The returned L{SFTPAttributes} objects will each have an additional - field: C{longname}, which may contain a formatted string of the file's + The returned `.SFTPAttributes` objects will each have an additional + field: ``longname``, which may contain a formatted string of the file's attributes, in unix format. The content of this string will probably depend on the SFTP server implementation. - @param path: path to list (defaults to C{'.'}) - @type path: str - @return: list of attributes - @rtype: list of L{SFTPAttributes} + :param str path: path to list (defaults to ``'.'``) + :return: list of `.SFTPAttributes` objects - @since: 1.2 + .. versionadded:: 1.2 """ path = self._adjust_cwd(path) self._log(DEBUG, 'listdir(%r)' % path) t, msg = self._request(CMD_OPENDIR, path) if t != CMD_HANDLE: raise SFTPError('Expected handle') - handle = msg.get_string() + handle = msg.get_binary() filelist = [] while True: try: t, msg = self._request(CMD_READDIR, handle) - except EOFError, e: + except EOFError: # done with handle break if t != CMD_NAME: raise SFTPError('Expected name response') count = msg.get_int() for i in range(count): - filename = _to_unicode(msg.get_string()) - longname = _to_unicode(msg.get_string()) + filename = msg.get_text() + longname = msg.get_text() attr = SFTPAttributes._from_msg(msg, filename, longname) if (filename != '.') and (filename != '..'): filelist.append(attr) @@ -196,37 +199,34 @@ class SFTPClient (BaseSFTP): def open(self, filename, mode='r', bufsize=-1): """ Open a file on the remote server. The arguments are the same as for - python's built-in C{file} (aka C{open}). A file-like object is - returned, which closely mimics the behavior of a normal python file - object, including the ability to be used as a context manager. - - The mode indicates how the file is to be opened: C{'r'} for reading, - C{'w'} for writing (truncating an existing file), C{'a'} for appending, - C{'r+'} for reading/writing, C{'w+'} for reading/writing (truncating an - existing file), C{'a+'} for reading/appending. The python C{'b'} flag - is ignored, since SSH treats all files as binary. The C{'U'} flag is - supported in a compatible way. - - Since 1.5.2, an C{'x'} flag indicates that the operation should only + Python's built-in `python:file` (aka `python:open`). A file-like + object is returned, which closely mimics the behavior of a normal + Python file object, including the ability to be used as a context + manager. + + The mode indicates how the file is to be opened: ``'r'`` for reading, + ``'w'`` for writing (truncating an existing file), ``'a'`` for + appending, ``'r+'`` for reading/writing, ``'w+'`` for reading/writing + (truncating an existing file), ``'a+'`` for reading/appending. The + Python ``'b'`` flag is ignored, since SSH treats all files as binary. + The ``'U'`` flag is supported in a compatible way. + + Since 1.5.2, an ``'x'`` flag indicates that the operation should only succeed if the file was created and did not previously exist. This has - no direct mapping to python's file flags, but is commonly known as the - C{O_EXCL} flag in posix. + no direct mapping to Python's file flags, but is commonly known as the + ``O_EXCL`` flag in posix. - The file will be buffered in standard python style by default, but - can be altered with the C{bufsize} parameter. C{0} turns off - buffering, C{1} uses line buffering, and any number greater than 1 - (C{>1}) uses that specific buffer size. + The file will be buffered in standard Python style by default, but + can be altered with the ``bufsize`` parameter. ``0`` turns off + buffering, ``1`` uses line buffering, and any number greater than 1 + (``>1``) uses that specific buffer size. - @param filename: name of the file to open - @type filename: str - @param mode: mode (python-style) to open in - @type mode: str - @param bufsize: desired buffering (-1 = default buffer size) - @type bufsize: int - @return: a file object representing the open file - @rtype: SFTPFile + :param str filename: name of the file to open + :param str mode: mode (Python-style) to open in + :param int bufsize: desired buffering (-1 = default buffer size) + :return: an `.SFTPFile` object representing the open file - @raise IOError: if the file could not be opened. + :raises IOError: if the file could not be opened. """ filename = self._adjust_cwd(filename) self._log(DEBUG, 'open(%r, %r)' % (filename, mode)) @@ -235,32 +235,31 @@ class SFTPClient (BaseSFTP): imode |= SFTP_FLAG_READ if ('w' in mode) or ('+' in mode) or ('a' in mode): imode |= SFTP_FLAG_WRITE - if ('w' in mode): + if 'w' in mode: imode |= SFTP_FLAG_CREATE | SFTP_FLAG_TRUNC - if ('a' in mode): + if 'a' in mode: imode |= SFTP_FLAG_CREATE | SFTP_FLAG_APPEND - if ('x' in mode): + if 'x' in mode: imode |= SFTP_FLAG_CREATE | SFTP_FLAG_EXCL attrblock = SFTPAttributes() t, msg = self._request(CMD_OPEN, filename, imode, attrblock) if t != CMD_HANDLE: raise SFTPError('Expected handle') - handle = msg.get_string() + handle = msg.get_binary() self._log(DEBUG, 'open(%r, %r) -> %s' % (filename, mode, hexlify(handle))) return SFTPFile(self, handle, mode, bufsize) - # python continues to vacillate about "open" vs "file"... + # Python continues to vacillate about "open" vs "file"... file = open def remove(self, path): """ Remove the file at the given path. This only works on files; for - removing folders (directories), use L{rmdir}. + removing folders (directories), use `rmdir`. - @param path: path (absolute or relative) of the file to remove - @type path: str + :param str path: path (absolute or relative) of the file to remove - @raise IOError: if the path refers to a folder (directory) + :raises IOError: if the path refers to a folder (directory) """ path = self._adjust_cwd(path) self._log(DEBUG, 'remove(%r)' % path) @@ -270,14 +269,12 @@ class SFTPClient (BaseSFTP): def rename(self, oldpath, newpath): """ - Rename a file or folder from C{oldpath} to C{newpath}. + Rename a file or folder from ``oldpath`` to ``newpath``. - @param oldpath: existing name of the file or folder - @type oldpath: str - @param newpath: new name for the file or folder - @type newpath: str + :param str oldpath: existing name of the file or folder + :param str newpath: new name for the file or folder - @raise IOError: if C{newpath} is a folder, or something else goes + :raises IOError: if ``newpath`` is a folder, or something else goes wrong """ oldpath = self._adjust_cwd(oldpath) @@ -285,16 +282,14 @@ class SFTPClient (BaseSFTP): self._log(DEBUG, 'rename(%r, %r)' % (oldpath, newpath)) self._request(CMD_RENAME, oldpath, newpath) - def mkdir(self, path, mode=0777): + def mkdir(self, path, mode=o777): """ - Create a folder (directory) named C{path} with numeric mode C{mode}. + Create a folder (directory) named ``path`` with numeric mode ``mode``. The default mode is 0777 (octal). On some systems, mode is ignored. Where it is used, the current umask value is first masked out. - @param path: name of the folder to create - @type path: str - @param mode: permissions (posix-style) for the newly-created folder - @type mode: int + :param str path: name of the folder to create + :param int mode: permissions (posix-style) for the newly-created folder """ path = self._adjust_cwd(path) self._log(DEBUG, 'mkdir(%r, %r)' % (path, mode)) @@ -304,10 +299,9 @@ class SFTPClient (BaseSFTP): def rmdir(self, path): """ - Remove the folder named C{path}. + Remove the folder named ``path``. - @param path: name of the folder to remove - @type path: str + :param str path: name of the folder to remove """ path = self._adjust_cwd(path) self._log(DEBUG, 'rmdir(%r)' % path) @@ -317,20 +311,20 @@ class SFTPClient (BaseSFTP): """ Retrieve information about a file on the remote system. The return value is an object whose attributes correspond to the attributes of - python's C{stat} structure as returned by C{os.stat}, except that it + Python's ``stat`` structure as returned by ``os.stat``, except that it contains fewer fields. An SFTP server may return as much or as little info as it wants, so the results may vary from server to server. - Unlike a python C{stat} object, the result may not be accessed as a - tuple. This is mostly due to the author's slack factor. + Unlike a Python `python:stat` object, the result may not be accessed as + a tuple. This is mostly due to the author's slack factor. - The fields supported are: C{st_mode}, C{st_size}, C{st_uid}, C{st_gid}, - C{st_atime}, and C{st_mtime}. + The fields supported are: ``st_mode``, ``st_size``, ``st_uid``, + ``st_gid``, ``st_atime``, and ``st_mtime``. - @param path: the filename to stat - @type path: str - @return: an object containing attributes about the given file - @rtype: SFTPAttributes + :param str path: the filename to stat + :return: + an `.SFTPAttributes` object containing attributes about the given + file """ path = self._adjust_cwd(path) self._log(DEBUG, 'stat(%r)' % path) @@ -343,12 +337,12 @@ class SFTPClient (BaseSFTP): """ Retrieve information about a file on the remote system, without following symbolic links (shortcuts). This otherwise behaves exactly - the same as L{stat}. + the same as `stat`. - @param path: the filename to stat - @type path: str - @return: an object containing attributes about the given file - @rtype: SFTPAttributes + :param str path: the filename to stat + :return: + an `.SFTPAttributes` object containing attributes about the given + file """ path = self._adjust_cwd(path) self._log(DEBUG, 'lstat(%r)' % path) @@ -359,30 +353,25 @@ class SFTPClient (BaseSFTP): def symlink(self, source, dest): """ - Create a symbolic link (shortcut) of the C{source} path at - C{destination}. + Create a symbolic link (shortcut) of the ``source`` path at + ``destination``. - @param source: path of the original file - @type source: str - @param dest: path of the newly created symlink - @type dest: str + :param str source: path of the original file + :param str dest: path of the newly created symlink """ dest = self._adjust_cwd(dest) self._log(DEBUG, 'symlink(%r, %r)' % (source, dest)) - if type(source) is unicode: - source = source.encode('utf-8') + source = bytestring(source) self._request(CMD_SYMLINK, source, dest) def chmod(self, path, mode): """ Change the mode (permissions) of a file. The permissions are - unix-style and identical to those used by python's C{os.chmod} + unix-style and identical to those used by Python's `os.chmod` function. - @param path: path of the file to change the permissions of - @type path: str - @param mode: new permissions - @type mode: int + :param str path: path of the file to change the permissions of + :param int mode: new permissions """ path = self._adjust_cwd(path) self._log(DEBUG, 'chmod(%r, %r)' % (path, mode)) @@ -392,17 +381,14 @@ class SFTPClient (BaseSFTP): def chown(self, path, uid, gid): """ - Change the owner (C{uid}) and group (C{gid}) of a file. As with - python's C{os.chown} function, you must pass both arguments, so if you - only want to change one, use L{stat} first to retrieve the current + Change the owner (``uid``) and group (``gid``) of a file. As with + Python's `os.chown` function, you must pass both arguments, so if you + only want to change one, use `stat` first to retrieve the current owner and group. - @param path: path of the file to change the owner and group of - @type path: str - @param uid: new owner's uid - @type uid: int - @param gid: new group id - @type gid: int + :param str path: path of the file to change the owner and group of + :param int uid: new owner's uid + :param int gid: new group id """ path = self._adjust_cwd(path) self._log(DEBUG, 'chown(%r, %r, %r)' % (path, uid, gid)) @@ -412,18 +398,17 @@ class SFTPClient (BaseSFTP): def utime(self, path, times): """ - Set the access and modified times of the file specified by C{path}. If - C{times} is C{None}, then the file's access and modified times are set - to the current time. Otherwise, C{times} must be a 2-tuple of numbers, - of the form C{(atime, mtime)}, which is used to set the access and - modified times, respectively. This bizarre API is mimicked from python + Set the access and modified times of the file specified by ``path``. If + ``times`` is ``None``, then the file's access and modified times are set + to the current time. Otherwise, ``times`` must be a 2-tuple of numbers, + of the form ``(atime, mtime)``, which is used to set the access and + modified times, respectively. This bizarre API is mimicked from Python for the sake of consistency -- I apologize. - @param path: path of the file to modify - @type path: str - @param times: C{None} or a tuple of (access time, modified time) in - standard internet epoch time (seconds since 01 January 1970 GMT) - @type times: tuple(int) + :param str path: path of the file to modify + :param tuple times: + ``None`` or a tuple of (access time, modified time) in standard + internet epoch time (seconds since 01 January 1970 GMT) """ path = self._adjust_cwd(path) if times is None: @@ -435,14 +420,13 @@ class SFTPClient (BaseSFTP): def truncate(self, path, size): """ - Change the size of the file specified by C{path}. This usually extends - or shrinks the size of the file, just like the C{truncate()} method on - python file objects. + Change the size of the file specified by ``path``. This usually + extends or shrinks the size of the file, just like the `~file.truncate` + method on Python file objects. - @param path: path of the file to modify - @type path: str - @param size: the new size of the file - @type size: int or long + :param str path: path of the file to modify + :param size: the new size of the file + :type size: int or long """ path = self._adjust_cwd(path) self._log(DEBUG, 'truncate(%r, %r)' % (path, size)) @@ -453,13 +437,11 @@ class SFTPClient (BaseSFTP): def readlink(self, path): """ Return the target of a symbolic link (shortcut). You can use - L{symlink} to create these. The result may be either an absolute or + `symlink` to create these. The result may be either an absolute or relative pathname. - @param path: path of the symbolic link file - @type path: str - @return: target path - @rtype: str + :param str path: path of the symbolic link file + :return: target path, as a `str` """ path = self._adjust_cwd(path) self._log(DEBUG, 'readlink(%r)' % path) @@ -477,15 +459,13 @@ class SFTPClient (BaseSFTP): """ Return the normalized path (on the server) of a given path. This can be used to quickly resolve symbolic links or determine what the - server is considering to be the "current folder" (by passing C{'.'} - as C{path}). + server is considering to be the "current folder" (by passing ``'.'`` + as ``path``). - @param path: path to be normalized - @type path: str - @return: normalized form of the given path - @rtype: str + :param str path: path to be normalized + :return: normalized form of the given path (as a `str`) - @raise IOError: if the path can't be resolved on the server + :raises IOError: if the path can't be resolved on the server """ path = self._adjust_cwd(path) self._log(DEBUG, 'normalize(%r)' % path) @@ -495,76 +475,72 @@ class SFTPClient (BaseSFTP): count = msg.get_int() if count != 1: raise SFTPError('Realpath returned %d results' % count) - return _to_unicode(msg.get_string()) + return msg.get_text() - def chdir(self, path): + def chdir(self, path=None): """ Change the "current directory" of this SFTP session. Since SFTP - doesn't really have the concept of a current working directory, this - is emulated by paramiko. Once you use this method to set a working - directory, all operations on this SFTPClient object will be relative - to that path. You can pass in C{None} to stop using a current working + doesn't really have the concept of a current working directory, this is + emulated by Paramiko. Once you use this method to set a working + directory, all operations on this `.SFTPClient` object will be relative + to that path. You can pass in ``None`` to stop using a current working directory. - @param path: new current working directory - @type path: str + :param str path: new current working directory - @raise IOError: if the requested path doesn't exist on the server + :raises IOError: if the requested path doesn't exist on the server - @since: 1.4 + .. versionadded:: 1.4 """ if path is None: self._cwd = None return if not stat.S_ISDIR(self.stat(path).st_mode): raise SFTPError(errno.ENOTDIR, "%s: %s" % (os.strerror(errno.ENOTDIR), path)) - self._cwd = self.normalize(path).encode('utf-8') + self._cwd = b(self.normalize(path)) def getcwd(self): """ Return the "current working directory" for this SFTP session, as - emulated by paramiko. If no directory has been set with L{chdir}, - this method will return C{None}. - - @return: the current working directory on the server, or C{None} - @rtype: str + emulated by Paramiko. If no directory has been set with `chdir`, + this method will return ``None``. - @since: 1.4 + .. versionadded:: 1.4 """ - return self._cwd + return self._cwd and u(self._cwd) def putfo(self, fl, remotepath, file_size=0, callback=None, confirm=True): """ - Copy the contents of an open file object (C{fl}) to the SFTP server as - C{remotepath}. Any exception raised by operations will be passed through. + Copy the contents of an open file object (``fl``) to the SFTP server as + ``remotepath``. Any exception raised by operations will be passed + through. The SFTP operations use pipelining for speed. - @param fl: opened file or file-like object to copy - @type localpath: object - @param remotepath: the destination path on the SFTP server - @type remotepath: str - @param file_size: optional size parameter passed to callback. If none is - specified, size defaults to 0 - @type file_size: int - @param callback: optional callback function that accepts the bytes - transferred so far and the total bytes to be transferred + :param file fl: opened file or file-like object to copy + :param str remotepath: the destination path on the SFTP server + :param int file_size: + optional size parameter passed to callback. If none is specified, + size defaults to 0 + :param callable callback: + optional callback function (form: ``func(int, int)``) that accepts + the bytes transferred so far and the total bytes to be transferred (since 1.7.4) - @type callback: function(int, int) - @param confirm: whether to do a stat() on the file afterwards to - confirm the file size (since 1.7.7) - @type confirm: bool + :param bool confirm: + whether to do a stat() on the file afterwards to confirm the file + size (since 1.7.7) - @return: an object containing attributes about the given file - (since 1.7.4) - @rtype: SFTPAttributes + :return: + an `.SFTPAttributes` object containing attributes about the given + file. - @since: 1.4 + .. versionadded:: 1.4 + .. versionchanged:: 1.7.4 + Began returning rich attribute objects. """ - fr = self.file(remotepath, 'wb') - fr.set_pipelined(True) - size = 0 - try: + with self.file(remotepath, 'wb') as fr: + fr.set_pipelined(True) + size = 0 while True: data = fl.read(32768) fr.write(data) @@ -573,8 +549,6 @@ class SFTPClient (BaseSFTP): callback(size, file_size) if len(data) == 0: break - finally: - fr.close() if confirm: s = self.stat(remotepath) if s.st_size != size: @@ -585,61 +559,55 @@ class SFTPClient (BaseSFTP): def put(self, localpath, remotepath, callback=None, confirm=True): """ - Copy a local file (C{localpath}) to the SFTP server as C{remotepath}. + Copy a local file (``localpath``) to the SFTP server as ``remotepath``. Any exception raised by operations will be passed through. This method is primarily provided as a convenience. The SFTP operations use pipelining for speed. - @param localpath: the local file to copy - @type localpath: str - @param remotepath: the destination path on the SFTP server - @type remotepath: str - @param callback: optional callback function that accepts the bytes - transferred so far and the total bytes to be transferred - (since 1.7.4) - @type callback: function(int, int) - @param confirm: whether to do a stat() on the file afterwards to - confirm the file size (since 1.7.7) - @type confirm: bool + :param str localpath: the local file to copy + :param str remotepath: the destination path on the SFTP server + :param callable callback: + optional callback function (form: ``func(int, int)``) that accepts + the bytes transferred so far and the total bytes to be transferred + :param bool confirm: + whether to do a stat() on the file afterwards to confirm the file + size - @return: an object containing attributes about the given file - (since 1.7.4) - @rtype: SFTPAttributes + :return: an `.SFTPAttributes` object containing attributes about the given file - @since: 1.4 + .. versionadded:: 1.4 + .. versionchanged:: 1.7.4 + ``callback`` and rich attribute return value added. + .. versionchanged:: 1.7.7 + ``confirm`` param added. """ file_size = os.stat(localpath).st_size - fl = file(localpath, 'rb') - try: + with open(localpath, 'rb') as fl: return self.putfo(fl, remotepath, os.stat(localpath).st_size, callback, confirm) - finally: - fl.close() def getfo(self, remotepath, fl, callback=None): """ - Copy a remote file (C{remotepath}) from the SFTP server and write to - an open file or file-like object, C{fl}. Any exception raised by + Copy a remote file (``remotepath``) from the SFTP server and write to + an open file or file-like object, ``fl``. Any exception raised by operations will be passed through. This method is primarily provided as a convenience. - @param remotepath: opened file or file-like object to copy to - @type remotepath: object - @param fl: the destination path on the local host or open file - object - @type localpath: str - @param callback: optional callback function that accepts the bytes - transferred so far and the total bytes to be transferred - (since 1.7.4) - @type callback: function(int, int) - @return: the number of bytes written to the opened file object - - @since: 1.4 - """ - fr = self.file(remotepath, 'rb') - file_size = self.stat(remotepath).st_size - fr.prefetch() - try: + :param object remotepath: opened file or file-like object to copy to + :param str fl: + the destination path on the local host or open file object + :param callable callback: + optional callback function (form: ``func(int, int)``) that accepts + the bytes transferred so far and the total bytes to be transferred + :return: the `number <int>` of bytes written to the opened file object + + .. versionadded:: 1.4 + .. versionchanged:: 1.7.4 + Added the ``callable`` param. + """ + with self.open(remotepath, 'rb') as fr: + file_size = self.stat(remotepath).st_size + fr.prefetch() size = 0 while True: data = fr.read(32768) @@ -649,41 +617,33 @@ class SFTPClient (BaseSFTP): callback(size, file_size) if len(data) == 0: break - finally: - fr.close() return size def get(self, remotepath, localpath, callback=None): """ - Copy a remote file (C{remotepath}) from the SFTP server to the local - host as C{localpath}. Any exception raised by operations will be + Copy a remote file (``remotepath``) from the SFTP server to the local + host as ``localpath``. Any exception raised by operations will be passed through. This method is primarily provided as a convenience. - @param remotepath: the remote file to copy - @type remotepath: str - @param localpath: the destination path on the local host - @type localpath: str - @param callback: optional callback function that accepts the bytes - transferred so far and the total bytes to be transferred - (since 1.7.4) - @type callback: function(int, int) + :param str remotepath: the remote file to copy + :param str localpath: the destination path on the local host + :param callable callback: + optional callback function (form: ``func(int, int)``) that accepts + the bytes transferred so far and the total bytes to be transferred - @since: 1.4 + .. versionadded:: 1.4 + .. versionchanged:: 1.7.4 + Added the ``callback`` param """ file_size = self.stat(remotepath).st_size - fl = file(localpath, 'wb') - try: + with open(localpath, 'wb') as fl: size = self.getfo(remotepath, fl, callback) - finally: - fl.close() s = os.stat(localpath) if s.st_size != size: raise IOError('size mismatch in get! %d != %d' % (s.st_size, size)) - ### internals... - def _request(self, t, *arg): num = self._async_request(type(None), t, *arg) return self._read_response(num) @@ -695,11 +655,11 @@ class SFTPClient (BaseSFTP): msg = Message() msg.add_int(self.request_number) for item in arg: - if isinstance(item, int): - msg.add_int(item) - elif isinstance(item, long): + if isinstance(item, long): msg.add_int64(item) - elif isinstance(item, str): + elif isinstance(item, int): + msg.add_int(item) + elif isinstance(item, (string_types, bytes_types)): msg.add_string(item) elif isinstance(item, SFTPAttributes): item._pack(msg) @@ -707,7 +667,7 @@ class SFTPClient (BaseSFTP): raise Exception('unknown type for %r type %r' % (item, type(item))) num = self.request_number self._expecting[num] = fileobj - self._send_packet(t, str(msg)) + self._send_packet(t, msg) self.request_number += 1 finally: self._lock.release() @@ -717,8 +677,8 @@ class SFTPClient (BaseSFTP): while True: try: t, data = self._read_packet() - except EOFError, e: - raise SSHException('Server connection dropped: %s' % (str(e),)) + except EOFError as e: + raise SSHException('Server connection dropped: %s' % str(e)) msg = Message(data) num = msg.get_int() if num not in self._expecting: @@ -740,7 +700,7 @@ class SFTPClient (BaseSFTP): if waitfor is None: # just doing a single check break - return (None, None) + return None, None def _finish_responses(self, fileobj): while fileobj in self._expecting.values(): @@ -752,7 +712,7 @@ class SFTPClient (BaseSFTP): Raises EOFError or IOError on error status; otherwise does nothing. """ code = msg.get_int() - text = msg.get_string() + text = msg.get_text() if code == SFTP_OK: return elif code == SFTP_EOF: @@ -770,18 +730,19 @@ class SFTPClient (BaseSFTP): Return an adjusted path if we're emulating a "current working directory" for the server. """ - if type(path) is unicode: - path = path.encode('utf-8') + path = b(path) if self._cwd is None: return path - if (len(path) > 0) and (path[0] == '/'): + if len(path) and path[0:1] == b_slash: # absolute path return path - if self._cwd == '/': + if self._cwd == b_slash: return self._cwd + path - return self._cwd + '/' + path + return self._cwd + b_slash + path -class SFTP (SFTPClient): - "an alias for L{SFTPClient} for backwards compatability" +class SFTP(SFTPClient): + """ + An alias for `.SFTPClient` for backwards compatability. + """ pass |