exec-helpers 8.1.2__tar.gz → 8.1.3__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.1.2 → exec_helpers-8.1.3}/.pre-commit-config.yaml +4 -4
  2. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/PKG-INFO +9 -11
  3. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/classifiers.txt +3 -2
  4. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/__init__.py +1 -1
  5. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/_helpers.py +1 -1
  6. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/_ssh_base.py +8 -6
  7. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/_ssh_helpers.py +4 -5
  8. exec_helpers-8.1.3/exec_helpers/_version.py +24 -0
  9. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/api.py +12 -8
  10. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/async_api/api.py +7 -4
  11. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/async_api/subprocess.py +5 -3
  12. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/exceptions.py +1 -1
  13. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/exec_result.py +14 -22
  14. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/ssh_auth.py +9 -8
  15. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/subprocess.py +6 -6
  16. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers.egg-info/PKG-INFO +9 -11
  17. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers.egg-info/SOURCES.txt +0 -1
  18. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers.egg-info/requires.txt +0 -3
  19. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/pyproject.toml +12 -11
  20. exec_helpers-8.1.2/exec_helpers/_version.py +0 -16
  21. exec_helpers-8.1.2/setup.py +0 -40
  22. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/.editorconfig +0 -0
  23. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/.readthedocs.yaml +0 -0
  24. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/CI_REQUIREMENTS.txt +0 -0
  25. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/LICENSE +0 -0
  26. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/MANIFEST.in +0 -0
  27. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/README.rst +0 -0
  28. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/build_requirements.txt +0 -0
  29. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/__init__.pyi +0 -0
  30. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/_log_templates.py +0 -0
  31. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/_subprocess_helpers.py +0 -0
  32. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/async_api/__init__.py +0 -0
  33. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/async_api/__init__.pyi +0 -0
  34. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/async_api/exec_result.py +0 -0
  35. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/constants.py +0 -0
  36. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/proc_enums.py +0 -0
  37. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/py.typed +0 -0
  38. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/ssh.py +0 -0
  39. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers.egg-info/dependency_links.txt +0 -0
  40. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers.egg-info/not-zip-safe +0 -0
  41. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers.egg-info/top_level.txt +0 -0
  42. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/flake8_requirements.txt +0 -0
  43. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/mypy_requirements.txt +0 -0
  44. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/pytest_requirements.txt +0 -0
  45. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/requirements.txt +0 -0
  46. {exec_helpers-8.1.2 → exec_helpers-8.1.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  repos:
2
2
  - repo: https://github.com/pre-commit/pre-commit-hooks
3
- rev: v4.6.0
3
+ rev: v6.0.0
4
4
  hooks:
5
5
  - id: check-yaml
6
6
  - id: end-of-file-fixer
@@ -8,7 +8,7 @@ repos:
8
8
  - id: mixed-line-ending
9
9
 
10
10
  - repo: https://github.com/pycqa/isort
11
- rev: 5.13.2
11
+ rev: 9.0.0a3
12
12
  hooks:
13
13
  - id: isort
14
14
  name: isort (python)
@@ -20,7 +20,7 @@ repos:
20
20
  types: [pyi]
21
21
 
22
22
  - repo: https://github.com/psf/black
23
- rev: 24.4.2
23
+ rev: 26.3.1
24
24
  hooks:
25
25
  - id: black
26
26
  # It is recommended to specify the latest version of Python
@@ -30,7 +30,7 @@ repos:
30
30
 
31
31
  - repo: https://github.com/astral-sh/ruff-pre-commit
32
32
  # Ruff version.
33
- rev: v0.4.6
33
+ rev: v0.15.12
34
34
  hooks:
35
35
  - id: ruff
36
36
  args: [ --fix, --exit-non-zero-on-fix ]
@@ -1,11 +1,10 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: exec-helpers
3
- Version: 8.1.2
3
+ Version: 8.1.3
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
@@ -13,36 +12,35 @@ Keywords: subprocess,ssh
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
17
  Classifier: Programming Language :: Python :: 3.9
21
18
  Classifier: Programming Language :: Python :: 3.10
22
19
  Classifier: Programming Language :: Python :: 3.11
23
20
  Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Programming Language :: Python :: 3.15
24
24
  Classifier: Programming Language :: Python :: Implementation :: CPython
25
25
  Classifier: Programming Language :: Python :: Implementation :: PyPy
26
- Requires-Python: >=3.8.0
26
+ Requires-Python: >=3.9.0
27
27
  Description-Content-Type: text/x-rst
28
28
  License-File: LICENSE
29
29
  Requires-Dist: paramiko>=2.4
30
30
  Requires-Dist: tenacity>=4.4.0
31
31
  Requires-Dist: psutil>=5.0
32
32
  Provides-Extra: xml
33
- Requires-Dist: defusedxml; extra == "xml"
34
33
  Provides-Extra: lxml
35
34
  Requires-Dist: lxml>=4.6.2; extra == "lxml"
36
35
  Provides-Extra: yaml
37
36
  Requires-Dist: PyYAML>=3.12; extra == "yaml"
38
37
  Provides-Extra: all-formats
39
- Requires-Dist: defusedxml; extra == "all-formats"
40
38
  Requires-Dist: lxml>=4.6.2; extra == "all-formats"
41
39
  Requires-Dist: PyYAML>=3.12; extra == "all-formats"
42
40
  Provides-Extra: all-formats
43
- Requires-Dist: defusedxml; extra == "all-formats"
44
41
  Requires-Dist: lxml>=4.6.2; extra == "all-formats"
45
42
  Requires-Dist: PyYAML>=3.12; extra == "all-formats"
43
+ Dynamic: license-file
46
44
 
47
45
  exec-helpers
48
46
  ============
@@ -1,13 +1,14 @@
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
6
  Programming Language :: Python :: 3.9
9
7
  Programming Language :: Python :: 3.10
10
8
  Programming Language :: Python :: 3.11
11
9
  Programming Language :: Python :: 3.12
10
+ Programming Language :: Python :: 3.13
11
+ Programming Language :: Python :: 3.14
12
+ Programming Language :: Python :: 3.15
12
13
  Programming Language :: Python :: Implementation :: CPython
13
14
  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
@@ -135,7 +136,7 @@ class SshExecuteAsyncResult(api.ExecuteAsyncResult):
135
136
  return super().stdout
136
137
 
137
138
 
138
- class _SSHExecuteContext(api.ExecuteContext, typing.ContextManager[SshExecuteAsyncResult]):
139
+ class _SSHExecuteContext(api.ExecuteContext, contextlib.AbstractContextManager[SshExecuteAsyncResult]):
139
140
  """SSH Execute context."""
140
141
 
141
142
  __slots__ = (
@@ -358,7 +359,7 @@ class _SSHExecuteContext(api.ExecuteContext, typing.ContextManager[SshExecuteAsy
358
359
  self.__chan = None
359
360
 
360
361
 
361
- class _SudoContext(typing.ContextManager[None]):
362
+ class _SudoContext(contextlib.AbstractContextManager[None]):
362
363
  """Context manager for call commands with sudo.
363
364
 
364
365
  :param ssh: Connection instance.
@@ -389,7 +390,7 @@ class _SudoContext(typing.ContextManager[None]):
389
390
  self.__ssh.sudo_mode = self.__sudo_status
390
391
 
391
392
 
392
- class _KeepAliveContext(typing.ContextManager[None]):
393
+ class _KeepAliveContext(contextlib.AbstractContextManager[None]):
393
394
  """Context manager for keepalive management.
394
395
 
395
396
  :param ssh: Connection instance.
@@ -869,7 +870,8 @@ class SSHClientBase(api.ExecHelper):
869
870
  try:
870
871
  self.__ssh.close()
871
872
  except BaseException as e: # pragma: no cover # NOSONAR
872
- self.logger.debug(f"Exception in {self!s} destructor call: {e}")
873
+ with contextlib.suppress(AttributeError):
874
+ self.logger.debug(f"Exception in {self!s} destructor call: {e}")
873
875
  self.__sftp = None
874
876
 
875
877
  def __exit__(
@@ -995,12 +997,12 @@ class SSHClientBase(api.ExecHelper):
995
997
  return super()._prepare_command(cmd=cmd, chroot_path=chroot_path, chroot_exe=chroot_exe)
996
998
  quoted_command: str = shlex.quote(cmd)
997
999
  if chroot_path is self._chroot_path is None:
998
- return f'sudo -S sh -c {shlex.quote(f"eval {quoted_command}")}'
1000
+ return f"sudo -S sh -c {shlex.quote(f'eval {quoted_command}')}"
999
1001
  if chroot_path is not None:
1000
1002
  target_path: str = shlex.quote(chroot_path)
1001
1003
  else:
1002
1004
  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}")}'
1005
+ return f"{chroot_exe} {target_path} sudo sh -c {shlex.quote(f'eval {quoted_command}')}"
1004
1006
 
1005
1007
  def _exec_command( # type: ignore[override]
1006
1008
  self,
@@ -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, typing.Union[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.1.3'
22
+ __version_tuple__ = version_tuple = (8, 1, 3)
23
+
24
+ __commit_id__ = commit_id = 'g88b7c628a'
@@ -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
@@ -55,13 +59,13 @@ __all__ = (
55
59
  "OptionalTimeoutT",
56
60
  )
57
61
 
58
- CommandT = typing.Union[str, typing.Iterable[str]]
59
- LogMaskReT = typing.Union[str, typing.Pattern[str], None]
62
+ CommandT = typing.Union[str, Iterable[str]]
63
+ LogMaskReT = typing.Union[str, re.Pattern[str], None]
60
64
  ErrorInfoT = typing.Optional[str]
61
65
  ChRootPathSetT = typing.Optional[typing.Union[str, pathlib.Path]]
62
- ExpectedExitCodesT = typing.Iterable[ExitCodeT]
66
+ ExpectedExitCodesT = Iterable[ExitCodeT]
63
67
  OptionalTimeoutT = typing.Union[int, float, None]
64
- CalledProcessErrorSubClassT = typing.Type[exceptions.CalledProcessError]
68
+ CalledProcessErrorSubClassT = type[exceptions.CalledProcessError]
65
69
 
66
70
 
67
71
  class ExecuteAsyncResult(typing.NamedTuple):
@@ -74,7 +78,7 @@ class ExecuteAsyncResult(typing.NamedTuple):
74
78
  started: datetime.datetime
75
79
 
76
80
 
77
- class ExecuteContext(typing.ContextManager[ExecuteAsyncResult], abc.ABC):
81
+ class ExecuteContext(contextlib.AbstractContextManager[ExecuteAsyncResult], abc.ABC):
78
82
  """Execute context manager."""
79
83
 
80
84
  __slots__ = (
@@ -165,7 +169,7 @@ class ExecuteContext(typing.ContextManager[ExecuteAsyncResult], abc.ABC):
165
169
 
166
170
 
167
171
  # noinspection PyProtectedMember
168
- class _ChRootContext(typing.ContextManager[None]):
172
+ class _ChRootContext(contextlib.AbstractContextManager[None]):
169
173
  """Context manager for call commands with chroot.
170
174
 
171
175
  :param conn: Connection instance.
@@ -219,8 +223,8 @@ class _ChRootContext(typing.ContextManager[None]):
219
223
 
220
224
 
221
225
  class ExecHelper(
222
- typing.Callable[..., exec_result.ExecResult], # type: ignore[misc]
223
- typing.ContextManager["ExecHelper"],
226
+ Callable[..., exec_result.ExecResult], # type: ignore[misc]
227
+ contextlib.AbstractContextManager["ExecHelper"],
224
228
  abc.ABC,
225
229
  ):
226
230
  """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
@@ -62,7 +65,7 @@ __all__ = (
62
65
  )
63
66
 
64
67
 
65
- class ExecuteContext(typing.AsyncContextManager[api.ExecuteAsyncResult], abc.ABC):
68
+ class ExecuteContext(contextlib.AbstractAsyncContextManager[api.ExecuteAsyncResult], abc.ABC):
66
69
  """Execute context manager."""
67
70
 
68
71
  __slots__ = (
@@ -153,7 +156,7 @@ class ExecuteContext(typing.AsyncContextManager[api.ExecuteAsyncResult], abc.ABC
153
156
 
154
157
 
155
158
  # noinspection PyProtectedMember
156
- class _ChRootContext(typing.AsyncContextManager[None]):
159
+ class _ChRootContext(contextlib.AbstractAsyncContextManager[None]):
157
160
  """Async extension for chroot.
158
161
 
159
162
  :param conn: Connection instance.
@@ -203,8 +206,8 @@ class _ChRootContext(typing.AsyncContextManager[None]):
203
206
 
204
207
 
205
208
  class ExecHelper(
206
- typing.Callable[..., typing.Awaitable["ExecHelper"]], # type: ignore[misc]
207
- typing.AsyncContextManager["ExecHelper"],
209
+ Callable[..., Awaitable["ExecHelper"]], # type: ignore[misc]
210
+ contextlib.AbstractAsyncContextManager["ExecHelper"],
208
211
  abc.ABC,
209
212
  ):
210
213
  """Subprocess helper with timeouts and lock-free FIFO.
@@ -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")
@@ -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),
@@ -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,
@@ -25,6 +25,7 @@ import json
25
25
  import logging
26
26
  import threading
27
27
  import typing
28
+ import xml.etree
28
29
 
29
30
  from exec_helpers import exceptions
30
31
  from exec_helpers import proc_enums
@@ -38,11 +39,6 @@ try:
38
39
  from ruamel import yaml as ruamel_yaml
39
40
  except ImportError:
40
41
  ruamel_yaml = None # type: ignore[assignment]
41
- try:
42
- # noinspection PyPackageRequirements
43
- import defusedxml.ElementTree
44
- except ImportError:
45
- defusedxml = None # pylint: disable=invalid-name
46
42
  try:
47
43
  # noinspection PyPackageRequirements
48
44
  import lxml.etree # nosec
@@ -50,7 +46,6 @@ except ImportError:
50
46
  lxml = None # pylint: disable=invalid-name
51
47
 
52
48
  if typing.TYPE_CHECKING:
53
- import xml.etree.ElementTree as ET # nosec # for typing only
54
49
  from collections.abc import Callable
55
50
  from collections.abc import Collection
56
51
  from collections.abc import Iterable
@@ -673,19 +668,17 @@ class ExecResult:
673
668
  return yaml.safe_load(self.stdout_str) # pragma: no cover
674
669
  return ruamel_yaml.YAML(typ="safe").load(self.stdout_str) # nosec # Safe
675
670
 
676
- if defusedxml is not None:
677
- # noinspection PyUnresolvedReferences
678
- @property
679
- @_handle_deserialize("xml")
680
- def stdout_xml(self) -> ET.Element:
681
- """XML from stdout.
671
+ @property
672
+ @_handle_deserialize("xml")
673
+ def stdout_xml(self) -> xml.etree.ElementTree.Element: # type: ignore[name-defined]
674
+ """XML from stdout.
682
675
 
683
- :return: Decoded XML document.
684
- :rtype: xml.etree.ElementTree.Element
685
- :raises DeserializeValueError: STDOUT cannot be deserialized as XML.
686
- """
687
- with self.stdout_lock:
688
- return defusedxml.ElementTree.fromstring(b"".join(self.stdout)) # type: ignore[no-any-return]
676
+ :return: Decoded XML document.
677
+ :rtype: xml.etree.ElementTree.Element
678
+ :raises DeserializeValueError: STDOUT cannot be deserialized as XML.
679
+ """
680
+ with self.stdout_lock:
681
+ return xml.etree.ElementTree.fromstring(b"".join(self.stdout)) # type: ignore[attr-defined]
689
682
 
690
683
  if lxml is not None:
691
684
 
@@ -724,11 +717,10 @@ class ExecResult:
724
717
  "stderr_lines",
725
718
  "stdout_json",
726
719
  "lock",
720
+ "stdout_xml",
727
721
  ]
728
722
  if yaml is not None or ruamel_yaml is not None:
729
723
  content.append("stdout_yaml")
730
- if defusedxml is not None:
731
- content.append("stdout_xml")
732
724
  if lxml is not None:
733
725
  content.append("stdout_lxml")
734
726
  return content
@@ -802,8 +794,8 @@ class ExecResult:
802
794
  if self.started:
803
795
  started = f"\tstarted={self.started.strftime('%Y-%m-%d %H:%M:%S')},\n"
804
796
  if self.timestamp:
805
- _spent = (self.timestamp - self.started).seconds
806
- spent = f"\tspent={_spent // (60 * 60):02d}:{_spent // 60:02d}:{_spent % 60:02d},\n"
797
+ spent_ = (self.timestamp - self.started).seconds
798
+ spent = f"\tspent={spent_ // (60 * 60):02d}:{spent_ // 60:02d}:{spent_ % 60:02d},\n"
807
799
  else:
808
800
  spent = ""
809
801
  else:
@@ -21,6 +21,7 @@ from __future__ import annotations
21
21
  import copy
22
22
  import logging
23
23
  import typing
24
+ from collections import UserDict
24
25
 
25
26
  import paramiko
26
27
 
@@ -74,7 +75,7 @@ class SSHAuth:
74
75
  self.__username: str | None = username
75
76
  self.__password: str | None = password
76
77
 
77
- self.__keys: list[None | paramiko.PKey] = []
78
+ self.__keys: list[paramiko.PKey | None] = []
78
79
 
79
80
  if key is not None:
80
81
  # noinspection PyTypeChecker
@@ -298,22 +299,22 @@ class SSHAuth:
298
299
  :rtype: str
299
300
  """
300
301
  if self.__keys[self.__key_index] is None:
301
- _key: str | None = None
302
+ key: str | None = None
302
303
  else:
303
- _key = f"<private for pub: {self.public_key}>"
304
- _keys: list[str | None] = []
304
+ key = f"<private for pub: {self.public_key}>"
305
+ keys: list[str | None] = []
305
306
  for idx, k in enumerate(self.__keys):
306
307
  if idx == self.__key_index:
307
308
  continue
308
309
  # noinspection PyTypeChecker
309
- _keys.append(f"<private for pub: {self.__get_public_key(key=k)}>" if k is not None else None)
310
+ keys.append(f"<private for pub: {self.__get_public_key(key=k)}>" if k is not None else None)
310
311
 
311
312
  return (
312
313
  f"{self.__class__.__name__}("
313
314
  f"username={self.username!r}, "
314
315
  f"password=<*masked*>, "
315
- f"key={_key}, "
316
- f"keys={_keys}, "
316
+ f"key={key}, "
317
+ f"keys={keys}, "
317
318
  f"key_filename={self.key_filename!r}, "
318
319
  f"passphrase=<*masked*>,"
319
320
  f")"
@@ -328,7 +329,7 @@ class SSHAuth:
328
329
  return f"{self.__class__.__name__} for {self.username}"
329
330
 
330
331
 
331
- class SSHAuthMapping(typing.Dict[str, SSHAuth]):
332
+ class SSHAuthMapping(UserDict[str, SSHAuth]):
332
333
  """Specific dictionary for ssh hostname - auth mapping.
333
334
 
334
335
  Keys are always string and saved/collected lowercase.
@@ -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 errno
@@ -28,6 +29,7 @@ import pathlib
28
29
  import subprocess # nosec # Expected usage
29
30
  import typing
30
31
  import warnings
32
+ from collections.abc import Mapping
31
33
 
32
34
  from exec_helpers import api
33
35
  from exec_helpers import constants
@@ -51,9 +53,7 @@ if typing.TYPE_CHECKING:
51
53
 
52
54
  __all__ = ("CwdT", "EnvT", "Subprocess", "SubprocessExecuteAsyncResult")
53
55
 
54
- EnvT = typing.Optional[
55
- typing.Union[typing.Mapping[bytes, typing.Union[bytes, str]], typing.Mapping[str, typing.Union[bytes, str]]]
56
- ]
56
+ EnvT = typing.Optional[Mapping[str, str]]
57
57
  CwdT = typing.Optional[typing.Union[str, bytes, pathlib.Path]]
58
58
 
59
59
 
@@ -105,7 +105,7 @@ class SubprocessExecuteAsyncResult(api.ExecuteAsyncResult):
105
105
  return super().stdout
106
106
 
107
107
 
108
- class _SubprocessExecuteContext(api.ExecuteContext, typing.ContextManager[SubprocessExecuteAsyncResult]):
108
+ class _SubprocessExecuteContext(api.ExecuteContext, contextlib.AbstractContextManager[SubprocessExecuteAsyncResult]):
109
109
  """Subprocess Execute context."""
110
110
 
111
111
  __slots__ = ("__cwd", "__env", "__process")
@@ -411,8 +411,8 @@ class Subprocess(api.ExecHelper):
411
411
  """
412
412
  if env_patch is not None:
413
413
  # make mutable copy
414
- env = dict(copy.deepcopy(os.environ) if env is None else copy.deepcopy(env)) # type: ignore[arg-type]
415
- env.update(env_patch) # type: ignore[arg-type]
414
+ env = dict(copy.deepcopy(os.environ) if env is None else copy.deepcopy(env))
415
+ env.update(env_patch)
416
416
  return _SubprocessExecuteContext(
417
417
  command=f"{self._prepare_command(cmd=command, chroot_path=chroot_path, chroot_exe=chroot_exe)}\n",
418
418
  stdin=None if stdin is None else self._string_bytes_bytearray_as_bytes(stdin),
@@ -1,11 +1,10 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: exec-helpers
3
- Version: 8.1.2
3
+ Version: 8.1.3
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
@@ -13,36 +12,35 @@ Keywords: subprocess,ssh
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
17
  Classifier: Programming Language :: Python :: 3.9
21
18
  Classifier: Programming Language :: Python :: 3.10
22
19
  Classifier: Programming Language :: Python :: 3.11
23
20
  Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Programming Language :: Python :: 3.15
24
24
  Classifier: Programming Language :: Python :: Implementation :: CPython
25
25
  Classifier: Programming Language :: Python :: Implementation :: PyPy
26
- Requires-Python: >=3.8.0
26
+ Requires-Python: >=3.9.0
27
27
  Description-Content-Type: text/x-rst
28
28
  License-File: LICENSE
29
29
  Requires-Dist: paramiko>=2.4
30
30
  Requires-Dist: tenacity>=4.4.0
31
31
  Requires-Dist: psutil>=5.0
32
32
  Provides-Extra: xml
33
- Requires-Dist: defusedxml; extra == "xml"
34
33
  Provides-Extra: lxml
35
34
  Requires-Dist: lxml>=4.6.2; extra == "lxml"
36
35
  Provides-Extra: yaml
37
36
  Requires-Dist: PyYAML>=3.12; extra == "yaml"
38
37
  Provides-Extra: all-formats
39
- Requires-Dist: defusedxml; extra == "all-formats"
40
38
  Requires-Dist: lxml>=4.6.2; extra == "all-formats"
41
39
  Requires-Dist: PyYAML>=3.12; extra == "all-formats"
42
40
  Provides-Extra: all-formats
43
- Requires-Dist: defusedxml; extra == "all-formats"
44
41
  Requires-Dist: lxml>=4.6.2; extra == "all-formats"
45
42
  Requires-Dist: PyYAML>=3.12; extra == "all-formats"
43
+ Dynamic: license-file
46
44
 
47
45
  exec-helpers
48
46
  ============
@@ -12,7 +12,6 @@ mypy_requirements.txt
12
12
  pyproject.toml
13
13
  pytest_requirements.txt
14
14
  requirements.txt
15
- setup.py
16
15
  exec_helpers/__init__.py
17
16
  exec_helpers/__init__.pyi
18
17
  exec_helpers/_helpers.py
@@ -3,12 +3,10 @@ tenacity>=4.4.0
3
3
  psutil>=5.0
4
4
 
5
5
  [all-formats]
6
- defusedxml
7
6
  lxml>=4.6.2
8
7
  PyYAML>=3.12
9
8
 
10
9
  [all_formats]
11
- defusedxml
12
10
  lxml>=4.6.2
13
11
  PyYAML>=3.12
14
12
 
@@ -16,7 +14,6 @@ PyYAML>=3.12
16
14
  lxml>=4.6.2
17
15
 
18
16
  [xml]
19
- defusedxml
20
17
 
21
18
  [yaml]
22
19
  PyYAML>=3.12
@@ -11,11 +11,11 @@ build-backend="setuptools.build_meta"
11
11
  [project]
12
12
  name = "exec-helpers"
13
13
  description = "Execution helpers for simplified usage of subprocess and ssh."
14
- requires-python = ">=3.8.0"
14
+ requires-python = ">=3.9.0"
15
15
  keywords = ["subprocess", "ssh"]
16
- license={text="Apache-2.0"} # Use SPDX classifier
16
+ license="Apache-2.0" # Use SPDX classifier
17
17
  readme = {file = "README.rst", content-type = "text/x-rst"}
18
- authors=[{name="Alexey Stepanov", email="penguinolog@gmail.com"}]
18
+ authors=[{name="Aleksei Stepanov", email="penguinolog@gmail.com"}]
19
19
  maintainers=[
20
20
  {name="Aleksei Stepanov", email="penguinolog@gmail.com"},
21
21
  {name="Antonio Esposito", email="esposito.cloud@gmail.com"},
@@ -53,11 +53,11 @@ dependencies = {file = ["requirements.txt"]}
53
53
  classifiers = {file = ["classifiers.txt"]}
54
54
 
55
55
  [project.optional-dependencies]
56
- xml = ["defusedxml"]
56
+ xml = []
57
57
  lxml = ["lxml>=4.6.2"]
58
58
  yaml = ["PyYAML>=3.12"]
59
- all_formats = ["defusedxml", "lxml>=4.6.2", "PyYAML>=3.12"]
60
- all-formats = ["defusedxml", "lxml>=4.6.2", "PyYAML>=3.12"]
59
+ all_formats = ["lxml>=4.6.2", "PyYAML>=3.12"]
60
+ all-formats = ["lxml>=4.6.2", "PyYAML>=3.12"]
61
61
 
62
62
  [tool.distutils.bdist_wheel]
63
63
  universal = 0
@@ -67,7 +67,7 @@ write_to = "exec_helpers/_version.py"
67
67
 
68
68
  [tool.black]
69
69
  line-length = 120
70
- target-version = ["py38"]
70
+ target-version = ["py39"]
71
71
 
72
72
  [tool.isort]
73
73
  line_length = 120
@@ -104,7 +104,6 @@ pretty = true
104
104
  [[tool.mypy.overrides]]
105
105
  module = [
106
106
  "psutil",
107
- "defusedxml.*",
108
107
  "lxml.*"
109
108
  ]
110
109
  ignore_missing_imports = true
@@ -160,7 +159,7 @@ pretty_print = true
160
159
  [tool.ruff]
161
160
  line-length = 120
162
161
  output-format = "full"
163
- target-version = "py38"
162
+ target-version = "py39"
164
163
 
165
164
  [tool.ruff.lint]
166
165
  extend-select = [
@@ -191,6 +190,7 @@ extend-ignore = [
191
190
  "PLR6301", # "maybe staticmethod"
192
191
  "RET504", # Unnecessary variable assignment before return statement
193
192
  "SIM108", # Use ternary operator,
193
+ "S314", # use "defusedxml" which is stale
194
194
  "TRY003", # long messages prepare outside, ...
195
195
  "TID252", # relative imports from parent modules
196
196
  ]
@@ -209,7 +209,7 @@ convention = "pep257"
209
209
  docstring-code-format = true
210
210
 
211
211
  [tool.refurb]
212
- python_version = "3.8"
212
+ python_version = "3.9"
213
213
  enable_all = true
214
214
  ignore = ["FURB120"]
215
215
 
@@ -217,7 +217,7 @@ ignore = ["FURB120"]
217
217
  extension-pkg-whitelist = ["lxml.etree"]
218
218
  ignore = ["CVS", "_version.py"]
219
219
  jobs = 0
220
- py-version = "3.8"
220
+ py-version = "3.9"
221
221
 
222
222
  load-plugins = [
223
223
  "pylint.extensions.docparams",
@@ -247,6 +247,7 @@ disable = [
247
247
  "too-many-locals",
248
248
  "too-many-statements",
249
249
  "too-many-instance-attributes",
250
+ "too-many-positional-arguments",
250
251
  "too-many-lines",
251
252
  "broad-except",
252
253
  "logging-fstring-interpolation",
@@ -1,16 +0,0 @@
1
- # file generated by setuptools_scm
2
- # don't change, don't track in version control
3
- TYPE_CHECKING = False
4
- if TYPE_CHECKING:
5
- from typing import Tuple, Union
6
- VERSION_TUPLE = Tuple[Union[int, str], ...]
7
- else:
8
- VERSION_TUPLE = object
9
-
10
- version: str
11
- __version__: str
12
- __version_tuple__: VERSION_TUPLE
13
- version_tuple: VERSION_TUPLE
14
-
15
- __version__ = version = '8.1.2'
16
- __version_tuple__ = version_tuple = (8, 1, 2)
@@ -1,40 +0,0 @@
1
- # Copyright 2018 - 2023 Aleksei Stepanov aka penguinolog
2
-
3
- # Copyright 2016 Mirantis, Inc.
4
-
5
- # Licensed under the Apache License, Version 2.0 (the "License"); you may
6
- # not use this file except in compliance with the License. You may obtain
7
- # a copy of the License at
8
- #
9
- # http://www.apache.org/licenses/LICENSE-2.0
10
- #
11
- # Unless required by applicable law or agreed to in writing, software
12
- # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
- # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
- # License for the specific language governing permissions and limitations
15
- # under the License.
16
-
17
- """Execution helpers for simplified usage of subprocess and ssh."""
18
-
19
- from __future__ import annotations
20
-
21
- # External Dependencies
22
- import setuptools
23
-
24
- PACKAGE_NAME = "exec_helpers"
25
-
26
- setuptools.setup(
27
- name=PACKAGE_NAME.replace("_", "-"),
28
- url="https://github.com/python-useful-helpers/exec-helpers",
29
- python_requires=">=3.8.0",
30
- # While setuptools cannot deal with pre-installed incompatible versions,
31
- # setting a lower bound is not harmful - it makes error messages cleaner. DO
32
- # NOT set an upper bound on setuptools, as that will lead to uninstallable
33
- # situations as progressive releases of projects are done.
34
- setup_requires=[
35
- "setuptools >= 61.0.0",
36
- "setuptools_scm[toml]>=6.2",
37
- "wheel",
38
- ],
39
- package_data={PACKAGE_NAME: ["py.typed"]},
40
- )
File without changes
File without changes
File without changes
File without changes
File without changes