diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | .travis.yml | 4 | ||||
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | NEWS | 15 | ||||
-rw-r--r-- | README | 2 | ||||
-rw-r--r-- | paramiko/__init__.py | 2 | ||||
-rw-r--r-- | paramiko/client.py | 8 | ||||
-rw-r--r-- | paramiko/config.py | 2 | ||||
-rw-r--r-- | paramiko/file.py | 4 | ||||
-rw-r--r-- | paramiko/sftp_client.py | 128 | ||||
-rw-r--r-- | setup.py | 2 |
11 files changed, 122 insertions, 48 deletions
@@ -4,3 +4,4 @@ dist/ .tox/ paramiko.egg-info/ test.log +docs/ diff --git a/.travis.yml b/.travis.yml index 90dbb80f..6896b897 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,9 +8,7 @@ install: - pip install -e . script: python test.py notifications: - email: - on_failure: change irc: - channels: "irc.freenode.org#fabric" + channels: "irc.freenode.org#paramiko" on_success: change on_failure: change @@ -1,7 +1,7 @@ release: docs python setup.py sdist register upload -docs: +docs: paramiko/* epydoc --no-private -o docs/ paramiko clean: @@ -12,6 +12,21 @@ Issues noted as "Fabric #NN" can be found at https://github.com/fabric/fabric/. Releases ======== +v1.10.0 (DD MM YYYY) +-------------------- + +* #71: Add `SFTPClient.putfo` and `.getfo` methods to allow direct + uploading/downloading of file-like objects. Thanks to Eric Buehl for the + patch. +* #113: Add `timeout` parameter to `SSHClient.exec_command` for easier setting + of the command's internal channel object's timeout. Thanks to Cernov Vladimir + for the patch. +* #94: Remove duplication of SSH port constant. Thanks to Olle Lundberg for the + catch. +* #80: Expose the internal "is closed" property of the file transfer class + `BufferedFile` as `.closed`, better conforming to Python's file interface. + Thanks to `@smunaut` and James Hiscock for catch & patch. + v1.9.0 (6th Nov 2012) --------------------- @@ -5,7 +5,7 @@ paramiko :Paramiko: Python SSH module :Copyright: Copyright (c) 2003-2009 Robey Pointer <robeypointer@gmail.com> -:Copyright: Copyright (c) 2012 Jeff Forcier <jeff@bitprophet.org> +:Copyright: Copyright (c) 2013 Jeff Forcier <jeff@bitprophet.org> :License: LGPL :Homepage: https://github.com/paramiko/paramiko/ diff --git a/paramiko/__init__.py b/paramiko/__init__.py index 29e470a6..7d7dcbf4 100644 --- a/paramiko/__init__.py +++ b/paramiko/__init__.py @@ -55,7 +55,7 @@ if sys.version_info < (2, 2): __author__ = "Jeff Forcier <jeff@bitprophet.org>" -__version__ = "1.9.0" +__version__ = "1.10.0" __license__ = "GNU Lesser General Public License (LGPL)" diff --git a/paramiko/client.py b/paramiko/client.py index 07560a39..a777b45b 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -28,6 +28,7 @@ import warnings from paramiko.agent import Agent from paramiko.common import * +from paramiko.config import SSH_PORT from paramiko.dsskey import DSSKey from paramiko.hostkeys import HostKeys from paramiko.resource import ResourceManager @@ -37,8 +38,6 @@ from paramiko.transport import Transport from paramiko.util import retry_on_signal -SSH_PORT = 22 - class MissingHostKeyPolicy (object): """ Interface for defining the policy that L{SSHClient} should use when the @@ -350,7 +349,7 @@ class SSHClient (object): self._agent.close() self._agent = None - def exec_command(self, command, bufsize=-1): + def exec_command(self, command, bufsize=-1, timeout=None): """ Execute a command on the SSH server. A new L{Channel} is opened and the requested command is executed. The command's input and output @@ -361,12 +360,15 @@ class SSHClient (object): @type command: str @param bufsize: interpreted the same way as by the built-in C{file()} function in python @type bufsize: int + @param timeout: set command's channel timeout. See L{Channel.settimeout}.settimeout + @type timeout: int @return: the stdin, stdout, and stderr of the executing command @rtype: tuple(L{ChannelFile}, L{ChannelFile}, L{ChannelFile}) @raise SSHException: if the server fails to execute the command """ chan = self._transport.open_session() + chan.settimeout(timeout) chan.exec_command(command) stdin = chan.makefile('wb', bufsize) stdout = chan.makefile('rb', bufsize) diff --git a/paramiko/config.py b/paramiko/config.py index 2828d903..d1ce9490 100644 --- a/paramiko/config.py +++ b/paramiko/config.py @@ -25,7 +25,7 @@ import os import re import socket -SSH_PORT=22 +SSH_PORT = 22 proxy_re = re.compile(r"^(proxycommand)\s*=*\s*(.*)", re.I) diff --git a/paramiko/file.py b/paramiko/file.py index d4aec8e3..7e2904e1 100644 --- a/paramiko/file.py +++ b/paramiko/file.py @@ -354,6 +354,10 @@ class BufferedFile (object): """ return self + @property + def closed(self): + return self._closed + ### overrides... diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py index 3eaefc9c..8cb8ceaf 100644 --- a/paramiko/sftp_client.py +++ b/paramiko/sftp_client.py @@ -533,6 +533,56 @@ class SFTPClient (BaseSFTP): """ return 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. + + 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 + (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 + + @return: an object containing attributes about the given file + (since 1.7.4) + @rtype: SFTPAttributes + + @since: 1.4 + """ + fr = self.file(remotepath, 'wb') + fr.set_pipelined(True) + size = 0 + try: + while True: + data = fl.read(32768) + fr.write(data) + size += len(data) + if callback is not None: + callback(size, file_size) + if len(data) == 0: + break + finally: + fr.close() + if confirm and file_size: + s = self.stat(remotepath) + if s.st_size != size: + raise IOError('size mismatch in put! %d != %d' % (s.st_size, size)) + else: + s = SFTPAttributes() + return s + def put(self, localpath, remotepath, callback=None, confirm=True): """ Copy a local file (C{localpath}) to the SFTP server as C{remotepath}. @@ -562,29 +612,46 @@ class SFTPClient (BaseSFTP): file_size = os.stat(localpath).st_size fl = file(localpath, 'rb') try: - fr = self.file(remotepath, 'wb') - fr.set_pipelined(True) - size = 0 - try: - while True: - data = fl.read(32768) - if len(data) == 0: - break - fr.write(data) - size += len(data) - if callback is not None: - callback(size, file_size) - finally: - fr.close() + return self.putfo(fl, remotepath, os.stat(localpath).st_size, callback, confirm) finally: fl.close() - if confirm: - s = self.stat(remotepath) - if s.st_size != size: - raise IOError('size mismatch in put! %d != %d' % (s.st_size, size)) - else: - s = SFTPAttributes() - return s + + 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 + 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: + size = 0 + while True: + data = fr.read(32768) + fl.write(data) + size += len(data) + if callback is not None: + callback(size, file_size) + if len(data) == 0: + break + finally: + fr.close() + return size def get(self, remotepath, localpath, callback=None): """ @@ -603,25 +670,12 @@ class SFTPClient (BaseSFTP): @since: 1.4 """ - fr = self.file(remotepath, 'rb') file_size = self.stat(remotepath).st_size - fr.prefetch() + fl = file(localpath, 'wb') try: - fl = file(localpath, 'wb') - try: - size = 0 - while True: - data = fr.read(32768) - fl.write(data) - size += len(data) - if callback is not None: - callback(size, file_size) - if len(data) == 0: - break - finally: - fl.close() + size = self.getfo(remotepath, fl, callback) finally: - fr.close() + fl.close() s = os.stat(localpath) if s.st_size != size: raise IOError('size mismatch in get! %d != %d' % (s.st_size, size)) @@ -52,7 +52,7 @@ if sys.platform == 'darwin': setup(name = "paramiko", - version = "1.9.0", + version = "1.10.0", description = "SSH2 protocol library", author = "Jeff Forcier", author_email = "jeff@bitprophet.org", |