Hello community,

here is the log from the commit of package python-scp for openSUSE:Factory 
checked in at 2019-03-14 15:00:29
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-scp (Old)
 and      /work/SRC/openSUSE:Factory/.python-scp.new.28833 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-scp"

Thu Mar 14 15:00:29 2019 rev:3 rq:684703 version:0.13.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-scp/python-scp.changes    2018-12-24 
11:43:40.897330329 +0100
+++ /work/SRC/openSUSE:Factory/.python-scp.new.28833/python-scp.changes 
2019-03-14 15:01:14.331696844 +0100
@@ -1,0 +2,16 @@
+Wed Mar 13 13:52:24 UTC 2019 - Tomáš Chvátal <[email protected]>
+
+- Update to 0.13.1:
+  * Guard against some malformed messages from the server
+  * Remove all introspection logic for progress callback introduced in 0.12
+  * progress callback only accept 3 arguments again
+  * Introduce progress4 parameter which accepts the peername as 4th argument
+  * Fix progress callback failing when it is an instance or class method
+  * Fix README.rst for PyPI
+  * Add possibility of getting the peer IP and port from the progress callback
+  * Make putfo() work with file-like objects that don't provide getvalue()
+  * Add putfo() method, allowing one to upload a file-like object
+  * Add top-level get() and put() functions for convenience
+  * Increase default socket time from 5 to 10 seconds
+
+-------------------------------------------------------------------

Old:
----
  scp-0.10.2.tar.gz

New:
----
  scp-0.13.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-scp.spec ++++++
--- /var/tmp/diff_new_pack.dRCgyd/_old  2019-03-14 15:01:14.991696715 +0100
+++ /var/tmp/diff_new_pack.dRCgyd/_new  2019-03-14 15:01:14.991696715 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-scp
 #
-# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
 # Copyright (c) 2017, Martin Hauke <[email protected]>
 #
 # All modifications and additions to the file contributed by third parties
@@ -18,22 +18,20 @@
 
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
-%bcond_without test
 Name:           python-scp
-Version:        0.10.2
+Version:        0.13.0
 Release:        0
 Summary:        SSH scp module for paramiko
 License:        LGPL-2.1-or-later
 Group:          Development/Languages/Python
 URL:            https://github.com/jbardin/scp.py
 Source:         
https://files.pythonhosted.org/packages/source/s/scp/scp-%{version}.tar.gz
+BuildRequires:  %{python_module paramiko}
 BuildRequires:  %{python_module setuptools}
+BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
 Requires:       python-paramiko
 BuildArch:      noarch
-%if %{with test}
-BuildRequires:  %{python_module paramiko}
-%endif
 %python_subpackages
 
 %description
@@ -49,6 +47,11 @@
 
 %install
 %python_install
+%python_expand %fdupes %{buildroot}%{$python_sitelib}
+
+%check
+# tests require running ssh server
+#%%python_exec -m unittest discover
 
 %files %{python_files}
 %license LICENSE.txt

++++++ scp-0.10.2.tar.gz -> scp-0.13.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/scp-0.10.2/CHANGELOG.md new/scp-0.13.0/CHANGELOG.md
--- old/scp-0.10.2/CHANGELOG.md 1970-01-01 01:00:00.000000000 +0100
+++ new/scp-0.13.0/CHANGELOG.md 2018-11-12 21:53:11.000000000 +0100
@@ -0,0 +1,40 @@
+# Changelog
+
+## 0.13.0 (2018-11-12)
+
+- Remove all introspection logic for `progress` callback introduced in 0.12
+- `progress` callback only accept 3 arguments again
+- Introduce `progress4` parameter which accepts the peername as 4th argument
+
+## 0.12.1 (2018-10-12)
+
+- Fix `progress` callback failing when it is an instance or class method
+
+## 0.12.0 (2018-10-09)
+
+- Fix README.rst for PyPI
+- Add possibility of getting the peer IP and port from the `progress` callback
+- Make `putfo()` work with file-like objects that don't provide `getvalue()`
+
+## 0.11.0 (2018-05-05)
+
+- Add `putfo()` method, allowing one to upload a file-like object
+- Add top-level `get()` and `put()` functions for convenience
+- Increase default socket time from 5 to 10 seconds
+
+## 0.10.2 (2015-05-15)
+
+- Fixes using the SCPClient multiple times
+
+## 0.10.0 (2015-05-07)
+
+- SCPClient can be used as a context manager
+- Added `close()`
+
+## 0.9.0 (2015-02-04)
+
+- Add changelog
+- Finish up py3k and unicode support
+- Unicode should work on OSX, Windows and Linux
+- Some tests have been added
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/scp-0.10.2/MANIFEST.in new/scp-0.13.0/MANIFEST.in
--- old/scp-0.10.2/MANIFEST.in  2015-05-15 15:30:51.000000000 +0200
+++ new/scp-0.13.0/MANIFEST.in  2018-05-05 19:52:53.000000000 +0200
@@ -1,2 +1,4 @@
 include LICENSE.txt
 include README.rst
+include CHANGELOG.md
+include test.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/scp-0.10.2/PKG-INFO new/scp-0.13.0/PKG-INFO
--- old/scp-0.10.2/PKG-INFO     2015-05-15 16:24:53.000000000 +0200
+++ new/scp-0.13.0/PKG-INFO     2018-11-12 21:55:19.000000000 +0100
@@ -1,6 +1,6 @@
-Metadata-Version: 1.0
+Metadata-Version: 1.1
 Name: scp
-Version: 0.10.2
+Version: 0.13.0
 Summary: scp module for paramiko
 Home-page: https://github.com/jbardin/scp.py
 Author: James Bardin
@@ -26,12 +26,16 @@
             ssh.load_system_host_keys()
             ssh.connect('example.com')
         
-            # SCPCLient takes a paramiko transport as its only argument
+            # SCPCLient takes a paramiko transport as an argument
             scp = SCPClient(ssh.get_transport())
         
             scp.put('test.txt', 'test2.txt')
             scp.get('test2.txt')
         
+            # Uploading the 'test' directory with its content in the
+            # '/home/user/dump' remote directory
+            scp.put('test', recursive=True, remote_path='/home/user/dump')
+        
             scp.close()
         
         
@@ -42,7 +46,7 @@
             fc264c65fb17b7db5237cf7ce1780769 test2.txt
         
         Using 'with' keyword
-        ------------------
+        --------------------
         
         ..  code-block:: python
         
@@ -64,4 +68,84 @@
             fc264c65fb17b7db5237cf7ce1780769 test.txt
             fc264c65fb17b7db5237cf7ce1780769 test2.txt
         
+        
+        Uploading file-like objects
+        ---------------------------
+        
+        The ``putfo`` method can be used to upload file-like objects:
+        
+        ..  code-block:: python
+        
+            import io
+            from paramiko import SSHClient
+            from scp import SCPClient
+        
+            ssh = SSHClient()
+            ssh.load_system_host_keys()
+            ssh.connect('example.com')
+        
+            # SCPCLient takes a paramiko transport as an argument
+            scp = SCPClient(ssh.get_transport())
+        
+            # generate in-memory file-like object
+            fl = io.BytesIO()
+            fl.write(b'test')
+            fl.seek(0)
+            # upload it directly from memory
+            scp.putfo(fl, '/tmp/test.txt')
+            # close connection
+            scp.close()
+            # close file handler
+            fl.close()
+        
+        
+        Tracking progress of your file uploads/downloads
+        ------------------------------------------------
+        
+        A ``progress`` function can be given as a callback to the SCPClient to 
handle
+        how the current SCP operation handles the progress of the transfers. 
In the
+        example below we print the percentage complete of the file transfer.
+        
+        ..  code-block:: python
+        
+            from paramiko import SSHClient
+            from scp import SCPClient
+            import sys
+        
+            ssh = SSHClient()
+            ssh.load_system_host_keys()
+            ssh.connect('example.com')
+        
+            # Define progress callback that prints the current percentage 
completed for the file
+            def progress(filename, size, sent):
+                sys.stdout.write("%s\'s progress: %.2f%%   \r" % (filename, 
float(sent)/float(size)*100) )
+        
+            # SCPCLient takes a paramiko transport and progress callback as 
its arguments.
+            scp = SCPClient(ssh.get_transport(), progress=progress)
+        
+            # you can also use progress4, which adds a 4th parameter to track 
IP and port
+            # useful with multiple threads to track source
+            def progress4(filename, size, sent, peername):
+                sys.stdout.write("(%s:%s) %s\'s progress: %.2f%%   \r" % 
(peername[0], peername[1], filename, float(sent)/float(size)*100) )
+            scp = SCPClient(ssh.get_transport(), progress4=progress4)
+        
+            scp.put('test.txt', '~/test.txt')
+            # Should now be printing the current progress of your put function.
+        
+            scp.close()
+        
+Keywords: paramiko,ssh,scp,transfer
 Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: GNU Library or Lesser General Public 
License (LGPL)
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Topic :: Internet
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/scp-0.10.2/README.rst new/scp-0.13.0/README.rst
--- old/scp-0.10.2/README.rst   2015-05-15 15:36:01.000000000 +0200
+++ new/scp-0.13.0/README.rst   2018-11-12 21:51:26.000000000 +0100
@@ -18,12 +18,16 @@
     ssh.load_system_host_keys()
     ssh.connect('example.com')
 
-    # SCPCLient takes a paramiko transport as its only argument
+    # SCPCLient takes a paramiko transport as an argument
     scp = SCPClient(ssh.get_transport())
 
     scp.put('test.txt', 'test2.txt')
     scp.get('test2.txt')
 
+    # Uploading the 'test' directory with its content in the
+    # '/home/user/dump' remote directory
+    scp.put('test', recursive=True, remote_path='/home/user/dump')
+
     scp.close()
 
 
@@ -34,7 +38,7 @@
     fc264c65fb17b7db5237cf7ce1780769 test2.txt
 
 Using 'with' keyword
-------------------
+--------------------
 
 ..  code-block:: python
 
@@ -55,3 +59,69 @@
     $ md5sum test.txt test2.txt
     fc264c65fb17b7db5237cf7ce1780769 test.txt
     fc264c65fb17b7db5237cf7ce1780769 test2.txt
+
+
+Uploading file-like objects
+---------------------------
+
+The ``putfo`` method can be used to upload file-like objects:
+
+..  code-block:: python
+
+    import io
+    from paramiko import SSHClient
+    from scp import SCPClient
+
+    ssh = SSHClient()
+    ssh.load_system_host_keys()
+    ssh.connect('example.com')
+
+    # SCPCLient takes a paramiko transport as an argument
+    scp = SCPClient(ssh.get_transport())
+
+    # generate in-memory file-like object
+    fl = io.BytesIO()
+    fl.write(b'test')
+    fl.seek(0)
+    # upload it directly from memory
+    scp.putfo(fl, '/tmp/test.txt')
+    # close connection
+    scp.close()
+    # close file handler
+    fl.close()
+
+
+Tracking progress of your file uploads/downloads
+------------------------------------------------
+
+A ``progress`` function can be given as a callback to the SCPClient to handle
+how the current SCP operation handles the progress of the transfers. In the
+example below we print the percentage complete of the file transfer.
+
+..  code-block:: python
+
+    from paramiko import SSHClient
+    from scp import SCPClient
+    import sys
+
+    ssh = SSHClient()
+    ssh.load_system_host_keys()
+    ssh.connect('example.com')
+
+    # Define progress callback that prints the current percentage completed 
for the file
+    def progress(filename, size, sent):
+        sys.stdout.write("%s\'s progress: %.2f%%   \r" % (filename, 
float(sent)/float(size)*100) )
+
+    # SCPCLient takes a paramiko transport and progress callback as its 
arguments.
+    scp = SCPClient(ssh.get_transport(), progress=progress)
+
+    # you can also use progress4, which adds a 4th parameter to track IP and 
port
+    # useful with multiple threads to track source
+    def progress4(filename, size, sent, peername):
+        sys.stdout.write("(%s:%s) %s\'s progress: %.2f%%   \r" % (peername[0], 
peername[1], filename, float(sent)/float(size)*100) )
+    scp = SCPClient(ssh.get_transport(), progress4=progress4)
+
+    scp.put('test.txt', '~/test.txt')
+    # Should now be printing the current progress of your put function.
+
+    scp.close()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/scp-0.10.2/scp.egg-info/PKG-INFO 
new/scp-0.13.0/scp.egg-info/PKG-INFO
--- old/scp-0.10.2/scp.egg-info/PKG-INFO        2015-05-15 16:24:53.000000000 
+0200
+++ new/scp-0.13.0/scp.egg-info/PKG-INFO        2018-11-12 21:55:19.000000000 
+0100
@@ -1,6 +1,6 @@
-Metadata-Version: 1.0
+Metadata-Version: 1.1
 Name: scp
-Version: 0.10.2
+Version: 0.13.0
 Summary: scp module for paramiko
 Home-page: https://github.com/jbardin/scp.py
 Author: James Bardin
@@ -26,12 +26,16 @@
             ssh.load_system_host_keys()
             ssh.connect('example.com')
         
-            # SCPCLient takes a paramiko transport as its only argument
+            # SCPCLient takes a paramiko transport as an argument
             scp = SCPClient(ssh.get_transport())
         
             scp.put('test.txt', 'test2.txt')
             scp.get('test2.txt')
         
+            # Uploading the 'test' directory with its content in the
+            # '/home/user/dump' remote directory
+            scp.put('test', recursive=True, remote_path='/home/user/dump')
+        
             scp.close()
         
         
@@ -42,7 +46,7 @@
             fc264c65fb17b7db5237cf7ce1780769 test2.txt
         
         Using 'with' keyword
-        ------------------
+        --------------------
         
         ..  code-block:: python
         
@@ -64,4 +68,84 @@
             fc264c65fb17b7db5237cf7ce1780769 test.txt
             fc264c65fb17b7db5237cf7ce1780769 test2.txt
         
+        
+        Uploading file-like objects
+        ---------------------------
+        
+        The ``putfo`` method can be used to upload file-like objects:
+        
+        ..  code-block:: python
+        
+            import io
+            from paramiko import SSHClient
+            from scp import SCPClient
+        
+            ssh = SSHClient()
+            ssh.load_system_host_keys()
+            ssh.connect('example.com')
+        
+            # SCPCLient takes a paramiko transport as an argument
+            scp = SCPClient(ssh.get_transport())
+        
+            # generate in-memory file-like object
+            fl = io.BytesIO()
+            fl.write(b'test')
+            fl.seek(0)
+            # upload it directly from memory
+            scp.putfo(fl, '/tmp/test.txt')
+            # close connection
+            scp.close()
+            # close file handler
+            fl.close()
+        
+        
+        Tracking progress of your file uploads/downloads
+        ------------------------------------------------
+        
+        A ``progress`` function can be given as a callback to the SCPClient to 
handle
+        how the current SCP operation handles the progress of the transfers. 
In the
+        example below we print the percentage complete of the file transfer.
+        
+        ..  code-block:: python
+        
+            from paramiko import SSHClient
+            from scp import SCPClient
+            import sys
+        
+            ssh = SSHClient()
+            ssh.load_system_host_keys()
+            ssh.connect('example.com')
+        
+            # Define progress callback that prints the current percentage 
completed for the file
+            def progress(filename, size, sent):
+                sys.stdout.write("%s\'s progress: %.2f%%   \r" % (filename, 
float(sent)/float(size)*100) )
+        
+            # SCPCLient takes a paramiko transport and progress callback as 
its arguments.
+            scp = SCPClient(ssh.get_transport(), progress=progress)
+        
+            # you can also use progress4, which adds a 4th parameter to track 
IP and port
+            # useful with multiple threads to track source
+            def progress4(filename, size, sent, peername):
+                sys.stdout.write("(%s:%s) %s\'s progress: %.2f%%   \r" % 
(peername[0], peername[1], filename, float(sent)/float(size)*100) )
+            scp = SCPClient(ssh.get_transport(), progress4=progress4)
+        
+            scp.put('test.txt', '~/test.txt')
+            # Should now be printing the current progress of your put function.
+        
+            scp.close()
+        
+Keywords: paramiko,ssh,scp,transfer
 Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: GNU Library or Lesser General Public 
License (LGPL)
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Topic :: Internet
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/scp-0.10.2/scp.egg-info/SOURCES.txt 
new/scp-0.13.0/scp.egg-info/SOURCES.txt
--- old/scp-0.10.2/scp.egg-info/SOURCES.txt     2015-05-15 16:24:53.000000000 
+0200
+++ new/scp-0.13.0/scp.egg-info/SOURCES.txt     2018-11-12 21:55:19.000000000 
+0100
@@ -1,8 +1,11 @@
+CHANGELOG.md
 LICENSE.txt
 MANIFEST.in
 README.rst
 scp.py
+setup.cfg
 setup.py
+test.py
 scp.egg-info/PKG-INFO
 scp.egg-info/SOURCES.txt
 scp.egg-info/dependency_links.txt
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/scp-0.10.2/scp.py new/scp-0.13.0/scp.py
--- old/scp-0.10.2/scp.py       2015-05-15 16:24:03.000000000 +0200
+++ new/scp-0.13.0/scp.py       2018-11-12 21:53:50.000000000 +0100
@@ -5,12 +5,13 @@
 Utilities for sending files over ssh using the scp1 protocol.
 """
 
-__version__ = '0.10.2'
+__version__ = '0.13.0'
 
 import locale
 import os
 import re
 from socket import timeout as SocketTimeout
+import types
 
 
 # this is quote from the shlex module, added in py3.3
@@ -83,8 +84,8 @@
     Since scp doesn't support symlinks, we send file symlinks as the file
     (matching scp behaviour), but we make no attempt at symlinked directories.
     """
-    def __init__(self, transport, buff_size=16384, socket_timeout=5.0,
-                 progress=None, sanitize=_sh_quote):
+    def __init__(self, transport, buff_size=16384, socket_timeout=10.0,
+                 progress=None, progress4=None, sanitize=_sh_quote):
         """
         Create an scp1 client.
 
@@ -96,21 +97,31 @@
         @type socket_timeout: float
         @param progress: callback - called with (filename, size, sent) during
             transfers
+        @param progress4: callback - called with (filename, size, sent, 
peername)
+            during transfers. peername is a tuple contains (IP, PORT)
         @param sanitize: function - called with filename, should return
             safe or escaped string.  Uses _sh_quote by default.
-        @type progress: function(string, int, int)
+        @type progress: function(string, int, int, tuple)
         """
         self.transport = transport
         self.buff_size = buff_size
         self.socket_timeout = socket_timeout
         self.channel = None
         self.preserve_times = False
-        self._progress = progress
+        if progress is not None and progress4 is not None:
+            raise TypeError("You may only set one of progress, progress4")
+        elif progress4 is not None:
+            self._progress = progress4
+        elif progress is not None:
+            self._progress = lambda *a: progress(*a[:3])
+        else:
+            self._progress = None
         self._recv_dir = b''
         self._rename = False
         self._utime = None
         self.sanitize = sanitize
         self._dirtimes = {}
+        self.peername = self.transport.getpeername()
 
     def __enter__(self):
         self.channel = self._open()
@@ -122,9 +133,9 @@
     def put(self, files, remote_path=b'.',
             recursive=False, preserve_times=False):
         """
-        Transfer files to remote host.
+        Transfer files and directories to remote host.
 
-        @param files: A single path, or a list of paths to be transfered.
+        @param files: A single path, or a list of paths to be transferred.
             recursive must be True to transfer directories.
         @type files: string OR list of strings
         @param remote_path: path in which to receive the files on the remote
@@ -132,7 +143,7 @@
         @type remote_path: str
         @param recursive: transfer files and directories recursively
         @type recursive: bool
-        @param preserve_times: preserve mtime and atime of transfered files
+        @param preserve_times: preserve mtime and atime of transferred files
             and directories.
         @type preserve_times: bool
         """
@@ -155,12 +166,39 @@
 
         self.close()
 
+    def putfo(self, fl, remote_path, mode='0644', size=None):
+        """
+        Transfer file-like object to remote host.
+
+        @param fl: opened file or file-like object to copy
+        @type fl: file-like object
+        @param remote_path: full destination path
+        @type remote_path: str
+        @param mode: permissions (posix-style) for the uploaded file
+        @type mode: str
+        @param size: size of the file in bytes. If ``None``, the size will be
+            computed using `seek()` and `tell()`.
+        """
+        if size is None:
+            pos = fl.tell()
+            fl.seek(0, os.SEEK_END)  # Seek to end
+            size = fl.tell() - pos
+            fl.seek(pos, os.SEEK_SET)  # Seek back
+
+        self.channel = self._open()
+        self.channel.settimeout(self.socket_timeout)
+        self.channel.exec_command(b'scp -t ' +
+                                  self.sanitize(asbytes(remote_path)))
+        self._recv_confirm()
+        self._send_file(fl, remote_path, mode, size=size)
+        self.close()
+
     def get(self, remote_path, local_path='',
             recursive=False, preserve_times=False):
         """
-        Transfer files from remote host to localhost
+        Transfer files and directories from remote host to localhost.
 
-        @param remote_path: path to retreive from remote host. since this is
+        @param remote_path: path to retrieve from remote host. since this is
             evaluated by scp on the remote host, shell wildcards and
             environment variables may be used.
         @type remote_path: str
@@ -168,7 +206,7 @@
         @type local_path: str
         @param recursive: transfer files and directories recursively
         @type recursive: bool
-        @param preserve_times: preserve mtime and atime of transfered files
+        @param preserve_times: preserve mtime and atime of transferred files
             and directories.
         @type preserve_times: bool
         """
@@ -200,7 +238,7 @@
 
     def _open(self):
         """open a scp channel"""
-        if self.channel is None:
+        if self.channel is None or self.channel.closed:
             self.channel = self.transport.open_session()
 
         return self.channel
@@ -224,35 +262,37 @@
 
     def _send_files(self, files):
         for name in files:
-            basename = asbytes(os.path.basename(name))
             (mode, size, mtime, atime) = self._read_stats(name)
             if self.preserve_times:
                 self._send_time(mtime, atime)
-            file_hdl = open(name, 'rb')
-
-            # The protocol can't handle \n in the filename.
-            # Quote them as the control sequence \^J for now,
-            # which is how openssh handles it.
-            self.channel.sendall(("C%s %d " % (mode, size)).encode('ascii') +
-                                 basename.replace(b'\n', b'\\^J') + b"\n")
-            self._recv_confirm()
-            file_pos = 0
+            fl = open(name, 'rb')
+            self._send_file(fl, name, mode, size)
+            fl.close()
+
+    def _send_file(self, fl, name, mode, size):
+        basename = asbytes(os.path.basename(name))
+        # The protocol can't handle \n in the filename.
+        # Quote them as the control sequence \^J for now,
+        # which is how openssh handles it.
+        self.channel.sendall(("C%s %d " % (mode, size)).encode('ascii') +
+                             basename.replace(b'\n', b'\\^J') + b"\n")
+        self._recv_confirm()
+        file_pos = 0
+        if self._progress:
+            if size == 0:
+                # avoid divide-by-zero
+                self._progress(basename, 1, 1, self.peername)
+            else:
+                self._progress(basename, size, 0, self.peername)
+        buff_size = self.buff_size
+        chan = self.channel
+        while file_pos < size:
+            chan.sendall(fl.read(buff_size))
+            file_pos = fl.tell()
             if self._progress:
-                if size == 0:
-                    # avoid divide-by-zero
-                    self._progress(basename, 1, 1)
-                else:
-                    self._progress(basename, size, 0)
-            buff_size = self.buff_size
-            chan = self.channel
-            while file_pos < size:
-                chan.sendall(file_hdl.read(buff_size))
-                file_pos = file_hdl.tell()
-                if self._progress:
-                    self._progress(basename, size, file_pos)
-            chan.sendall('\x00')
-            file_hdl.close()
-            self._recv_confirm()
+                self._progress(basename, size, file_pos, self.peername)
+        chan.sendall('\x00')
+        self._recv_confirm()
 
     def _chdir(self, from_dir, to_dir):
         # Pop until we're one level up from our next push.
@@ -264,7 +304,7 @@
         common = os.path.commonprefix([from_dir + bytes_sep,
                                        to_dir + bytes_sep])
         # now take the dirname, since commonprefix is character based,
-        # and we either have a seperator, or a partial name
+        # and we either have a separator, or a partial name
         common = os.path.dirname(common)
         cur_dir = from_dir.rstrip(bytes_sep)
         while cur_dir != common:
@@ -313,7 +353,7 @@
         try:
             msg = self.channel.recv(512)
         except SocketTimeout:
-            raise SCPException('Timout waiting for scp response')
+            raise SCPException('Timeout waiting for scp response')
         # slice off the first byte, so this compare will work in py2 and py3
         if msg and msg[0:1] == b'\x00':
             return
@@ -342,10 +382,9 @@
             assert msg[-1:] == b'\n'
             msg = msg[:-1]
             code = msg[0:1]
-            try:
-                command[code](msg[1:])
-            except KeyError:
+            if code not in command:
                 raise SCPException(asunicode(msg[1:]))
+            command[code](msg[1:])
         # directory times can't be set until we're done writing files
         self._set_dirtimes()
 
@@ -391,9 +430,9 @@
         if self._progress:
             if size == 0:
                 # avoid divide-by-zero
-                self._progress(path, 1, 1)
+                self._progress(path, 1, 1, self.peername)
             else:
-                self._progress(path, size, 0)
+                self._progress(path, size, 0, self.peername)
         buff_size = self.buff_size
         pos = 0
         chan.send(b'\x00')
@@ -405,8 +444,7 @@
                 file_hdl.write(chan.recv(buff_size))
                 pos = file_hdl.tell()
                 if self._progress:
-                    self._progress(path, size, pos)
-
+                    self._progress(path, size, pos, self.peername)
             msg = chan.recv(512)
             if msg and msg[0:1] != b'\x00':
                 raise SCPException(asunicode(msg[1:]))
@@ -468,3 +506,53 @@
 class SCPException(Exception):
     """SCP exception class"""
     pass
+
+
+def put(transport, files, remote_path=b'.',
+        recursive=False, preserve_times=False):
+    """
+    Transfer files and directories to remote host.
+
+    This is a convenience function that creates a SCPClient from the given
+    transport and closes it at the end, useful for one-off transfers.
+
+    @param files: A single path, or a list of paths to be transferred.
+        recursive must be True to transfer directories.
+    @type files: string OR list of strings
+    @param remote_path: path in which to receive the files on the remote host.
+        defaults to '.'
+    @type remote_path: str
+    @param recursive: transfer files and directories recursively
+    @type recursive: bool
+    @param preserve_times: preserve mtime and atime of transferred files and
+        directories.
+    @type preserve_times: bool
+    """
+    with SCPClient(transport) as client:
+        client.put(files, remote_path, recursive, preserve_times)
+
+
+def get(transport, remote_path, local_path='',
+        recursive=False, preserve_times=False):
+    """
+    Transfer files and directories from remote host to localhost.
+
+    This is a convenience function that creates a SCPClient from the given
+    transport and closes it at the end, useful for one-off transfers.
+
+    @param transport: an paramiko L{Transport}
+    @type transport: L{Transport}
+    @param remote_path: path to retrieve from remote host. since this is
+        evaluated by scp on the remote host, shell wildcards and environment
+        variables may be used.
+    @type remote_path: str
+    @param local_path: path in which to receive files locally
+    @type local_path: str
+    @param recursive: transfer files and directories recursively
+    @type recursive: bool
+    @param preserve_times: preserve mtime and atime of transferred files
+        and directories.
+    @type preserve_times: bool
+    """
+    with SCPClient(transport) as client:
+        client.get(remote_path, local_path, recursive, preserve_times)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/scp-0.10.2/setup.cfg new/scp-0.13.0/setup.cfg
--- old/scp-0.10.2/setup.cfg    2015-05-15 16:24:53.000000000 +0200
+++ new/scp-0.13.0/setup.cfg    2018-11-12 21:55:19.000000000 +0100
@@ -1,5 +1,7 @@
+[bdist_wheel]
+universal = 1
+
 [egg_info]
 tag_build = 
 tag_date = 0
-tag_svn_revision = 0
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/scp-0.10.2/setup.py new/scp-0.13.0/setup.py
--- old/scp-0.10.2/setup.py     2015-05-15 16:22:38.000000000 +0200
+++ new/scp-0.13.0/setup.py     2018-11-12 21:53:43.000000000 +0100
@@ -10,7 +10,7 @@
     description = fp.read()
 setup(
         name = 'scp',
-        version = '0.10.2',
+        version = '0.13.0',
         author = 'James Bardin',
         author_email = '[email protected]',
         license = 'LGPL',
@@ -19,4 +19,20 @@
         long_description=description,
         py_modules = ['scp'],
         install_requires = ['paramiko'],
+        keywords=['paramiko', 'ssh', 'scp', 'transfer'],
+        classifiers=[
+            'Development Status :: 5 - Production/Stable',
+            'Intended Audience :: Developers',
+            'License :: OSI Approved :: GNU Library or Lesser General Public 
License (LGPL)',
+            'Operating System :: OS Independent',
+            'Programming Language :: Python',
+            'Programming Language :: Python :: 2',
+            'Programming Language :: Python :: 2.6',
+            'Programming Language :: Python :: 2.7',
+            'Programming Language :: Python :: 3',
+            'Programming Language :: Python :: 3.3',
+            'Programming Language :: Python :: 3.4',
+            'Programming Language :: Python :: 3.5',
+            'Topic :: Internet',
+        ],
 )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/scp-0.10.2/test.py new/scp-0.13.0/test.py
--- old/scp-0.10.2/test.py      1970-01-01 01:00:00.000000000 +0100
+++ new/scp-0.13.0/test.py      2018-11-12 21:51:26.000000000 +0100
@@ -0,0 +1,318 @@
+from __future__ import print_function
+
+from io import BytesIO
+import os
+import paramiko
+import random
+import shutil
+import sys
+from scp import SCPClient, SCPException, put, get
+import tempfile
+try:
+    import unittest2 as unittest
+    sys.modules['unittest'] = unittest
+except ImportError:
+    import unittest
+
+
+ssh_info = {
+    'hostname': os.environ.get('SCPPY_HOSTNAME', '127.0.0.1'),
+    'port': int(os.environ.get('SCPPY_PORT', 22)),
+    'username': os.environ.get('SCPPY_USERNAME', None),
+}
+
+
+# Environment info
+PY3 = sys.version_info >= (3,)
+WINDOWS = os.name == 'nt'
+MACOS = sys.platform == 'darwin'
+
+
+if MACOS:
+    import unicodedata
+
+    def normalize_paths(names):
+        """Ensures the test names are normalized (NFC).
+
+        HFS (on Mac OS X) will normalize filenames if necessary.
+        """
+        normed = set()
+        for n in names:
+            if isinstance(n, bytes):
+                n = n.decode('utf-8')
+
+            normed.add(unicodedata.normalize('NFC', n).encode('utf-8'))
+        return normed
+else:
+    normalize_paths = set
+
+
+def unique_names():
+    """Generates unique sequences of bytes.
+    """
+    characters = (b"abcdefghijklmnopqrstuvwxyz"
+                  b"0123456789")
+    characters = [characters[i:i + 1] for i in range(len(characters))]
+    rng = random.Random()
+    while True:
+        letters = [rng.choice(characters) for i in range(10)]
+        yield b''.join(letters)
+unique_names = unique_names()
+
+
+class TestDownload(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        # Server connection
+        cls.ssh = paramiko.SSHClient()
+        cls.ssh.load_system_host_keys()
+        cls.ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
+        cls.ssh.connect(**ssh_info)
+
+        # Makes some files on the server
+        chan = cls.ssh.get_transport().open_session()
+        chan.exec_command(
+            b'if ! echo -ne "/tmp/r\\xC3\\xA9mi" | xargs test -d; then '
+            # Directory
+            b'echo -ne "/tmp/bien rang\\xC3\\xA9" | xargs -0 mkdir; '
+            # Files
+            b'echo -ne "'
+            b'/tmp/r\\xC3\\xA9mi\\x00'
+            b'/tmp/bien rang\\xC3\\xA9/file\\x00'
+            b'/tmp/bien rang\\xC3\\xA9/b\\xC3\\xA8te\\x00'
+            b'/tmp/p\\xE9t\\xE9'  # invalid UTF-8 here
+            b'" | xargs -0 touch; '
+            b'fi')
+        assert chan.recv_exit_status() == 0
+
+        print("Running tests on %s with %s" % (
+              "Windows" if WINDOWS else
+              "Mac OS X" if MACOS else
+              "POSIX",
+              "Python 3" if PY3 else "Python 2"))
+
+    def download_test(self, filename, recursive, destination=None,
+                      expected_win=[], expected_posix=[]):
+        # Make a temporary directory
+        temp = tempfile.mkdtemp(prefix='scp-py_test_')
+        # Add some unicode in the path
+        if WINDOWS:
+            if isinstance(temp, bytes):
+                temp = temp.decode(sys.getfilesystemencoding())
+            temp_in = os.path.join(temp, u'cl\xE9')
+        else:
+            if not isinstance(temp, bytes):
+                temp = temp.encode('utf-8')
+            temp_in = os.path.join(temp, b'cl\xC3\xA9')
+        previous = os.getcwd()
+        os.mkdir(temp_in)
+        os.chdir(temp_in)
+        cb3 = lambda filename, size, sent: None
+        try:
+            with SCPClient(self.ssh.get_transport(), progress=cb3) as scp:
+                scp.get(filename,
+                        destination if destination is not None else u'.',
+                        preserve_times=True, recursive=recursive)
+            actual = []
+
+            def listdir(path, fpath):
+                for name in os.listdir(fpath):
+                    fname = os.path.join(fpath, name)
+                    actual.append(os.path.join(path, name))
+                    if os.path.isdir(fname):
+                        listdir(name, fname)
+            listdir(u'' if WINDOWS else b'',
+                    u'.' if WINDOWS else b'.')
+            self.assertEqual(normalize_paths(actual),
+                             set(expected_win if WINDOWS else expected_posix))
+        finally:
+            os.chdir(previous)
+            shutil.rmtree(temp)
+
+    def test_get_bytes(self):
+        self.download_test(b'/tmp/r\xC3\xA9mi', False, b'target',
+                           [u'target'], [b'target'])
+        self.download_test(b'/tmp/r\xC3\xA9mi', False, u'target',
+                           [u'target'], [b'target'])
+        self.download_test(b'/tmp/r\xC3\xA9mi', False, None,
+                           [u'r\xE9mi'], [b'r\xC3\xA9mi'])
+        self.download_test([b'/tmp/bien rang\xC3\xA9/file',
+                            b'/tmp/bien rang\xC3\xA9/b\xC3\xA8te'],
+                           False, None,
+                           [u'file', u'b\xE8te'], [b'file', b'b\xC3\xA8te'])
+
+    def test_get_unicode(self):
+        self.download_test(u'/tmp/r\xE9mi', False, b'target',
+                           [u'target'], [b'target'])
+        self.download_test(u'/tmp/r\xE9mi', False, u'target',
+                           [u'target'], [b'target'])
+        self.download_test(u'/tmp/r\xE9mi', False, None,
+                           [u'r\xE9mi'], [b'r\xC3\xA9mi'])
+        self.download_test([u'/tmp/bien rang\xE9/file',
+                            u'/tmp/bien rang\xE9/b\xE8te'],
+                           False, None,
+                           [u'file', u'b\xE8te'], [b'file', b'b\xC3\xA8te'])
+
+    def test_get_folder(self):
+        self.download_test(b'/tmp/bien rang\xC3\xA9', True, None,
+                           [u'bien rang\xE9', u'bien rang\xE9\\file',
+                            u'bien rang\xE9\\b\xE8te'],
+                           [b'bien rang\xC3\xA9', b'bien rang\xC3\xA9/file',
+                            b'bien rang\xC3\xA9/b\xC3\xA8te'])
+
+    def test_get_invalid_unicode(self):
+        self.download_test(b'/tmp/p\xE9t\xE9', False, u'target',
+                           [u'target'], [b'target'])
+        if WINDOWS:
+            with self.assertRaises(SCPException):
+                self.download_test(b'/tmp/p\xE9t\xE9', False, None,
+                                   [], [])
+        elif MACOS:
+            self.download_test(b'/tmp/p\xE9t\xE9', False, None,
+                               [u'not windows'], [b'p%E9t%E9'])
+        else:
+            self.download_test(b'/tmp/p\xE9t\xE9', False, None,
+                               [u'not windows'], [b'p\xE9t\xE9'])
+
+
+class TestUpload(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        # Server connection
+        cls.ssh = paramiko.SSHClient()
+        cls.ssh.load_system_host_keys()
+        cls.ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
+        cls.ssh.connect(**ssh_info)
+
+        # Makes some files locally
+        cls._temp = tempfile.mkdtemp(prefix='scp_py_test_')
+        if isinstance(cls._temp, bytes):
+            cls._temp = cls._temp.decode(sys.getfilesystemencoding())
+        inner = os.path.join(cls._temp, u'cl\xE9')
+        os.mkdir(inner)
+        os.mkdir(os.path.join(inner, u'dossi\xE9'))
+        os.mkdir(os.path.join(inner, u'dossi\xE9', u'bien rang\xE9'))
+        open(os.path.join(inner, u'dossi\xE9', u'bien rang\xE9', u'test'),
+             'w').close()
+        open(os.path.join(inner, u'r\xE9mi'), 'w').close()
+
+    @classmethod
+    def tearDownClass(cls):
+        shutil.rmtree(cls._temp)
+
+    def upload_test(self, filenames, recursive, expected=[], fl=None):
+        destination = b'/tmp/upp\xC3\xA9' + next(unique_names)
+        chan = self.ssh.get_transport().open_session()
+        chan.exec_command(b'mkdir ' + destination)
+        assert chan.recv_exit_status() == 0
+        previous = os.getcwd()
+        cb4 = lambda filename, size, sent, peername: None
+        try:
+            os.chdir(self._temp)
+            with SCPClient(self.ssh.get_transport(), progress4=cb4) as scp:
+                if not fl:
+                    scp.put(filenames, destination, recursive)
+                else:
+                    prefix = destination.decode(sys.getfilesystemencoding())
+                    remote_path = '%s/%s' % (prefix, filenames)
+                    scp.putfo(fl, remote_path)
+                    fl.close()
+
+            chan = self.ssh.get_transport().open_session()
+            chan.exec_command(
+                b'echo -ne "' +
+                destination.decode('iso-8859-1')
+                    .encode('ascii', 'backslashreplace') +
+                b'" | xargs find')
+            out_list = b''
+            while True:
+                data = chan.recv(1024)
+                if not data:
+                    break
+                out_list += data
+            prefix = len(destination) + 1
+            out_list = [l[prefix:] for l in out_list.splitlines()
+                        if len(l) > prefix]
+            self.assertEqual(normalize_paths(out_list), set(expected))
+        finally:
+            os.chdir(previous)
+            chan = self.ssh.get_transport().open_session()
+            chan.exec_command(b'rm -Rf ' + destination)
+            assert chan.recv_exit_status() == 0
+
+    @unittest.skipIf(WINDOWS, "Use unicode paths on Windows")
+    def test_put_bytes(self):
+        self.upload_test(b'cl\xC3\xA9/r\xC3\xA9mi', False, [b'r\xC3\xA9mi'])
+        self.upload_test(b'cl\xC3\xA9/dossi\xC3\xA9/bien rang\xC3\xA9/test',
+                         False,
+                         [b'test'])
+        self.upload_test(b'cl\xC3\xA9/dossi\xC3\xA9', True,
+                         [b'dossi\xC3\xA9',
+                          b'dossi\xC3\xA9/bien rang\xC3\xA9',
+                          b'dossi\xC3\xA9/bien rang\xC3\xA9/test'])
+
+    def test_put_unicode(self):
+        self.upload_test(u'cl\xE9/r\xE9mi', False, [b'r\xC3\xA9mi'])
+        self.upload_test(u'cl\xE9/dossi\xE9/bien rang\xE9/test', False,
+                         [b'test'])
+        self.upload_test(u'cl\xE9/dossi\xE9', True,
+                         [b'dossi\xC3\xA9',
+                          b'dossi\xC3\xA9/bien rang\xC3\xA9',
+                          b'dossi\xC3\xA9/bien rang\xC3\xA9/test'])
+        self.upload_test([u'cl\xE9/dossi\xE9/bien rang\xE9',
+                          u'cl\xE9/r\xE9mi'], True,
+                         [b'bien rang\xC3\xA9',
+                          b'bien rang\xC3\xA9/test',
+                          b'r\xC3\xA9mi'])
+        self.upload_test([u'cl\xE9/dossi\xE9',
+                          u'cl\xE9/r\xE9mi'], True,
+                         [b'dossi\xC3\xA9',
+                          b'dossi\xC3\xA9/bien rang\xC3\xA9',
+                          b'dossi\xC3\xA9/bien rang\xC3\xA9/test',
+                          b'r\xC3\xA9mi'])
+
+    def test_putfo(self):
+        fl = BytesIO()
+        fl.write(b'r\xC3\xA9mi')
+        fl.seek(0)
+        self.upload_test(u'putfo-test', False, [b'putfo-test'], fl)
+
+
+class TestUpAndDown(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        # Server connection
+        cls.ssh = paramiko.SSHClient()
+        cls.ssh.load_system_host_keys()
+        cls.ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
+        cls.ssh.connect(**ssh_info)
+
+        # Makes some files locally
+        cls._temp = tempfile.mkdtemp(prefix='scp_py_test_')
+        if isinstance(cls._temp, bytes):
+            cls._temp = cls._temp.decode(sys.getfilesystemencoding())
+
+    @classmethod
+    def tearDownClass(cls):
+        shutil.rmtree(cls._temp)
+
+    def test_up_and_down(self):
+        '''send and receive files with the same client'''
+        previous = os.getcwd()
+        testfile = os.path.join(self._temp, 'testfile')
+        testfile_sent = os.path.join(self._temp, 'testfile_sent')
+        testfile_rcvd = os.path.join(self._temp, 'testfile_rcvd')
+        try:
+            os.chdir(self._temp)
+            with open(testfile, 'w') as f:
+                f.write("TESTING\n")
+            put(self.ssh.get_transport(), testfile, testfile_sent)
+            get(self.ssh.get_transport(), testfile_sent, testfile_rcvd)
+
+            assert open(testfile_rcvd).read() == 'TESTING\n'
+        finally:
+            os.chdir(previous)
+
+
+if __name__ == '__main__':
+    unittest.main()


Reply via email to