omlish 0.0.0.dev224__py3-none-any.whl → 0.0.0.dev225__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/asyncs/asyncio/subprocesses.py +15 -15
- omlish/asyncs/asyncs.py +0 -1
- omlish/bootstrap/sys.py +2 -2
- omlish/dataclasses/impl/metaclass.py +5 -0
- omlish/http/coro/server.py +5 -54
- omlish/http/coro/simple.py +1 -1
- omlish/http/coro/sockets.py +59 -0
- omlish/lang/__init__.py +1 -0
- omlish/lang/imports.py +22 -0
- omlish/libc.py +10 -0
- omlish/multiprocessing/__init__.py +0 -7
- omlish/os/pidfiles/__init__.py +0 -0
- omlish/os/pidfiles/manager.py +97 -0
- omlish/os/pidfiles/pidfile.py +142 -0
- omlish/secrets/crypto.py +1 -2
- omlish/secrets/openssl.py +1 -1
- omlish/secrets/tempssl.py +4 -7
- omlish/sockets/handlers.py +4 -0
- omlish/sockets/server/handlers.py +22 -0
- omlish/subprocesses/__init__.py +0 -0
- omlish/subprocesses/async_.py +96 -0
- omlish/subprocesses/base.py +215 -0
- omlish/subprocesses/run.py +98 -0
- omlish/subprocesses/sync.py +147 -0
- omlish/subprocesses/utils.py +22 -0
- omlish/subprocesses/wrap.py +23 -0
- {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev225.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev225.dist-info}/RECORD +34 -25
- omlish/os/pidfile.py +0 -69
- omlish/subprocesses.py +0 -510
- /omlish/{multiprocessing → os}/death.py +0 -0
- {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev225.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev225.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev225.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev225.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,96 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import abc
|
4
|
+
import sys
|
5
|
+
import typing as ta
|
6
|
+
|
7
|
+
from .base import BaseSubprocesses
|
8
|
+
from .run import SubprocessRun
|
9
|
+
from .run import SubprocessRunOutput
|
10
|
+
|
11
|
+
|
12
|
+
##
|
13
|
+
|
14
|
+
|
15
|
+
class AbstractAsyncSubprocesses(BaseSubprocesses):
|
16
|
+
@abc.abstractmethod
|
17
|
+
async def run_(self, run: SubprocessRun) -> SubprocessRunOutput:
|
18
|
+
raise NotImplementedError
|
19
|
+
|
20
|
+
def run(
|
21
|
+
self,
|
22
|
+
*cmd: str,
|
23
|
+
input: ta.Any = None, # noqa
|
24
|
+
timeout: ta.Optional[float] = None,
|
25
|
+
check: bool = False,
|
26
|
+
capture_output: ta.Optional[bool] = None,
|
27
|
+
**kwargs: ta.Any,
|
28
|
+
) -> ta.Awaitable[SubprocessRunOutput]:
|
29
|
+
return self.run_(SubprocessRun(
|
30
|
+
cmd=cmd,
|
31
|
+
input=input,
|
32
|
+
timeout=timeout,
|
33
|
+
check=check,
|
34
|
+
capture_output=capture_output,
|
35
|
+
kwargs=kwargs,
|
36
|
+
))
|
37
|
+
|
38
|
+
#
|
39
|
+
|
40
|
+
@abc.abstractmethod
|
41
|
+
async def check_call(
|
42
|
+
self,
|
43
|
+
*cmd: str,
|
44
|
+
stdout: ta.Any = sys.stderr,
|
45
|
+
**kwargs: ta.Any,
|
46
|
+
) -> None:
|
47
|
+
raise NotImplementedError
|
48
|
+
|
49
|
+
@abc.abstractmethod
|
50
|
+
async def check_output(
|
51
|
+
self,
|
52
|
+
*cmd: str,
|
53
|
+
**kwargs: ta.Any,
|
54
|
+
) -> bytes:
|
55
|
+
raise NotImplementedError
|
56
|
+
|
57
|
+
#
|
58
|
+
|
59
|
+
async def check_output_str(
|
60
|
+
self,
|
61
|
+
*cmd: str,
|
62
|
+
**kwargs: ta.Any,
|
63
|
+
) -> str:
|
64
|
+
return (await self.check_output(*cmd, **kwargs)).decode().strip()
|
65
|
+
|
66
|
+
#
|
67
|
+
|
68
|
+
async def try_call(
|
69
|
+
self,
|
70
|
+
*cmd: str,
|
71
|
+
**kwargs: ta.Any,
|
72
|
+
) -> bool:
|
73
|
+
if isinstance(await self.async_try_fn(self.check_call, *cmd, **kwargs), Exception):
|
74
|
+
return False
|
75
|
+
else:
|
76
|
+
return True
|
77
|
+
|
78
|
+
async def try_output(
|
79
|
+
self,
|
80
|
+
*cmd: str,
|
81
|
+
**kwargs: ta.Any,
|
82
|
+
) -> ta.Optional[bytes]:
|
83
|
+
if isinstance(ret := await self.async_try_fn(self.check_output, *cmd, **kwargs), Exception):
|
84
|
+
return None
|
85
|
+
else:
|
86
|
+
return ret
|
87
|
+
|
88
|
+
async def try_output_str(
|
89
|
+
self,
|
90
|
+
*cmd: str,
|
91
|
+
**kwargs: ta.Any,
|
92
|
+
) -> ta.Optional[str]:
|
93
|
+
if (ret := await self.try_output(*cmd, **kwargs)) is None:
|
94
|
+
return None
|
95
|
+
else:
|
96
|
+
return ret.decode().strip()
|
@@ -0,0 +1,215 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import abc
|
4
|
+
import contextlib
|
5
|
+
import logging
|
6
|
+
import os
|
7
|
+
import subprocess
|
8
|
+
import time
|
9
|
+
import typing as ta
|
10
|
+
|
11
|
+
from .wrap import subprocess_maybe_shell_wrap_exec
|
12
|
+
|
13
|
+
|
14
|
+
T = ta.TypeVar('T')
|
15
|
+
SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull'] # ta.TypeAlias
|
16
|
+
|
17
|
+
|
18
|
+
##
|
19
|
+
|
20
|
+
|
21
|
+
# Valid channel type kwarg values:
|
22
|
+
# - A special flag negative int
|
23
|
+
# - A positive fd int
|
24
|
+
# - A file-like object
|
25
|
+
# - None
|
26
|
+
|
27
|
+
SUBPROCESS_CHANNEL_OPTION_VALUES: ta.Mapping[SubprocessChannelOption, int] = {
|
28
|
+
'pipe': subprocess.PIPE,
|
29
|
+
'stdout': subprocess.STDOUT,
|
30
|
+
'devnull': subprocess.DEVNULL,
|
31
|
+
}
|
32
|
+
|
33
|
+
|
34
|
+
##
|
35
|
+
|
36
|
+
|
37
|
+
class VerboseCalledProcessError(subprocess.CalledProcessError):
|
38
|
+
@classmethod
|
39
|
+
def from_std(cls, e: subprocess.CalledProcessError) -> 'VerboseCalledProcessError':
|
40
|
+
return cls(
|
41
|
+
e.returncode,
|
42
|
+
e.cmd,
|
43
|
+
output=e.output,
|
44
|
+
stderr=e.stderr,
|
45
|
+
)
|
46
|
+
|
47
|
+
def __str__(self) -> str:
|
48
|
+
msg = super().__str__()
|
49
|
+
if self.output is not None:
|
50
|
+
msg += f' Output: {self.output!r}'
|
51
|
+
if self.stderr is not None:
|
52
|
+
msg += f' Stderr: {self.stderr!r}'
|
53
|
+
return msg
|
54
|
+
|
55
|
+
|
56
|
+
class BaseSubprocesses(abc.ABC): # noqa
|
57
|
+
DEFAULT_LOGGER: ta.ClassVar[ta.Optional[logging.Logger]] = None
|
58
|
+
|
59
|
+
def __init__(
|
60
|
+
self,
|
61
|
+
*,
|
62
|
+
log: ta.Optional[logging.Logger] = None,
|
63
|
+
try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
|
64
|
+
) -> None:
|
65
|
+
super().__init__()
|
66
|
+
|
67
|
+
self._log = log if log is not None else self.DEFAULT_LOGGER
|
68
|
+
self._try_exceptions = try_exceptions if try_exceptions is not None else self.DEFAULT_TRY_EXCEPTIONS
|
69
|
+
|
70
|
+
def set_logger(self, log: ta.Optional[logging.Logger]) -> None:
|
71
|
+
self._log = log
|
72
|
+
|
73
|
+
#
|
74
|
+
|
75
|
+
def prepare_args(
|
76
|
+
self,
|
77
|
+
*cmd: str,
|
78
|
+
env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
79
|
+
extra_env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
80
|
+
quiet: bool = False,
|
81
|
+
shell: bool = False,
|
82
|
+
**kwargs: ta.Any,
|
83
|
+
) -> ta.Tuple[ta.Tuple[ta.Any, ...], ta.Dict[str, ta.Any]]:
|
84
|
+
if self._log:
|
85
|
+
self._log.debug('Subprocesses.prepare_args: cmd=%r', cmd)
|
86
|
+
if extra_env:
|
87
|
+
self._log.debug('Subprocesses.prepare_args: extra_env=%r', extra_env)
|
88
|
+
|
89
|
+
#
|
90
|
+
|
91
|
+
if extra_env:
|
92
|
+
env = {**(env if env is not None else os.environ), **extra_env}
|
93
|
+
|
94
|
+
#
|
95
|
+
|
96
|
+
if quiet and 'stderr' not in kwargs:
|
97
|
+
if self._log and not self._log.isEnabledFor(logging.DEBUG):
|
98
|
+
kwargs['stderr'] = subprocess.DEVNULL
|
99
|
+
|
100
|
+
for chk in ('stdout', 'stderr'):
|
101
|
+
try:
|
102
|
+
chv = kwargs[chk]
|
103
|
+
except KeyError:
|
104
|
+
continue
|
105
|
+
kwargs[chk] = SUBPROCESS_CHANNEL_OPTION_VALUES.get(chv, chv)
|
106
|
+
|
107
|
+
#
|
108
|
+
|
109
|
+
if not shell:
|
110
|
+
cmd = subprocess_maybe_shell_wrap_exec(*cmd)
|
111
|
+
|
112
|
+
#
|
113
|
+
|
114
|
+
return cmd, dict(
|
115
|
+
env=env,
|
116
|
+
shell=shell,
|
117
|
+
**kwargs,
|
118
|
+
)
|
119
|
+
|
120
|
+
@contextlib.contextmanager
|
121
|
+
def wrap_call(
|
122
|
+
self,
|
123
|
+
*cmd: ta.Any,
|
124
|
+
raise_verbose: bool = False,
|
125
|
+
**kwargs: ta.Any,
|
126
|
+
) -> ta.Iterator[None]:
|
127
|
+
start_time = time.time()
|
128
|
+
try:
|
129
|
+
if self._log:
|
130
|
+
self._log.debug('Subprocesses.wrap_call.try: cmd=%r', cmd)
|
131
|
+
|
132
|
+
yield
|
133
|
+
|
134
|
+
except Exception as exc: # noqa
|
135
|
+
if self._log:
|
136
|
+
self._log.debug('Subprocesses.wrap_call.except: exc=%r', exc)
|
137
|
+
|
138
|
+
if (
|
139
|
+
raise_verbose and
|
140
|
+
isinstance(exc, subprocess.CalledProcessError) and
|
141
|
+
not isinstance(exc, VerboseCalledProcessError) and
|
142
|
+
(exc.output is not None or exc.stderr is not None)
|
143
|
+
):
|
144
|
+
raise VerboseCalledProcessError.from_std(exc) from exc
|
145
|
+
|
146
|
+
raise
|
147
|
+
|
148
|
+
finally:
|
149
|
+
end_time = time.time()
|
150
|
+
elapsed_s = end_time - start_time
|
151
|
+
|
152
|
+
if self._log:
|
153
|
+
self._log.debug('Subprocesses.wrap_call.finally: elapsed_s=%f cmd=%r', elapsed_s, cmd)
|
154
|
+
|
155
|
+
@contextlib.contextmanager
|
156
|
+
def prepare_and_wrap(
|
157
|
+
self,
|
158
|
+
*cmd: ta.Any,
|
159
|
+
raise_verbose: bool = False,
|
160
|
+
**kwargs: ta.Any,
|
161
|
+
) -> ta.Iterator[ta.Tuple[
|
162
|
+
ta.Tuple[ta.Any, ...],
|
163
|
+
ta.Dict[str, ta.Any],
|
164
|
+
]]:
|
165
|
+
cmd, kwargs = self.prepare_args(*cmd, **kwargs)
|
166
|
+
|
167
|
+
with self.wrap_call(
|
168
|
+
*cmd,
|
169
|
+
raise_verbose=raise_verbose,
|
170
|
+
**kwargs,
|
171
|
+
):
|
172
|
+
yield cmd, kwargs
|
173
|
+
|
174
|
+
#
|
175
|
+
|
176
|
+
DEFAULT_TRY_EXCEPTIONS: ta.Tuple[ta.Type[Exception], ...] = (
|
177
|
+
FileNotFoundError,
|
178
|
+
subprocess.CalledProcessError,
|
179
|
+
)
|
180
|
+
|
181
|
+
def try_fn(
|
182
|
+
self,
|
183
|
+
fn: ta.Callable[..., T],
|
184
|
+
*cmd: str,
|
185
|
+
try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
|
186
|
+
**kwargs: ta.Any,
|
187
|
+
) -> ta.Union[T, Exception]:
|
188
|
+
if try_exceptions is None:
|
189
|
+
try_exceptions = self._try_exceptions
|
190
|
+
|
191
|
+
try:
|
192
|
+
return fn(*cmd, **kwargs)
|
193
|
+
|
194
|
+
except try_exceptions as e: # noqa
|
195
|
+
if self._log and self._log.isEnabledFor(logging.DEBUG):
|
196
|
+
self._log.exception('command failed')
|
197
|
+
return e
|
198
|
+
|
199
|
+
async def async_try_fn(
|
200
|
+
self,
|
201
|
+
fn: ta.Callable[..., ta.Awaitable[T]],
|
202
|
+
*cmd: ta.Any,
|
203
|
+
try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
|
204
|
+
**kwargs: ta.Any,
|
205
|
+
) -> ta.Union[T, Exception]:
|
206
|
+
if try_exceptions is None:
|
207
|
+
try_exceptions = self._try_exceptions
|
208
|
+
|
209
|
+
try:
|
210
|
+
return await fn(*cmd, **kwargs)
|
211
|
+
|
212
|
+
except try_exceptions as e: # noqa
|
213
|
+
if self._log and self._log.isEnabledFor(logging.DEBUG):
|
214
|
+
self._log.exception('command failed')
|
215
|
+
return e
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import abc
|
4
|
+
import dataclasses as dc
|
5
|
+
import typing as ta
|
6
|
+
|
7
|
+
from ..lite.check import check
|
8
|
+
|
9
|
+
|
10
|
+
T = ta.TypeVar('T')
|
11
|
+
|
12
|
+
|
13
|
+
##
|
14
|
+
|
15
|
+
|
16
|
+
@dc.dataclass(frozen=True)
|
17
|
+
class SubprocessRunOutput(ta.Generic[T]):
|
18
|
+
proc: T
|
19
|
+
|
20
|
+
returncode: int # noqa
|
21
|
+
|
22
|
+
stdout: ta.Optional[bytes] = None
|
23
|
+
stderr: ta.Optional[bytes] = None
|
24
|
+
|
25
|
+
|
26
|
+
##
|
27
|
+
|
28
|
+
|
29
|
+
@dc.dataclass(frozen=True)
|
30
|
+
class SubprocessRun:
|
31
|
+
cmd: ta.Sequence[str]
|
32
|
+
input: ta.Any = None
|
33
|
+
timeout: ta.Optional[float] = None
|
34
|
+
check: bool = False
|
35
|
+
capture_output: ta.Optional[bool] = None
|
36
|
+
kwargs: ta.Optional[ta.Mapping[str, ta.Any]] = None
|
37
|
+
|
38
|
+
@classmethod
|
39
|
+
def of(
|
40
|
+
cls,
|
41
|
+
*cmd: str,
|
42
|
+
input: ta.Any = None, # noqa
|
43
|
+
timeout: ta.Optional[float] = None,
|
44
|
+
check: bool = False, # noqa
|
45
|
+
capture_output: ta.Optional[bool] = None,
|
46
|
+
**kwargs: ta.Any,
|
47
|
+
) -> 'SubprocessRun':
|
48
|
+
return cls(
|
49
|
+
cmd=cmd,
|
50
|
+
input=input,
|
51
|
+
timeout=timeout,
|
52
|
+
check=check,
|
53
|
+
capture_output=capture_output,
|
54
|
+
kwargs=kwargs,
|
55
|
+
)
|
56
|
+
|
57
|
+
#
|
58
|
+
|
59
|
+
_DEFAULT_SUBPROCESSES: ta.ClassVar[ta.Optional[ta.Any]] = None # AbstractSubprocesses
|
60
|
+
|
61
|
+
def run(
|
62
|
+
self,
|
63
|
+
subprocesses: ta.Optional[ta.Any] = None, # AbstractSubprocesses
|
64
|
+
) -> SubprocessRunOutput:
|
65
|
+
if subprocesses is None:
|
66
|
+
subprocesses = self._DEFAULT_SUBPROCESSES
|
67
|
+
return check.not_none(subprocesses).run_(self) # type: ignore[attr-defined]
|
68
|
+
|
69
|
+
_DEFAULT_ASYNC_SUBPROCESSES: ta.ClassVar[ta.Optional[ta.Any]] = None # AbstractAsyncSubprocesses
|
70
|
+
|
71
|
+
async def async_run(
|
72
|
+
self,
|
73
|
+
async_subprocesses: ta.Optional[ta.Any] = None, # AbstractAsyncSubprocesses
|
74
|
+
) -> SubprocessRunOutput:
|
75
|
+
if async_subprocesses is None:
|
76
|
+
async_subprocesses = self._DEFAULT_ASYNC_SUBPROCESSES
|
77
|
+
return await check.not_none(async_subprocesses).run_(self) # type: ignore[attr-defined]
|
78
|
+
|
79
|
+
|
80
|
+
##
|
81
|
+
|
82
|
+
|
83
|
+
class SubprocessRunnable(abc.ABC, ta.Generic[T]):
|
84
|
+
@abc.abstractmethod
|
85
|
+
def make_run(self) -> SubprocessRun:
|
86
|
+
raise NotImplementedError
|
87
|
+
|
88
|
+
@abc.abstractmethod
|
89
|
+
def handle_run_output(self, output: SubprocessRunOutput) -> T:
|
90
|
+
raise NotImplementedError
|
91
|
+
|
92
|
+
#
|
93
|
+
|
94
|
+
def run(self, subprocesses: ta.Optional[ta.Any] = None) -> T: # AbstractSubprocesses
|
95
|
+
return self.handle_run_output(self.make_run().run(subprocesses))
|
96
|
+
|
97
|
+
async def async_run(self, async_subprocesses: ta.Optional[ta.Any] = None) -> T: # AbstractAsyncSubprocesses
|
98
|
+
return self.handle_run_output(await self.make_run().async_run(async_subprocesses))
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import abc
|
4
|
+
import subprocess
|
5
|
+
import sys
|
6
|
+
import typing as ta
|
7
|
+
|
8
|
+
from .base import BaseSubprocesses
|
9
|
+
from .run import SubprocessRun
|
10
|
+
from .run import SubprocessRunOutput
|
11
|
+
|
12
|
+
|
13
|
+
##
|
14
|
+
|
15
|
+
|
16
|
+
class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
|
17
|
+
@abc.abstractmethod
|
18
|
+
def run_(self, run: SubprocessRun) -> SubprocessRunOutput:
|
19
|
+
raise NotImplementedError
|
20
|
+
|
21
|
+
def run(
|
22
|
+
self,
|
23
|
+
*cmd: str,
|
24
|
+
input: ta.Any = None, # noqa
|
25
|
+
timeout: ta.Optional[float] = None,
|
26
|
+
check: bool = False,
|
27
|
+
capture_output: ta.Optional[bool] = None,
|
28
|
+
**kwargs: ta.Any,
|
29
|
+
) -> SubprocessRunOutput:
|
30
|
+
return self.run_(SubprocessRun(
|
31
|
+
cmd=cmd,
|
32
|
+
input=input,
|
33
|
+
timeout=timeout,
|
34
|
+
check=check,
|
35
|
+
capture_output=capture_output,
|
36
|
+
kwargs=kwargs,
|
37
|
+
))
|
38
|
+
|
39
|
+
#
|
40
|
+
|
41
|
+
@abc.abstractmethod
|
42
|
+
def check_call(
|
43
|
+
self,
|
44
|
+
*cmd: str,
|
45
|
+
stdout: ta.Any = sys.stderr,
|
46
|
+
**kwargs: ta.Any,
|
47
|
+
) -> None:
|
48
|
+
raise NotImplementedError
|
49
|
+
|
50
|
+
@abc.abstractmethod
|
51
|
+
def check_output(
|
52
|
+
self,
|
53
|
+
*cmd: str,
|
54
|
+
**kwargs: ta.Any,
|
55
|
+
) -> bytes:
|
56
|
+
raise NotImplementedError
|
57
|
+
|
58
|
+
#
|
59
|
+
|
60
|
+
def check_output_str(
|
61
|
+
self,
|
62
|
+
*cmd: str,
|
63
|
+
**kwargs: ta.Any,
|
64
|
+
) -> str:
|
65
|
+
return self.check_output(*cmd, **kwargs).decode().strip()
|
66
|
+
|
67
|
+
#
|
68
|
+
|
69
|
+
def try_call(
|
70
|
+
self,
|
71
|
+
*cmd: str,
|
72
|
+
**kwargs: ta.Any,
|
73
|
+
) -> bool:
|
74
|
+
if isinstance(self.try_fn(self.check_call, *cmd, **kwargs), Exception):
|
75
|
+
return False
|
76
|
+
else:
|
77
|
+
return True
|
78
|
+
|
79
|
+
def try_output(
|
80
|
+
self,
|
81
|
+
*cmd: str,
|
82
|
+
**kwargs: ta.Any,
|
83
|
+
) -> ta.Optional[bytes]:
|
84
|
+
if isinstance(ret := self.try_fn(self.check_output, *cmd, **kwargs), Exception):
|
85
|
+
return None
|
86
|
+
else:
|
87
|
+
return ret
|
88
|
+
|
89
|
+
def try_output_str(
|
90
|
+
self,
|
91
|
+
*cmd: str,
|
92
|
+
**kwargs: ta.Any,
|
93
|
+
) -> ta.Optional[str]:
|
94
|
+
if (ret := self.try_output(*cmd, **kwargs)) is None:
|
95
|
+
return None
|
96
|
+
else:
|
97
|
+
return ret.decode().strip()
|
98
|
+
|
99
|
+
|
100
|
+
##
|
101
|
+
|
102
|
+
|
103
|
+
class Subprocesses(AbstractSubprocesses):
|
104
|
+
def run_(self, run: SubprocessRun) -> SubprocessRunOutput[subprocess.CompletedProcess]:
|
105
|
+
with self.prepare_and_wrap(
|
106
|
+
*run.cmd,
|
107
|
+
input=run.input,
|
108
|
+
timeout=run.timeout,
|
109
|
+
check=run.check,
|
110
|
+
capture_output=run.capture_output or False,
|
111
|
+
**(run.kwargs or {}),
|
112
|
+
) as (cmd, kwargs):
|
113
|
+
proc = subprocess.run(cmd, **kwargs) # noqa
|
114
|
+
|
115
|
+
return SubprocessRunOutput(
|
116
|
+
proc=proc,
|
117
|
+
|
118
|
+
returncode=proc.returncode,
|
119
|
+
|
120
|
+
stdout=proc.stdout, # noqa
|
121
|
+
stderr=proc.stderr, # noqa
|
122
|
+
)
|
123
|
+
|
124
|
+
def check_call(
|
125
|
+
self,
|
126
|
+
*cmd: str,
|
127
|
+
stdout: ta.Any = sys.stderr,
|
128
|
+
**kwargs: ta.Any,
|
129
|
+
) -> None:
|
130
|
+
with self.prepare_and_wrap(*cmd, stdout=stdout, **kwargs) as (cmd, kwargs): # noqa
|
131
|
+
subprocess.check_call(cmd, **kwargs)
|
132
|
+
|
133
|
+
def check_output(
|
134
|
+
self,
|
135
|
+
*cmd: str,
|
136
|
+
**kwargs: ta.Any,
|
137
|
+
) -> bytes:
|
138
|
+
with self.prepare_and_wrap(*cmd, **kwargs) as (cmd, kwargs): # noqa
|
139
|
+
return subprocess.check_output(cmd, **kwargs)
|
140
|
+
|
141
|
+
|
142
|
+
##
|
143
|
+
|
144
|
+
|
145
|
+
subprocesses = Subprocesses()
|
146
|
+
|
147
|
+
SubprocessRun._DEFAULT_SUBPROCESSES = subprocesses # noqa
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import subprocess
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
|
7
|
+
##
|
8
|
+
|
9
|
+
|
10
|
+
def subprocess_close(
|
11
|
+
proc: subprocess.Popen,
|
12
|
+
timeout: ta.Optional[float] = None,
|
13
|
+
) -> None:
|
14
|
+
# TODO: terminate, sleep, kill
|
15
|
+
if proc.stdout:
|
16
|
+
proc.stdout.close()
|
17
|
+
if proc.stderr:
|
18
|
+
proc.stderr.close()
|
19
|
+
if proc.stdin:
|
20
|
+
proc.stdin.close()
|
21
|
+
|
22
|
+
proc.wait(timeout)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import shlex
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
from ..lite.runtime import is_debugger_attached
|
7
|
+
|
8
|
+
|
9
|
+
##
|
10
|
+
|
11
|
+
|
12
|
+
_SUBPROCESS_SHELL_WRAP_EXECS = False
|
13
|
+
|
14
|
+
|
15
|
+
def subprocess_shell_wrap_exec(*cmd: str) -> ta.Tuple[str, ...]:
|
16
|
+
return ('sh', '-c', ' '.join(map(shlex.quote, cmd)))
|
17
|
+
|
18
|
+
|
19
|
+
def subprocess_maybe_shell_wrap_exec(*cmd: str) -> ta.Tuple[str, ...]:
|
20
|
+
if _SUBPROCESS_SHELL_WRAP_EXECS or is_debugger_attached():
|
21
|
+
return subprocess_shell_wrap_exec(*cmd)
|
22
|
+
else:
|
23
|
+
return cmd
|