msdev 0.9.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.
@@ -0,0 +1,136 @@
1
+ """Typed command execution application service."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Any, Callable, Mapping
7
+
8
+ from ..limits import (
9
+ DEFAULT_EXEC_TIMEOUT_SECONDS,
10
+ validate_exec_timeout_seconds,
11
+ )
12
+ from .context import ServiceContext
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class ExecRequest:
17
+ environment: str
18
+ argv: tuple[str, ...]
19
+ cwd: str | None = None
20
+ env: Mapping[str, str] = field(default_factory=dict)
21
+ timeout_seconds: float = DEFAULT_EXEC_TIMEOUT_SECONDS
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class ExecResult:
26
+ environment: str
27
+ returncode: int
28
+ stdout: str
29
+ stderr: str
30
+ stdout_truncated: bool = False
31
+ stderr_truncated: bool = False
32
+ output_drain_truncated: bool = False
33
+ timed_out: bool = False
34
+ cancelled: bool = False
35
+ cwd: str | None = None
36
+ runtime: dict[str, Any] | None = None
37
+ environments: tuple[Any, ...] = ()
38
+ raw: dict[str, Any] = field(default_factory=dict, repr=False)
39
+
40
+ def to_dict(self) -> dict[str, Any]:
41
+ return {"environment": self.environment, **self.raw}
42
+
43
+
44
+ class ExecutionService:
45
+ def __init__(self, context: ServiceContext):
46
+ self.context = context
47
+
48
+ @staticmethod
49
+ def _validate(request: ExecRequest) -> tuple[str, tuple[str, ...], dict[str, str]]:
50
+ environment = (
51
+ request.environment
52
+ if isinstance(request.environment, str) and request.environment.strip()
53
+ else ""
54
+ )
55
+ if not environment:
56
+ raise ValueError("environment must be a non-empty string")
57
+ argv = tuple(request.argv)
58
+ if not argv or not all(isinstance(item, str) and item for item in argv):
59
+ raise ValueError("exec argv must be a non-empty string array")
60
+ env: dict[str, str] = {}
61
+ for key, value in request.env.items():
62
+ if not isinstance(key, str) or not key:
63
+ raise ValueError("environment names must be non-empty strings")
64
+ if not isinstance(value, str):
65
+ raise ValueError("environment values must be strings")
66
+ env[key] = value
67
+ if request.cwd is not None and not isinstance(request.cwd, str):
68
+ raise ValueError("cwd must be a string")
69
+ validate_exec_timeout_seconds(request.timeout_seconds)
70
+ return environment, argv, env
71
+
72
+ @staticmethod
73
+ def _params(
74
+ request: ExecRequest,
75
+ argv: tuple[str, ...],
76
+ env: dict[str, str],
77
+ ) -> dict[str, Any]:
78
+ return {
79
+ "command": list(argv),
80
+ "cwd": request.cwd,
81
+ "env": env,
82
+ "timeout_seconds": validate_exec_timeout_seconds(
83
+ request.timeout_seconds
84
+ ),
85
+ }
86
+
87
+ @staticmethod
88
+ def _result(environment: str, result: Any) -> ExecResult:
89
+ if not isinstance(result, dict):
90
+ raise RuntimeError("exec returned a non-object result")
91
+ if "returncode" not in result:
92
+ raise RuntimeError("exec result is missing returncode")
93
+ raw = dict(result)
94
+ runtime = raw.get("runtime")
95
+ if runtime is not None and not isinstance(runtime, dict):
96
+ raise RuntimeError("exec result runtime must be an object")
97
+ environments = raw.get("environments") or ()
98
+ if not isinstance(environments, (list, tuple)):
99
+ raise RuntimeError("exec result environments must be an array")
100
+ return ExecResult(
101
+ environment=environment,
102
+ returncode=int(raw["returncode"]),
103
+ stdout=str(raw.get("stdout") or ""),
104
+ stderr=str(raw.get("stderr") or ""),
105
+ stdout_truncated=bool(raw.get("stdout_truncated", False)),
106
+ stderr_truncated=bool(raw.get("stderr_truncated", False)),
107
+ output_drain_truncated=bool(
108
+ raw.get("output_drain_truncated", False)
109
+ ),
110
+ timed_out=bool(raw.get("timed_out", False)),
111
+ cancelled=bool(raw.get("cancelled", False)),
112
+ cwd=raw.get("cwd"),
113
+ runtime=runtime,
114
+ environments=tuple(environments),
115
+ raw=raw,
116
+ )
117
+
118
+ def run(self, request: ExecRequest) -> ExecResult:
119
+ environment, argv, env = self._validate(request)
120
+ result = self.context.transport_for_environment(environment).call(
121
+ "exec",
122
+ self._params(request, argv, env),
123
+ )
124
+ return self._result(environment, result)
125
+
126
+ def stream(
127
+ self,
128
+ request: ExecRequest,
129
+ on_output: Callable[[str, bytes], None],
130
+ ) -> ExecResult:
131
+ environment, argv, env = self._validate(request)
132
+ result = self.context.transport_for_environment(environment).stream_exec(
133
+ self._params(request, argv, env),
134
+ on_output,
135
+ )
136
+ return self._result(environment, result)