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.
Files changed (46) hide show
  1. exec_helpers-8.2.0/.pre-commit-config.yaml +34 -0
  2. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/PKG-INFO +6 -8
  3. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/classifiers.txt +0 -2
  4. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/_ssh_base.py +17 -9
  5. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/_ssh_helpers.py +1 -1
  6. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/_version.py +3 -3
  7. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/api.py +6 -7
  8. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/async_api/api.py +6 -7
  9. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/async_api/subprocess.py +7 -7
  10. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/exec_result.py +5 -5
  11. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/proc_enums.py +1 -1
  12. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/subprocess.py +3 -3
  13. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers.egg-info/PKG-INFO +6 -8
  14. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers.egg-info/SOURCES.txt +0 -3
  15. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers.egg-info/requires.txt +3 -3
  16. exec_helpers-8.2.0/pyproject.toml +425 -0
  17. exec_helpers-8.1.3/.pre-commit-config.yaml +0 -44
  18. exec_helpers-8.1.3/flake8_requirements.txt +0 -14
  19. exec_helpers-8.1.3/mypy_requirements.txt +0 -1
  20. exec_helpers-8.1.3/pyproject.toml +0 -270
  21. exec_helpers-8.1.3/pytest_requirements.txt +0 -6
  22. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/.editorconfig +0 -0
  23. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/.readthedocs.yaml +0 -0
  24. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/CI_REQUIREMENTS.txt +0 -0
  25. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/LICENSE +0 -0
  26. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/MANIFEST.in +0 -0
  27. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/README.rst +0 -0
  28. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/build_requirements.txt +0 -0
  29. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/__init__.py +0 -0
  30. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/__init__.pyi +0 -0
  31. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/_helpers.py +0 -0
  32. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/_log_templates.py +0 -0
  33. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/_subprocess_helpers.py +0 -0
  34. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/async_api/__init__.py +0 -0
  35. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/async_api/__init__.pyi +0 -0
  36. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/async_api/exec_result.py +0 -0
  37. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/constants.py +0 -0
  38. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/exceptions.py +0 -0
  39. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/py.typed +0 -0
  40. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/ssh.py +0 -0
  41. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers/ssh_auth.py +0 -0
  42. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers.egg-info/dependency_links.txt +0 -0
  43. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers.egg-info/not-zip-safe +0 -0
  44. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/exec_helpers.egg-info/top_level.txt +0 -0
  45. {exec_helpers-8.1.3 → exec_helpers-8.2.0}/requirements.txt +0 -0
  46. {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.1.3
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,ssh
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.9.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
@@ -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: PyYAML>=3.12; extra == "yaml"
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: PyYAML>=3.12; extra == "all-formats"
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: PyYAML>=3.12; extra == "all-formats"
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 = typing.Union[int, bool]
70
- SupportPathT = typing.Union[str, pathlib.PurePath]
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.timezone.utc)
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.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)
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.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)
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.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)
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: # noqa: PERF203
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, typing.Union[str, int, bool, Collection[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.1.3'
22
- __version_tuple__ = version_tuple = (8, 1, 3)
21
+ __version__ = version = '8.2.0'
22
+ __version_tuple__ = version_tuple = (8, 2, 0)
23
23
 
24
- __commit_id__ = commit_id = 'g88b7c628a'
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 = typing.Union[str, Iterable[str]]
63
- LogMaskReT = typing.Union[str, re.Pattern[str], None]
64
- ErrorInfoT = typing.Optional[str]
65
- ChRootPathSetT = typing.Optional[typing.Union[str, pathlib.Path]]
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 = typing.Union[int, float, None]
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.timezone.utc)
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 asyncio.TimeoutError as exc:
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 = typing.Union[bytes, str, bytearray, None]
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.timezone.utc)
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.timezone.utc)
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[blacklist]
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 = typing.Union[int, ExitCodes]
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 = typing.Optional[Mapping[str, str]]
57
- CwdT = typing.Optional[typing.Union[str, bytes, pathlib.Path]]
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.timezone.utc)
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.1.3
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,ssh
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.9.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
@@ -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: PyYAML>=3.12; extra == "yaml"
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: PyYAML>=3.12; extra == "all-formats"
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: PyYAML>=3.12; extra == "all-formats"
40
+ Requires-Dist: pyyaml>=3.12; extra == "all-formats"
43
41
  Dynamic: license-file
44
42
 
45
43
  exec-helpers
@@ -7,10 +7,7 @@ MANIFEST.in
7
7
  README.rst
8
8
  build_requirements.txt
9
9
  classifiers.txt
10
- flake8_requirements.txt
11
- mypy_requirements.txt
12
10
  pyproject.toml
13
- pytest_requirements.txt
14
11
  requirements.txt
15
12
  exec_helpers/__init__.py
16
13
  exec_helpers/__init__.pyi
@@ -4,11 +4,11 @@ psutil>=5.0
4
4
 
5
5
  [all-formats]
6
6
  lxml>=4.6.2
7
- PyYAML>=3.12
7
+ pyyaml>=3.12
8
8
 
9
9
  [all_formats]
10
10
  lxml>=4.6.2
11
- PyYAML>=3.12
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
- PyYAML>=3.12
19
+ pyyaml>=3.12