omlish 0.0.0.dev154__py3-none-any.whl → 0.0.0.dev156__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.
- omlish/__about__.py +2 -2
- omlish/io/fdio/__init__.py +1 -0
- omlish/lite/asyncio/subprocesses.py +132 -161
- omlish/lite/check.py +0 -2
- omlish/lite/contextmanagers.py +14 -0
- omlish/lite/marshal.py +11 -5
- omlish/lite/subprocesses.py +196 -141
- omlish/os/linux.py +484 -0
- omlish/term.py +29 -14
- {omlish-0.0.0.dev154.dist-info → omlish-0.0.0.dev156.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev154.dist-info → omlish-0.0.0.dev156.dist-info}/RECORD +15 -14
- {omlish-0.0.0.dev154.dist-info → omlish-0.0.0.dev156.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev154.dist-info → omlish-0.0.0.dev156.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev154.dist-info → omlish-0.0.0.dev156.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev154.dist-info → omlish-0.0.0.dev156.dist-info}/top_level.txt +0 -0
omlish/lite/subprocesses.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
|
+
import abc
|
2
3
|
import contextlib
|
3
4
|
import logging
|
4
5
|
import os
|
@@ -8,8 +9,8 @@ import sys
|
|
8
9
|
import time
|
9
10
|
import typing as ta
|
10
11
|
|
11
|
-
from .logs import log
|
12
|
-
from .runtime import is_debugger_attached
|
12
|
+
from omlish.lite.logs import log
|
13
|
+
from omlish.lite.runtime import is_debugger_attached
|
13
14
|
|
14
15
|
|
15
16
|
T = ta.TypeVar('T')
|
@@ -32,165 +33,219 @@ SUBPROCESS_CHANNEL_OPTION_VALUES: ta.Mapping[SubprocessChannelOption, int] = {
|
|
32
33
|
_SUBPROCESS_SHELL_WRAP_EXECS = False
|
33
34
|
|
34
35
|
|
35
|
-
def subprocess_shell_wrap_exec(*
|
36
|
-
return ('sh', '-c', ' '.join(map(shlex.quote,
|
36
|
+
def subprocess_shell_wrap_exec(*cmd: str) -> ta.Tuple[str, ...]:
|
37
|
+
return ('sh', '-c', ' '.join(map(shlex.quote, cmd)))
|
37
38
|
|
38
39
|
|
39
|
-
def subprocess_maybe_shell_wrap_exec(*
|
40
|
+
def subprocess_maybe_shell_wrap_exec(*cmd: str) -> ta.Tuple[str, ...]:
|
40
41
|
if _SUBPROCESS_SHELL_WRAP_EXECS or is_debugger_attached():
|
41
|
-
return subprocess_shell_wrap_exec(*
|
42
|
+
return subprocess_shell_wrap_exec(*cmd)
|
42
43
|
else:
|
43
|
-
return
|
44
|
-
|
45
|
-
|
46
|
-
def prepare_subprocess_invocation(
|
47
|
-
*args: str,
|
48
|
-
env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
49
|
-
extra_env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
50
|
-
quiet: bool = False,
|
51
|
-
shell: bool = False,
|
52
|
-
**kwargs: ta.Any,
|
53
|
-
) -> ta.Tuple[ta.Tuple[ta.Any, ...], ta.Dict[str, ta.Any]]:
|
54
|
-
log.debug('prepare_subprocess_invocation: args=%r', args)
|
55
|
-
if extra_env:
|
56
|
-
log.debug('prepare_subprocess_invocation: extra_env=%r', extra_env)
|
57
|
-
|
58
|
-
if extra_env:
|
59
|
-
env = {**(env if env is not None else os.environ), **extra_env}
|
60
|
-
|
61
|
-
if quiet and 'stderr' not in kwargs:
|
62
|
-
if not log.isEnabledFor(logging.DEBUG):
|
63
|
-
kwargs['stderr'] = subprocess.DEVNULL
|
64
|
-
|
65
|
-
if not shell:
|
66
|
-
args = subprocess_maybe_shell_wrap_exec(*args)
|
67
|
-
|
68
|
-
return args, dict(
|
69
|
-
env=env,
|
70
|
-
shell=shell,
|
71
|
-
**kwargs,
|
72
|
-
)
|
73
|
-
|
74
|
-
|
75
|
-
##
|
76
|
-
|
77
|
-
|
78
|
-
@contextlib.contextmanager
|
79
|
-
def subprocess_common_context(*args: ta.Any, **kwargs: ta.Any) -> ta.Iterator[None]:
|
80
|
-
start_time = time.time()
|
81
|
-
try:
|
82
|
-
log.debug('subprocess_common_context.try: args=%r', args)
|
83
|
-
yield
|
84
|
-
|
85
|
-
except Exception as exc: # noqa
|
86
|
-
log.debug('subprocess_common_context.except: exc=%r', exc)
|
87
|
-
raise
|
88
|
-
|
89
|
-
finally:
|
90
|
-
end_time = time.time()
|
91
|
-
elapsed_s = end_time - start_time
|
92
|
-
log.debug('subprocess_common_context.finally: elapsed_s=%f args=%r', elapsed_s, args)
|
44
|
+
return cmd
|
93
45
|
|
94
46
|
|
95
47
|
##
|
96
48
|
|
97
49
|
|
98
|
-
def
|
99
|
-
|
100
|
-
|
101
|
-
**kwargs: ta.Any,
|
50
|
+
def subprocess_close(
|
51
|
+
proc: subprocess.Popen,
|
52
|
+
timeout: ta.Optional[float] = None,
|
102
53
|
) -> None:
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
**kwargs: ta.Any,
|
111
|
-
) -> bytes:
|
112
|
-
args, kwargs = prepare_subprocess_invocation(*args, **kwargs)
|
113
|
-
with subprocess_common_context(*args, **kwargs):
|
114
|
-
return subprocess.check_output(args, **kwargs)
|
115
|
-
|
54
|
+
# TODO: terminate, sleep, kill
|
55
|
+
if proc.stdout:
|
56
|
+
proc.stdout.close()
|
57
|
+
if proc.stderr:
|
58
|
+
proc.stderr.close()
|
59
|
+
if proc.stdin:
|
60
|
+
proc.stdin.close()
|
116
61
|
|
117
|
-
|
118
|
-
return subprocess_check_output(*args, **kwargs).decode().strip()
|
62
|
+
proc.wait(timeout)
|
119
63
|
|
120
64
|
|
121
65
|
##
|
122
66
|
|
123
67
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
) ->
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
68
|
+
class AbstractSubprocesses(abc.ABC): # noqa
|
69
|
+
DEFAULT_LOGGER: ta.ClassVar[ta.Optional[logging.Logger]] = log
|
70
|
+
|
71
|
+
def __init__(
|
72
|
+
self,
|
73
|
+
*,
|
74
|
+
log: ta.Optional[logging.Logger] = None,
|
75
|
+
try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
|
76
|
+
) -> None:
|
77
|
+
super().__init__()
|
78
|
+
|
79
|
+
self._log = log if log is not None else self.DEFAULT_LOGGER
|
80
|
+
self._try_exceptions = try_exceptions if try_exceptions is not None else self.DEFAULT_TRY_EXCEPTIONS
|
81
|
+
|
82
|
+
#
|
83
|
+
|
84
|
+
def prepare_args(
|
85
|
+
self,
|
86
|
+
*cmd: str,
|
87
|
+
env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
88
|
+
extra_env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
89
|
+
quiet: bool = False,
|
90
|
+
shell: bool = False,
|
91
|
+
**kwargs: ta.Any,
|
92
|
+
) -> ta.Tuple[ta.Tuple[ta.Any, ...], ta.Dict[str, ta.Any]]:
|
93
|
+
if self._log:
|
94
|
+
self._log.debug('Subprocesses.prepare_args: cmd=%r', cmd)
|
95
|
+
if extra_env:
|
96
|
+
self._log.debug('Subprocesses.prepare_args: extra_env=%r', extra_env)
|
97
|
+
|
98
|
+
if extra_env:
|
99
|
+
env = {**(env if env is not None else os.environ), **extra_env}
|
100
|
+
|
101
|
+
if quiet and 'stderr' not in kwargs:
|
102
|
+
if self._log and not self._log.isEnabledFor(logging.DEBUG):
|
103
|
+
kwargs['stderr'] = subprocess.DEVNULL
|
104
|
+
|
105
|
+
if not shell:
|
106
|
+
cmd = subprocess_maybe_shell_wrap_exec(*cmd)
|
107
|
+
|
108
|
+
return cmd, dict(
|
109
|
+
env=env,
|
110
|
+
shell=shell,
|
153
111
|
**kwargs,
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
112
|
+
)
|
113
|
+
|
114
|
+
@contextlib.contextmanager
|
115
|
+
def wrap_call(self, *cmd: ta.Any, **kwargs: ta.Any) -> ta.Iterator[None]:
|
116
|
+
start_time = time.time()
|
117
|
+
try:
|
118
|
+
if self._log:
|
119
|
+
self._log.debug('Subprocesses.wrap_call.try: cmd=%r', cmd)
|
120
|
+
yield
|
121
|
+
|
122
|
+
except Exception as exc: # noqa
|
123
|
+
if self._log:
|
124
|
+
self._log.debug('Subprocesses.wrap_call.except: exc=%r', exc)
|
125
|
+
raise
|
126
|
+
|
127
|
+
finally:
|
128
|
+
end_time = time.time()
|
129
|
+
elapsed_s = end_time - start_time
|
130
|
+
if self._log:
|
131
|
+
self._log.debug('sSubprocesses.wrap_call.finally: elapsed_s=%f cmd=%r', elapsed_s, cmd)
|
132
|
+
|
133
|
+
@contextlib.contextmanager
|
134
|
+
def prepare_and_wrap(
|
135
|
+
self,
|
136
|
+
*cmd: ta.Any,
|
137
|
+
**kwargs: ta.Any,
|
138
|
+
) -> ta.Iterator[ta.Tuple[
|
139
|
+
ta.Tuple[ta.Any, ...],
|
140
|
+
ta.Dict[str, ta.Any],
|
141
|
+
]]:
|
142
|
+
cmd, kwargs = self.prepare_args(*cmd, **kwargs)
|
143
|
+
with self.wrap_call(*cmd, **kwargs):
|
144
|
+
yield cmd, kwargs
|
145
|
+
|
146
|
+
#
|
147
|
+
|
148
|
+
DEFAULT_TRY_EXCEPTIONS: ta.Tuple[ta.Type[Exception], ...] = (
|
149
|
+
FileNotFoundError,
|
150
|
+
subprocess.CalledProcessError,
|
151
|
+
)
|
175
152
|
|
176
|
-
def
|
177
|
-
|
178
|
-
|
153
|
+
def try_fn(
|
154
|
+
self,
|
155
|
+
fn: ta.Callable[..., T],
|
156
|
+
*cmd: str,
|
157
|
+
try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
|
158
|
+
**kwargs: ta.Any,
|
159
|
+
) -> ta.Union[T, Exception]:
|
160
|
+
if try_exceptions is None:
|
161
|
+
try_exceptions = self._try_exceptions
|
162
|
+
|
163
|
+
try:
|
164
|
+
return fn(*cmd, **kwargs)
|
165
|
+
|
166
|
+
except try_exceptions as e: # noqa
|
167
|
+
if self._log and self._log.isEnabledFor(logging.DEBUG):
|
168
|
+
self._log.exception('command failed')
|
169
|
+
return e
|
170
|
+
|
171
|
+
async def async_try_fn(
|
172
|
+
self,
|
173
|
+
fn: ta.Callable[..., ta.Awaitable[T]],
|
174
|
+
*cmd: ta.Any,
|
175
|
+
try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
|
176
|
+
**kwargs: ta.Any,
|
177
|
+
) -> ta.Union[T, Exception]:
|
178
|
+
if try_exceptions is None:
|
179
|
+
try_exceptions = self._try_exceptions
|
180
|
+
|
181
|
+
try:
|
182
|
+
return await fn(*cmd, **kwargs)
|
183
|
+
|
184
|
+
except try_exceptions as e: # noqa
|
185
|
+
if self._log and self._log.isEnabledFor(logging.DEBUG):
|
186
|
+
self._log.exception('command failed')
|
187
|
+
return e
|
179
188
|
|
180
189
|
|
181
190
|
##
|
182
191
|
|
183
192
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
193
|
+
class Subprocesses(AbstractSubprocesses):
|
194
|
+
def check_call(
|
195
|
+
self,
|
196
|
+
*cmd: str,
|
197
|
+
stdout: ta.Any = sys.stderr,
|
198
|
+
**kwargs: ta.Any,
|
199
|
+
) -> None:
|
200
|
+
with self.prepare_and_wrap(*cmd, stdout=stdout, **kwargs) as (cmd, kwargs): # noqa
|
201
|
+
subprocess.check_call(cmd, **kwargs)
|
202
|
+
|
203
|
+
def check_output(
|
204
|
+
self,
|
205
|
+
*cmd: str,
|
206
|
+
**kwargs: ta.Any,
|
207
|
+
) -> bytes:
|
208
|
+
with self.prepare_and_wrap(*cmd, **kwargs) as (cmd, kwargs): # noqa
|
209
|
+
return subprocess.check_output(cmd, **kwargs)
|
210
|
+
|
211
|
+
def check_output_str(
|
212
|
+
self,
|
213
|
+
*cmd: str,
|
214
|
+
**kwargs: ta.Any,
|
215
|
+
) -> str:
|
216
|
+
return self.check_output(*cmd, **kwargs).decode().strip()
|
217
|
+
|
218
|
+
#
|
219
|
+
|
220
|
+
def try_call(
|
221
|
+
self,
|
222
|
+
*cmd: str,
|
223
|
+
**kwargs: ta.Any,
|
224
|
+
) -> bool:
|
225
|
+
if isinstance(self.try_fn(self.check_call, *cmd, **kwargs), Exception):
|
226
|
+
return False
|
227
|
+
else:
|
228
|
+
return True
|
229
|
+
|
230
|
+
def try_output(
|
231
|
+
self,
|
232
|
+
*cmd: str,
|
233
|
+
**kwargs: ta.Any,
|
234
|
+
) -> ta.Optional[bytes]:
|
235
|
+
if isinstance(ret := self.try_fn(self.check_output, *cmd, **kwargs), Exception):
|
236
|
+
return None
|
237
|
+
else:
|
238
|
+
return ret
|
239
|
+
|
240
|
+
def try_output_str(
|
241
|
+
self,
|
242
|
+
*cmd: str,
|
243
|
+
**kwargs: ta.Any,
|
244
|
+
) -> ta.Optional[str]:
|
245
|
+
if (ret := self.try_output(*cmd, **kwargs)) is None:
|
246
|
+
return None
|
247
|
+
else:
|
248
|
+
return ret.decode().strip()
|
249
|
+
|
250
|
+
|
251
|
+
subprocesses = Subprocesses()
|