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 ShellResult, shell, ssh_shell # nosec
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
- "shell",
18
- "ssh_shell",
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, obj: Any) -> Any: # noqa: ANN401
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(obj, type_):
56
- return handler(obj)
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(obj) and not isinstance(obj, type):
60
- return asdict(obj) # Don't need recursive serialization
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(obj)
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
@@ -6,14 +6,8 @@ TIMEOUT_EXIT_CODE = 255
6
6
 
7
7
 
8
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
- """
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 shell(cmd: str, timeout: int | None = 60, capture_output: bool = True, echo_command: bool = False) -> ShellResult:
36
- """Execute a shell command.
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
- ShellResult with stdout, stderr and exit code
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
- process = subprocess.run(cmd, timeout=timeout, capture_output=capture_output, shell=True, check=False) # noqa: S602 # nosec
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 ShellResult(stdout=stdout, stderr=stderr, code=process.returncode)
68
+ return CmdResult(stdout=stdout, stderr=stderr, code=process.returncode)
54
69
  except subprocess.TimeoutExpired:
55
- return ShellResult(stdout="", stderr="timeout", code=TIMEOUT_EXIT_CODE)
70
+ return CmdResult(stdout="", stderr="timeout", code=TIMEOUT_EXIT_CODE)
56
71
 
57
72
 
58
- def ssh_shell(host: str, cmd: str, ssh_key_path: str | None = None, timeout: int = 60, echo_command: bool = False) -> ShellResult:
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
- ShellResult with stdout, stderr and exit code
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 shell(ssh_cmd, timeout=timeout, echo_command=echo_command)
96
+ return run_cmd(ssh_cmd, timeout=timeout, echo_command=echo_command)
@@ -1,4 +1,4 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mm-std
3
- Version: 0.5.4
3
+ Version: 0.6.0
4
4
  Requires-Python: >=3.13
@@ -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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -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,,