"'Connection' object has no attribute '_sftp_live'" when pysftp connection fails

31.5k Views Asked by At

I'd like to catch nicely the error when "No hostkey for host *** is found" and give an appropriate message to the end user. I tried this:

import pysftp, paramiko
try: 
    with pysftp.Connection('1.2.3.4', username='root', password='') as sftp:
        sftp.listdir()
except paramiko.ssh_exception.SSHException as e:
    print('SSH error, you need to add the public key of your remote in your local known_hosts file first.', e)

but unfortunately the output is not very nice:

SSH error, you need to add the public key of your remote in your local known_hosts file first. No hostkey for host 1.2.3.4 found.
Exception ignored in: <function Connection.__del__ at 0x00000000036B6D38>
Traceback (most recent call last):
  File "C:\Python37\lib\site-packages\pysftp\__init__.py", line 1013, in __del__
    self.close()
  File "C:\Python37\lib\site-packages\pysftp\__init__.py", line 784, in close
    if self._sftp_live:
AttributeError: 'Connection' object has no attribute '_sftp_live'

How to nicely avoid these last lines / this "exception ignored" with a try: except:?

3

There are 3 best solutions below

3
On BEST ANSWER

The analysis by @reverse_engineer is correct. However:

  1. It seems that an additional attribute, self._transport, also is defined too late.
  2. The problem can be temporarily corrected until a permanent fix comes by subclassing the pysftp.Connection class as follows:
import pysftp
import paramiko


class My_Connection(pysftp.Connection):
    def __init__(self, *args, **kwargs):
        self._sftp_live = False
        self._transport = None
        super().__init__(*args, **kwargs)

try: 
    with My_Connection('1.2.3.4', username='root', password='') as sftp:
        l = sftp.listdir()
        print(l)
except paramiko.ssh_exception.SSHException as e:
    print('SSH error, you need to add the public key of your remote in your local known_hosts file first.', e)

Update

I could not duplicate this error on my desktop. However, I see in the source for pysftp in the code where it initializes its _cnopts attribute with self._cnopts = cnopts or CnOpts() where cnopts is a keyword parameter to the pysftp.Connection constructor and there is a possibilty of the CnOpts constructor throwing a HostKeysException exception if no host keys are found resulting in the _cnopts attribute not being set.

Try the following updated code and let me know if it works:

import pysftp
import paramiko

class My_Connection(pysftp.Connection):
    def __init__(self, *args, **kwargs):
        try:
            if kwargs.get('cnopts') is None:
                kwargs['cnopts'] = pysftp.CnOpts()
        except pysftp.HostKeysException as e:
            self._init_error = True
            raise paramiko.ssh_exception.SSHException(str(e))
        else:
            self._init_error = False

        self._sftp_live = False
        self._transport = None
        super().__init__(*args, **kwargs)

    def __del__(self):
        if not self._init_error:
            self.close()

try:
    with My_Connection('1.2.3.4', username='root', password='') as sftp:
        l = sftp.listdir()
        print(l)
except paramiko.ssh_exception.SSHException as e:
    print('SSH error, you need to add the public key of your remote in your local known_hosts file first.', e)
2
On

I think that's a bug in pysftp. You will always have that behavior when pysftp.Connection fails on a No hostkey for XXX found exception, because the failed Connection object (it fails so you can't access it, but it exists in the Python interpreter) gets cleaned up by the GC, which deletes it, and as you can see here, that tries to close the connection first.

We see that close() checks whether the connection is live by checking self._sftp_live. However, the Exception was thrown in the constructor of Connection before that attribute is defined (the exception happens line 132, while _sftp_live is defined line 134), so that leaves the failed Connection object in an inconsistent state and hence the uncaught exception you see.

This has no easy solution that I can think of except introducing a nice bug fix to the pysftp project ;)

1
On

I had the same problem. I solved this by disabling the hostkeys in the cnops:

import pysftp as sftp

FTP_HOST = "sftp.abcd.com"
FTP_USER = "root"
FTP_PASS = ""

cnopts = sftp.CnOpts()
cnopts.hostkeys = None

with sftp.Connection(host=FTP_HOST, username=FTP_USER, password=FTP_PASS, cnopts=cnopts) as sftp:
   print("Connection succesfully stablished ... ")
   sftp.cwd('/folder/')  # Switch to a remote directory
   directory_structure = sftp.listdir_attr() # Obtain structure of the remote directory

for attr in directory_structure:
   print(attr.filename, attr)