diff options
-rw-r--r-- | paramiko/channel.py | 5 | ||||
-rw-r--r-- | paramiko/client.py | 6 | ||||
-rw-r--r-- | paramiko/ecdsakey.py | 9 | ||||
-rw-r--r-- | paramiko/file.py | 13 | ||||
-rw-r--r-- | paramiko/kex_gex.py | 2 | ||||
-rw-r--r-- | paramiko/proxy.py | 5 | ||||
-rw-r--r-- | paramiko/server.py | 15 | ||||
-rw-r--r-- | paramiko/sftp_client.py | 5 | ||||
-rw-r--r-- | paramiko/sftp_file.py | 6 | ||||
-rw-r--r-- | paramiko/sftp_handle.py | 5 | ||||
-rw-r--r-- | paramiko/transport.py | 11 | ||||
-rw-r--r-- | paramiko/util.py | 9 | ||||
-rw-r--r-- | sites/docs/api/keys.rst | 17 | ||||
-rw-r--r-- | sites/www/changelog.rst | 1 | ||||
-rw-r--r-- | tasks.py | 2 | ||||
-rw-r--r-- | tests/test_client.py | 25 | ||||
-rwxr-xr-x | tests/test_sftp.py | 20 | ||||
-rw-r--r-- | tests/test_transport.py | 18 |
18 files changed, 133 insertions, 41 deletions
diff --git a/paramiko/channel.py b/paramiko/channel.py index 78a14795..9de278cb 100644 --- a/paramiko/channel.py +++ b/paramiko/channel.py @@ -38,6 +38,7 @@ from paramiko.ssh_exception import SSHException from paramiko.file import BufferedFile from paramiko.buffered_pipe import BufferedPipe, PipeTimeout from paramiko import pipe +from paramiko.util import ClosingContextManager def open_only(func): @@ -60,7 +61,7 @@ def open_only(func): return _check -class Channel (object): +class Channel (ClosingContextManager): """ A secure tunnel across an SSH `.Transport`. A Channel is meant to behave like a socket, and has an API that should be indistinguishable from the @@ -73,6 +74,8 @@ class Channel (object): flow-controlled independently.) Similarly, if the server isn't reading data you send, calls to `send` may block, unless you set a timeout. This is exactly like a normal network socket, so it shouldn't be too surprising. + + Instances of this class may be used as context managers. """ def __init__(self, chanid): diff --git a/paramiko/client.py b/paramiko/client.py index 265389de..05686d97 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -37,10 +37,10 @@ from paramiko.resource import ResourceManager from paramiko.rsakey import RSAKey from paramiko.ssh_exception import SSHException, BadHostKeyException from paramiko.transport import Transport -from paramiko.util import retry_on_signal +from paramiko.util import retry_on_signal, ClosingContextManager -class SSHClient (object): +class SSHClient (ClosingContextManager): """ A high-level representation of a session with an SSH server. This class wraps `.Transport`, `.Channel`, and `.SFTPClient` to take care of most @@ -55,6 +55,8 @@ class SSHClient (object): checking. The default mechanism is to try to use local key files or an SSH agent (if one is running). + Instances of this class may be used as context managers. + .. versionadded:: 1.6 """ diff --git a/paramiko/ecdsakey.py b/paramiko/ecdsakey.py index 9a000682..fefd5eb5 100644 --- a/paramiko/ecdsakey.py +++ b/paramiko/ecdsakey.py @@ -17,7 +17,7 @@ # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. """ -L{ECDSAKey} +ECDSA keys """ import binascii @@ -131,11 +131,8 @@ class ECDSAKey (PKey): Generate a new private RSA key. This factory function can be used to generate a new host key or authentication key. - @param bits: number of bits the generated key should be. - @type bits: int - @param progress_func: Unused. - @return: new private key - @rtype: L{RSAKey} + :param function progress_func: Unused + :returns: A new private key (`.RSAKey`) object """ signing_key = SigningKey.generate(curve) key = ECDSAKey(vals=(signing_key, signing_key.get_verifying_key())) diff --git a/paramiko/file.py b/paramiko/file.py index 2238f0bf..311e1982 100644 --- a/paramiko/file.py +++ b/paramiko/file.py @@ -19,8 +19,10 @@ from paramiko.common import linefeed_byte_value, crlf, cr_byte, linefeed_byte, \ cr_byte_value from paramiko.py3compat import BytesIO, PY2, u, b, bytes_types +from paramiko.util import ClosingContextManager -class BufferedFile (object): + +class BufferedFile (ClosingContextManager): """ Reusable base class to implement Python-style file buffering around a simpler stream. @@ -104,14 +106,13 @@ class BufferedFile (object): else: def __next__(self): """ - Returns the next line from the input, or raises L{StopIteration} when + Returns the next line from the input, or raises `.StopIteration` when EOF is hit. Unlike python file objects, it's okay to mix calls to - C{next} and L{readline}. + `.next` and `.readline`. - @raise StopIteration: when the end of the file is reached. + :raises StopIteration: when the end of the file is reached. - @return: a line read from the file. - @rtype: str + :returns: a line (`str`) read from the file. """ line = self.readline() if not line: diff --git a/paramiko/kex_gex.py b/paramiko/kex_gex.py index 5ff8a287..cb548f33 100644 --- a/paramiko/kex_gex.py +++ b/paramiko/kex_gex.py @@ -19,7 +19,7 @@ """ Variant on `KexGroup1 <paramiko.kex_group1.KexGroup1>` where the prime "p" and generator "g" are provided by the server. A bit more work is required on the -client side, and a B{lot} more on the server side. +client side, and a **lot** more on the server side. """ import os diff --git a/paramiko/proxy.py b/paramiko/proxy.py index 8959b244..0664ac6e 100644 --- a/paramiko/proxy.py +++ b/paramiko/proxy.py @@ -26,9 +26,10 @@ from select import select import socket from paramiko.ssh_exception import ProxyCommandFailure +from paramiko.util import ClosingContextManager -class ProxyCommand(object): +class ProxyCommand(ClosingContextManager): """ Wraps a subprocess running ProxyCommand-driven programs. @@ -36,6 +37,8 @@ class ProxyCommand(object): `.Transport` and `.Packetizer` classes. Using this class instead of a regular socket makes it possible to talk with a Popen'd command that will proxy traffic between the client and a server hosted in another machine. + + Instances of this class may be used as context managers. """ def __init__(self, command_line): """ diff --git a/paramiko/server.py b/paramiko/server.py index cf396b15..bf5039a2 100644 --- a/paramiko/server.py +++ b/paramiko/server.py @@ -547,21 +547,18 @@ class ServerInterface (object): def check_channel_env_request(self, channel, name, value): """ Check whether a given environment variable can be specified for the - given channel. This method should return C{True} if the server + given channel. This method should return ``True`` if the server is willing to set the specified environment variable. Note that some environment variables (e.g., PATH) can be exceedingly dangerous, so blindly allowing the client to set the environment is almost certainly not a good idea. - The default implementation always returns C{False}. + The default implementation always returns ``False``. - @param channel: the L{Channel} the env request arrived on - @type channel: L{Channel} - @param name: foo bar baz - @type name: str - @param value: flklj - @type value: str - @rtype: bool + :param channel: the `.Channel` the env request arrived on + :param str name: name + :param str value: Channel value + :returns: A boolean """ return False diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py index 9c30426d..62127cc2 100644 --- a/paramiko/sftp_client.py +++ b/paramiko/sftp_client.py @@ -39,6 +39,7 @@ from paramiko.sftp import BaseSFTP, CMD_OPENDIR, CMD_HANDLE, SFTPError, CMD_READ from paramiko.sftp_attr import SFTPAttributes from paramiko.ssh_exception import SSHException from paramiko.sftp_file import SFTPFile +from paramiko.util import ClosingContextManager def _to_unicode(s): @@ -58,12 +59,14 @@ def _to_unicode(s): b_slash = b'/' -class SFTPClient(BaseSFTP): +class SFTPClient(BaseSFTP, ClosingContextManager): """ SFTP client object. Used to open an SFTP session across an open SSH `.Transport` and perform remote file operations. + + Instances of this class may be used as context managers. """ def __init__(self, sock): """ diff --git a/paramiko/sftp_file.py b/paramiko/sftp_file.py index 03d67b33..d0a37da3 100644 --- a/paramiko/sftp_file.py +++ b/paramiko/sftp_file.py @@ -488,9 +488,3 @@ class SFTPFile (BufferedFile): x = self._saved_exception self._saved_exception = None raise x - - def __enter__(self): - return self - - def __exit__(self, type, value, traceback): - self.close() diff --git a/paramiko/sftp_handle.py b/paramiko/sftp_handle.py index 92dd9cfe..edceb5ad 100644 --- a/paramiko/sftp_handle.py +++ b/paramiko/sftp_handle.py @@ -22,9 +22,10 @@ Abstraction of an SFTP file handle (for server mode). import os from paramiko.sftp import SFTP_OP_UNSUPPORTED, SFTP_OK +from paramiko.util import ClosingContextManager -class SFTPHandle (object): +class SFTPHandle (ClosingContextManager): """ Abstract object representing a handle to an open file (or folder) in an SFTP server implementation. Each handle has a string representation used @@ -32,6 +33,8 @@ class SFTPHandle (object): Server implementations can (and should) subclass SFTPHandle to implement features of a file handle, like `stat` or `chattr`. + + Instances of this class may be used as context managers. """ def __init__(self, flags=0): """ diff --git a/paramiko/transport.py b/paramiko/transport.py index 50c7a80a..62eae90a 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -64,7 +64,7 @@ from paramiko.server import ServerInterface from paramiko.sftp_client import SFTPClient from paramiko.ssh_exception import (SSHException, BadAuthenticationType, ChannelException, ProxyCommandFailure) -from paramiko.util import retry_on_signal, clamp_value +from paramiko.util import retry_on_signal, ClosingContextManager, clamp_value @@ -79,13 +79,15 @@ import atexit atexit.register(_join_lingering_threads) -class Transport (threading.Thread): +class Transport (threading.Thread, ClosingContextManager): """ An SSH Transport attaches to a stream (usually a socket), negotiates an encrypted session, authenticates, and then creates stream tunnels, called `channels <.Channel>`, across the session. Multiple channels can be multiplexed across a single session (and often are, in the case of port forwardings). + + Instances of this class may be used as context managers. """ _ENCRYPT = object() _DECRYPT = object() @@ -1107,10 +1109,9 @@ class Transport (threading.Thread): def get_banner(self): """ Return the banner supplied by the server upon connect. If no banner is - supplied, this method returns C{None}. + supplied, this method returns ``None``. - @return: server supplied banner, or C{None}. - @rtype: string + :returns: server supplied banner (`str`), or ``None``. """ if not self.active or (self.auth_handler is None): return None diff --git a/paramiko/util.py b/paramiko/util.py index f966099d..25062d00 100644 --- a/paramiko/util.py +++ b/paramiko/util.py @@ -290,5 +290,14 @@ def constant_time_bytes_eq(a, b): res |= byte_ord(a[i]) ^ byte_ord(b[i]) return res == 0 + +class ClosingContextManager(object): + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self.close() + + def clamp_value(minimum, val, maximum): return max(minimum, min(val, maximum)) diff --git a/sites/docs/api/keys.rst b/sites/docs/api/keys.rst index af7b58c4..c6412f77 100644 --- a/sites/docs/api/keys.rst +++ b/sites/docs/api/keys.rst @@ -1,6 +1,23 @@ +============ Key handling ============ +Parent key class +================ + .. automodule:: paramiko.pkey + +DSA (DSS) +========= + .. automodule:: paramiko.dsskey + +RSA +=== + .. automodule:: paramiko.rsakey + +ECDSA +===== + +.. automodule:: paramiko.ecdsakey diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 38a56101..d0bd481c 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,7 @@ Changelog ========= +* :release:`1.15.0 <2014-09-18>` * :support:`393` Replace internal use of PyCrypto's ``SHA.new`` with the stdlib's ``hashlib.sha1``. Thanks to Alex Gaynor. * :feature:`267` (also :issue:`250`, :issue:`241`, :issue:`228`) Add GSS-API / @@ -46,6 +46,8 @@ def release(ctx): copytree(docs_build, target) # Publish publish(ctx, wheel=True) + # Remind + print("\n\nDon't forget to update RTD's versions page for new minor releases!") ns = Collection(test, release, docs=docs, www=www) diff --git a/tests/test_client.py b/tests/test_client.py index 49f2a64a..12022172 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -20,7 +20,10 @@ Some unit tests for SSHClient. """ +from __future__ import with_statement + import gc + import socket from tempfile import mkstemp import threading @@ -298,6 +301,28 @@ class SSHClientTest (unittest.TestCase): self.assertTrue(p() is None) + def test_client_can_be_used_as_context_manager(self): + """ + verify that an SSHClient can be used a context manager + """ + threading.Thread(target=self._run).start() + host_key = paramiko.RSAKey.from_private_key_file(test_path('test_rsa.key')) + public_host_key = paramiko.RSAKey(data=host_key.asbytes()) + + with paramiko.SSHClient() as tc: + self.tc = tc + self.tc.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + self.assertEquals(0, len(self.tc.get_host_keys())) + self.tc.connect(self.addr, self.port, username='slowdive', password='pygmalion') + + self.event.wait(1.0) + self.assertTrue(self.event.isSet()) + self.assertTrue(self.ts.is_active()) + + self.assertTrue(self.tc._transport is not None) + + self.assertTrue(self.tc._transport is None) + def test_7_banner_timeout(self): """ verify that the SSHClient has a configurable banner timeout. diff --git a/tests/test_sftp.py b/tests/test_sftp.py index 1ae9781d..72c7ba03 100755 --- a/tests/test_sftp.py +++ b/tests/test_sftp.py @@ -23,12 +23,13 @@ a real actual sftp server is contacted, and a new folder is created there to do test file operations in (so no existing files will be harmed). """ -from binascii import hexlify import os +import socket import sys -import warnings import threading import unittest +import warnings +from binascii import hexlify from tempfile import mkstemp import paramiko @@ -195,6 +196,21 @@ class SFTPTest (unittest.TestCase): pass sftp = paramiko.SFTP.from_transport(tc) + def test_2_sftp_can_be_used_as_context_manager(self): + """ + verify that the sftp session is closed when exiting the context manager + """ + global sftp + with sftp: + pass + try: + sftp.open(FOLDER + '/test2', 'w') + self.fail('expected exception') + except (EOFError, socket.error): + pass + finally: + sftp = paramiko.SFTP.from_transport(tc) + def test_3_write(self): """ verify that a file can be created and written, and the size is correct. diff --git a/tests/test_transport.py b/tests/test_transport.py index 344d64b8..50b1d86b 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -20,6 +20,8 @@ Some unit tests for the ssh2 protocol in Transport. """ +from __future__ import with_statement + from binascii import hexlify import select import socket @@ -281,6 +283,22 @@ class TransportTest(unittest.TestCase): self.assertEqual('Hello there.\n', f.readline()) self.assertEqual('This is on stderr.\n', f.readline()) self.assertEqual('', f.readline()) + + def test_6a_channel_can_be_used_as_context_manager(self): + """ + verify that exec_command() does something reasonable. + """ + self.setup_test_server() + + with self.tc.open_session() as chan: + with self.ts.accept(1.0) as schan: + chan.exec_command('yes') + schan.send('Hello there.\n') + schan.close() + + f = chan.makefile() + self.assertEqual('Hello there.\n', f.readline()) + self.assertEqual('', f.readline()) def test_7_invoke_shell(self): """ |