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.
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/.pre-commit-config.yaml +4 -4
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/PKG-INFO +9 -11
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/classifiers.txt +3 -2
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/__init__.py +1 -1
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/_helpers.py +1 -1
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/_ssh_base.py +8 -6
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/_ssh_helpers.py +4 -5
- exec_helpers-8.1.3/exec_helpers/_version.py +24 -0
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/api.py +12 -8
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/async_api/api.py +7 -4
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/async_api/subprocess.py +5 -3
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/exceptions.py +1 -1
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/exec_result.py +14 -22
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/ssh_auth.py +9 -8
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/subprocess.py +6 -6
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers.egg-info/PKG-INFO +9 -11
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers.egg-info/SOURCES.txt +0 -1
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers.egg-info/requires.txt +0 -3
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/pyproject.toml +12 -11
- exec_helpers-8.1.2/exec_helpers/_version.py +0 -16
- exec_helpers-8.1.2/setup.py +0 -40
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/.editorconfig +0 -0
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/.readthedocs.yaml +0 -0
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/CI_REQUIREMENTS.txt +0 -0
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/LICENSE +0 -0
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/MANIFEST.in +0 -0
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/README.rst +0 -0
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/build_requirements.txt +0 -0
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/__init__.pyi +0 -0
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/_log_templates.py +0 -0
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/_subprocess_helpers.py +0 -0
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/async_api/__init__.py +0 -0
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/async_api/__init__.pyi +0 -0
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/async_api/exec_result.py +0 -0
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/constants.py +0 -0
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/proc_enums.py +0 -0
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/py.typed +0 -0
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers/ssh.py +0 -0
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers.egg-info/dependency_links.txt +0 -0
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers.egg-info/not-zip-safe +0 -0
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/exec_helpers.egg-info/top_level.txt +0 -0
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/flake8_requirements.txt +0 -0
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/mypy_requirements.txt +0 -0
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/pytest_requirements.txt +0 -0
- {exec_helpers-8.1.2 → exec_helpers-8.1.3}/requirements.txt +0 -0
- {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:
|
|
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:
|
|
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:
|
|
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.
|
|
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
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: exec-helpers
|
|
3
|
-
Version: 8.1.
|
|
3
|
+
Version: 8.1.3
|
|
4
4
|
Summary: Execution helpers for simplified usage of subprocess and ssh.
|
|
5
|
-
|
|
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.
|
|
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
|
|
@@ -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
|
|
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,
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
18
|
-
SSHConfigsDictT =
|
|
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(
|
|
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,
|
|
59
|
-
LogMaskReT = typing.Union[str,
|
|
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 =
|
|
66
|
+
ExpectedExitCodesT = Iterable[ExitCodeT]
|
|
63
67
|
OptionalTimeoutT = typing.Union[int, float, None]
|
|
64
|
-
CalledProcessErrorSubClassT =
|
|
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(
|
|
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(
|
|
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
|
-
|
|
223
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
207
|
-
|
|
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(
|
|
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))
|
|
393
|
-
env.update(env_patch)
|
|
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__ = ("
|
|
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
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
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
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
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
|
-
|
|
806
|
-
spent = f"\tspent={
|
|
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[
|
|
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
|
-
|
|
302
|
+
key: str | None = None
|
|
302
303
|
else:
|
|
303
|
-
|
|
304
|
-
|
|
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
|
-
|
|
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={
|
|
316
|
-
f"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(
|
|
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,
|
|
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))
|
|
415
|
-
env.update(env_patch)
|
|
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
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: exec-helpers
|
|
3
|
-
Version: 8.1.
|
|
3
|
+
Version: 8.1.3
|
|
4
4
|
Summary: Execution helpers for simplified usage of subprocess and ssh.
|
|
5
|
-
|
|
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.
|
|
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
|
============
|
|
@@ -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.
|
|
14
|
+
requires-python = ">=3.9.0"
|
|
15
15
|
keywords = ["subprocess", "ssh"]
|
|
16
|
-
license=
|
|
16
|
+
license="Apache-2.0" # Use SPDX classifier
|
|
17
17
|
readme = {file = "README.rst", content-type = "text/x-rst"}
|
|
18
|
-
authors=[{name="
|
|
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 = [
|
|
56
|
+
xml = []
|
|
57
57
|
lxml = ["lxml>=4.6.2"]
|
|
58
58
|
yaml = ["PyYAML>=3.12"]
|
|
59
|
-
all_formats = ["
|
|
60
|
-
all-formats = ["
|
|
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 = ["
|
|
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 = "
|
|
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.
|
|
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.
|
|
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)
|
exec_helpers-8.1.2/setup.py
DELETED
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|