mm-std 0.5.4__py3-none-any.whl → 0.6.0__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,19 +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
|
|
6
|
+
from .subprocess_utils import CmdResult, run_cmd, run_ssh_cmd # nosec
|
|
7
7
|
|
|
8
8
|
__all__ = [
|
|
9
|
+
"CmdResult",
|
|
9
10
|
"ExtendedJSONEncoder",
|
|
10
|
-
"ShellResult",
|
|
11
11
|
"json_dumps",
|
|
12
12
|
"parse_date",
|
|
13
13
|
"parse_lines",
|
|
14
14
|
"random_datetime",
|
|
15
15
|
"random_decimal",
|
|
16
16
|
"replace_empty_dict_entries",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
17
|
+
"run_cmd",
|
|
18
|
+
"run_ssh_cmd",
|
|
19
19
|
"str_contains_any",
|
|
20
20
|
"str_ends_with_any",
|
|
21
21
|
"str_starts_with_any",
|
mm_std/json_utils.py
CHANGED
|
@@ -49,17 +49,17 @@ class ExtendedJSONEncoder(json.JSONEncoder):
|
|
|
49
49
|
raise ValueError(f"Cannot override built-in JSON type: {type_.__name__}")
|
|
50
50
|
cls._type_handlers[type_] = serializer
|
|
51
51
|
|
|
52
|
-
def default(self,
|
|
52
|
+
def default(self, o: Any) -> Any: # noqa: ANN401
|
|
53
53
|
# Check registered type handlers first
|
|
54
54
|
for type_, handler in self._type_handlers.items():
|
|
55
|
-
if isinstance(
|
|
56
|
-
return handler(
|
|
55
|
+
if isinstance(o, type_):
|
|
56
|
+
return handler(o)
|
|
57
57
|
|
|
58
58
|
# Special case: dataclasses (requires is_dataclass check, not isinstance)
|
|
59
|
-
if is_dataclass(
|
|
60
|
-
return asdict(
|
|
59
|
+
if is_dataclass(o) and not isinstance(o, type):
|
|
60
|
+
return asdict(o) # Don't need recursive serialization
|
|
61
61
|
|
|
62
|
-
return super().default(
|
|
62
|
+
return super().default(o)
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
def json_dumps(data: Any, type_handlers: dict[type[Any], Callable[[Any], Any]] | None = None, **kwargs: Any) -> str: # noqa: ANN401
|
mm_std/subprocess_utils.py
CHANGED
|
@@ -6,14 +6,8 @@ TIMEOUT_EXIT_CODE = 255
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
@dataclass
|
|
9
|
-
class
|
|
10
|
-
"""Result of
|
|
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
|
-
"""
|
|
9
|
+
class CmdResult:
|
|
10
|
+
"""Result of command execution."""
|
|
17
11
|
|
|
18
12
|
stdout: str
|
|
19
13
|
stderr: str
|
|
@@ -32,30 +26,57 @@ class ShellResult:
|
|
|
32
26
|
return result
|
|
33
27
|
|
|
34
28
|
|
|
35
|
-
def
|
|
36
|
-
|
|
29
|
+
def run_cmd(
|
|
30
|
+
cmd: str,
|
|
31
|
+
timeout: int | None = 60,
|
|
32
|
+
capture_output: bool = True,
|
|
33
|
+
echo_command: bool = False,
|
|
34
|
+
shell: bool = False,
|
|
35
|
+
) -> CmdResult:
|
|
36
|
+
"""Execute a command.
|
|
37
37
|
|
|
38
38
|
Args:
|
|
39
39
|
cmd: Command to execute
|
|
40
40
|
timeout: Timeout in seconds, None for no timeout
|
|
41
41
|
capture_output: Whether to capture stdout/stderr
|
|
42
42
|
echo_command: Whether to print the command before execution
|
|
43
|
+
shell: If False (default), the command is parsed with shlex.split() and
|
|
44
|
+
executed without shell interpretation. Special characters like
|
|
45
|
+
backticks, $(), pipes (|), redirects (>, <), and wildcards (*) are
|
|
46
|
+
treated as literal text. This is the safe mode for commands with
|
|
47
|
+
user input.
|
|
48
|
+
If True, the command is passed to the shell as-is, enabling pipes,
|
|
49
|
+
redirects, command substitution, and other shell features. Use this
|
|
50
|
+
only for trusted commands that need shell functionality.
|
|
43
51
|
|
|
44
52
|
Returns:
|
|
45
|
-
|
|
53
|
+
CmdResult with stdout, stderr and exit code
|
|
46
54
|
"""
|
|
47
55
|
if echo_command:
|
|
48
56
|
print(cmd) # noqa: T201
|
|
49
57
|
try:
|
|
50
|
-
|
|
58
|
+
if shell:
|
|
59
|
+
process = subprocess.run( # noqa: S602 # nosec
|
|
60
|
+
cmd, timeout=timeout, capture_output=capture_output, shell=True, check=False
|
|
61
|
+
)
|
|
62
|
+
else:
|
|
63
|
+
process = subprocess.run( # noqa: S603 # nosec
|
|
64
|
+
shlex.split(cmd), timeout=timeout, capture_output=capture_output, shell=False, check=False
|
|
65
|
+
)
|
|
51
66
|
stdout = process.stdout.decode("utf-8", errors="replace") if capture_output else ""
|
|
52
67
|
stderr = process.stderr.decode("utf-8", errors="replace") if capture_output else ""
|
|
53
|
-
return
|
|
68
|
+
return CmdResult(stdout=stdout, stderr=stderr, code=process.returncode)
|
|
54
69
|
except subprocess.TimeoutExpired:
|
|
55
|
-
return
|
|
70
|
+
return CmdResult(stdout="", stderr="timeout", code=TIMEOUT_EXIT_CODE)
|
|
56
71
|
|
|
57
72
|
|
|
58
|
-
def
|
|
73
|
+
def run_ssh_cmd(
|
|
74
|
+
host: str,
|
|
75
|
+
cmd: str,
|
|
76
|
+
ssh_key_path: str | None = None,
|
|
77
|
+
timeout: int = 60,
|
|
78
|
+
echo_command: bool = False,
|
|
79
|
+
) -> CmdResult:
|
|
59
80
|
"""Execute a command on remote host via SSH.
|
|
60
81
|
|
|
61
82
|
Args:
|
|
@@ -66,10 +87,10 @@ def ssh_shell(host: str, cmd: str, ssh_key_path: str | None = None, timeout: int
|
|
|
66
87
|
echo_command: Whether to print the command before execution
|
|
67
88
|
|
|
68
89
|
Returns:
|
|
69
|
-
|
|
90
|
+
CmdResult with stdout, stderr and exit code
|
|
70
91
|
"""
|
|
71
92
|
ssh_cmd = "ssh -o 'StrictHostKeyChecking=no' -o 'LogLevel=ERROR'"
|
|
72
93
|
if ssh_key_path:
|
|
73
94
|
ssh_cmd += f" -i {shlex.quote(ssh_key_path)}"
|
|
74
95
|
ssh_cmd += f" {shlex.quote(host)} {shlex.quote(cmd)}"
|
|
75
|
-
return
|
|
96
|
+
return run_cmd(ssh_cmd, timeout=timeout, echo_command=echo_command)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
mm_std/__init__.py,sha256=6VZQzG9evSp0V1HES_OLkbVPP41-ziJ2AwBsSkF4N5M,719
|
|
2
|
+
mm_std/date_utils.py,sha256=aFdIacoNgDSPGeUkZihXZADd86TeHu4hr1uIT9zcqvw,1732
|
|
3
|
+
mm_std/dict_utils.py,sha256=Gq_LYuidf24SWvzNmUx8wVPGuop9263GOxcIflB__uQ,2850
|
|
4
|
+
mm_std/json_utils.py,sha256=SXrqNk5vlUxzJC8TE9yteMl8eTxBcWSQK63CHnQ3ZxI,3983
|
|
5
|
+
mm_std/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
mm_std/random_utils.py,sha256=3q5ZylCqfFHKN3VaDXuy4heo8C1jNbLHNNOFvyYJoZY,2253
|
|
7
|
+
mm_std/str_utils.py,sha256=I6vVC81dGBDTHm7FxW-ka5OlUPjHmgagei7Zjld65lk,1520
|
|
8
|
+
mm_std/subprocess_utils.py,sha256=PsrwPdpfPLozVIFbqBNgIAOIKpj0LAeVcq833YGcHbo,3212
|
|
9
|
+
mm_std-0.6.0.dist-info/METADATA,sha256=kGDjddlSMx1LnxwnME8L0qAS6dLmu8H7wOvxW8u0sxw,74
|
|
10
|
+
mm_std-0.6.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
11
|
+
mm_std-0.6.0.dist-info/RECORD,,
|
mm_std-0.5.4.dist-info/RECORD
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
mm_std/__init__.py,sha256=V1na3dOxX52p44hOwhHFaIgrZEHR3yEjFdmSIcB2n0c,715
|
|
2
|
-
mm_std/date_utils.py,sha256=aFdIacoNgDSPGeUkZihXZADd86TeHu4hr1uIT9zcqvw,1732
|
|
3
|
-
mm_std/dict_utils.py,sha256=Gq_LYuidf24SWvzNmUx8wVPGuop9263GOxcIflB__uQ,2850
|
|
4
|
-
mm_std/json_utils.py,sha256=NuDomTThfCkJBCmfQ6vkr7dvChKGsuLoAyLW0TON_dQ,3997
|
|
5
|
-
mm_std/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
mm_std/random_utils.py,sha256=3q5ZylCqfFHKN3VaDXuy4heo8C1jNbLHNNOFvyYJoZY,2253
|
|
7
|
-
mm_std/str_utils.py,sha256=I6vVC81dGBDTHm7FxW-ka5OlUPjHmgagei7Zjld65lk,1520
|
|
8
|
-
mm_std/subprocess_utils.py,sha256=6Bkw6ZYHT1NLIYbQUV9LGkBzUl5RtaVzMJiLJrTGq48,2507
|
|
9
|
-
mm_std-0.5.4.dist-info/METADATA,sha256=SnanvJ0gBE6Y_5-dSOkUaH5BVV482k3KPXFhuUKeV5Q,74
|
|
10
|
-
mm_std-0.5.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
-
mm_std-0.5.4.dist-info/RECORD,,
|