1616
1717"""SSH client helper based on Paramiko. Base class."""
1818
19+ import abc
1920import base64
2021import collections
2122import concurrent .futures
5859CPYTHON = 'CPython' == platform .python_implementation ()
5960
6061
61- class _MemorizedSSH (type ):
62+ class _MemorizedSSH (abc . ABCMeta ):
6263 """Memorize metaclass for SSHClient.
6364
6465 This class implements caching and managing of SSHClient connections.
@@ -112,16 +113,23 @@ def __call__( # type: ignore
112113 auth : typing .Optional [ssh_auth .SSHAuth ] = None ,
113114 verbose : bool = True ,
114115 ) -> 'SSHClientBase' :
115- """Main memorize method: check for cached instance and return it.
116+ """Main memorize method: check for cached instance and return it. API follows target __init__.
116117
118+ :param host: remote hostname
117119 :type host: str
120+ :param port: remote ssh port
118121 :type port: int
119- :type username: str
120- :type password: str
121- :type private_keys: list
122- :type auth: ssh_auth.SSHAuth
122+ :param username: remote username.
123+ :type username: typing.Optional[str]
124+ :param password: remote password
125+ :type password: typing.Optional[str]
126+ :param private_keys: private keys for connection
127+ :type private_keys: typing.Optional[typing.Iterable[paramiko.RSAKey]]
128+ :param auth: credentials for connection
129+ :type auth: typing.Optional[ssh_auth.SSHAuth]
130+ :param verbose: show additional error/warning messages
123131 :type verbose: bool
124- :rtype: SSHClient
132+ :rtype: SSHClientBase
125133 """
126134 if (host , port ) in cls .__cache :
127135 key = host , port
@@ -164,7 +172,7 @@ def __call__( # type: ignore
164172 def clear_cache (mcs : typing .Type ['_MemorizedSSH' ]) -> None :
165173 """Clear cached connections for initialize new instance on next call.
166174
167- getrefcount is used to check for usage.
175+ getrefcount is used to check for usage, so connections closed on CPYTHON only .
168176 """
169177 n_count = 3
170178 # PY3: cache, ssh, temporary
@@ -210,8 +218,8 @@ def __init__(
210218 ) -> None :
211219 """Context manager for call commands with sudo.
212220
213- :type ssh: SSHClient
214- :type enforce: bool
221+ :type ssh: SSHClientBase
222+ :type enforce: typing.Optional[ bool]
215223 """
216224 self .__ssh = ssh
217225 self .__sudo_status = ssh .sudo_mode
@@ -241,7 +249,7 @@ def __init__(
241249 ) -> None :
242250 """Context manager for keepalive management.
243251
244- :type ssh: SSHClient
252+ :type ssh: SSHClientBase
245253 :type enforce: bool
246254 :param enforce: Keep connection alive after context manager exit
247255 """
@@ -465,8 +473,8 @@ def _clear_cache(cls: typing.Type['SSHClientBase']) -> None:
465473 def __del__ (self ) -> None :
466474 """Destructor helper: close channel and threads BEFORE closing others.
467475
468- Due to threading in paramiko, default destructor could generate asserts
469- on close, so we calling channel close before closing main ssh object.
476+ Due to threading in paramiko, default destructor could generate asserts on close,
477+ so we calling channel close before closing main ssh object.
470478 """
471479 try :
472480 self .__ssh .close ()
@@ -540,6 +548,7 @@ def sudo(
540548
541549 :param enforce: Enforce sudo enabled or disabled. By default: None
542550 :type enforce: typing.Optional[bool]
551+ :rtype: typing.ContextManager
543552 """
544553 return self .__get_sudo (ssh = self , enforce = enforce )
545554
@@ -551,6 +560,7 @@ def keepalive(
551560
552561 :param enforce: Enforce keepalive enabled or disabled.
553562 :type enforce: bool
563+ :rtype: typing.ContextManager
554564
555565 .. Note:: Enter and exit ssh context manager is produced as well.
556566 .. versionadded:: 1.2.1
@@ -572,7 +582,7 @@ def execute_async(
572582 :param command: Command for execution
573583 :type command: str
574584 :param stdin: pass STDIN text to the process
575- :type stdin: typing.Union[str, bytes , bytearray, None]
585+ :type stdin: typing.Union[bytes, str , bytearray, None]
576586 :param open_stdout: open STDOUT stream for read
577587 :type open_stdout: bool
578588 :param open_stderr: open STDERR stream for read
@@ -623,6 +633,7 @@ def execute_async(
623633 cmd = "sudo -S bash -c 'eval \" $(base64 -d <(echo \" {0}\" ))\" '" .format (encoded_cmd )
624634 chan .exec_command (cmd ) # nosec # Sanitize on caller side
625635 if stdout .channel .closed is False :
636+ # noinspection PyTypeChecker
626637 self .auth .enter_password (_stdin )
627638 _stdin .flush ()
628639 else :
@@ -716,6 +727,7 @@ def poll_pipes(stop: threading.Event) -> None:
716727 stop_event = threading .Event ()
717728
718729 # pylint: disable=assignment-from-no-return
730+ # noinspection PyNoneFunctionAssignment
719731 future = poll_pipes (stop = stop_event ) # type: concurrent.futures.Future
720732 # pylint: enable=assignment-from-no-return
721733
0 commit comments