exec-helpers 8.1.2__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.
Files changed (48) hide show
  1. exec_helpers-8.2.0/.pre-commit-config.yaml +34 -0
  2. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/PKG-INFO +13 -17
  3. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/classifiers.txt +3 -4
  4. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers/__init__.py +1 -1
  5. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers/_helpers.py +1 -1
  6. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers/_ssh_base.py +25 -15
  7. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers/_ssh_helpers.py +4 -5
  8. exec_helpers-8.2.0/exec_helpers/_version.py +24 -0
  9. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers/api.py +16 -13
  10. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers/async_api/api.py +13 -11
  11. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers/async_api/subprocess.py +12 -10
  12. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers/exceptions.py +1 -1
  13. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers/exec_result.py +18 -26
  14. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers/proc_enums.py +1 -1
  15. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers/ssh_auth.py +9 -8
  16. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers/subprocess.py +8 -8
  17. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers.egg-info/PKG-INFO +13 -17
  18. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers.egg-info/SOURCES.txt +0 -4
  19. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers.egg-info/requires.txt +3 -6
  20. exec_helpers-8.2.0/pyproject.toml +425 -0
  21. exec_helpers-8.1.2/.pre-commit-config.yaml +0 -44
  22. exec_helpers-8.1.2/exec_helpers/_version.py +0 -16
  23. exec_helpers-8.1.2/flake8_requirements.txt +0 -14
  24. exec_helpers-8.1.2/mypy_requirements.txt +0 -1
  25. exec_helpers-8.1.2/pyproject.toml +0 -269
  26. exec_helpers-8.1.2/pytest_requirements.txt +0 -6
  27. exec_helpers-8.1.2/setup.py +0 -40
  28. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/.editorconfig +0 -0
  29. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/.readthedocs.yaml +0 -0
  30. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/CI_REQUIREMENTS.txt +0 -0
  31. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/LICENSE +0 -0
  32. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/MANIFEST.in +0 -0
  33. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/README.rst +0 -0
  34. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/build_requirements.txt +0 -0
  35. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers/__init__.pyi +0 -0
  36. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers/_log_templates.py +0 -0
  37. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers/_subprocess_helpers.py +0 -0
  38. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers/async_api/__init__.py +0 -0
  39. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers/async_api/__init__.pyi +0 -0
  40. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers/async_api/exec_result.py +0 -0
  41. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers/constants.py +0 -0
  42. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers/py.typed +0 -0
  43. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers/ssh.py +0 -0
  44. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers.egg-info/dependency_links.txt +0 -0
  45. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers.egg-info/not-zip-safe +0 -0
  46. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/exec_helpers.egg-info/top_level.txt +0 -0
  47. {exec_helpers-8.1.2 → exec_helpers-8.2.0}/requirements.txt +0 -0
  48. {exec_helpers-8.1.2 → 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,48 +1,44 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: exec-helpers
3
- Version: 8.1.2
3
+ Version: 8.2.0
4
4
  Summary: Execution helpers for simplified usage of subprocess and ssh.
5
- Home-page: https://github.com/python-useful-helpers/exec-helpers
6
- Author-email: Alexey Stepanov <penguinolog@gmail.com>
5
+ Author-email: Aleksei Stepanov <penguinolog@gmail.com>
7
6
  Maintainer-email: Aleksei Stepanov <penguinolog@gmail.com>, Antonio Esposito <esposito.cloud@gmail.com>, Dennis Dmitriev <dis-xcom@gmail.com>
8
- License: Apache-2.0
7
+ License-Expression: Apache-2.0
9
8
  Project-URL: Documentation, https://exec-helpers.readthedocs.io/
10
9
  Project-URL: Repository, https://github.com/python-useful-helpers/exec-helpers
11
10
  Project-URL: Bug Tracker, https://github.com/python-useful-helpers/exec-helpers/issues
12
- Keywords: subprocess,ssh
11
+ Keywords: ssh,subprocess
13
12
  Classifier: Development Status :: 5 - Production/Stable
14
13
  Classifier: Intended Audience :: Developers
15
14
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
- Classifier: License :: OSI Approved :: Apache Software License
17
15
  Classifier: Programming Language :: Python :: 3
18
16
  Classifier: Programming Language :: Python :: 3 :: Only
19
- Classifier: Programming Language :: Python :: 3.8
20
- Classifier: Programming Language :: Python :: 3.9
21
- Classifier: Programming Language :: Python :: 3.10
22
17
  Classifier: Programming Language :: Python :: 3.11
23
18
  Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
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.8.0
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
30
28
  Requires-Dist: tenacity>=4.4.0
31
29
  Requires-Dist: psutil>=5.0
32
30
  Provides-Extra: xml
33
- Requires-Dist: defusedxml; extra == "xml"
34
31
  Provides-Extra: lxml
35
32
  Requires-Dist: lxml>=4.6.2; extra == "lxml"
36
33
  Provides-Extra: yaml
37
- Requires-Dist: PyYAML>=3.12; extra == "yaml"
34
+ Requires-Dist: pyyaml>=3.12; extra == "yaml"
38
35
  Provides-Extra: all-formats
39
- Requires-Dist: defusedxml; extra == "all-formats"
40
36
  Requires-Dist: lxml>=4.6.2; extra == "all-formats"
41
- Requires-Dist: PyYAML>=3.12; extra == "all-formats"
37
+ Requires-Dist: pyyaml>=3.12; extra == "all-formats"
42
38
  Provides-Extra: all-formats
43
- Requires-Dist: defusedxml; extra == "all-formats"
44
39
  Requires-Dist: lxml>=4.6.2; extra == "all-formats"
45
- Requires-Dist: PyYAML>=3.12; extra == "all-formats"
40
+ Requires-Dist: pyyaml>=3.12; extra == "all-formats"
41
+ Dynamic: license-file
46
42
 
47
43
  exec-helpers
48
44
  ============
@@ -1,13 +1,12 @@
1
1
  Development Status :: 5 - Production/Stable
2
2
  Intended Audience :: Developers
3
3
  Topic :: Software Development :: Libraries :: Python Modules
4
- License :: OSI Approved :: Apache Software License
5
4
  Programming Language :: Python :: 3
6
5
  Programming Language :: Python :: 3 :: Only
7
- Programming Language :: Python :: 3.8
8
- Programming Language :: Python :: 3.9
9
- Programming Language :: Python :: 3.10
10
6
  Programming Language :: Python :: 3.11
11
7
  Programming Language :: Python :: 3.12
8
+ Programming Language :: Python :: 3.13
9
+ Programming Language :: Python :: 3.14
10
+ Programming Language :: Python :: 3.15
12
11
  Programming Language :: Python :: Implementation :: CPython
13
12
  Programming Language :: Python :: Implementation :: PyPy
@@ -20,7 +20,7 @@ import importlib
20
20
  import typing
21
21
  import warnings
22
22
 
23
- try: # noqa: FURB107,SIM105,RUF100
23
+ try: # noqa: SIM105,S314,RUF100
24
24
  from ._version import version as __version__ # noqa: F401
25
25
  except ImportError:
26
26
  pass
@@ -94,5 +94,5 @@ def chroot_command(command: str, chroot_path: str | None = None, chroot_exe: str
94
94
  if chroot_path and chroot_path != "/":
95
95
  chroot_dst: str = shlex.quote(chroot_path.strip())
96
96
  quoted_command = shlex.quote(command)
97
- return f'{chroot_exe} {chroot_dst} sh -c {shlex.quote(f"eval {quoted_command}")}'
97
+ return f"{chroot_exe} {chroot_dst} sh -c {shlex.quote(f'eval {quoted_command}')}"
98
98
  return command
@@ -19,6 +19,7 @@
19
19
  from __future__ import annotations
20
20
 
21
21
  import concurrent.futures
22
+ import contextlib
22
23
  import copy
23
24
  import datetime
24
25
  import getpass
@@ -49,8 +50,7 @@ if typing.TYPE_CHECKING:
49
50
  from collections.abc import Iterable
50
51
  from collections.abc import Sequence
51
52
  from types import TracebackType
52
-
53
- from typing_extensions import Self
53
+ from typing import Self
54
54
 
55
55
  from exec_helpers.api import CalledProcessErrorSubClassT
56
56
  from exec_helpers.api import CommandT
@@ -65,8 +65,8 @@ if typing.TYPE_CHECKING:
65
65
 
66
66
  __all__ = ("SSHClientBase", "SshExecuteAsyncResult", "SupportPathT")
67
67
 
68
- KeepAlivePeriodT = typing.Union[int, bool]
69
- SupportPathT = typing.Union[str, pathlib.PurePath]
68
+ KeepAlivePeriodT = int | bool
69
+ SupportPathT = str | pathlib.PurePath
70
70
 
71
71
 
72
72
  class RetryOnExceptions(tenacity.retry_if_exception):
@@ -135,7 +135,7 @@ class SshExecuteAsyncResult(api.ExecuteAsyncResult):
135
135
  return super().stdout
136
136
 
137
137
 
138
- class _SSHExecuteContext(api.ExecuteContext, typing.ContextManager[SshExecuteAsyncResult]):
138
+ class _SSHExecuteContext(api.ExecuteContext, contextlib.AbstractContextManager[SshExecuteAsyncResult]):
139
139
  """SSH Execute context."""
140
140
 
141
141
  __slots__ = (
@@ -308,7 +308,7 @@ class _SSHExecuteContext(api.ExecuteContext, typing.ContextManager[SshExecuteAsy
308
308
  stderr = None
309
309
  _stdin: paramiko.channel.ChannelFile
310
310
  with chan.makefile("wb") as _stdin:
311
- started = datetime.datetime.now(tz=datetime.timezone.utc)
311
+ started = datetime.datetime.now(tz=datetime.UTC)
312
312
  if self.__sudo_mode:
313
313
  chan.exec_command(self.command) # nosec # Sanitize on caller side
314
314
  if not stdout.channel.closed:
@@ -358,7 +358,7 @@ class _SSHExecuteContext(api.ExecuteContext, typing.ContextManager[SshExecuteAsy
358
358
  self.__chan = None
359
359
 
360
360
 
361
- class _SudoContext(typing.ContextManager[None]):
361
+ class _SudoContext(contextlib.AbstractContextManager[None]):
362
362
  """Context manager for call commands with sudo.
363
363
 
364
364
  :param ssh: Connection instance.
@@ -389,7 +389,7 @@ class _SudoContext(typing.ContextManager[None]):
389
389
  self.__ssh.sudo_mode = self.__sudo_status
390
390
 
391
391
 
392
- class _KeepAliveContext(typing.ContextManager[None]):
392
+ class _KeepAliveContext(contextlib.AbstractContextManager[None]):
393
393
  """Context manager for keepalive management.
394
394
 
395
395
  :param ssh: Connection instance.
@@ -456,6 +456,8 @@ class SSHClientBase(api.ExecHelper):
456
456
  :type keepalive: int | bool
457
457
  :param allow_ssh_agent: Use SSH Agent if available.
458
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]
459
461
 
460
462
  .. note:: auth has priority over username/password/private_keys.
461
463
  .. note::
@@ -474,6 +476,7 @@ class SSHClientBase(api.ExecHelper):
474
476
  .. versionchanged:: 7.0.0 keepalive_mode is removed.
475
477
  .. versionchanged:: 7.4.0 Return of keepalive_mode to prevent mix with a keepalive period. Default is `False`.
476
478
  .. versionchanged:: 8.0.0 Expose SSH Agent usage override.
479
+ .. versionchanged:: 8.1.4 Allow specifying missing_host_key_policy.
477
480
  """
478
481
 
479
482
  __slots__ = (
@@ -490,6 +493,7 @@ class SSHClientBase(api.ExecHelper):
490
493
  "__ssh_config",
491
494
  "__sudo_mode",
492
495
  "__verbose",
496
+ "_missing_host_key_policy",
493
497
  )
494
498
 
495
499
  def __hash__(self) -> int:
@@ -514,6 +518,7 @@ class SSHClientBase(api.ExecHelper):
514
518
  sock: paramiko.ProxyCommand | paramiko.Channel | socket.socket | None = None,
515
519
  keepalive: KeepAlivePeriodT = 1,
516
520
  allow_ssh_agent: bool = True,
521
+ missing_host_key_policy: type[paramiko.MissingHostKeyPolicy] = paramiko.WarningPolicy,
517
522
  ) -> None:
518
523
  """Main SSH Client helper."""
519
524
  self.__sudo_mode = False
@@ -521,6 +526,7 @@ class SSHClientBase(api.ExecHelper):
521
526
  self.__keepalive_mode = False
522
527
  self.__verbose: bool = verbose
523
528
  self.__sock = sock
529
+ self._missing_host_key_policy = missing_host_key_policy
524
530
 
525
531
  self.__ssh: paramiko.SSHClient
526
532
  self.__sftp: paramiko.SFTPClient | None = None
@@ -754,7 +760,8 @@ class SSHClientBase(api.ExecHelper):
754
760
  with self.lock:
755
761
  if self.__sock is not None:
756
762
  self.__ssh = paramiko.SSHClient()
757
- self.__ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
763
+ self.__ssh.load_system_host_keys()
764
+ self.__ssh.set_missing_host_key_policy(self._missing_host_key_policy)
758
765
  self.auth.connect(
759
766
  client=self.__ssh,
760
767
  hostname=self.hostname,
@@ -780,7 +787,8 @@ class SSHClientBase(api.ExecHelper):
780
787
  """
781
788
 
782
789
  last_ssh_client: paramiko.SSHClient = paramiko.SSHClient()
783
- last_ssh_client.set_missing_host_key_policy(paramiko.WarningPolicy()) # noqa: S507,RUF100
790
+ last_ssh_client.load_system_host_keys()
791
+ last_ssh_client.set_missing_host_key_policy(self._missing_host_key_policy)
784
792
 
785
793
  config, auth = self.__conn_chain[0]
786
794
 
@@ -794,7 +802,8 @@ class SSHClientBase(api.ExecHelper):
794
802
 
795
803
  for config, auth in self.__conn_chain[1:]: # start has another logic, so do it out of cycle
796
804
  ssh = paramiko.SSHClient()
797
- ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) # noqa: S507,RUF100
805
+ ssh.load_system_host_keys()
806
+ ssh.set_missing_host_key_policy(self._missing_host_key_policy)
798
807
 
799
808
  if config.proxyjump:
800
809
  transport = last_ssh_client.get_transport()
@@ -869,7 +878,8 @@ class SSHClientBase(api.ExecHelper):
869
878
  try:
870
879
  self.__ssh.close()
871
880
  except BaseException as e: # pragma: no cover # NOSONAR
872
- self.logger.debug(f"Exception in {self!s} destructor call: {e}")
881
+ with contextlib.suppress(AttributeError):
882
+ self.logger.debug(f"Exception in {self!s} destructor call: {e}")
873
883
  self.__sftp = None
874
884
 
875
885
  def __exit__(
@@ -995,12 +1005,12 @@ class SSHClientBase(api.ExecHelper):
995
1005
  return super()._prepare_command(cmd=cmd, chroot_path=chroot_path, chroot_exe=chroot_exe)
996
1006
  quoted_command: str = shlex.quote(cmd)
997
1007
  if chroot_path is self._chroot_path is None:
998
- return f'sudo -S sh -c {shlex.quote(f"eval {quoted_command}")}'
1008
+ return f"sudo -S sh -c {shlex.quote(f'eval {quoted_command}')}"
999
1009
  if chroot_path is not None:
1000
1010
  target_path: str = shlex.quote(chroot_path)
1001
1011
  else:
1002
1012
  target_path = shlex.quote(self._chroot_path) # type: ignore[arg-type]
1003
- return f'{chroot_exe} {target_path} sudo sh -c {shlex.quote(f"eval {quoted_command}")}'
1013
+ return f"{chroot_exe} {target_path} sudo sh -c {shlex.quote(f'eval {quoted_command}')}"
1004
1014
 
1005
1015
  def _exec_command( # type: ignore[override]
1006
1016
  self,
@@ -1777,7 +1787,7 @@ class SSHClientBase(api.ExecHelper):
1777
1787
  results[remote.hostname, remote.port] = result
1778
1788
  if result.exit_code not in prep_expected:
1779
1789
  errors[remote.hostname, remote.port] = result
1780
- except Exception as e: # noqa: PERF203
1790
+ except Exception as e:
1781
1791
  raised_exceptions[remote.hostname, remote.port] = e
1782
1792
 
1783
1793
  if raised_exceptions: # always raise
@@ -5,17 +5,16 @@ from __future__ import annotations
5
5
  import functools
6
6
  import pathlib
7
7
  import typing
8
+ from collections.abc import Collection
8
9
 
9
10
  import paramiko
10
11
 
11
12
  if typing.TYPE_CHECKING:
12
- from collections.abc import Collection
13
-
14
13
  # noinspection PyPackageRequirements
15
14
  import logwrap
16
15
 
17
- SSHConfigDictLikeT = typing.Dict[str, typing.Union[str, int, bool, typing.Collection[str]]]
18
- SSHConfigsDictT = typing.Dict[str, SSHConfigDictLikeT]
16
+ SSHConfigDictLikeT = dict[str, str | int | bool | Collection[str]]
17
+ SSHConfigsDictT = dict[str, SSHConfigDictLikeT]
19
18
 
20
19
 
21
20
  # Parse default SSHConfig if available
@@ -379,7 +378,7 @@ class SSHConfig:
379
378
  return self.__controlmaster
380
379
 
381
380
 
382
- class HostsSSHConfigs(typing.Dict[str, SSHConfig]):
381
+ class HostsSSHConfigs(dict[str, SSHConfig]):
383
382
  """Specific dictionary for managing SSHConfig records.
384
383
 
385
384
  Instead of creating a new record by request, just generate default value and return if not exists.
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '8.2.0'
22
+ __version_tuple__ = version_tuple = (8, 2, 0)
23
+
24
+ __commit_id__ = commit_id = 'g9b1f831aa'
@@ -21,10 +21,14 @@
21
21
  from __future__ import annotations
22
22
 
23
23
  import abc
24
+ import contextlib
24
25
  import logging
25
26
  import pathlib
27
+ import re
26
28
  import threading
27
29
  import typing
30
+ from collections.abc import Callable
31
+ from collections.abc import Iterable
28
32
 
29
33
  from exec_helpers import constants
30
34
  from exec_helpers import exceptions
@@ -39,8 +43,7 @@ if typing.TYPE_CHECKING:
39
43
  import datetime
40
44
  from collections.abc import Sequence
41
45
  from types import TracebackType
42
-
43
- from typing_extensions import Self
46
+ from typing import Self
44
47
 
45
48
  __all__ = (
46
49
  "CalledProcessErrorSubClassT",
@@ -55,13 +58,13 @@ __all__ = (
55
58
  "OptionalTimeoutT",
56
59
  )
57
60
 
58
- CommandT = typing.Union[str, typing.Iterable[str]]
59
- LogMaskReT = typing.Union[str, typing.Pattern[str], None]
60
- ErrorInfoT = typing.Optional[str]
61
- ChRootPathSetT = typing.Optional[typing.Union[str, pathlib.Path]]
62
- ExpectedExitCodesT = typing.Iterable[ExitCodeT]
63
- OptionalTimeoutT = typing.Union[int, float, None]
64
- CalledProcessErrorSubClassT = typing.Type[exceptions.CalledProcessError]
61
+ CommandT = str | Iterable[str]
62
+ LogMaskReT = str | re.Pattern[str] | None
63
+ ErrorInfoT = str | None
64
+ ChRootPathSetT = str | pathlib.Path | None
65
+ ExpectedExitCodesT = Iterable[ExitCodeT]
66
+ OptionalTimeoutT = int | float | None
67
+ CalledProcessErrorSubClassT = type[exceptions.CalledProcessError]
65
68
 
66
69
 
67
70
  class ExecuteAsyncResult(typing.NamedTuple):
@@ -74,7 +77,7 @@ class ExecuteAsyncResult(typing.NamedTuple):
74
77
  started: datetime.datetime
75
78
 
76
79
 
77
- class ExecuteContext(typing.ContextManager[ExecuteAsyncResult], abc.ABC):
80
+ class ExecuteContext(contextlib.AbstractContextManager[ExecuteAsyncResult], abc.ABC):
78
81
  """Execute context manager."""
79
82
 
80
83
  __slots__ = (
@@ -165,7 +168,7 @@ class ExecuteContext(typing.ContextManager[ExecuteAsyncResult], abc.ABC):
165
168
 
166
169
 
167
170
  # noinspection PyProtectedMember
168
- class _ChRootContext(typing.ContextManager[None]):
171
+ class _ChRootContext(contextlib.AbstractContextManager[None]):
169
172
  """Context manager for call commands with chroot.
170
173
 
171
174
  :param conn: Connection instance.
@@ -219,8 +222,8 @@ class _ChRootContext(typing.ContextManager[None]):
219
222
 
220
223
 
221
224
  class ExecHelper(
222
- typing.Callable[..., exec_result.ExecResult], # type: ignore[misc]
223
- typing.ContextManager["ExecHelper"],
225
+ Callable[..., exec_result.ExecResult], # type: ignore[misc]
226
+ contextlib.AbstractContextManager["ExecHelper"],
224
227
  abc.ABC,
225
228
  ):
226
229
  """ExecHelper global API.
@@ -21,9 +21,12 @@ from __future__ import annotations
21
21
 
22
22
  import abc
23
23
  import asyncio
24
+ import contextlib
24
25
  import logging
25
26
  import pathlib
26
27
  import typing
28
+ from collections.abc import Awaitable
29
+ from collections.abc import Callable
27
30
 
28
31
  from exec_helpers import api
29
32
  from exec_helpers import constants
@@ -43,8 +46,7 @@ from .. import _helpers
43
46
  if typing.TYPE_CHECKING:
44
47
  import types
45
48
  from collections.abc import Sequence
46
-
47
- from typing_extensions import Self
49
+ from typing import Self
48
50
 
49
51
  from exec_helpers.async_api import exec_result
50
52
  from exec_helpers.proc_enums import ExitCodeT
@@ -62,7 +64,7 @@ __all__ = (
62
64
  )
63
65
 
64
66
 
65
- class ExecuteContext(typing.AsyncContextManager[api.ExecuteAsyncResult], abc.ABC):
67
+ class ExecuteContext(contextlib.AbstractAsyncContextManager[api.ExecuteAsyncResult], abc.ABC):
66
68
  """Execute context manager."""
67
69
 
68
70
  __slots__ = (
@@ -153,7 +155,7 @@ class ExecuteContext(typing.AsyncContextManager[api.ExecuteAsyncResult], abc.ABC
153
155
 
154
156
 
155
157
  # noinspection PyProtectedMember
156
- class _ChRootContext(typing.AsyncContextManager[None]):
158
+ class _ChRootContext(contextlib.AbstractAsyncContextManager[None]):
157
159
  """Async extension for chroot.
158
160
 
159
161
  :param conn: Connection instance.
@@ -203,8 +205,8 @@ class _ChRootContext(typing.AsyncContextManager[None]):
203
205
 
204
206
 
205
207
  class ExecHelper(
206
- typing.Callable[..., typing.Awaitable["ExecHelper"]], # type: ignore[misc]
207
- typing.AsyncContextManager["ExecHelper"],
208
+ Callable[..., Awaitable["ExecHelper"]], # type: ignore[misc]
209
+ contextlib.AbstractAsyncContextManager["ExecHelper"],
208
210
  abc.ABC,
209
211
  ):
210
212
  """Subprocess helper with timeouts and lock-free FIFO.
@@ -374,7 +376,7 @@ class ExecHelper(
374
376
  self,
375
377
  command: str,
376
378
  async_result: api.ExecuteAsyncResult,
377
- timeout: OptionalTimeoutT,
379
+ timeout: OptionalTimeoutT, # noqa: ASYNC109
378
380
  *,
379
381
  verbose: bool = False,
380
382
  log_mask_re: LogMaskReT = None,
@@ -460,7 +462,7 @@ class ExecHelper(
460
462
  self,
461
463
  command: CommandT,
462
464
  verbose: bool = False,
463
- timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT,
465
+ timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT, # noqa: ASYNC109
464
466
  *,
465
467
  log_mask_re: LogMaskReT = None,
466
468
  stdin: OptionalStdinT = None,
@@ -543,7 +545,7 @@ class ExecHelper(
543
545
  self,
544
546
  command: CommandT,
545
547
  verbose: bool = False,
546
- timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT,
548
+ timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT, # noqa: ASYNC109
547
549
  *,
548
550
  log_mask_re: LogMaskReT = None,
549
551
  stdin: OptionalStdinT = None,
@@ -607,7 +609,7 @@ class ExecHelper(
607
609
  self,
608
610
  command: CommandT,
609
611
  verbose: bool = False,
610
- timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT,
612
+ timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT, # noqa: ASYNC109
611
613
  error_info: ErrorInfoT = None,
612
614
  expected: ExpectedExitCodesT = (proc_enums.EXPECTED,),
613
615
  raise_on_err: bool = True,
@@ -685,7 +687,7 @@ class ExecHelper(
685
687
  self,
686
688
  command: CommandT,
687
689
  verbose: bool = False,
688
- timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT,
690
+ timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT, # noqa: ASYNC109
689
691
  error_info: ErrorInfoT = None,
690
692
  raise_on_err: bool = True,
691
693
  *,
@@ -106,7 +106,9 @@ class SubprocessExecuteAsyncResult(subprocess.SubprocessExecuteAsyncResult):
106
106
  return super().stdout # type: ignore[return-value]
107
107
 
108
108
 
109
- class _SubprocessExecuteContext(api.ExecuteContext, typing.AsyncContextManager[SubprocessExecuteAsyncResult]):
109
+ class _SubprocessExecuteContext(
110
+ api.ExecuteContext, contextlib.AbstractAsyncContextManager[SubprocessExecuteAsyncResult]
111
+ ):
110
112
  """Subprocess Execute context."""
111
113
 
112
114
  __slots__ = ("__cwd", "__env", "__process")
@@ -181,7 +183,7 @@ class _SubprocessExecuteContext(api.ExecuteContext, typing.AsyncContextManager[S
181
183
 
182
184
  The Command is executed only in the context manager to be sure that everything will be cleaned up properly.
183
185
  """
184
- started = datetime.datetime.now(tz=datetime.timezone.utc)
186
+ started = datetime.datetime.now(tz=datetime.UTC)
185
187
 
186
188
  self.__process = await asyncio.create_subprocess_shell(
187
189
  cmd=self.command,
@@ -269,7 +271,7 @@ class Subprocess(api.ExecHelper):
269
271
  self,
270
272
  command: str,
271
273
  async_result: SubprocessExecuteAsyncResult,
272
- timeout: OptionalTimeoutT,
274
+ timeout: OptionalTimeoutT, # noqa: ASYNC109
273
275
  *,
274
276
  verbose: bool = False,
275
277
  log_mask_re: LogMaskReT = None,
@@ -326,7 +328,7 @@ class Subprocess(api.ExecHelper):
326
328
  # Wait real timeout here
327
329
  exit_code: int = await asyncio.wait_for(async_result.interface.wait(), timeout=timeout)
328
330
  result.exit_code = exit_code
329
- except asyncio.TimeoutError as exc:
331
+ except TimeoutError as exc:
330
332
  # kill -9 for all subprocesses
331
333
  _subprocess_helpers.kill_proc_tree(async_result.interface.pid)
332
334
  exit_signal: int | None = await asyncio.wait_for(async_result.interface.wait(), timeout=0.001)
@@ -389,8 +391,8 @@ class Subprocess(api.ExecHelper):
389
391
  """
390
392
  if env_patch is not None:
391
393
  # make mutable copy
392
- env = dict(copy.deepcopy(os.environ) if env is None else copy.deepcopy(env)) # type: ignore[arg-type]
393
- env.update(env_patch) # type: ignore[arg-type]
394
+ env = dict(copy.deepcopy(os.environ) if env is None else copy.deepcopy(env))
395
+ env.update(env_patch)
394
396
  return _SubprocessExecuteContext(
395
397
  command=f"{self._prepare_command(cmd=command, chroot_path=chroot_path, chroot_exe=chroot_exe)}\n",
396
398
  stdin=None if stdin is None else self._string_bytes_bytearray_as_bytes(stdin),
@@ -406,7 +408,7 @@ class Subprocess(api.ExecHelper):
406
408
  self,
407
409
  command: CommandT,
408
410
  verbose: bool = False,
409
- timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT,
411
+ timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT, # noqa: ASYNC109
410
412
  *,
411
413
  log_mask_re: LogMaskReT = None,
412
414
  stdin: OptionalStdinT = None,
@@ -485,7 +487,7 @@ class Subprocess(api.ExecHelper):
485
487
  self,
486
488
  command: CommandT,
487
489
  verbose: bool = False,
488
- timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT,
490
+ timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT, # noqa: ASYNC109
489
491
  *,
490
492
  log_mask_re: LogMaskReT = None,
491
493
  stdin: OptionalStdinT = None,
@@ -562,7 +564,7 @@ class Subprocess(api.ExecHelper):
562
564
  self,
563
565
  command: CommandT,
564
566
  verbose: bool = False,
565
- timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT,
567
+ timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT, # noqa: ASYNC109
566
568
  error_info: ErrorInfoT = None,
567
569
  expected: ExpectedExitCodesT = (proc_enums.EXPECTED,),
568
570
  raise_on_err: bool = True,
@@ -649,7 +651,7 @@ class Subprocess(api.ExecHelper):
649
651
  self,
650
652
  command: CommandT,
651
653
  verbose: bool = False,
652
- timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT,
654
+ timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT, # noqa: ASYNC109
653
655
  error_info: ErrorInfoT = None,
654
656
  raise_on_err: bool = True,
655
657
  *,
@@ -274,7 +274,7 @@ class ParallelCallProcessError(ExecCalledProcessError):
274
274
  class ParallelCallExceptionsError(ParallelCallProcessError):
275
275
  """Exception raised during parallel call as result of exceptions."""
276
276
 
277
- __slots__ = ("cmd", "exceptions")
277
+ __slots__ = ("exceptions",)
278
278
 
279
279
  def __init__(
280
280
  self,