diff --git a/doc/source/ExecResult.rst b/doc/source/ExecResult.rst index 05ced46..fb8362c 100644 --- a/doc/source/ExecResult.rst +++ b/doc/source/ExecResult.rst @@ -40,7 +40,7 @@ API: ExecResult .. py:attribute:: stdin - ``str`` + ``typing.Text`` Stdin input as string. .. py:attribute:: stdout @@ -65,22 +65,22 @@ API: ExecResult .. py:attribute:: stdout_str - ``str`` + ``typing.Text`` Stdout output as string. .. py:attribute:: stderr_str - ``str`` + ``typing.Text`` Stderr output as string. .. py:attribute:: stdout_brief - ``str`` + ``typing.Text`` Brief stdout output (mostly for exceptions). .. py:attribute:: stderr_brief - ``str`` + ``typing.Text`` Brief stderr output (mostly for exceptions). .. py:attribute:: exit_code diff --git a/doc/source/SSHClient.rst b/doc/source/SSHClient.rst index b976786..1b0a88b 100644 --- a/doc/source/SSHClient.rst +++ b/doc/source/SSHClient.rst @@ -106,6 +106,7 @@ API: SSHClient and SSHAuth. :param enforce: Enforce sudo enabled or disabled. By default: None :type enforce: ``typing.Optional[bool]`` + :rtype: ``typing.ContextManager`` .. py:method:: keepalive(enforce=None) @@ -113,6 +114,7 @@ API: SSHClient and SSHAuth. :param enforce: Enforce keepalive enabled or disabled. By default: True :type enforce: ``typing.bool`` + :rtype: ``typing.ContextManager`` .. Note:: Enter and exit ssh context manager is produced as well. .. versionadded:: 1.2.1 @@ -124,7 +126,7 @@ API: SSHClient and SSHAuth. :param command: Command for execution :type command: ``str`` :param stdin: pass STDIN text to the process - :type stdin: ``typing.Union[six.text_type, six.binary_type, bytearray, None]`` + :type stdin: ``typing.Union[typing.AnyStr, bytearray, None]`` :param open_stdout: open STDOUT stream for read :type open_stdout: bool :param open_stderr: open STDERR stream for read @@ -149,7 +151,7 @@ API: SSHClient and SSHAuth. :param verbose: Produce log.info records for command call and output :type verbose: ``bool`` :param timeout: Timeout for command execution. - :type timeout: ``typing.Optional[int]`` + :type timeout: ``typing.Union[int, None]`` :rtype: ExecResult :raises ExecHelperTimeoutError: Timeout exceeded @@ -164,7 +166,7 @@ API: SSHClient and SSHAuth. :param verbose: Produce log.info records for command call and output :type verbose: ``bool`` :param timeout: Timeout for command execution. - :type timeout: ``typing.Optional[int]`` + :type timeout: ``typing.Union[int, None]`` :param error_info: Text for error details, if fail happens :type error_info: ``typing.Optional[str]`` :param expected: expected return codes (0 by default) @@ -186,7 +188,7 @@ API: SSHClient and SSHAuth. :param verbose: Produce log.info records for command call and output :type verbose: ``bool`` :param timeout: Timeout for command execution. - :type timeout: ``typing.Optional[int]`` + :type timeout: ``typing.Union[int, None]`` :param error_info: Text for error details, if fail happens :type error_info: ``typing.Optional[str]`` :param raise_on_err: Raise exception on unexpected return code @@ -213,7 +215,7 @@ API: SSHClient and SSHAuth. :param verbose: Produce log.info records for command call and output :type verbose: ``bool`` :param timeout: Timeout for command execution. - :type timeout: ``typing.Optional[int]`` + :type timeout: ``typing.Union[int, None]`` :param get_pty: open PTY on target machine :type get_pty: ``bool`` :rtype: ExecResult @@ -230,7 +232,7 @@ API: SSHClient and SSHAuth. :param command: Command for execution :type command: ``str`` :param timeout: Timeout for command execution. - :type timeout: ``typing.Optional[int]`` + :type timeout: ``typing.Union[int, None]`` :param expected: expected return codes (0 by default) :type expected: ``typing.Optional[typing.Iterable[]]`` :param raise_on_err: Raise exception on unexpected return code @@ -271,6 +273,7 @@ API: SSHClient and SSHAuth. :type path: str :param times: (atime, mtime) :type times: typing.Optional[typing.Tuple[int, int]] + :rtype: None .. versionadded:: 1.0.0 @@ -347,7 +350,7 @@ API: SSHClient and SSHAuth. .. py:attribute:: username - ``str`` + ``typing.Optional[str]`` .. py:attribute:: public_key diff --git a/doc/source/Subprocess.rst b/doc/source/Subprocess.rst index bf3bfd8..d9b76a5 100644 --- a/doc/source/Subprocess.rst +++ b/doc/source/Subprocess.rst @@ -46,16 +46,16 @@ API: Subprocess :param command: Command for execution :type command: str :param stdin: pass STDIN text to the process - :type stdin: typing.Union[six.text_type, six.binary_type, bytearray, None] + :type stdin: ``typing.Union[typing.AnyStr, bytearray, None]`` :param open_stdout: open STDOUT stream for read - :type open_stdout: bool + :type open_stdout: ``bool`` :param open_stderr: open STDERR stream for read - :type open_stderr: bool + :type open_stderr: ``bool`` :param verbose: produce verbose log record on command call - :type verbose: bool + :type verbose: ``bool`` :param log_mask_re: regex lookup rule to mask command for logger. all MATCHED groups will be replaced by '<*masked*>' - :type log_mask_re: typing.Optional[str] + :type log_mask_re: ``typing.Optional[str]`` :rtype: ``typing.Tuple[subprocess.Popen, None, typing.Optional[typing.IO], typing.Optional[typing.IO], ]`` .. versionadded:: 1.2.0 @@ -66,12 +66,10 @@ API: Subprocess :param command: Command for execution :type command: ``str`` - :param stdin: STDIN passed to execution - :type stdin: ``typing.Union[six.text_type, six.binary_type, None]`` :param verbose: Produce log.info records for command call and output :type verbose: ``bool`` :param timeout: Timeout for command execution. - :type timeout: ``typing.Optional[int]`` + :type timeout: ``typing.Union[int, None]`` :rtype: ExecResult :raises ExecHelperTimeoutError: Timeout exceeded @@ -90,7 +88,7 @@ API: Subprocess :param verbose: Produce log.info records for command call and output :type verbose: ``bool`` :param timeout: Timeout for command execution. - :type timeout: ``typing.Optional[int]`` + :type timeout: ``typing.Union[int, None]`` :param error_info: Text for error details, if fail happens :type error_info: ``typing.Optional[str]`` :param expected: expected return codes (0 by default) @@ -113,7 +111,7 @@ API: Subprocess :param verbose: Produce log.info records for command call and output :type verbose: ``bool`` :param timeout: Timeout for command execution. - :type timeout: ``typing.Optional[int]`` + :type timeout: ``typing.Union[int, None]`` :param error_info: Text for error details, if fail happens :type error_info: ``typing.Optional[str]`` :param raise_on_err: Raise exception on unexpected return code diff --git a/exec_helpers/_api.py b/exec_helpers/_api.py index 55c32ad..07d61f1 100644 --- a/exec_helpers/_api.py +++ b/exec_helpers/_api.py @@ -25,18 +25,13 @@ import logging import re import threading -import typing - -import six # noqa # pylint: disable=unused-import +import typing # noqa # pylint: disable=unused-import from exec_helpers import constants from exec_helpers import exceptions from exec_helpers import exec_result # noqa # pylint: disable=unused-import from exec_helpers import proc_enums -_type_exit_codes = typing.Union[int, proc_enums.ExitCodes] -_type_expected = typing.Optional[typing.Iterable[_type_exit_codes]] - class ExecHelper(object): """ExecHelper global API.""" @@ -136,19 +131,19 @@ def mask(text, rules): # type: (str, str) -> str def execute_async( self, command, # type: str - stdin=None, # type: typing.Union[six.text_type, six.binary_type, bytearray, None] + stdin=None, # type: typing.Union[typing.AnyStr, bytearray, None] open_stdout=True, # type: bool open_stderr=True, # type: bool verbose=False, # type: bool log_mask_re=None, # type: typing.Optional[str] **kwargs - ): + ): # type: (...) -> typing.Tuple[typing.Any, typing.Any, typing.Any, typing.Any,] """Execute command in async mode and return remote interface with IO objects. :param command: Command for execution :type command: str :param stdin: pass STDIN text to the process - :type stdin: typing.Union[six.text_type, six.binary_type, bytearray, None] + :type stdin: typing.Union[typing.AnyStr, bytearray, None] :param open_stdout: open STDOUT stream for read :type open_stdout: bool :param open_stderr: open STDERR stream for read @@ -176,7 +171,7 @@ def _exec_command( interface, # type: typing.Any stdout, # type: typing.Any stderr, # type: typing.Any - timeout, # type: int + timeout, # type: typing.Union[int, None] verbose=False, # type: bool log_mask_re=None, # type: typing.Optional[str] **kwargs @@ -209,7 +204,7 @@ def execute( self, command, # type: str verbose=False, # type: bool - timeout=constants.DEFAULT_TIMEOUT, # type: typing.Optional[int] + timeout=constants.DEFAULT_TIMEOUT, # type: typing.Union[int, None] **kwargs ): # type: (...) -> exec_result.ExecResult """Execute command and wait for return code. @@ -221,7 +216,7 @@ def execute( :param verbose: Produce log.info records for command call and output :type verbose: bool :param timeout: Timeout for command execution. - :type timeout: typing.Optional[int] + :type timeout: typing.Union[int, None] :rtype: ExecResult :raises ExecHelperTimeoutError: Timeout exceeded @@ -259,9 +254,9 @@ def check_call( self, command, # type: str verbose=False, # type: bool - timeout=constants.DEFAULT_TIMEOUT, # type: typing.Optional[int] + timeout=constants.DEFAULT_TIMEOUT, # type: typing.Union[int, None] error_info=None, # type: typing.Optional[str] - expected=None, # type: _type_expected + expected=None, # type: typing.Optional[typing.Iterable[typing.Union[int, proc_enums.ExitCodes]]] raise_on_err=True, # type: bool **kwargs ): # type: (...) -> exec_result.ExecResult @@ -274,11 +269,11 @@ def check_call( :param verbose: Produce log.info records for command call and output :type verbose: bool :param timeout: Timeout for command execution. - :type timeout: typing.Optional[int] + :type timeout: typing.Union[int, None] :param error_info: Text for error details, if fail happens :type error_info: typing.Optional[str] :param expected: expected return codes (0 by default) - :type expected: typing.Optional[typing.Iterable[int]] + :type expected: typing.Optional[typing.Iterable[typing.Union[int, proc_enums.ExitCodes]]] :param raise_on_err: Raise exception on unexpected return code :type raise_on_err: bool :rtype: ExecResult @@ -309,7 +304,7 @@ def check_stderr( self, command, # type: str verbose=False, # type: bool - timeout=constants.DEFAULT_TIMEOUT, # type: typing.Optional[int] + timeout=constants.DEFAULT_TIMEOUT, # type: typing.Union[int, None] error_info=None, # type: typing.Optional[str] raise_on_err=True, # type: bool **kwargs @@ -323,7 +318,7 @@ def check_stderr( :param verbose: Produce log.info records for command call and output :type verbose: bool :param timeout: Timeout for command execution. - :type timeout: typing.Optional[int] + :type timeout: typing.Union[int, None] :param error_info: Text for error details, if fail happens :type error_info: typing.Optional[str] :param raise_on_err: Raise exception on unexpected return code diff --git a/exec_helpers/_api.pyi b/exec_helpers/_api.pyi new file mode 100644 index 0000000..5f27ad0 --- /dev/null +++ b/exec_helpers/_api.pyi @@ -0,0 +1,74 @@ +import logging +import threading +import typing + +from exec_helpers import exec_result, proc_enums + +class ExecHelper: + log_mask_re: typing.Optional[str] = ... + + def __init__(self, logger: logging.Logger, log_mask_re: typing.Optional[str]=...) -> None: ... + + @property + def logger(self) -> logging.Logger: ... + + @property + def lock(self) -> threading.RLock: ... + + def __enter__(self): ... + + def __exit__(self, exc_type: typing.Any, exc_val: typing.Any, exc_tb: typing.Any) -> None: ... + + def _mask_command(self, cmd: str, log_mask_re: typing.Optional[str]=...) -> str: ... + + def execute_async( + self, + command: str, + stdin: typing.Union[typing.AnyStr, bytearray, None]=..., + open_stdout: bool=..., + open_stderr: bool=..., + verbose: bool=..., + log_mask_re: typing.Optional[str]=..., + **kwargs + ) -> typing.Tuple[typing.Any, typing.Any, typing.Any, typing.Any,]: ... + + def _exec_command( + self, + command: str, + interface: typing.Any, + stdout: typing.Any, + stderr: typing.Any, + timeout: typing.Union[int, None], + verbose: bool=..., + log_mask_re: typing.Optional[str]=..., + **kwargs + ) -> exec_result.ExecResult: ... + + def execute( + self, + command: str, + verbose: bool=..., + timeout: typing.Union[int, None]=..., + **kwargs + ) -> exec_result.ExecResult: ... + + def check_call( + self, + command: str, + verbose: bool=..., + timeout: typing.Union[int, None]=..., + error_info: typing.Optional[str]=..., + expected: typing.Optional[typing.Iterable[typing.Union[int, proc_enums.ExitCodes]]]=..., + raise_on_err: bool=..., + **kwargs + ) -> exec_result.ExecResult: ... + + def check_stderr( + self, + command: str, + verbose: bool=..., + timeout: typing.Union[int, None]=..., + error_info: typing.Optional[str]=..., + raise_on_err: bool=..., + **kwargs + ) -> exec_result.ExecResult: ... diff --git a/exec_helpers/_ssh_client_base.py b/exec_helpers/_ssh_client_base.py index e30ca28..65395bc 100644 --- a/exec_helpers/_ssh_client_base.py +++ b/exec_helpers/_ssh_client_base.py @@ -53,11 +53,7 @@ logging.getLogger('paramiko').setLevel(logging.WARNING) logging.getLogger('iso8601').setLevel(logging.WARNING) -_type_ConnectSSH = typing.Union[ - paramiko.client.SSHClient, paramiko.transport.Transport -] -_type_RSAKeys = typing.Iterable[paramiko.RSAKey] -_type_exit_codes = typing.Union[int, proc_enums.ExitCodes] + _type_execute_async = typing.Tuple[ paramiko.Channel, paramiko.ChannelFile, @@ -102,10 +98,10 @@ class _MemorizedSSH(type): @classmethod def __prepare__( mcs, - name, - bases, + name, # type: str + bases, # type: typing.Iterable[typing.Type] **kwargs - ): # pylint: disable=unused-argument + ): # type: (...) -> collections.OrderedDict # pylint: disable=unused-argument """Metaclass magic for object storage. .. versionadded:: 1.2.0 @@ -118,7 +114,7 @@ def __call__( port=22, # type: int username=None, # type: typing.Optional[str] password=None, # type: typing.Optional[str] - private_keys=None, # type: typing.Optional[_type_RSAKeys] + private_keys=None, # type: typing.Optional[typing.Iterable[paramiko.RSAKey]] auth=None, # type: typing.Optional[ssh_auth.SSHAuth] ): # type: (...) -> SSHClientBase """Main memorize method: check for cached instance and return it. @@ -281,7 +277,7 @@ def __init__( port=22, # type: int username=None, # type: typing.Optional[str] password=None, # type: typing.Optional[str] - private_keys=None, # type: typing.Optional[_type_RSAKeys] + private_keys=None, # type: typing.Optional[typing.Iterable[paramiko.RSAKey]] auth=None, # type: typing.Optional[ssh_auth.SSHAuth] ): # type: (...) -> None """SSHClient helper. @@ -367,14 +363,14 @@ def is_alive(self): # type: () -> bool """ return self.__ssh.get_transport() is not None - def __repr__(self): + def __repr__(self): # type: () -> str """Representation for debug purposes.""" return '{cls}(host={host}, port={port}, auth={auth!r})'.format( cls=self.__class__.__name__, host=self.hostname, port=self.port, auth=self.auth ) - def __str__(self): # pragma: no cover + def __str__(self): # type: () -> str # pragma: no cover """Representation for debug purposes.""" return '{cls}(host={host}, port={port}) for user {user}'.format( cls=self.__class__.__name__, host=self.hostname, port=self.port, @@ -539,7 +535,7 @@ def reconnect(self): # type: () -> None def sudo( self, enforce=None # type: typing.Optional[bool] - ): + ): # type: (...) -> typing.ContextManager """Call contextmanager for sudo mode change. :param enforce: Enforce sudo enabled or disabled. By default: None @@ -550,7 +546,7 @@ def sudo( def keepalive( self, enforce=True # type: bool - ): + ): # type: (...) -> typing.ContextManager """Call contextmanager with keepalive mode change. :param enforce: Enforce keepalive enabled or disabled. @@ -564,7 +560,7 @@ def keepalive( def execute_async( self, command, # type: str - stdin=None, # type: typing.Union[six.text_type, six.binary_type, bytearray, None] + stdin=None, # type: typing.Union[typing.AnyStr, bytearray, None] open_stdout=True, # type: bool open_stderr=True, # type: bool verbose=False, # type: bool @@ -576,7 +572,7 @@ def execute_async( :param command: Command for execution :type command: str :param stdin: pass STDIN text to the process - :type stdin: typing.Union[six.text_type, six.binary_type, bytearray, None] + :type stdin: typing.Union[typing.AnyStr, bytearray, None] :param open_stdout: open STDOUT stream for read :type open_stdout: bool :param open_stderr: open STDERR stream for read @@ -647,7 +643,7 @@ def _exec_command( interface, # type: paramiko.channel.Channel stdout, # type: paramiko.channel.ChannelFile stderr, # type: paramiko.channel.ChannelFile - timeout, # type: int + timeout, # type: typing.Union[int, None] verbose=False, # type: bool log_mask_re=None, # type: typing.Optional[str] **kwargs @@ -658,7 +654,7 @@ def _exec_command( :type interface: paramiko.channel.Channel :type stdout: paramiko.channel.ChannelFile :type stderr: paramiko.channel.ChannelFile - :type timeout: int + :type timeout: typing.Union[int, None] :type verbose: bool :param log_mask_re: regex lookup rule to mask command for logger. all MATCHED groups will be replaced by '<*masked*>' @@ -748,7 +744,7 @@ def execute_through_host( auth=None, # type: typing.Optional[ssh_auth.SSHAuth] target_port=22, # type: int verbose=False, # type: bool - timeout=constants.DEFAULT_TIMEOUT, # type: typing.Optional[int] + timeout=constants.DEFAULT_TIMEOUT, # type: typing.Union[int, None] get_pty=False, # type: bool **kwargs ): # type: (...) -> exec_result.ExecResult @@ -765,7 +761,7 @@ def execute_through_host( :param verbose: Produce log.info records for command call and output :type verbose: bool :param timeout: Timeout for command execution. - :type timeout: typing.Optional[int] + :type timeout: typing.Union[int, None] :param get_pty: open PTY on target machine :type get_pty: bool :rtype: ExecResult @@ -826,7 +822,7 @@ def execute_together( cls, remotes, # type: typing.Iterable[SSHClientBase] command, # type: str - timeout=constants.DEFAULT_TIMEOUT, # type: typing.Optional[int] + timeout=constants.DEFAULT_TIMEOUT, # type: typing.Union[int, None] expected=None, # type: typing.Optional[typing.Iterable[int]] raise_on_err=True, # type: bool **kwargs @@ -838,7 +834,7 @@ def execute_together( :param command: Command for execution :type command: str :param timeout: Timeout for command execution. - :type timeout: typing.Optional[int] + :type timeout: typing.Union[int, None] :param expected: expected return codes (0 by default) :type expected: typing.Optional[typing.Iterable[]] :param raise_on_err: Raise exception on unexpected return code @@ -930,7 +926,7 @@ def get_result(): # type: () -> exec_result.ExecResult ) return results - def open(self, path, mode='r'): + def open(self, path, mode='r'): # type: (str, str) -> paramiko.SFTPFile """Open file on remote using SFTP session. :type path: str @@ -963,7 +959,7 @@ def utime( self, path, # type: str times=None # type: typing.Optional[typing.Tuple[int, int]] - ): + ): # type: (...) -> None """Set atime, mtime. :param path: filesystem object path diff --git a/exec_helpers/_ssh_client_base.pyi b/exec_helpers/_ssh_client_base.pyi new file mode 100644 index 0000000..ba004d1 --- /dev/null +++ b/exec_helpers/_ssh_client_base.pyi @@ -0,0 +1,151 @@ +import collections +import typing + +import paramiko # type: ignore + +from exec_helpers import exec_result, ssh_auth, _api + + +class _MemorizedSSH(type): + @classmethod + def __prepare__(mcs, name: str, bases: typing.Iterable[typing.Type], **kwargs) -> collections.OrderedDict: ... + + def __call__( # type: ignore + cls, + host: str, + port: int=..., + username: typing.Optional[str]=..., + password: typing.Optional[str]=..., + private_keys: typing.Optional[typing.Iterable[paramiko.RSAKey]]=..., + auth: typing.Optional[ssh_auth.SSHAuth]=... + ) -> SSHClientBase: ... + + @classmethod + def clear_cache(mcs) -> None: ... + + @classmethod + def close_connections(mcs) -> None: ... + + +class SSHClientBase(_api.ExecHelper, metaclass=_MemorizedSSH): + def __hash__(self): ... + + def __init__( + self, + host: str, + port: int=..., + username: typing.Optional[str]=..., + password: typing.Optional[str]=..., + private_keys: typing.Optional[typing.Iterable[paramiko.RSAKey]]=..., + auth: typing.Optional[ssh_auth.SSHAuth]=... + ) -> None: ... + + @property + def auth(self) -> ssh_auth.SSHAuth: ... + + @property + def hostname(self) -> str: ... + + @property + def port(self) -> int: ... + + @property + def is_alive(self) -> bool: ... + + def __repr__(self) -> str: ... + + def __str__(self) -> str: ... + + @property + def _ssh(self) -> paramiko.SSHClient: ... + + @property + def _sftp(self) -> paramiko.sftp_client.SFTPClient: ... + + def close(self) -> None: ... + + @classmethod + def close(cls) -> None: ... + + @classmethod + def _clear_cache(cls) -> None: ... + + def __del__(self) -> None: ... + + def __exit__(self, exc_type: typing.Any, exc_val: typing.Any, exc_tb: typing.Any) -> None: ... + + @property + def sudo_mode(self) -> bool: ... + + @sudo_mode.setter + def sudo_mode(self, mode: bool) -> None: ... + + @property + def keepalive_mode(self) -> bool: ... + + @keepalive_mode.setter + def keepalive_mode(self, mode: bool) -> None: ... + + def reconnect(self) -> None: ... + + def sudo(self, enforce: typing.Optional[bool]=...) -> typing.ContextManager: ... + + def keepalive(self, enforce: bool=...) -> typing.ContextManager: ... + + def execute_async( + self, + command: str, + stdin: typing.Union[typing.AnyStr, bytearray, None]=..., + open_stdout: bool=..., + open_stderr: bool=..., + verbose: bool=..., + log_mask_re: typing.Optional[str]=..., + **kwargs + ) -> typing.Tuple[paramiko.Channel, paramiko.ChannelFile, typing.Optional[paramiko.ChannelFile], typing.Optional[paramiko.ChannelFile]]: ... + + def _exec_command( + self, + command: str, + interface: paramiko.channel.Channel, + stdout: paramiko.channel.ChannelFile, + stderr: paramiko.channel.ChannelFile, + timeout: typing.Union[int, None], + verbose: bool=..., + log_mask_re: typing.Optional[str]=..., + **kwargs + ) -> exec_result.ExecResult: ... + + def execute_through_host( + self, + hostname: str, + command: str, + auth: typing.Optional[ssh_auth.SSHAuth]=..., + target_port: int=..., + verbose: bool=..., + timeout: typing.Union[int, None]=..., + get_pty: bool=..., + **kwargs + ) -> exec_result.ExecResult: ... + + @classmethod + def execute_together( + cls, + remotes: typing.Iterable[SSHClientBase], + command: str, + timeout: typing.Union[int, None]=..., + expected: typing.Optional[typing.Iterable[int]]=..., + raise_on_err: bool=..., + **kwargs + ) -> typing.Dict[typing.Tuple[str, int], exec_result.ExecResult]: ... + + def open(self, path: str, mode: str = ...) -> paramiko.SFTPFile: ... + + def exists(self, path: str) -> bool: ... + + def stat(self, path: str) -> paramiko.sftp_attr.SFTPAttributes: ... + + def utime(self, path: str, times: typing.Optional[typing.Tuple[int, int]]=...) -> None: ... + + def isfile(self, path: str) -> bool: ... + + def isdir(self, path: str) -> bool: ... diff --git a/exec_helpers/exceptions.py b/exec_helpers/exceptions.py index 28a486e..07d2cb4 100644 --- a/exec_helpers/exceptions.py +++ b/exec_helpers/exceptions.py @@ -18,7 +18,7 @@ from __future__ import division from __future__ import unicode_literals -import typing +import typing # noqa # pylint: disable=unused-import from exec_helpers import proc_enums @@ -31,9 +31,6 @@ 'ParallelCallExceptions', ) -_type_exit_codes = typing.Union[int, proc_enums.ExitCodes] -_type_multiple_results = typing.Dict[typing.Tuple[str, int], typing.Any] - class ExecHelperError(Exception): """Base class for all exceptions raised inside.""" @@ -69,8 +66,8 @@ class CalledProcessError(ExecCalledProcessError): def __init__( self, - result=None, # type: exec_result.ExecResult - expected=None, # type: typing.Optional[typing.List[_type_exit_codes]] + result, # type: exec_result.ExecResult + expected=None, # type: typing.Optional[typing.List[typing.Union[int, proc_enums.ExitCodes]]] ): # type: (...) -> None """Exception for error on process calls. @@ -111,12 +108,12 @@ def cmd(self): # type: () -> str return self.result.cmd @property - def stdout(self): # type: () -> str + def stdout(self): # type: () -> typing.Text """Command stdout.""" return self.result.stdout_str @property - def stderr(self): # type: () -> str + def stderr(self): # type: () -> typing.Text """Command stderr.""" return self.result.stderr_str @@ -136,9 +133,9 @@ def __init__( self, command, # type: str exceptions, # type: typing.Dict[typing.Tuple[str, int], Exception] - errors, # type: _type_multiple_results - results, # type: _type_multiple_results - expected=None, # type: typing.Optional[typing.List[_type_exit_codes]] + errors, # type: typing.Dict[typing.Tuple[str, int], exec_result.ExecResult] + results, # type: typing.Dict[typing.Tuple[str, int], exec_result.ExecResult] + expected=None, # type: typing.Optional[typing.List[typing.Union[int, proc_enums.ExitCodes]]] ): # type: (...) -> None """Exception raised during parallel call as result of exceptions. @@ -190,9 +187,9 @@ class ParallelCallProcessError(ExecCalledProcessError): def __init__( self, command, # type: str - errors, # type: _type_multiple_results - results, # type: _type_multiple_results - expected=None, # type: typing.Optional[typing.List[_type_exit_codes]] + errors, # type: typing.Dict[typing.Tuple[str, int], exec_result.ExecResult] + results, # type: typing.Dict[typing.Tuple[str, int], exec_result.ExecResult] + expected=None, # type: typing.Optional[typing.List[typing.Union[int, proc_enums.ExitCodes]]] ): # type: (...) -> None """Exception during parallel execution. diff --git a/exec_helpers/exceptions.pyi b/exec_helpers/exceptions.pyi new file mode 100644 index 0000000..4a22763 --- /dev/null +++ b/exec_helpers/exceptions.pyi @@ -0,0 +1,72 @@ +import typing +from exec_helpers import proc_enums, exec_result + +class ExecHelperError(Exception): + ... + +class DeserializeValueError(ExecHelperError, ValueError): + ... + +class ExecHelperTimeoutError(ExecHelperError): + ... + +class ExecCalledProcessError(ExecHelperError): + ... + + +class CalledProcessError(ExecCalledProcessError): + + result: exec_result.ExecResult = ... + + expected: typing.List[typing.Union[int, proc_enums.ExitCodes]] = ... + + def __init__( + self, + result: exec_result.ExecResult, + expected: typing.Optional[typing.List[typing.Union[int, proc_enums.ExitCodes]]]=... + ) -> None: ... + + @property + def returncode(self) -> typing.Union[int, proc_enums.ExitCodes]: ... + + @property + def cmd(self) -> str: ... + + @property + def stdout(self) -> typing.Text: ... + + @property + def stderr(self) -> typing.Text: ... + + +class ParallelCallExceptions(ExecCalledProcessError): + + expected: typing.List[typing.Union[int, proc_enums.ExitCodes]] = ... + cmd: str = ... + exceptions: typing.Dict[typing.Tuple[str, int], Exception] = ... + errors: typing.Dict[typing.Tuple[str, int], exec_result.ExecResult] = ... + results: typing.Dict[typing.Tuple[str, int], exec_result.ExecResult] = ... + + def __init__( + self, + command: str, + exceptions: typing.Dict[typing.Tuple[str, int], Exception], + errors: typing.Dict[typing.Tuple[str, int], exec_result.ExecResult], + results: typing.Dict[typing.Tuple[str, int], exec_result.ExecResult], + expected: typing.Optional[typing.List[typing.Union[int, proc_enums.ExitCodes]]]=... + ) -> None: ... + +class ParallelCallProcessError(ExecCalledProcessError): + + expected: typing.List[typing.Union[int, proc_enums.ExitCodes]] = ... + cmd: str = ... + errors: typing.Dict[typing.Tuple[str, int], exec_result.ExecResult] = ... + results: typing.Dict[typing.Tuple[str, int], exec_result.ExecResult] = ... + + def __init__( + self, + command: str, + errors: typing.Dict[typing.Tuple[str, int], exec_result.ExecResult], + results: typing.Dict[typing.Tuple[str, int], exec_result.ExecResult], + expected: typing.Optional[typing.List[typing.Union[int, proc_enums.ExitCodes]]]=... + ) -> None: ... diff --git a/exec_helpers/exec_result.py b/exec_helpers/exec_result.py index 64b66e3..7401f91 100644 --- a/exec_helpers/exec_result.py +++ b/exec_helpers/exec_result.py @@ -24,7 +24,7 @@ import json import logging import threading -import typing +import typing # noqa # pylint: disable=unused-import import six import yaml @@ -35,7 +35,6 @@ __all__ = ('ExecResult', ) logger = logging.getLogger(__name__) -_type_exit_codes = typing.Union[int, proc_enums.ExitCodes] class ExecResult(object): @@ -51,17 +50,17 @@ class ExecResult(object): def __init__( self, cmd, # type: str - stdin=None, # type: typing.Union[six.text_type, six.binary_type, bytearray, None] + stdin=None, # type: typing.Union[typing.AnyStr, bytearray, None] stdout=None, # type: typing.Optional[typing.Iterable[bytes]] stderr=None, # type: typing.Optional[typing.Iterable[bytes]] - exit_code=proc_enums.ExitCodes.EX_INVALID # type: _type_exit_codes + exit_code=proc_enums.ExitCodes.EX_INVALID # type: typing.Union[int, proc_enums.ExitCodes] ): # type: (...) -> None """Command execution result. :param cmd: command :type cmd: str :param stdin: string STDIN - :type stdin: typing.Union[six.text_type, six.binary_type, bytearray, None] + :type stdin: typing.Union[typing.AnyStr, bytearray, None] :param stdout: binary STDOUT :type stdout: typing.Optional[typing.Iterable[bytes]] :param stderr: binary STDERR @@ -76,7 +75,7 @@ def __init__( stdin = self._get_str_from_bin(bytearray(stdin)) elif isinstance(stdin, bytearray): stdin = self._get_str_from_bin(stdin) - self.__stdin = stdin + self.__stdin = stdin # type: typing.Optional[typing.Text] self.__stdout = tuple(stdout) if stdout is not None else () # type: typing.Tuple[bytes] self.__stderr = tuple(stderr) if stderr is not None else () # type: typing.Tuple[bytes] @@ -118,7 +117,7 @@ def _get_bytearray_from_array( return bytearray(b''.join(src)) @staticmethod - def _get_str_from_bin(src): # type: (bytearray) -> str + def _get_str_from_bin(src): # type: (bytearray) -> typing.Text """Join data in list to the string, with python 2&3 compatibility. :type src: bytearray @@ -130,7 +129,7 @@ def _get_str_from_bin(src): # type: (bytearray) -> str ) @classmethod - def _get_brief(cls, data): # type: (typing.Tuple[bytes]) -> str + def _get_brief(cls, data): # type: (typing.Tuple[bytes]) -> typing.Text """Get brief output: 7 lines maximum (3 first + ... + 3 last). :type data: typing.Tuple[bytes] @@ -142,7 +141,7 @@ def _get_brief(cls, data): # type: (typing.Tuple[bytes]) -> str ) @property - def cmd(self): # type: () -> str + def cmd(self): # type: () -> typing.Text """Executed command. :rtype: str @@ -150,10 +149,10 @@ def cmd(self): # type: () -> str return self.__cmd @property - def stdin(self): # type: () -> typing.Optional[str] + def stdin(self): # type: () -> typing.Optional[typing.Text] """Stdin input as string. - :rtype: str + :rtype: typing.Optional[typing.Text] """ return self.__stdin @@ -200,7 +199,7 @@ def read_stdout( src=None, # type: typing.Optional[typing.Iterable] log=None, # type: typing.Optional[logging.Logger] verbose=False # type: bool - ): + ): # type: (...) -> None """Read stdout file-like object to stdout. :param src: source @@ -225,7 +224,7 @@ def read_stderr( src=None, # type: typing.Optional[typing.Iterable] log=None, # type: typing.Optional[logging.Logger] verbose=False # type: bool - ): + ): # type: (...) -> None """Read stderr file-like object to stdout. :param src: source @@ -266,7 +265,7 @@ def stderr_bin(self): # type: () -> bytearray return self._get_bytearray_from_array(self.stderr) @property - def stdout_str(self): # type: () -> str + def stdout_str(self): # type: () -> typing.Text """Stdout output as string. :rtype: str @@ -277,7 +276,7 @@ def stdout_str(self): # type: () -> str return self.__stdout_str @property - def stderr_str(self): # type: () -> str + def stderr_str(self): # type: () -> typing.Text """Stderr output as string. :rtype: str @@ -288,7 +287,7 @@ def stderr_str(self): # type: () -> str return self.__stderr_str @property - def stdout_brief(self): # type: () -> str + def stdout_brief(self): # type: () -> typing.Text """Brief stdout output (mostly for exceptions). :rtype: str @@ -299,7 +298,7 @@ def stdout_brief(self): # type: () -> str return self.__stdout_brief @property - def stderr_brief(self): # type: () -> str + def stderr_brief(self): # type: () -> typing.Text """Brief stderr output (mostly for exceptions). :rtype: str @@ -318,7 +317,7 @@ def exit_code(self): # type: () -> typing.Union[int, proc_enums.ExitCodes] return self.__exit_code @exit_code.setter - def exit_code(self, new_val): # type: (_type_exit_codes) -> None + def exit_code(self, new_val): # type: (typing.Union[int, proc_enums.ExitCodes]) -> None """Return(exit) code of command. :type new_val: int @@ -387,7 +386,7 @@ def __dir__(self): 'lock' ] - def __getitem__(self, item): + def __getitem__(self, item): # type: (str) -> typing.Any """Dict like get data.""" if item in dir(self): return getattr(self, item) @@ -397,7 +396,7 @@ def __getitem__(self, item): ) ) - def __repr__(self): + def __repr__(self): # type: () -> str """Representation for debugging.""" return ( '{cls}(cmd={cmd!r}, stdout={stdout}, stderr={stderr}, ' @@ -409,7 +408,7 @@ def __repr__(self): exit_code=self.exit_code )) - def __str__(self): + def __str__(self): # type: () -> str """Representation for logging.""" return ( "{cls}(\n\tcmd={cmd!r}," @@ -424,7 +423,7 @@ def __str__(self): ) ) - def __eq__(self, other): + def __eq__(self, other): # type: (typing.Any) -> bool """Comparision.""" return all( ( @@ -433,7 +432,7 @@ def __eq__(self, other): ) ) - def __ne__(self, other): + def __ne__(self, other): # type: (typing.Any) -> bool """Comparision.""" return not self.__eq__(other) diff --git a/exec_helpers/exec_result.pyi b/exec_helpers/exec_result.pyi new file mode 100644 index 0000000..9780772 --- /dev/null +++ b/exec_helpers/exec_result.pyi @@ -0,0 +1,96 @@ +import datetime +import logging +import threading +import typing +from exec_helpers import proc_enums + +class ExecResult: + def __init__( + self, + cmd: str, + stdin: typing.Union[typing.AnyStr, bytearray, None]=..., + stdout: typing.Optional[typing.Iterable[bytes]]=..., + stderr: typing.Optional[typing.Iterable[bytes]]=..., + exit_code: typing.Union[int, proc_enums.ExitCodes]=... + ) -> None: ... + + @property + def lock(self) -> threading.RLock: ... + + @property + def timestamp(self) -> typing.Optional[datetime.datetime]: ... + + @staticmethod + def _get_bytearray_from_array(src: typing.Iterable[bytes]) -> bytearray: ... + + @staticmethod + def _get_str_from_bin(src: bytearray) -> typing.Text: ... + + @classmethod + def _get_brief(cls, data: typing.Tuple[bytes]) -> typing.Text: ... + + @property + def cmd(self) -> str: ... + + @property + def stdin(self) -> typing.Optional[typing.Text]: ... + + @property + def stdout(self) -> typing.Tuple[bytes]: ... + + @property + def stderr(self) -> typing.Tuple[bytes]: ... + + def read_stdout( + self, + src: typing.Optional[typing.Iterable]=..., + log: typing.Optional[logging.Logger]=..., + verbose: bool=... + ) -> None: ... + + def read_stderr( + self, + src: typing.Optional[typing.Iterable]=..., + log: typing.Optional[logging.Logger]=..., + verbose: bool=... + ) -> None: ... + + @property + def stdout_bin(self) -> bytearray: ... + + @property + def stderr_bin(self) -> bytearray: ... + + @property + def stdout_str(self) -> typing.Text: ... + + @property + def stderr_str(self) -> typing.Text: ... + + @property + def stdout_brief(self) -> typing.Text: ... + + @property + def stderr_brief(self) -> typing.Text: ... + + @property + def exit_code(self) -> typing.Union[int, proc_enums.ExitCodes]: ... + + @exit_code.setter + def exit_code(self, new_val: typing.Union[int, proc_enums.ExitCodes]) -> None: ... + + @property + def stdout_json(self) -> typing.Any: ... + + @property + def stdout_yaml(self) -> typing.Any: ... + + def __getitem__(self, item: str) -> typing.Any: ... + + def __repr__(self) -> str: ... + + def __str__(self) -> str: ... + + def __eq__(self, other: typing.Any) -> bool: ... + + def __ne__(self, other: typing.Any) -> bool: ... diff --git a/exec_helpers/proc_enums.py b/exec_helpers/proc_enums.py index 3437407..ddf4bb5 100644 --- a/exec_helpers/proc_enums.py +++ b/exec_helpers/proc_enums.py @@ -84,7 +84,7 @@ def __str__(self): # pragma: no cover digit_type = int else: # pragma: no cover # noinspection PyUnresolvedReferences - digit_type = long # noqa # pylint: disable=undefined-variable + digit_type = long # noqa # pylint: disable=undefined-variable, long-builtin @enum.unique diff --git a/exec_helpers/py.typed b/exec_helpers/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/exec_helpers/ssh_auth.py b/exec_helpers/ssh_auth.py index 63ca33b..171c741 100644 --- a/exec_helpers/ssh_auth.py +++ b/exec_helpers/ssh_auth.py @@ -88,7 +88,7 @@ def __init__( self.__passphrase = passphrase @property - def username(self): # type: () -> str + def username(self): # type: () -> typing.Optional[str] """Username for auth. :rtype: str @@ -238,7 +238,7 @@ def __copy__(self): # type: () -> SSHAuth keys=self.__keys ) - def __repr__(self): + def __repr__(self): # type: (...) -> str """Representation for debug purposes.""" _key = ( None if self.__key is None else @@ -268,7 +268,7 @@ def __repr__(self): keys=_keys) ) - def __str__(self): + def __str__(self): # type: (...) -> str """Representation for debug purposes.""" return ( '{cls} for {username}'.format( diff --git a/exec_helpers/ssh_auth.pyi b/exec_helpers/ssh_auth.pyi new file mode 100644 index 0000000..9711e0b --- /dev/null +++ b/exec_helpers/ssh_auth.pyi @@ -0,0 +1,45 @@ +import io +import paramiko # type: ignore +import typing + +class SSHAuth: + def __init__( + self, + username: typing.Optional[str]=..., + password: typing.Optional[str]=..., + key: typing.Optional[paramiko.RSAKey]=..., + keys: typing.Optional[typing.Iterable[paramiko.RSAKey]]=..., + key_filename: typing.Union[typing.List[str], str, None]=..., + passphrase: typing.Optional[str]=... + ) -> None: ... + + @property + def username(self) -> typing.Optional[str]: ... + + @property + def public_key(self) -> typing.Optional[str]: ... + + @property + def key_filename(self) -> typing.Union[typing.List[str], str, None]: ... + + def enter_password(self, tgt: io.StringIO) -> None: ... + + def connect( + self, + client: typing.Union[paramiko.SSHClient, paramiko.Transport], + hostname: typing.Optional[str]=..., + port: int=..., + log: bool=... + ) -> None: ... + + def __eq__(self, other: typing.Any) -> bool: ... + + def __ne__(self, other: typing.Any) -> bool: ... + + def __deepcopy__(self, memo: typing.Any) -> SSHAuth: ... + + def __copy__(self) -> SSHAuth: ... + + def __repr__(self) -> str: ... + + def __str__(self) -> str: ... diff --git a/exec_helpers/subprocess_runner.py b/exec_helpers/subprocess_runner.py index 57625df..8b79cc2 100644 --- a/exec_helpers/subprocess_runner.py +++ b/exec_helpers/subprocess_runner.py @@ -41,22 +41,22 @@ from exec_helpers import exceptions from exec_helpers import _log_templates -logger = logging.getLogger(__name__) +logger = logging.getLogger(__name__) # type: logging.Logger # noinspection PyUnresolvedReferences devnull = open(os.devnull) # subprocess.DEVNULL is py3.3+ -_win = sys.platform == "win32" -_posix = 'posix' in sys.builtin_module_names +_win = sys.platform == "win32" # type: bool +_posix = 'posix' in sys.builtin_module_names # type: bool if _posix: # pragma: no cover import fcntl # pylint: disable=import-error elif _win: # pragma: no cover - # noinspection PyUnresolvedReferences - import msvcrt # pylint: disable=import-error import ctypes from ctypes import wintypes # pylint: disable=import-error from ctypes import windll # pylint: disable=import-error + # noinspection PyUnresolvedReferences + import msvcrt # pylint: disable=import-error class SingletonMeta(type): @@ -66,7 +66,7 @@ class SingletonMeta(type): """ _instances = {} # type: typing.Dict[typing.Type, typing.Any] - _lock = threading.RLock() + _lock = threading.RLock() # type: threading.RLock def __call__(cls, *args, **kwargs): """Singleton.""" @@ -80,10 +80,10 @@ def __call__(cls, *args, **kwargs): @classmethod def __prepare__( mcs, - name, - bases, + name, # type: str + bases, # type: typing.Iterable[typing.Type] **kwargs - ): # pylint: disable=unused-argument + ): # type: (...) -> collections.OrderedDict # pylint: disable=unused-argument """Metaclass magic for object storage. .. versionadded:: 1.2.0 @@ -151,7 +151,7 @@ def _exec_command( interface, # type: subprocess.Popen stdout, # type: typing.Optional[typing.IO] stderr, # type: typing.Optional[typing.IO] - timeout, # type: int + timeout, # type: typing.Union[int, None] verbose=False, # type: bool log_mask_re=None, # type: typing.Optional[str] **kwargs @@ -167,7 +167,7 @@ def _exec_command( :param stderr: STDERR pipe or file-like object :type stderr: typing.Any :param timeout: Timeout for command execution - :type timeout: int + :type timeout: typing.Union[int, None] :param verbose: produce verbose log record on command call :type verbose: bool :param log_mask_re: regex lookup rule to mask command for logger. @@ -203,7 +203,7 @@ def poll_streams(): verbose=verbose ) - @threaded.threadpooled() + @threaded.threadpooled def poll_pipes(stop, ): # type: (threading.Event) -> None """Polling task for FIFO buffers. @@ -274,7 +274,7 @@ def poll_pipes(stop, ): # type: (threading.Event) -> None def execute_async( self, command, # type: str - stdin=None, # type: typing.Union[six.text_type, six.binary_type, bytearray, None] + stdin=None, # type: typing.Union[typing.AnyStr, bytearray, None] open_stdout=True, # type: bool open_stderr=True, # type: bool verbose=False, # type: bool @@ -286,7 +286,7 @@ def execute_async( :param command: Command for execution :type command: str :param stdin: pass STDIN text to the process - :type stdin: typing.Union[six.text_type, six.binary_type, bytearray, None] + :type stdin: typing.Union[typing.AnyStr, bytearray, None] :param open_stdout: open STDOUT stream for read :type open_stdout: bool :param open_stderr: open STDERR stream for read diff --git a/exec_helpers/subprocess_runner.pyi b/exec_helpers/subprocess_runner.pyi new file mode 100644 index 0000000..ff2c768 --- /dev/null +++ b/exec_helpers/subprocess_runner.pyi @@ -0,0 +1,55 @@ +import collections +import logging +import subprocess +import threading +import typing + +from exec_helpers import exec_result, _api + +logger: logging.Logger +devnull: typing.IO + +_win: bool +_posix: bool + +class SingletonMeta(type): + _instances: typing.Dict[typing.Type, typing.Any] = ... + _lock: threading.RLock = ... + + def __call__(cls, *args, **kwargs): ... + + @classmethod + def __prepare__(mcs, name: str, bases: typing.Iterable[typing.Type], **kwargs) -> collections.OrderedDict: ... + + +def set_nonblocking_pipe(pipe: typing.Any) -> None: ... + + +class Subprocess(_api.ExecHelper, metaclass=SingletonMeta): + def __init__( + self, + log_mask_re: typing.Optional[str]=... + ) -> None: ... + + def _exec_command( + self, + command: str, + interface: subprocess.Popen, + stdout: typing.Optional[typing.IO], + stderr: typing.Optional[typing.IO], + timeout: typing.Union[int, None], + verbose: bool=..., + log_mask_re: typing.Optional[str]=..., + **kwargs + ) -> exec_result.ExecResult: ... + + def execute_async( + self, + command: str, + stdin: typing.Union[typing.AnyStr, bytearray, None]=..., + open_stdout: bool=..., + open_stderr: bool=..., + verbose: bool=..., + log_mask_re: typing.Optional[str]=..., + **kwargs + ) -> typing.Tuple[subprocess.Popen, None, typing.Optional[typing.IO], typing.Optional[typing.IO]]: ... diff --git a/setup.py b/setup.py index fc30024..bcce286 100644 --- a/setup.py +++ b/setup.py @@ -25,6 +25,7 @@ import collections from distutils.command import build_ext import distutils.errors +import glob import os.path import shutil import sys @@ -273,6 +274,14 @@ def get_simple_vars_from_src(src): ], }, install_requires=required, + package_data={ + str('exec_helpers'): [ + os.path.basename(filename) + for filename in glob.glob(os.path.join('exec_helpers', '*.pyi')) + ] + [ + 'py.typed' + ], + }, ) if cythonize is not None: setup_args['ext_modules'] = ext_modules