'"'Connection' object has no attribute '_sftp_live'" when pysftp connection fails
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:
?
Solution 1:[1]
The analysis by @reverse_engineer is correct. However:
- It seems that an additional attribute,
self._transport
, also is defined too late. - 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)
Solution 2:[2]
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 ;)
Solution 3:[3]
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)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | reverse_engineer |
Solution 3 | toshin |