exec-helpers 8.1.3__tar.gz → 8.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- exec_helpers-8.2.0/.pre-commit-config.yaml +34 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/PKG-INFO +6 -8
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/classifiers.txt +0 -2
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/_ssh_base.py +17 -9
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/_ssh_helpers.py +1 -1
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/_version.py +3 -3
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/api.py +6 -7
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/async_api/api.py +6 -7
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/async_api/subprocess.py +7 -7
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/exec_result.py +5 -5
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/proc_enums.py +1 -1
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/subprocess.py +3 -3
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers.egg-info/PKG-INFO +6 -8
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers.egg-info/SOURCES.txt +0 -3
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers.egg-info/requires.txt +3 -3
- exec_helpers-8.2.0/pyproject.toml +425 -0
- exec_helpers-8.1.3/.pre-commit-config.yaml +0 -44
- exec_helpers-8.1.3/flake8_requirements.txt +0 -14
- exec_helpers-8.1.3/mypy_requirements.txt +0 -1
- exec_helpers-8.1.3/pyproject.toml +0 -270
- exec_helpers-8.1.3/pytest_requirements.txt +0 -6
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/.editorconfig +0 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/.readthedocs.yaml +0 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/CI_REQUIREMENTS.txt +0 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/LICENSE +0 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/MANIFEST.in +0 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/README.rst +0 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/build_requirements.txt +0 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/__init__.py +0 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/__init__.pyi +0 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/_helpers.py +0 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/_log_templates.py +0 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/_subprocess_helpers.py +0 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/async_api/__init__.py +0 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/async_api/__init__.pyi +0 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/async_api/exec_result.py +0 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/constants.py +0 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/exceptions.py +0 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/py.typed +0 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/ssh.py +0 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/ssh_auth.py +0 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers.egg-info/dependency_links.txt +0 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers.egg-info/not-zip-safe +0 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers.egg-info/top_level.txt +0 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/requirements.txt +0 -0
- {exec_helpers-8.1.3 → exec_helpers-8.2.0}/setup.cfg +0 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v6.0.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: check-yaml
|
|
6
|
+
- id: check-toml
|
|
7
|
+
- id: end-of-file-fixer
|
|
8
|
+
- id: trailing-whitespace
|
|
9
|
+
- id: mixed-line-ending
|
|
10
|
+
|
|
11
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
12
|
+
# Ruff version.
|
|
13
|
+
rev: v0.15.13
|
|
14
|
+
hooks:
|
|
15
|
+
# Run the linter.
|
|
16
|
+
- id: ruff-check
|
|
17
|
+
args: [ --fix ]
|
|
18
|
+
# Run the formatter.
|
|
19
|
+
- id: ruff-format
|
|
20
|
+
|
|
21
|
+
- repo: https://github.com/pre-commit/pygrep-hooks
|
|
22
|
+
rev: v1.10.0 # Use the ref you want to point at
|
|
23
|
+
hooks:
|
|
24
|
+
- id: python-check-blanket-noqa
|
|
25
|
+
- id: python-check-blanket-type-ignore
|
|
26
|
+
- id: rst-directive-colons
|
|
27
|
+
- id: rst-inline-touching-normal
|
|
28
|
+
|
|
29
|
+
- repo: https://github.com/tox-dev/pyproject-fmt
|
|
30
|
+
# Use the sha / tag you want to point at
|
|
31
|
+
# or use `pre-commit autoupdate` to get the latest version
|
|
32
|
+
rev: "v2.21.2"
|
|
33
|
+
hooks:
|
|
34
|
+
- id: pyproject-fmt
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: exec-helpers
|
|
3
|
-
Version: 8.
|
|
3
|
+
Version: 8.2.0
|
|
4
4
|
Summary: Execution helpers for simplified usage of subprocess and ssh.
|
|
5
5
|
Author-email: Aleksei Stepanov <penguinolog@gmail.com>
|
|
6
6
|
Maintainer-email: Aleksei Stepanov <penguinolog@gmail.com>, Antonio Esposito <esposito.cloud@gmail.com>, Dennis Dmitriev <dis-xcom@gmail.com>
|
|
@@ -8,14 +8,12 @@ License-Expression: Apache-2.0
|
|
|
8
8
|
Project-URL: Documentation, https://exec-helpers.readthedocs.io/
|
|
9
9
|
Project-URL: Repository, https://github.com/python-useful-helpers/exec-helpers
|
|
10
10
|
Project-URL: Bug Tracker, https://github.com/python-useful-helpers/exec-helpers/issues
|
|
11
|
-
Keywords: subprocess
|
|
11
|
+
Keywords: ssh,subprocess
|
|
12
12
|
Classifier: Development Status :: 5 - Production/Stable
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
16
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
19
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
@@ -23,7 +21,7 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
23
21
|
Classifier: Programming Language :: Python :: 3.15
|
|
24
22
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
25
23
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
26
|
-
Requires-Python: >=3.
|
|
24
|
+
Requires-Python: >=3.11.0
|
|
27
25
|
Description-Content-Type: text/x-rst
|
|
28
26
|
License-File: LICENSE
|
|
29
27
|
Requires-Dist: paramiko>=2.4
|
|
@@ -33,13 +31,13 @@ Provides-Extra: xml
|
|
|
33
31
|
Provides-Extra: lxml
|
|
34
32
|
Requires-Dist: lxml>=4.6.2; extra == "lxml"
|
|
35
33
|
Provides-Extra: yaml
|
|
36
|
-
Requires-Dist:
|
|
34
|
+
Requires-Dist: pyyaml>=3.12; extra == "yaml"
|
|
37
35
|
Provides-Extra: all-formats
|
|
38
36
|
Requires-Dist: lxml>=4.6.2; extra == "all-formats"
|
|
39
|
-
Requires-Dist:
|
|
37
|
+
Requires-Dist: pyyaml>=3.12; extra == "all-formats"
|
|
40
38
|
Provides-Extra: all-formats
|
|
41
39
|
Requires-Dist: lxml>=4.6.2; extra == "all-formats"
|
|
42
|
-
Requires-Dist:
|
|
40
|
+
Requires-Dist: pyyaml>=3.12; extra == "all-formats"
|
|
43
41
|
Dynamic: license-file
|
|
44
42
|
|
|
45
43
|
exec-helpers
|
|
@@ -3,8 +3,6 @@ Intended Audience :: Developers
|
|
|
3
3
|
Topic :: Software Development :: Libraries :: Python Modules
|
|
4
4
|
Programming Language :: Python :: 3
|
|
5
5
|
Programming Language :: Python :: 3 :: Only
|
|
6
|
-
Programming Language :: Python :: 3.9
|
|
7
|
-
Programming Language :: Python :: 3.10
|
|
8
6
|
Programming Language :: Python :: 3.11
|
|
9
7
|
Programming Language :: Python :: 3.12
|
|
10
8
|
Programming Language :: Python :: 3.13
|
|
@@ -50,8 +50,7 @@ if typing.TYPE_CHECKING:
|
|
|
50
50
|
from collections.abc import Iterable
|
|
51
51
|
from collections.abc import Sequence
|
|
52
52
|
from types import TracebackType
|
|
53
|
-
|
|
54
|
-
from typing_extensions import Self
|
|
53
|
+
from typing import Self
|
|
55
54
|
|
|
56
55
|
from exec_helpers.api import CalledProcessErrorSubClassT
|
|
57
56
|
from exec_helpers.api import CommandT
|
|
@@ -66,8 +65,8 @@ if typing.TYPE_CHECKING:
|
|
|
66
65
|
|
|
67
66
|
__all__ = ("SSHClientBase", "SshExecuteAsyncResult", "SupportPathT")
|
|
68
67
|
|
|
69
|
-
KeepAlivePeriodT =
|
|
70
|
-
SupportPathT =
|
|
68
|
+
KeepAlivePeriodT = int | bool
|
|
69
|
+
SupportPathT = str | pathlib.PurePath
|
|
71
70
|
|
|
72
71
|
|
|
73
72
|
class RetryOnExceptions(tenacity.retry_if_exception):
|
|
@@ -309,7 +308,7 @@ class _SSHExecuteContext(api.ExecuteContext, contextlib.AbstractContextManager[S
|
|
|
309
308
|
stderr = None
|
|
310
309
|
_stdin: paramiko.channel.ChannelFile
|
|
311
310
|
with chan.makefile("wb") as _stdin:
|
|
312
|
-
started = datetime.datetime.now(tz=datetime.
|
|
311
|
+
started = datetime.datetime.now(tz=datetime.UTC)
|
|
313
312
|
if self.__sudo_mode:
|
|
314
313
|
chan.exec_command(self.command) # nosec # Sanitize on caller side
|
|
315
314
|
if not stdout.channel.closed:
|
|
@@ -457,6 +456,8 @@ class SSHClientBase(api.ExecHelper):
|
|
|
457
456
|
:type keepalive: int | bool
|
|
458
457
|
:param allow_ssh_agent: Use SSH Agent if available.
|
|
459
458
|
:type allow_ssh_agent: bool
|
|
459
|
+
:param missing_host_key_policy: Missing host key policy class. See paramiko.MissingHostKeyPolicy for details.
|
|
460
|
+
:type missing_host_key_policy: type[paramiko.MissingHostKeyPolicy]
|
|
460
461
|
|
|
461
462
|
.. note:: auth has priority over username/password/private_keys.
|
|
462
463
|
.. note::
|
|
@@ -475,6 +476,7 @@ class SSHClientBase(api.ExecHelper):
|
|
|
475
476
|
.. versionchanged:: 7.0.0 keepalive_mode is removed.
|
|
476
477
|
.. versionchanged:: 7.4.0 Return of keepalive_mode to prevent mix with a keepalive period. Default is `False`.
|
|
477
478
|
.. versionchanged:: 8.0.0 Expose SSH Agent usage override.
|
|
479
|
+
.. versionchanged:: 8.1.4 Allow specifying missing_host_key_policy.
|
|
478
480
|
"""
|
|
479
481
|
|
|
480
482
|
__slots__ = (
|
|
@@ -491,6 +493,7 @@ class SSHClientBase(api.ExecHelper):
|
|
|
491
493
|
"__ssh_config",
|
|
492
494
|
"__sudo_mode",
|
|
493
495
|
"__verbose",
|
|
496
|
+
"_missing_host_key_policy",
|
|
494
497
|
)
|
|
495
498
|
|
|
496
499
|
def __hash__(self) -> int:
|
|
@@ -515,6 +518,7 @@ class SSHClientBase(api.ExecHelper):
|
|
|
515
518
|
sock: paramiko.ProxyCommand | paramiko.Channel | socket.socket | None = None,
|
|
516
519
|
keepalive: KeepAlivePeriodT = 1,
|
|
517
520
|
allow_ssh_agent: bool = True,
|
|
521
|
+
missing_host_key_policy: type[paramiko.MissingHostKeyPolicy] = paramiko.WarningPolicy,
|
|
518
522
|
) -> None:
|
|
519
523
|
"""Main SSH Client helper."""
|
|
520
524
|
self.__sudo_mode = False
|
|
@@ -522,6 +526,7 @@ class SSHClientBase(api.ExecHelper):
|
|
|
522
526
|
self.__keepalive_mode = False
|
|
523
527
|
self.__verbose: bool = verbose
|
|
524
528
|
self.__sock = sock
|
|
529
|
+
self._missing_host_key_policy = missing_host_key_policy
|
|
525
530
|
|
|
526
531
|
self.__ssh: paramiko.SSHClient
|
|
527
532
|
self.__sftp: paramiko.SFTPClient | None = None
|
|
@@ -755,7 +760,8 @@ class SSHClientBase(api.ExecHelper):
|
|
|
755
760
|
with self.lock:
|
|
756
761
|
if self.__sock is not None:
|
|
757
762
|
self.__ssh = paramiko.SSHClient()
|
|
758
|
-
self.__ssh.
|
|
763
|
+
self.__ssh.load_system_host_keys()
|
|
764
|
+
self.__ssh.set_missing_host_key_policy(self._missing_host_key_policy)
|
|
759
765
|
self.auth.connect(
|
|
760
766
|
client=self.__ssh,
|
|
761
767
|
hostname=self.hostname,
|
|
@@ -781,7 +787,8 @@ class SSHClientBase(api.ExecHelper):
|
|
|
781
787
|
"""
|
|
782
788
|
|
|
783
789
|
last_ssh_client: paramiko.SSHClient = paramiko.SSHClient()
|
|
784
|
-
last_ssh_client.
|
|
790
|
+
last_ssh_client.load_system_host_keys()
|
|
791
|
+
last_ssh_client.set_missing_host_key_policy(self._missing_host_key_policy)
|
|
785
792
|
|
|
786
793
|
config, auth = self.__conn_chain[0]
|
|
787
794
|
|
|
@@ -795,7 +802,8 @@ class SSHClientBase(api.ExecHelper):
|
|
|
795
802
|
|
|
796
803
|
for config, auth in self.__conn_chain[1:]: # start has another logic, so do it out of cycle
|
|
797
804
|
ssh = paramiko.SSHClient()
|
|
798
|
-
ssh.
|
|
805
|
+
ssh.load_system_host_keys()
|
|
806
|
+
ssh.set_missing_host_key_policy(self._missing_host_key_policy)
|
|
799
807
|
|
|
800
808
|
if config.proxyjump:
|
|
801
809
|
transport = last_ssh_client.get_transport()
|
|
@@ -1779,7 +1787,7 @@ class SSHClientBase(api.ExecHelper):
|
|
|
1779
1787
|
results[remote.hostname, remote.port] = result
|
|
1780
1788
|
if result.exit_code not in prep_expected:
|
|
1781
1789
|
errors[remote.hostname, remote.port] = result
|
|
1782
|
-
except Exception as e:
|
|
1790
|
+
except Exception as e:
|
|
1783
1791
|
raised_exceptions[remote.hostname, remote.port] = e
|
|
1784
1792
|
|
|
1785
1793
|
if raised_exceptions: # always raise
|
|
@@ -13,7 +13,7 @@ if typing.TYPE_CHECKING:
|
|
|
13
13
|
# noinspection PyPackageRequirements
|
|
14
14
|
import logwrap
|
|
15
15
|
|
|
16
|
-
SSHConfigDictLikeT = dict[str,
|
|
16
|
+
SSHConfigDictLikeT = dict[str, str | int | bool | Collection[str]]
|
|
17
17
|
SSHConfigsDictT = dict[str, SSHConfigDictLikeT]
|
|
18
18
|
|
|
19
19
|
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '8.
|
|
22
|
-
__version_tuple__ = version_tuple = (8,
|
|
21
|
+
__version__ = version = '8.2.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (8, 2, 0)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g9b1f831aa'
|
|
@@ -43,8 +43,7 @@ if typing.TYPE_CHECKING:
|
|
|
43
43
|
import datetime
|
|
44
44
|
from collections.abc import Sequence
|
|
45
45
|
from types import TracebackType
|
|
46
|
-
|
|
47
|
-
from typing_extensions import Self
|
|
46
|
+
from typing import Self
|
|
48
47
|
|
|
49
48
|
__all__ = (
|
|
50
49
|
"CalledProcessErrorSubClassT",
|
|
@@ -59,12 +58,12 @@ __all__ = (
|
|
|
59
58
|
"OptionalTimeoutT",
|
|
60
59
|
)
|
|
61
60
|
|
|
62
|
-
CommandT =
|
|
63
|
-
LogMaskReT =
|
|
64
|
-
ErrorInfoT =
|
|
65
|
-
ChRootPathSetT =
|
|
61
|
+
CommandT = str | Iterable[str]
|
|
62
|
+
LogMaskReT = str | re.Pattern[str] | None
|
|
63
|
+
ErrorInfoT = str | None
|
|
64
|
+
ChRootPathSetT = str | pathlib.Path | None
|
|
66
65
|
ExpectedExitCodesT = Iterable[ExitCodeT]
|
|
67
|
-
OptionalTimeoutT =
|
|
66
|
+
OptionalTimeoutT = int | float | None
|
|
68
67
|
CalledProcessErrorSubClassT = type[exceptions.CalledProcessError]
|
|
69
68
|
|
|
70
69
|
|
|
@@ -46,8 +46,7 @@ from .. import _helpers
|
|
|
46
46
|
if typing.TYPE_CHECKING:
|
|
47
47
|
import types
|
|
48
48
|
from collections.abc import Sequence
|
|
49
|
-
|
|
50
|
-
from typing_extensions import Self
|
|
49
|
+
from typing import Self
|
|
51
50
|
|
|
52
51
|
from exec_helpers.async_api import exec_result
|
|
53
52
|
from exec_helpers.proc_enums import ExitCodeT
|
|
@@ -377,7 +376,7 @@ class ExecHelper(
|
|
|
377
376
|
self,
|
|
378
377
|
command: str,
|
|
379
378
|
async_result: api.ExecuteAsyncResult,
|
|
380
|
-
timeout: OptionalTimeoutT,
|
|
379
|
+
timeout: OptionalTimeoutT, # noqa: ASYNC109
|
|
381
380
|
*,
|
|
382
381
|
verbose: bool = False,
|
|
383
382
|
log_mask_re: LogMaskReT = None,
|
|
@@ -463,7 +462,7 @@ class ExecHelper(
|
|
|
463
462
|
self,
|
|
464
463
|
command: CommandT,
|
|
465
464
|
verbose: bool = False,
|
|
466
|
-
timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT,
|
|
465
|
+
timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT, # noqa: ASYNC109
|
|
467
466
|
*,
|
|
468
467
|
log_mask_re: LogMaskReT = None,
|
|
469
468
|
stdin: OptionalStdinT = None,
|
|
@@ -546,7 +545,7 @@ class ExecHelper(
|
|
|
546
545
|
self,
|
|
547
546
|
command: CommandT,
|
|
548
547
|
verbose: bool = False,
|
|
549
|
-
timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT,
|
|
548
|
+
timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT, # noqa: ASYNC109
|
|
550
549
|
*,
|
|
551
550
|
log_mask_re: LogMaskReT = None,
|
|
552
551
|
stdin: OptionalStdinT = None,
|
|
@@ -610,7 +609,7 @@ class ExecHelper(
|
|
|
610
609
|
self,
|
|
611
610
|
command: CommandT,
|
|
612
611
|
verbose: bool = False,
|
|
613
|
-
timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT,
|
|
612
|
+
timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT, # noqa: ASYNC109
|
|
614
613
|
error_info: ErrorInfoT = None,
|
|
615
614
|
expected: ExpectedExitCodesT = (proc_enums.EXPECTED,),
|
|
616
615
|
raise_on_err: bool = True,
|
|
@@ -688,7 +687,7 @@ class ExecHelper(
|
|
|
688
687
|
self,
|
|
689
688
|
command: CommandT,
|
|
690
689
|
verbose: bool = False,
|
|
691
|
-
timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT,
|
|
690
|
+
timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT, # noqa: ASYNC109
|
|
692
691
|
error_info: ErrorInfoT = None,
|
|
693
692
|
raise_on_err: bool = True,
|
|
694
693
|
*,
|
|
@@ -183,7 +183,7 @@ class _SubprocessExecuteContext(
|
|
|
183
183
|
|
|
184
184
|
The Command is executed only in the context manager to be sure that everything will be cleaned up properly.
|
|
185
185
|
"""
|
|
186
|
-
started = datetime.datetime.now(tz=datetime.
|
|
186
|
+
started = datetime.datetime.now(tz=datetime.UTC)
|
|
187
187
|
|
|
188
188
|
self.__process = await asyncio.create_subprocess_shell(
|
|
189
189
|
cmd=self.command,
|
|
@@ -271,7 +271,7 @@ class Subprocess(api.ExecHelper):
|
|
|
271
271
|
self,
|
|
272
272
|
command: str,
|
|
273
273
|
async_result: SubprocessExecuteAsyncResult,
|
|
274
|
-
timeout: OptionalTimeoutT,
|
|
274
|
+
timeout: OptionalTimeoutT, # noqa: ASYNC109
|
|
275
275
|
*,
|
|
276
276
|
verbose: bool = False,
|
|
277
277
|
log_mask_re: LogMaskReT = None,
|
|
@@ -328,7 +328,7 @@ class Subprocess(api.ExecHelper):
|
|
|
328
328
|
# Wait real timeout here
|
|
329
329
|
exit_code: int = await asyncio.wait_for(async_result.interface.wait(), timeout=timeout)
|
|
330
330
|
result.exit_code = exit_code
|
|
331
|
-
except
|
|
331
|
+
except TimeoutError as exc:
|
|
332
332
|
# kill -9 for all subprocesses
|
|
333
333
|
_subprocess_helpers.kill_proc_tree(async_result.interface.pid)
|
|
334
334
|
exit_signal: int | None = await asyncio.wait_for(async_result.interface.wait(), timeout=0.001)
|
|
@@ -408,7 +408,7 @@ class Subprocess(api.ExecHelper):
|
|
|
408
408
|
self,
|
|
409
409
|
command: CommandT,
|
|
410
410
|
verbose: bool = False,
|
|
411
|
-
timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT,
|
|
411
|
+
timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT, # noqa: ASYNC109
|
|
412
412
|
*,
|
|
413
413
|
log_mask_re: LogMaskReT = None,
|
|
414
414
|
stdin: OptionalStdinT = None,
|
|
@@ -487,7 +487,7 @@ class Subprocess(api.ExecHelper):
|
|
|
487
487
|
self,
|
|
488
488
|
command: CommandT,
|
|
489
489
|
verbose: bool = False,
|
|
490
|
-
timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT,
|
|
490
|
+
timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT, # noqa: ASYNC109
|
|
491
491
|
*,
|
|
492
492
|
log_mask_re: LogMaskReT = None,
|
|
493
493
|
stdin: OptionalStdinT = None,
|
|
@@ -564,7 +564,7 @@ class Subprocess(api.ExecHelper):
|
|
|
564
564
|
self,
|
|
565
565
|
command: CommandT,
|
|
566
566
|
verbose: bool = False,
|
|
567
|
-
timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT,
|
|
567
|
+
timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT, # noqa: ASYNC109
|
|
568
568
|
error_info: ErrorInfoT = None,
|
|
569
569
|
expected: ExpectedExitCodesT = (proc_enums.EXPECTED,),
|
|
570
570
|
raise_on_err: bool = True,
|
|
@@ -651,7 +651,7 @@ class Subprocess(api.ExecHelper):
|
|
|
651
651
|
self,
|
|
652
652
|
command: CommandT,
|
|
653
653
|
verbose: bool = False,
|
|
654
|
-
timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT,
|
|
654
|
+
timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT, # noqa: ASYNC109
|
|
655
655
|
error_info: ErrorInfoT = None,
|
|
656
656
|
raise_on_err: bool = True,
|
|
657
657
|
*,
|
|
@@ -62,7 +62,7 @@ __all__ = ("ExecResult", "OptionalStdinT")
|
|
|
62
62
|
|
|
63
63
|
LOGGER: logging.Logger = logging.getLogger(__name__)
|
|
64
64
|
|
|
65
|
-
OptionalStdinT =
|
|
65
|
+
OptionalStdinT = bytes | str | bytearray | None
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
def _handle_deserialize(
|
|
@@ -307,7 +307,7 @@ class ExecResult:
|
|
|
307
307
|
.. versionadded:: 4.0.0
|
|
308
308
|
"""
|
|
309
309
|
if self.timestamp is None:
|
|
310
|
-
self.__timestamp = datetime.datetime.now(tz=datetime.
|
|
310
|
+
self.__timestamp = datetime.datetime.now(tz=datetime.UTC)
|
|
311
311
|
|
|
312
312
|
@classmethod
|
|
313
313
|
def _get_brief(cls, data: tuple[bytes, ...]) -> str:
|
|
@@ -558,7 +558,7 @@ class ExecResult:
|
|
|
558
558
|
with self.stdout_lock, self.stderr_lock:
|
|
559
559
|
self.__exit_code = proc_enums.exit_code_to_enum(new_val)
|
|
560
560
|
if self.__exit_code != proc_enums.INVALID:
|
|
561
|
-
self.__timestamp = datetime.datetime.now(tz=datetime.
|
|
561
|
+
self.__timestamp = datetime.datetime.now(tz=datetime.UTC)
|
|
562
562
|
|
|
563
563
|
@property
|
|
564
564
|
def ok(self) -> bool:
|
|
@@ -678,7 +678,7 @@ class ExecResult:
|
|
|
678
678
|
:raises DeserializeValueError: STDOUT cannot be deserialized as XML.
|
|
679
679
|
"""
|
|
680
680
|
with self.stdout_lock:
|
|
681
|
-
return xml.etree.ElementTree.fromstring(b"".join(self.stdout)) # type: ignore[attr-defined]
|
|
681
|
+
return xml.etree.ElementTree.fromstring(b"".join(self.stdout)) # type: ignore[attr-defined] # nosec[B314]
|
|
682
682
|
|
|
683
683
|
if lxml is not None:
|
|
684
684
|
|
|
@@ -694,7 +694,7 @@ class ExecResult:
|
|
|
694
694
|
.. note:: Can be insecure.
|
|
695
695
|
"""
|
|
696
696
|
with self.stdout_lock:
|
|
697
|
-
return lxml.etree.fromstring(b"".join(self.stdout)) # nosec[
|
|
697
|
+
return lxml.etree.fromstring(b"".join(self.stdout)) # nosec[B314]
|
|
698
698
|
|
|
699
699
|
def __dir__(self) -> list[str]:
|
|
700
700
|
"""Override dir for IDE and as source for getitem checks.
|
|
@@ -183,7 +183,7 @@ class ExitCodes(enum.IntEnum):
|
|
|
183
183
|
return f"{self.name}<{self.value:d}(0x{self.value:02X})>"
|
|
184
184
|
|
|
185
185
|
|
|
186
|
-
ExitCodeT =
|
|
186
|
+
ExitCodeT = int | ExitCodes
|
|
187
187
|
EXPECTED: ExitCodeT = 0 if sys.platform == "win32" else ExitCodes.EX_OK
|
|
188
188
|
INVALID: ExitCodeT = 0xDEADBEEF if sys.platform == "win32" else ExitCodes.EX_INVALID
|
|
189
189
|
|
|
@@ -53,8 +53,8 @@ if typing.TYPE_CHECKING:
|
|
|
53
53
|
|
|
54
54
|
__all__ = ("CwdT", "EnvT", "Subprocess", "SubprocessExecuteAsyncResult")
|
|
55
55
|
|
|
56
|
-
EnvT =
|
|
57
|
-
CwdT =
|
|
56
|
+
EnvT = Mapping[str, str] | None
|
|
57
|
+
CwdT = str | bytes | pathlib.Path | None
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
# noinspection PyTypeHints
|
|
@@ -180,7 +180,7 @@ class _SubprocessExecuteContext(api.ExecuteContext, contextlib.AbstractContextMa
|
|
|
180
180
|
|
|
181
181
|
The Command is executed only in the context manager to be sure that everything will be cleaned up properly.
|
|
182
182
|
"""
|
|
183
|
-
started = datetime.datetime.now(tz=datetime.
|
|
183
|
+
started = datetime.datetime.now(tz=datetime.UTC)
|
|
184
184
|
|
|
185
185
|
self.__process = subprocess.Popen(
|
|
186
186
|
args=self.command,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: exec-helpers
|
|
3
|
-
Version: 8.
|
|
3
|
+
Version: 8.2.0
|
|
4
4
|
Summary: Execution helpers for simplified usage of subprocess and ssh.
|
|
5
5
|
Author-email: Aleksei Stepanov <penguinolog@gmail.com>
|
|
6
6
|
Maintainer-email: Aleksei Stepanov <penguinolog@gmail.com>, Antonio Esposito <esposito.cloud@gmail.com>, Dennis Dmitriev <dis-xcom@gmail.com>
|
|
@@ -8,14 +8,12 @@ License-Expression: Apache-2.0
|
|
|
8
8
|
Project-URL: Documentation, https://exec-helpers.readthedocs.io/
|
|
9
9
|
Project-URL: Repository, https://github.com/python-useful-helpers/exec-helpers
|
|
10
10
|
Project-URL: Bug Tracker, https://github.com/python-useful-helpers/exec-helpers/issues
|
|
11
|
-
Keywords: subprocess
|
|
11
|
+
Keywords: ssh,subprocess
|
|
12
12
|
Classifier: Development Status :: 5 - Production/Stable
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
16
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
19
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
@@ -23,7 +21,7 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
23
21
|
Classifier: Programming Language :: Python :: 3.15
|
|
24
22
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
25
23
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
26
|
-
Requires-Python: >=3.
|
|
24
|
+
Requires-Python: >=3.11.0
|
|
27
25
|
Description-Content-Type: text/x-rst
|
|
28
26
|
License-File: LICENSE
|
|
29
27
|
Requires-Dist: paramiko>=2.4
|
|
@@ -33,13 +31,13 @@ Provides-Extra: xml
|
|
|
33
31
|
Provides-Extra: lxml
|
|
34
32
|
Requires-Dist: lxml>=4.6.2; extra == "lxml"
|
|
35
33
|
Provides-Extra: yaml
|
|
36
|
-
Requires-Dist:
|
|
34
|
+
Requires-Dist: pyyaml>=3.12; extra == "yaml"
|
|
37
35
|
Provides-Extra: all-formats
|
|
38
36
|
Requires-Dist: lxml>=4.6.2; extra == "all-formats"
|
|
39
|
-
Requires-Dist:
|
|
37
|
+
Requires-Dist: pyyaml>=3.12; extra == "all-formats"
|
|
40
38
|
Provides-Extra: all-formats
|
|
41
39
|
Requires-Dist: lxml>=4.6.2; extra == "all-formats"
|
|
42
|
-
Requires-Dist:
|
|
40
|
+
Requires-Dist: pyyaml>=3.12; extra == "all-formats"
|
|
43
41
|
Dynamic: license-file
|
|
44
42
|
|
|
45
43
|
exec-helpers
|
|
@@ -4,11 +4,11 @@ psutil>=5.0
|
|
|
4
4
|
|
|
5
5
|
[all-formats]
|
|
6
6
|
lxml>=4.6.2
|
|
7
|
-
|
|
7
|
+
pyyaml>=3.12
|
|
8
8
|
|
|
9
9
|
[all_formats]
|
|
10
10
|
lxml>=4.6.2
|
|
11
|
-
|
|
11
|
+
pyyaml>=3.12
|
|
12
12
|
|
|
13
13
|
[lxml]
|
|
14
14
|
lxml>=4.6.2
|
|
@@ -16,4 +16,4 @@ lxml>=4.6.2
|
|
|
16
16
|
[xml]
|
|
17
17
|
|
|
18
18
|
[yaml]
|
|
19
|
-
|
|
19
|
+
pyyaml>=3.12
|