PySFTP / Paramiko исключения попадают в stderr - PullRequest
0 голосов
/ 26 сентября 2019

Я пытаюсь перехватить paramiko исключений, но они все еще записываются в stderr.

Есть ли способ прекратить писать там?

РЕДАКТИРОВАТЬ: Это происходит еще до того, как в дело вступает paramiko:

import pysftp

try:
    pysftp.Connection(host="localhost")
except Exception as e:
    print(e)

Результат:

enter image description here

Пример с правильными параметрами SFTP:

enter image description here

ОБНОВЛЕНИЕ:

$ pipenv graph
...
pysftp==0.2.9
  - paramiko [required: >=1.17, installed: 2.6.0]
...

$ pipenv run python
Python 3.7.3 (default, Jul 19 2019, 11:21:39)
[Clang 11.0.0 (clang-1100.0.28.3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pysftp
>>> try:
...     pysftp.Connection(host="localhost")
... except Exception as e:
...     print(e)
...
No hostkey for host localhost found.
Exception ignored in: <function Connection.__del__ at 0x10f7e8268>
Traceback (most recent call last):
  File "/Users/andrei/Work/try/.venv/lib/python3.7/site-packages/pysftp/__init__.py", line 1013, in __del__
    self.close()
  File "/Users/andrei/Work/try/.venv/lib/python3.7/site-packages/pysftp/__init__.py", line 784, in close
    if self._sftp_live:
AttributeError: 'Connection' object has no attribute '_sftp_live'
>>>

1 Ответ

2 голосов
/ 29 сентября 2019

Я хочу начать с указания, что PySFTP ( [PyPI]: PySFTP ) не поддерживается для 3 + лет (или был перенесен в другое место - что пока держится в секрете :)).

Я воспроизвел проблему.Ниже приведена более сложная версия вашего кода.

code00.py :

#!/usr/bin/env python3

import sys
import pysftp
import traceback


def main(argv):
    hostname = argv[0] if argv else "localhost"
    print("Attempting to connect to {0:s} ...".format(hostname))
    try:
        print("----------Before conn----------")
        conn = pysftp.Connection(host=hostname)
        print("----------After conn----------")
    except:
        print("----------Before exc print----------")
        traceback.print_exc()
        print("----------After exc print----------")
    finally:
        print("----------Finally----------")
    print("----------After try / except / finally----------")

if __name__ == "__main__":
    print("Python {0:s} {1:d}bit on {2:s}".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    print("pysftp version: {0:s}\n".format(pysftp.__version__))
    main(sys.argv[1:])
    print("\nDone.")

Вывод :

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q058110732]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code00.py
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] 64bit on win32
pysftp version: 0.2.9

Attempting to connect to localhost ...
----------Before conn----------
e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\pysftp\__init__.py:61: UserWarning: Failed to load HostKeys from C:\Users\cfati\.ssh\known_hosts.  You will need to explicitly load HostKeys (cnopts.hostkeys.load(filename)) or disableHostKey checking (cnopts.hostkeys = None).
  warnings.warn(wmsg, UserWarning)
----------Before exc print----------
Traceback (most recent call last):
  File "code00.py", line 13, in main
    conn = pysftp.Connection(host=hostname)
  File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\pysftp\__init__.py", line 132, in __init__
    self._tconnect['hostkey'] = self._cnopts.get_hostkey(host)
  File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\pysftp\__init__.py", line 71, in get_hostkey
    raise SSHException("No hostkey for host %s found." % host)
paramiko.ssh_exception.SSHException: No hostkey for host localhost found.
----------After exc print----------
Exception ignored in: <function Connection.__del__ at 0x000001CC720C80D0>
Traceback (most recent call last):
  File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\pysftp\__init__.py", line 1013, in __del__
    self.close()
  File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\pysftp\__init__.py", line 784, in close
    if self._sftp_live:
AttributeError: 'Connection' object has no attribute '_sftp_live'
----------Finally----------
----------After try / except / finally----------

Done.

Это ошибка PySFTP :

  1. Создан объект Connection ( __ new __ )
  2. Инициализатор ( __ init __ ) называется
    1. Где-то в инициализаторе возникает исключение
    2. Строки после исключения не выполняются
  3. Когда объект (автоматически) собирается мусором (когда он выходит из области видимости, в конце блока , за исключением ), в его методе close (вызываетсядеструктором ( __ del __ )) на некоторые атрибуты ссылаются
    1. Но поскольку инициализация этих атрибутов происходит после строки (с исключением исключения) из # 2.2. , они никогда не инициализировались, поэтому их ссылка увеличивается AttributeError

Исправленоightforward: инициализируйте атрибуты некоторыми значениями по умолчанию в начале инициализатора, чтобы их ссылка не представляла проблему, если произойдет описанный выше сценарий.

Я заметил, что вы уже представили проблему на BitBucket .

Учитывая, что:

Я создал свой собственный repo (из вышеприведенного) и отправил изменения в: [BitBucket]: CristiFati0 / pysftp - [Issue # 144]: исключения просачиваются в stderr (пока один коммит).

Вывод (после ручного применения исправления к файлу, установленному pip ):

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q058110732]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code00.py
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] 64bit on win32
pysftp version: 0.2.9

Attempting to connect to localhost ...
----------Before conn----------
e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\pysftp\__init__.py:61: UserWarning: Failed to load HostKeys from C:\Users\cfati\.ssh\known_hosts.  You will need to explicitly load HostKeys (cnopts.hostkeys.load(filename)) or disableHostKey checking (cnopts.hostkeys = None).
  warnings.warn(wmsg, UserWarning)
----------Before exc print----------
Traceback (most recent call last):
  File "code00.py", line 13, in main
    conn = pysftp.Connection(host=hostname)
  File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\pysftp\__init__.py", line 135, in __init__
    self._tconnect['hostkey'] = self._cnopts.get_hostkey(host)
  File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\pysftp\__init__.py", line 71, in get_hostkey
    raise SSHException("No hostkey for host %s found." % host)
paramiko.ssh_exception.SSHException: No hostkey for host localhost found.
----------After exc print----------
----------Finally----------
----------After try / except / finally----------

Done.

Само собой разумеется, другие необработанные исключения могут быть вызваны для другого сценария.ios.

@ EDIT0

Очевидно, это нечто большее, чем кажется на первый взгляд.Помимо ошибки PySFTP , описанной выше, есть еще 2 вещи, которые загрязняют stderr .

1.Предупреждение

В моем случае (поскольку я использую Win и у меня не установлено нативное средство SSH ), оно появляется каждый раз (если я не создаю /скопируйте какой-нибудь действительный known_hosts файл в моем доме dir ), но на Nix системах он, скорее всего, не будет.

В любом случае, исправлениедля этого (если нужно) это просто, просто отключите UserWarning , например, установив % PYTHONWARNINGS% env var до ignore :: UserWarning .

2. Paramiko исключения

Мне удалось воспроизвести это, вручную изменив transport.py (и подняв socket.timeout).

paramiko.Transport, чтоинициализируется pysftp.Connection._start_transport (вызывается инициализатором) выполняет свою работу в потоке .Если в этом потоке возникает какое-то исключение, не может быть перехвачен вызывающим потоком .Это ограничение Python , отметьте [Python.Bugs]: threading.Thread должен иметь способ отловить исключение, выданное в .

Для этого есть(хромой) обходной путь ( gainarie ), и это перенаправляет stderr .Конечно, есть и другие обходные пути, но они подразумевают изменение Paramiko , поэтому я бы посоветовал против них.

Ниже приведен пример, который перенаправляет stderr на stdout (но вы можете выбрать любой другой файл, включая / dev / null (или nul on Win )).Это делается из кода (но это также может быть сделано из командной строки интерпретатора), так что влияет только на нужные (горячие) области (области) .

code01.py:

#!/usr/bin/env python3

import sys
import pysftp
import paramiko
import traceback
import threading


_sys_stderr = sys.stderr  # For restoring purposes


def main(argv):
    hostname = argv[0] if argv else "localhost"
    print("Attempting to connect to {0:s} ...".format(hostname))
    try:
        cnopts = pysftp.CnOpts()
        cnopts.hostkeys = None
        print("---------- STATS: {0:s} {1:d} ----------".format(__file__, threading.get_ident()))
        print("---------- Before conn ----------")
        sys.stderr.write("DUMMY TEXT before sent to stderr\n")
        sys.stderr = sys.stdout  # @TODO - cfati: decomment so that everything from stderr is redirected to stdout
        conn = pysftp.Connection(host=hostname, port=22001, username="usr", password="pwd", cnopts=cnopts,)
        print("---------- After conn ----------")
    except:
        sys.stderr = _sys_stderr
        print("---------- Before exc tb ----------")
        traceback.print_exc(file=sys.stdout)
        print("---------- After exc tb ----------")
    finally:
        sys.stderr = _sys_stderr
        print("---------- Finally ----------")
        sys.stderr.write("DUMMY TEXT after sent to stderr\n")
    print("---------- After try / except / finally ----------")


if __name__ == "__main__":
    print("Python {0:s} {1:d}bit on {2:s}".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    print("pysftp version: {0:s}\nparamiko version: {1:s}".format(pysftp.__version__, paramiko.__version__))
    main(sys.argv[1:])
    print("\nDone.")

Вывод :

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q058110732]> sopr.bat
*** Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ***

[prompt]> dir /b
code00.py
code01.py

[prompt]> :: Suppress warning
[prompt]> set PYTHONWARNINGS=ignore::UserWarning

[prompt]> :: Redirect stdout and stderr to different files, so it is obvious which is which
[prompt]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code01.py 1>1.out 2>1.err

[prompt]> type 1.err
DUMMY TEXT before sent to stderr
DUMMY TEXT after sent to stderr

[prompt]> type 1.out
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] 64bit on win32
pysftp version: 0.2.9
paramiko version: 2.6.0
Attempting to connect to localhost ...
---------- STATS: code01.py 23016 ----------
---------- Before conn ----------
---------- STATS: e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\paramiko\transport.py 45616 ----------
Exception: Error reading SSH protocol banner
Traceback (most recent call last):
  File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\paramiko\transport.py", line 2212, in _check_banner
    raise socket.timeout()
socket.timeout

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\paramiko\transport.py", line 2039, in run
    self._check_banner()
  File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\paramiko\transport.py", line 2218, in _check_banner
    "Error reading SSH protocol banner" + str(e)
paramiko.ssh_exception.SSHException: Error reading SSH protocol banner

---------- Before exc tb ----------
Traceback (most recent call last):
  File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\paramiko\transport.py", line 2212, in _check_banner
    raise socket.timeout()
socket.timeout

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "code01.py", line 23, in main
    conn = pysftp.Connection(host=hostname, port=22001, username="usr", password="pwd", cnopts=cnopts,)
  File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\pysftp\__init__.py", line 144, in __init__
    self._transport.connect(**self._tconnect)
  File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\paramiko\transport.py", line 1291, in connect
    self.start_client()
  File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\paramiko\transport.py", line 660, in start_client
    raise e
  File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\paramiko\transport.py", line 2039, in run
    self._check_banner()
  File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\paramiko\transport.py", line 2218, in _check_banner
    "Error reading SSH protocol banner" + str(e)
paramiko.ssh_exception.SSHException: Error reading SSH protocol banner
---------- After exc tb ----------
---------- Finally ----------
---------- After try / except / finally ----------

Done.

И как это выглядит из PyCharm (имелчтобы растянуть его до максимума, чтобы соответствовать целиком):

img0

...