mm-std 0.5.2__py3-none-any.whl → 0.5.3__py3-none-any.whl
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.
mm_std/__init__.py
CHANGED
@@ -3,15 +3,19 @@ from .dict_utils import replace_empty_dict_entries
|
|
3
3
|
from .json_utils import ExtendedJSONEncoder, json_dumps
|
4
4
|
from .random_utils import random_datetime, random_decimal
|
5
5
|
from .str_utils import parse_lines, str_contains_any, str_ends_with_any, str_starts_with_any
|
6
|
+
from .subprocess_utils import ShellResult, shell, ssh_shell # nosec
|
6
7
|
|
7
8
|
__all__ = [
|
8
9
|
"ExtendedJSONEncoder",
|
10
|
+
"ShellResult",
|
9
11
|
"json_dumps",
|
10
12
|
"parse_date",
|
11
13
|
"parse_lines",
|
12
14
|
"random_datetime",
|
13
15
|
"random_decimal",
|
14
16
|
"replace_empty_dict_entries",
|
17
|
+
"shell",
|
18
|
+
"ssh_shell",
|
15
19
|
"str_contains_any",
|
16
20
|
"str_ends_with_any",
|
17
21
|
"str_starts_with_any",
|
@@ -0,0 +1,75 @@
|
|
1
|
+
import shlex
|
2
|
+
import subprocess # nosec
|
3
|
+
from dataclasses import dataclass
|
4
|
+
|
5
|
+
TIMEOUT_EXIT_CODE = 255
|
6
|
+
|
7
|
+
|
8
|
+
@dataclass
|
9
|
+
class ShellResult:
|
10
|
+
"""Result of shell command execution.
|
11
|
+
|
12
|
+
Args:
|
13
|
+
stdout: Standard output from the command
|
14
|
+
stderr: Standard error from the command
|
15
|
+
code: Exit code of the command
|
16
|
+
"""
|
17
|
+
|
18
|
+
stdout: str
|
19
|
+
stderr: str
|
20
|
+
code: int
|
21
|
+
|
22
|
+
@property
|
23
|
+
def combined_output(self) -> str:
|
24
|
+
"""Combined stdout and stderr output."""
|
25
|
+
result = ""
|
26
|
+
if self.stdout:
|
27
|
+
result += self.stdout
|
28
|
+
if self.stderr:
|
29
|
+
if result:
|
30
|
+
result += "\n"
|
31
|
+
result += self.stderr
|
32
|
+
return result
|
33
|
+
|
34
|
+
|
35
|
+
def shell(cmd: str, timeout: int | None = 60, capture_output: bool = True, echo_command: bool = False) -> ShellResult:
|
36
|
+
"""Execute a shell command.
|
37
|
+
|
38
|
+
Args:
|
39
|
+
cmd: Command to execute
|
40
|
+
timeout: Timeout in seconds, None for no timeout
|
41
|
+
capture_output: Whether to capture stdout/stderr
|
42
|
+
echo_command: Whether to print the command before execution
|
43
|
+
|
44
|
+
Returns:
|
45
|
+
ShellResult with stdout, stderr and exit code
|
46
|
+
"""
|
47
|
+
if echo_command:
|
48
|
+
print(cmd) # noqa: T201
|
49
|
+
try:
|
50
|
+
process = subprocess.run(cmd, timeout=timeout, capture_output=capture_output, shell=True, check=False) # noqa: S602 # nosec
|
51
|
+
stdout = process.stdout.decode("utf-8", errors="replace") if capture_output else ""
|
52
|
+
stderr = process.stderr.decode("utf-8", errors="replace") if capture_output else ""
|
53
|
+
return ShellResult(stdout=stdout, stderr=stderr, code=process.returncode)
|
54
|
+
except subprocess.TimeoutExpired:
|
55
|
+
return ShellResult(stdout="", stderr="timeout", code=TIMEOUT_EXIT_CODE)
|
56
|
+
|
57
|
+
|
58
|
+
def ssh_shell(host: str, cmd: str, ssh_key_path: str | None = None, timeout: int = 60, echo_command: bool = False) -> ShellResult:
|
59
|
+
"""Execute a command on remote host via SSH.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
host: Remote host to connect to
|
63
|
+
cmd: Command to execute on remote host
|
64
|
+
ssh_key_path: Path to SSH private key file
|
65
|
+
timeout: Timeout in seconds
|
66
|
+
echo_command: Whether to print the command before execution
|
67
|
+
|
68
|
+
Returns:
|
69
|
+
ShellResult with stdout, stderr and exit code
|
70
|
+
"""
|
71
|
+
ssh_cmd = "ssh -o 'StrictHostKeyChecking=no' -o 'LogLevel=ERROR'"
|
72
|
+
if ssh_key_path:
|
73
|
+
ssh_cmd += f" -i {shlex.quote(ssh_key_path)}"
|
74
|
+
ssh_cmd += f" {shlex.quote(host)} {shlex.quote(cmd)}"
|
75
|
+
return shell(ssh_cmd, timeout=timeout, echo_command=echo_command)
|
@@ -1,10 +1,11 @@
|
|
1
|
-
mm_std/__init__.py,sha256=
|
1
|
+
mm_std/__init__.py,sha256=V1na3dOxX52p44hOwhHFaIgrZEHR3yEjFdmSIcB2n0c,715
|
2
2
|
mm_std/date_utils.py,sha256=aFdIacoNgDSPGeUkZihXZADd86TeHu4hr1uIT9zcqvw,1732
|
3
3
|
mm_std/dict_utils.py,sha256=GVegQXTIo3tzLGbBkiUSGTJkfaD5WWwz6OQnw9KcXlg,2275
|
4
4
|
mm_std/json_utils.py,sha256=3tOv2rowc9B18TpJyGSci1MvPEj5XogRy3qrJ1W_7Bg,4129
|
5
5
|
mm_std/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
6
|
mm_std/random_utils.py,sha256=x3uNuKjKY8GxYjbnOq0LU1pGXhI2wezpH2K-t9hrhfA,2225
|
7
7
|
mm_std/str_utils.py,sha256=I6vVC81dGBDTHm7FxW-ka5OlUPjHmgagei7Zjld65lk,1520
|
8
|
-
mm_std
|
9
|
-
mm_std-0.5.
|
10
|
-
mm_std-0.5.
|
8
|
+
mm_std/subprocess_utils.py,sha256=6Bkw6ZYHT1NLIYbQUV9LGkBzUl5RtaVzMJiLJrTGq48,2507
|
9
|
+
mm_std-0.5.3.dist-info/METADATA,sha256=GbbfchD5CvEJq3n5ZMNJyNPwUwvfOd_bJxj7tc6iGqY,74
|
10
|
+
mm_std-0.5.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
11
|
+
mm_std-0.5.3.dist-info/RECORD,,
|
File without changes
|