omlish 0.0.0.dev146__py3-none-any.whl → 0.0.0.dev148__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/lite/asyncio/__init__.py +0 -0
- omlish/lite/asyncio/asyncio.py +63 -0
- omlish/lite/asyncio/subprocesses.py +297 -0
- omlish/lite/cached.py +30 -5
- omlish/lite/deathsig.py +22 -0
- omlish/lite/pycharm.py +0 -1
- omlish/lite/subprocesses.py +71 -18
- omlish/manifests/load.py +6 -0
- omlish/testing/pytest/plugins/asyncs.py +6 -1
- {omlish-0.0.0.dev146.dist-info → omlish-0.0.0.dev148.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev146.dist-info → omlish-0.0.0.dev148.dist-info}/RECORD +16 -12
- {omlish-0.0.0.dev146.dist-info → omlish-0.0.0.dev148.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev146.dist-info → omlish-0.0.0.dev148.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev146.dist-info → omlish-0.0.0.dev148.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev146.dist-info → omlish-0.0.0.dev148.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
File without changes
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import asyncio.base_subprocess
|
3
|
+
import asyncio.subprocess
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
|
7
|
+
AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
|
8
|
+
|
9
|
+
|
10
|
+
##
|
11
|
+
|
12
|
+
|
13
|
+
ASYNCIO_DEFAULT_BUFFER_LIMIT = 2 ** 16
|
14
|
+
|
15
|
+
|
16
|
+
async def asyncio_open_stream_reader(
|
17
|
+
f: ta.IO,
|
18
|
+
loop: ta.Any = None,
|
19
|
+
*,
|
20
|
+
limit: int = ASYNCIO_DEFAULT_BUFFER_LIMIT,
|
21
|
+
) -> asyncio.StreamReader:
|
22
|
+
if loop is None:
|
23
|
+
loop = asyncio.get_running_loop()
|
24
|
+
|
25
|
+
reader = asyncio.StreamReader(limit=limit, loop=loop)
|
26
|
+
await loop.connect_read_pipe(
|
27
|
+
lambda: asyncio.StreamReaderProtocol(reader, loop=loop),
|
28
|
+
f,
|
29
|
+
)
|
30
|
+
|
31
|
+
return reader
|
32
|
+
|
33
|
+
|
34
|
+
async def asyncio_open_stream_writer(
|
35
|
+
f: ta.IO,
|
36
|
+
loop: ta.Any = None,
|
37
|
+
) -> asyncio.StreamWriter:
|
38
|
+
if loop is None:
|
39
|
+
loop = asyncio.get_running_loop()
|
40
|
+
|
41
|
+
writer_transport, writer_protocol = await loop.connect_write_pipe(
|
42
|
+
lambda: asyncio.streams.FlowControlMixin(loop=loop),
|
43
|
+
f,
|
44
|
+
)
|
45
|
+
|
46
|
+
return asyncio.streams.StreamWriter(
|
47
|
+
writer_transport,
|
48
|
+
writer_protocol,
|
49
|
+
None,
|
50
|
+
loop,
|
51
|
+
)
|
52
|
+
|
53
|
+
|
54
|
+
##
|
55
|
+
|
56
|
+
|
57
|
+
def asyncio_maybe_timeout(
|
58
|
+
fut: AwaitableT,
|
59
|
+
timeout: ta.Optional[float] = None,
|
60
|
+
) -> AwaitableT:
|
61
|
+
if timeout is not None:
|
62
|
+
fut = asyncio.wait_for(fut, timeout) # type: ignore
|
63
|
+
return fut
|
@@ -0,0 +1,297 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import asyncio.base_subprocess
|
3
|
+
import asyncio.subprocess
|
4
|
+
import contextlib
|
5
|
+
import functools
|
6
|
+
import logging
|
7
|
+
import subprocess
|
8
|
+
import sys
|
9
|
+
import typing as ta
|
10
|
+
|
11
|
+
from ..check import check_equal
|
12
|
+
from ..check import check_isinstance
|
13
|
+
from ..check import check_not_none
|
14
|
+
from ..check import check_single
|
15
|
+
from ..logs import log
|
16
|
+
from ..subprocesses import DEFAULT_SUBPROCESS_TRY_EXCEPTIONS
|
17
|
+
from ..subprocesses import prepare_subprocess_invocation
|
18
|
+
from ..subprocesses import subprocess_common_context
|
19
|
+
from .asyncio import asyncio_maybe_timeout
|
20
|
+
|
21
|
+
|
22
|
+
T = ta.TypeVar('T')
|
23
|
+
|
24
|
+
|
25
|
+
##
|
26
|
+
|
27
|
+
|
28
|
+
@contextlib.asynccontextmanager
|
29
|
+
async def asyncio_subprocess_popen(
|
30
|
+
*cmd: str,
|
31
|
+
shell: bool = False,
|
32
|
+
timeout: ta.Optional[float] = None,
|
33
|
+
**kwargs: ta.Any,
|
34
|
+
) -> ta.AsyncGenerator[asyncio.subprocess.Process, None]:
|
35
|
+
fac: ta.Any
|
36
|
+
if shell:
|
37
|
+
fac = functools.partial(
|
38
|
+
asyncio.create_subprocess_shell,
|
39
|
+
check_single(cmd),
|
40
|
+
)
|
41
|
+
else:
|
42
|
+
fac = functools.partial(
|
43
|
+
asyncio.create_subprocess_exec,
|
44
|
+
*cmd,
|
45
|
+
)
|
46
|
+
|
47
|
+
with subprocess_common_context(
|
48
|
+
*cmd,
|
49
|
+
shell=shell,
|
50
|
+
timeout=timeout,
|
51
|
+
**kwargs,
|
52
|
+
):
|
53
|
+
proc: asyncio.subprocess.Process
|
54
|
+
proc = await fac(**kwargs)
|
55
|
+
try:
|
56
|
+
yield proc
|
57
|
+
|
58
|
+
finally:
|
59
|
+
await asyncio_maybe_timeout(proc.wait(), timeout)
|
60
|
+
|
61
|
+
|
62
|
+
##
|
63
|
+
|
64
|
+
|
65
|
+
class AsyncioProcessCommunicator:
|
66
|
+
def __init__(
|
67
|
+
self,
|
68
|
+
proc: asyncio.subprocess.Process,
|
69
|
+
loop: ta.Optional[ta.Any] = None,
|
70
|
+
) -> None:
|
71
|
+
super().__init__()
|
72
|
+
|
73
|
+
if loop is None:
|
74
|
+
loop = asyncio.get_running_loop()
|
75
|
+
|
76
|
+
self._proc = proc
|
77
|
+
self._loop = loop
|
78
|
+
|
79
|
+
self._transport: asyncio.base_subprocess.BaseSubprocessTransport = check_isinstance(
|
80
|
+
proc._transport, # type: ignore # noqa
|
81
|
+
asyncio.base_subprocess.BaseSubprocessTransport,
|
82
|
+
)
|
83
|
+
|
84
|
+
@property
|
85
|
+
def _debug(self) -> bool:
|
86
|
+
return self._loop.get_debug()
|
87
|
+
|
88
|
+
async def _feed_stdin(self, input: bytes) -> None: # noqa
|
89
|
+
stdin = check_not_none(self._proc.stdin)
|
90
|
+
try:
|
91
|
+
if input is not None:
|
92
|
+
stdin.write(input)
|
93
|
+
if self._debug:
|
94
|
+
log.debug('%r communicate: feed stdin (%s bytes)', self, len(input))
|
95
|
+
|
96
|
+
await stdin.drain()
|
97
|
+
|
98
|
+
except (BrokenPipeError, ConnectionResetError) as exc:
|
99
|
+
# communicate() ignores BrokenPipeError and ConnectionResetError. write() and drain() can raise these
|
100
|
+
# exceptions.
|
101
|
+
if self._debug:
|
102
|
+
log.debug('%r communicate: stdin got %r', self, exc)
|
103
|
+
|
104
|
+
if self._debug:
|
105
|
+
log.debug('%r communicate: close stdin', self)
|
106
|
+
|
107
|
+
stdin.close()
|
108
|
+
|
109
|
+
async def _noop(self) -> None:
|
110
|
+
return None
|
111
|
+
|
112
|
+
async def _read_stream(self, fd: int) -> bytes:
|
113
|
+
transport: ta.Any = check_not_none(self._transport.get_pipe_transport(fd))
|
114
|
+
|
115
|
+
if fd == 2:
|
116
|
+
stream = check_not_none(self._proc.stderr)
|
117
|
+
else:
|
118
|
+
check_equal(fd, 1)
|
119
|
+
stream = check_not_none(self._proc.stdout)
|
120
|
+
|
121
|
+
if self._debug:
|
122
|
+
name = 'stdout' if fd == 1 else 'stderr'
|
123
|
+
log.debug('%r communicate: read %s', self, name)
|
124
|
+
|
125
|
+
output = await stream.read()
|
126
|
+
|
127
|
+
if self._debug:
|
128
|
+
name = 'stdout' if fd == 1 else 'stderr'
|
129
|
+
log.debug('%r communicate: close %s', self, name)
|
130
|
+
|
131
|
+
transport.close()
|
132
|
+
|
133
|
+
return output
|
134
|
+
|
135
|
+
class Communication(ta.NamedTuple):
|
136
|
+
stdout: ta.Optional[bytes]
|
137
|
+
stderr: ta.Optional[bytes]
|
138
|
+
|
139
|
+
async def _communicate(
|
140
|
+
self,
|
141
|
+
input: ta.Any = None, # noqa
|
142
|
+
) -> Communication:
|
143
|
+
stdin_fut: ta.Any
|
144
|
+
if self._proc.stdin is not None:
|
145
|
+
stdin_fut = self._feed_stdin(input)
|
146
|
+
else:
|
147
|
+
stdin_fut = self._noop()
|
148
|
+
|
149
|
+
stdout_fut: ta.Any
|
150
|
+
if self._proc.stdout is not None:
|
151
|
+
stdout_fut = self._read_stream(1)
|
152
|
+
else:
|
153
|
+
stdout_fut = self._noop()
|
154
|
+
|
155
|
+
stderr_fut: ta.Any
|
156
|
+
if self._proc.stderr is not None:
|
157
|
+
stderr_fut = self._read_stream(2)
|
158
|
+
else:
|
159
|
+
stderr_fut = self._noop()
|
160
|
+
|
161
|
+
stdin_res, stdout_res, stderr_res = await asyncio.gather(stdin_fut, stdout_fut, stderr_fut)
|
162
|
+
|
163
|
+
await self._proc.wait()
|
164
|
+
|
165
|
+
return AsyncioProcessCommunicator.Communication(stdout_res, stderr_res)
|
166
|
+
|
167
|
+
async def communicate(
|
168
|
+
self,
|
169
|
+
input: ta.Any = None, # noqa
|
170
|
+
timeout: ta.Optional[float] = None,
|
171
|
+
) -> Communication:
|
172
|
+
return await asyncio_maybe_timeout(self._communicate(input), timeout)
|
173
|
+
|
174
|
+
|
175
|
+
async def asyncio_subprocess_communicate(
|
176
|
+
proc: asyncio.subprocess.Process,
|
177
|
+
input: ta.Any = None, # noqa
|
178
|
+
timeout: ta.Optional[float] = None,
|
179
|
+
) -> ta.Tuple[ta.Optional[bytes], ta.Optional[bytes]]:
|
180
|
+
return await AsyncioProcessCommunicator(proc).communicate(input, timeout) # noqa
|
181
|
+
|
182
|
+
|
183
|
+
##
|
184
|
+
|
185
|
+
|
186
|
+
async def _asyncio_subprocess_check_run(
|
187
|
+
*args: str,
|
188
|
+
input: ta.Any = None, # noqa
|
189
|
+
timeout: ta.Optional[float] = None,
|
190
|
+
**kwargs: ta.Any,
|
191
|
+
) -> ta.Tuple[ta.Optional[bytes], ta.Optional[bytes]]:
|
192
|
+
args, kwargs = prepare_subprocess_invocation(*args, **kwargs)
|
193
|
+
|
194
|
+
proc: asyncio.subprocess.Process
|
195
|
+
async with asyncio_subprocess_popen(*args, **kwargs) as proc:
|
196
|
+
stdout, stderr = await asyncio_subprocess_communicate(proc, input, timeout)
|
197
|
+
|
198
|
+
if proc.returncode:
|
199
|
+
raise subprocess.CalledProcessError(
|
200
|
+
proc.returncode,
|
201
|
+
args,
|
202
|
+
output=stdout,
|
203
|
+
stderr=stderr,
|
204
|
+
)
|
205
|
+
|
206
|
+
return stdout, stderr
|
207
|
+
|
208
|
+
|
209
|
+
async def asyncio_subprocess_check_call(
|
210
|
+
*args: str,
|
211
|
+
stdout: ta.Any = sys.stderr,
|
212
|
+
input: ta.Any = None, # noqa
|
213
|
+
timeout: ta.Optional[float] = None,
|
214
|
+
**kwargs: ta.Any,
|
215
|
+
) -> None:
|
216
|
+
_, _ = await _asyncio_subprocess_check_run(
|
217
|
+
*args,
|
218
|
+
stdout=stdout,
|
219
|
+
input=input,
|
220
|
+
timeout=timeout,
|
221
|
+
**kwargs,
|
222
|
+
)
|
223
|
+
|
224
|
+
|
225
|
+
async def asyncio_subprocess_check_output(
|
226
|
+
*args: str,
|
227
|
+
input: ta.Any = None, # noqa
|
228
|
+
timeout: ta.Optional[float] = None,
|
229
|
+
**kwargs: ta.Any,
|
230
|
+
) -> bytes:
|
231
|
+
stdout, stderr = await _asyncio_subprocess_check_run(
|
232
|
+
*args,
|
233
|
+
stdout=asyncio.subprocess.PIPE,
|
234
|
+
input=input,
|
235
|
+
timeout=timeout,
|
236
|
+
**kwargs,
|
237
|
+
)
|
238
|
+
|
239
|
+
return check_not_none(stdout)
|
240
|
+
|
241
|
+
|
242
|
+
async def asyncio_subprocess_check_output_str(*args: str, **kwargs: ta.Any) -> str:
|
243
|
+
return (await asyncio_subprocess_check_output(*args, **kwargs)).decode().strip()
|
244
|
+
|
245
|
+
|
246
|
+
##
|
247
|
+
|
248
|
+
|
249
|
+
async def _asyncio_subprocess_try_run(
|
250
|
+
fn: ta.Callable[..., ta.Awaitable[T]],
|
251
|
+
*args: ta.Any,
|
252
|
+
try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
|
253
|
+
**kwargs: ta.Any,
|
254
|
+
) -> ta.Union[T, Exception]:
|
255
|
+
try:
|
256
|
+
return await fn(*args, **kwargs)
|
257
|
+
except try_exceptions as e: # noqa
|
258
|
+
if log.isEnabledFor(logging.DEBUG):
|
259
|
+
log.exception('command failed')
|
260
|
+
return e
|
261
|
+
|
262
|
+
|
263
|
+
async def asyncio_subprocess_try_call(
|
264
|
+
*args: str,
|
265
|
+
try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
|
266
|
+
**kwargs: ta.Any,
|
267
|
+
) -> bool:
|
268
|
+
if isinstance(await _asyncio_subprocess_try_run(
|
269
|
+
asyncio_subprocess_check_call,
|
270
|
+
*args,
|
271
|
+
try_exceptions=try_exceptions,
|
272
|
+
**kwargs,
|
273
|
+
), Exception):
|
274
|
+
return False
|
275
|
+
else:
|
276
|
+
return True
|
277
|
+
|
278
|
+
|
279
|
+
async def asyncio_subprocess_try_output(
|
280
|
+
*args: str,
|
281
|
+
try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
|
282
|
+
**kwargs: ta.Any,
|
283
|
+
) -> ta.Optional[bytes]:
|
284
|
+
if isinstance(ret := await _asyncio_subprocess_try_run(
|
285
|
+
asyncio_subprocess_check_output,
|
286
|
+
*args,
|
287
|
+
try_exceptions=try_exceptions,
|
288
|
+
**kwargs,
|
289
|
+
), Exception):
|
290
|
+
return None
|
291
|
+
else:
|
292
|
+
return ret
|
293
|
+
|
294
|
+
|
295
|
+
async def asyncio_subprocess_try_output_str(*args: str, **kwargs: ta.Any) -> ta.Optional[str]:
|
296
|
+
out = await asyncio_subprocess_try_output(*args, **kwargs)
|
297
|
+
return out.decode().strip() if out is not None else None
|
omlish/lite/cached.py
CHANGED
@@ -7,7 +7,10 @@ T = ta.TypeVar('T')
|
|
7
7
|
CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
|
8
8
|
|
9
9
|
|
10
|
-
|
10
|
+
##
|
11
|
+
|
12
|
+
|
13
|
+
class _AbstractCachedNullary:
|
11
14
|
def __init__(self, fn):
|
12
15
|
super().__init__()
|
13
16
|
self._fn = fn
|
@@ -15,20 +18,42 @@ class _cached_nullary: # noqa
|
|
15
18
|
functools.update_wrapper(self, fn)
|
16
19
|
|
17
20
|
def __call__(self, *args, **kwargs): # noqa
|
18
|
-
|
19
|
-
self._value = self._fn()
|
20
|
-
return self._value
|
21
|
+
raise TypeError
|
21
22
|
|
22
23
|
def __get__(self, instance, owner): # noqa
|
23
24
|
bound = instance.__dict__[self._fn.__name__] = self.__class__(self._fn.__get__(instance, owner))
|
24
25
|
return bound
|
25
26
|
|
26
27
|
|
28
|
+
##
|
29
|
+
|
30
|
+
|
31
|
+
class _CachedNullary(_AbstractCachedNullary):
|
32
|
+
def __call__(self, *args, **kwargs): # noqa
|
33
|
+
if self._value is self._missing:
|
34
|
+
self._value = self._fn()
|
35
|
+
return self._value
|
36
|
+
|
37
|
+
|
27
38
|
def cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
|
28
|
-
return
|
39
|
+
return _CachedNullary(fn)
|
29
40
|
|
30
41
|
|
31
42
|
def static_init(fn: CallableT) -> CallableT:
|
32
43
|
fn = cached_nullary(fn)
|
33
44
|
fn()
|
34
45
|
return fn
|
46
|
+
|
47
|
+
|
48
|
+
##
|
49
|
+
|
50
|
+
|
51
|
+
class _AsyncCachedNullary(_AbstractCachedNullary):
|
52
|
+
async def __call__(self, *args, **kwargs):
|
53
|
+
if self._value is self._missing:
|
54
|
+
self._value = await self._fn()
|
55
|
+
return self._value
|
56
|
+
|
57
|
+
|
58
|
+
def async_cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
|
59
|
+
return _AsyncCachedNullary(fn)
|
omlish/lite/deathsig.py
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
import ctypes as ct
|
2
|
+
import sys
|
3
|
+
|
4
|
+
|
5
|
+
LINUX_PR_SET_PDEATHSIG = 1 # Second arg is a signal
|
6
|
+
LINUX_PR_GET_PDEATHSIG = 2 # Second arg is a ptr to return the signal
|
7
|
+
|
8
|
+
|
9
|
+
def set_process_deathsig(sig: int) -> bool:
|
10
|
+
if sys.platform == 'linux':
|
11
|
+
libc = ct.CDLL('libc.so.6')
|
12
|
+
|
13
|
+
# int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);
|
14
|
+
libc.prctl.restype = ct.c_int
|
15
|
+
libc.prctl.argtypes = [ct.c_int, ct.c_ulong, ct.c_ulong, ct.c_ulong, ct.c_ulong]
|
16
|
+
|
17
|
+
libc.prctl(LINUX_PR_SET_PDEATHSIG, sig, 0, 0, 0, 0)
|
18
|
+
|
19
|
+
return True
|
20
|
+
|
21
|
+
else:
|
22
|
+
return False
|
omlish/lite/pycharm.py
CHANGED
omlish/lite/subprocesses.py
CHANGED
@@ -1,15 +1,18 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
|
+
import contextlib
|
2
3
|
import logging
|
3
4
|
import os
|
4
5
|
import shlex
|
5
6
|
import subprocess
|
6
7
|
import sys
|
8
|
+
import time
|
7
9
|
import typing as ta
|
8
10
|
|
9
11
|
from .logs import log
|
10
12
|
from .runtime import is_debugger_attached
|
11
13
|
|
12
14
|
|
15
|
+
T = ta.TypeVar('T')
|
13
16
|
SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull']
|
14
17
|
|
15
18
|
|
@@ -40,7 +43,7 @@ def subprocess_maybe_shell_wrap_exec(*args: str) -> ta.Tuple[str, ...]:
|
|
40
43
|
return args
|
41
44
|
|
42
45
|
|
43
|
-
def
|
46
|
+
def prepare_subprocess_invocation(
|
44
47
|
*args: str,
|
45
48
|
env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
46
49
|
extra_env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
@@ -48,9 +51,9 @@ def _prepare_subprocess_invocation(
|
|
48
51
|
shell: bool = False,
|
49
52
|
**kwargs: ta.Any,
|
50
53
|
) -> ta.Tuple[ta.Tuple[ta.Any, ...], ta.Dict[str, ta.Any]]:
|
51
|
-
log.debug(args)
|
54
|
+
log.debug('prepare_subprocess_invocation: args=%r', args)
|
52
55
|
if extra_env:
|
53
|
-
log.debug(extra_env)
|
56
|
+
log.debug('prepare_subprocess_invocation: extra_env=%r', extra_env)
|
54
57
|
|
55
58
|
if extra_env:
|
56
59
|
env = {**(env if env is not None else os.environ), **extra_env}
|
@@ -69,14 +72,46 @@ def _prepare_subprocess_invocation(
|
|
69
72
|
)
|
70
73
|
|
71
74
|
|
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)
|
93
|
+
|
94
|
+
|
95
|
+
##
|
96
|
+
|
97
|
+
|
98
|
+
def subprocess_check_call(
|
99
|
+
*args: str,
|
100
|
+
stdout: ta.Any = sys.stderr,
|
101
|
+
**kwargs: ta.Any,
|
102
|
+
) -> None:
|
103
|
+
args, kwargs = prepare_subprocess_invocation(*args, stdout=stdout, **kwargs)
|
104
|
+
with subprocess_common_context(*args, **kwargs):
|
105
|
+
return subprocess.check_call(args, **kwargs) # type: ignore
|
75
106
|
|
76
107
|
|
77
|
-
def subprocess_check_output(
|
78
|
-
|
79
|
-
|
108
|
+
def subprocess_check_output(
|
109
|
+
*args: str,
|
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)
|
80
115
|
|
81
116
|
|
82
117
|
def subprocess_check_output_str(*args: str, **kwargs: ta.Any) -> str:
|
@@ -92,16 +127,31 @@ DEFAULT_SUBPROCESS_TRY_EXCEPTIONS: ta.Tuple[ta.Type[Exception], ...] = (
|
|
92
127
|
)
|
93
128
|
|
94
129
|
|
95
|
-
def
|
96
|
-
|
130
|
+
def _subprocess_try_run(
|
131
|
+
fn: ta.Callable[..., T],
|
132
|
+
*args: ta.Any,
|
97
133
|
try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
|
98
134
|
**kwargs: ta.Any,
|
99
|
-
) ->
|
135
|
+
) -> ta.Union[T, Exception]:
|
100
136
|
try:
|
101
|
-
|
137
|
+
return fn(*args, **kwargs)
|
102
138
|
except try_exceptions as e: # noqa
|
103
139
|
if log.isEnabledFor(logging.DEBUG):
|
104
140
|
log.exception('command failed')
|
141
|
+
return e
|
142
|
+
|
143
|
+
|
144
|
+
def subprocess_try_call(
|
145
|
+
*args: str,
|
146
|
+
try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
|
147
|
+
**kwargs: ta.Any,
|
148
|
+
) -> bool:
|
149
|
+
if isinstance(_subprocess_try_run(
|
150
|
+
subprocess_check_call,
|
151
|
+
*args,
|
152
|
+
try_exceptions=try_exceptions,
|
153
|
+
**kwargs,
|
154
|
+
), Exception):
|
105
155
|
return False
|
106
156
|
else:
|
107
157
|
return True
|
@@ -112,12 +162,15 @@ def subprocess_try_output(
|
|
112
162
|
try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
|
113
163
|
**kwargs: ta.Any,
|
114
164
|
) -> ta.Optional[bytes]:
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
165
|
+
if isinstance(ret := _subprocess_try_run(
|
166
|
+
subprocess_check_output,
|
167
|
+
*args,
|
168
|
+
try_exceptions=try_exceptions,
|
169
|
+
**kwargs,
|
170
|
+
), Exception):
|
120
171
|
return None
|
172
|
+
else:
|
173
|
+
return ret
|
121
174
|
|
122
175
|
|
123
176
|
def subprocess_try_output_str(*args: str, **kwargs: ta.Any) -> ta.Optional[str]:
|
omlish/manifests/load.py
CHANGED
@@ -31,6 +31,8 @@ class ManifestLoader:
|
|
31
31
|
self._cls_cache: ta.Dict[str, type] = {}
|
32
32
|
self._raw_cache: ta.Dict[str, ta.Optional[ta.Sequence[Manifest]]] = {}
|
33
33
|
|
34
|
+
#
|
35
|
+
|
34
36
|
@classmethod
|
35
37
|
def from_entry_point(
|
36
38
|
cls,
|
@@ -52,6 +54,8 @@ class ManifestLoader:
|
|
52
54
|
|
53
55
|
return cls(module_remap=rm, **kwargs)
|
54
56
|
|
57
|
+
#
|
58
|
+
|
55
59
|
def load_cls(self, key: str) -> type:
|
56
60
|
try:
|
57
61
|
return self._cls_cache[key]
|
@@ -153,6 +157,8 @@ class ManifestLoader:
|
|
153
157
|
|
154
158
|
return lst
|
155
159
|
|
160
|
+
#
|
161
|
+
|
156
162
|
ENTRY_POINT_GROUP = 'omlish.manifests'
|
157
163
|
|
158
164
|
def discover(self) -> ta.Sequence[str]:
|
@@ -142,7 +142,12 @@ class AsyncsPlugin:
|
|
142
142
|
if len(bes) > 1 and set(bes) != {'trio', 'trio_asyncio'}:
|
143
143
|
raise Exception(f'{item.nodeid}: multiple async backends specified: {bes}')
|
144
144
|
elif is_async_function(item.obj) and not bes:
|
145
|
-
|
145
|
+
from _pytest.unittest import UnitTestCase # noqa
|
146
|
+
if isinstance(item.parent, UnitTestCase):
|
147
|
+
# unittest handles these itself.
|
148
|
+
pass
|
149
|
+
else:
|
150
|
+
raise Exception(f'{item.nodeid}: async def function and no async plugin specified')
|
146
151
|
|
147
152
|
if 'trio_asyncio' in bes:
|
148
153
|
obj = item.obj
|
@@ -1,5 +1,5 @@
|
|
1
1
|
omlish/.manifests.json,sha256=RX24SRc6DCEg77PUVnaXOKCWa5TF_c9RQJdGIf7gl9c,1135
|
2
|
-
omlish/__about__.py,sha256=
|
2
|
+
omlish/__about__.py,sha256=SGKzVFr0tcTK7dCoK2Y4-o8CVkBHmsj2aDPhw4bLYqc,3409
|
3
3
|
omlish/__init__.py,sha256=SsyiITTuK0v74XpKV8dqNaCmjOlan1JZKrHQv5rWKPA,253
|
4
4
|
omlish/argparse.py,sha256=cqKGAqcxuxv_s62z0gq29L9KAvg_3-_rFvXKjVpRJjo,8126
|
5
5
|
omlish/c3.py,sha256=ubu7lHwss5V4UznbejAI0qXhXahrU01MysuHOZI9C4U,8116
|
@@ -311,9 +311,10 @@ omlish/lifecycles/manager.py,sha256=Au66KaO-fI-SEJALaPUJsCHYW2GE20xextk1wKn2BEU,
|
|
311
311
|
omlish/lifecycles/states.py,sha256=zqMOU2ZU-MDNnWuwauM3_anIAiXM8LoBDElDEraptFg,1292
|
312
312
|
omlish/lifecycles/transitions.py,sha256=qQtFby-h4VzbvgaUqT2NnbNumlcOx9FVVADP9t83xj4,1939
|
313
313
|
omlish/lite/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
|
314
|
-
omlish/lite/cached.py,sha256=
|
314
|
+
omlish/lite/cached.py,sha256=hBW77-F7ZLtFqbLwVrlqaJ4-iFHMQleMWZXaZN1IubA,1308
|
315
315
|
omlish/lite/check.py,sha256=pQC412ffe_Zh7eHa4C1UYn6fA71Ls1vpVM0ZIOroPAY,1765
|
316
316
|
omlish/lite/contextmanagers.py,sha256=DRarS2gx15tbse1YzyI8ZLdBmWYjFgmKPe-i4CSNDYg,1458
|
317
|
+
omlish/lite/deathsig.py,sha256=Etz04WX6R2PXQ-BgqJyVJ0C5Pqym6Ds6PAG7p1cqr5o,626
|
317
318
|
omlish/lite/docker.py,sha256=Dj_7lQjs2sFPc_SmUn5CpJF3LnQQnckEBYGBKz8u5tE,392
|
318
319
|
omlish/lite/inject.py,sha256=aRRmFb6azTKF208ogYwVCEopNZx7496Ta1GZmL_IKBA,23716
|
319
320
|
omlish/lite/io.py,sha256=3ECgUXdRnXyS6pGTSoVr6oB4moI38EpWxTq08zaTM-U,5339
|
@@ -323,7 +324,7 @@ omlish/lite/logs.py,sha256=1pcGu0ekhVCcLUckLSP16VccnAoprjtl5Vkdfm7y1Wg,6184
|
|
323
324
|
omlish/lite/marshal.py,sha256=1wqZWKapZ5rFgDxSkb447Xv2ce2SFEpiB1EMg1nh2BI,13530
|
324
325
|
omlish/lite/maybes.py,sha256=7OlHJ8Q2r4wQ-aRbZSlJY7x0e8gDvufFdlohGEIJ3P4,833
|
325
326
|
omlish/lite/pidfile.py,sha256=PRSDOAXmNkNwxh-Vwif0Nrs8RAmWroiNhLKIbdjwzBc,1723
|
326
|
-
omlish/lite/pycharm.py,sha256=
|
327
|
+
omlish/lite/pycharm.py,sha256=pUOJevrPClSqTCEOkQBO11LKX2003tfDcp18a03QFrc,1163
|
327
328
|
omlish/lite/reflect.py,sha256=ad_ya_zZJOQB8HoNjs9yc66R54zgflwJVPJqiBXMzqA,1681
|
328
329
|
omlish/lite/resources.py,sha256=YNSmX1Ohck1aoWRs55a-o5ChVbFJIQhtbqE-XwF55Oc,326
|
329
330
|
omlish/lite/runtime.py,sha256=lVw5_yuQNHXZLwGf_l59u8IrAtBLZWPTml6owQ55uro,439
|
@@ -331,8 +332,11 @@ omlish/lite/secrets.py,sha256=3Mz3V2jf__XU9qNHcH56sBSw95L3U2UPL24bjvobG0c,816
|
|
331
332
|
omlish/lite/socket.py,sha256=7OYgkXTcQv0wq7TQuLnl9y6dJA1ZT6Vbc1JH59QlxgY,1792
|
332
333
|
omlish/lite/socketserver.py,sha256=Esy9dAo9dPnNavNx5hW52YZi5hv504a8XQUudMrPs2A,1595
|
333
334
|
omlish/lite/strings.py,sha256=QURcE4-1pKVW8eT_5VCJpXaHDWR2dW2pYOChTJnZDiQ,1504
|
334
|
-
omlish/lite/subprocesses.py,sha256=
|
335
|
+
omlish/lite/subprocesses.py,sha256=RTs8HJ1Lz8YOZTHw12Ja8KW7Eq4oyDFJZDiG0PSUBKY,4918
|
335
336
|
omlish/lite/typing.py,sha256=U3-JaEnkDSYxK4tsu_MzUn3RP6qALBe5FXQXpD-licE,1090
|
337
|
+
omlish/lite/asyncio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
338
|
+
omlish/lite/asyncio/asyncio.py,sha256=tsqQSLl5rG4GZHPYOBqI7V2yuw45ZVuGZEHe-J4QEhE,1320
|
339
|
+
omlish/lite/asyncio/subprocesses.py,sha256=crG4FlkVIA19sO2QtIagOCtXk8ak9rLaHgbTjGDy3mk,8274
|
336
340
|
omlish/lite/fdio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
337
341
|
omlish/lite/fdio/corohttp.py,sha256=FHdakDTGI2UYbCihahuwleyailclxQMUGhpkz3suww4,4080
|
338
342
|
omlish/lite/fdio/handlers.py,sha256=Wr0O2cvIC8NuLs3yoDHj9ZG4n1g_oVEeT87B0WDsg0Y,1341
|
@@ -352,7 +356,7 @@ omlish/logs/handlers.py,sha256=UpzUf3kWBBzWOnrtljoZsLjISw3Ix-ePz3Nsmp6lRgE,255
|
|
352
356
|
omlish/logs/noisy.py,sha256=Ubc-eTH6ZbGYsLfUUi69JAotwuUwzb-SJBeGo_0dIZI,348
|
353
357
|
omlish/logs/utils.py,sha256=MgGovbP0zUrZ3FGD3qYNQWn-l0jy0Y0bStcQvv5BOmQ,391
|
354
358
|
omlish/manifests/__init__.py,sha256=P2B0dpT8D7l5lJwRGPA92IcQj6oeXfd90X5-q9BJrKg,51
|
355
|
-
omlish/manifests/load.py,sha256=
|
359
|
+
omlish/manifests/load.py,sha256=8R-S5CyQpAbxDHt5wcNF6mAYri8bGndn6R2uEVOh52Y,4809
|
356
360
|
omlish/manifests/types.py,sha256=d8bv5tknCJqclRfxCpao_8XxHo2yofhLpVHQTB-MfNw,260
|
357
361
|
omlish/marshal/__init__.py,sha256=iVA7n31L08Bdub6HKPvYOXVvDhk2CMA6rPeKDL_u1to,2298
|
358
362
|
omlish/marshal/any.py,sha256=e82OyYK3Emm1P1ClnsnxP7fIWC2iNVyW0H5nK4mLmWM,779
|
@@ -487,7 +491,7 @@ omlish/testing/pytest/inject/__init__.py,sha256=pdRKv1HcDmJ_yArKJbYITPXXZthRSGgB
|
|
487
491
|
omlish/testing/pytest/inject/harness.py,sha256=v4DaKJ0KL8oQjzIMK43Gh8GHP4hiI6-lY37O9lyOHRk,5724
|
488
492
|
omlish/testing/pytest/plugins/__init__.py,sha256=ys1zXrYrNm7Uo6YOIVJ6Bd3dQo6kv387k7MbTYlqZSI,467
|
489
493
|
omlish/testing/pytest/plugins/_registry.py,sha256=IK04KlBgiOJxKAyCCgjpX2R-9tE-btalYJkgjLc8Te8,77
|
490
|
-
omlish/testing/pytest/plugins/asyncs.py,sha256=
|
494
|
+
omlish/testing/pytest/plugins/asyncs.py,sha256=CG-cWWxCtxVIyKJKEjxfFV0MVwYBHPo1mb-umCGz9X8,5532
|
491
495
|
omlish/testing/pytest/plugins/depskip.py,sha256=xithY-OMtjwhv8mcRNkv-WI_PSQtHldQ8H1s60MIXkk,2673
|
492
496
|
omlish/testing/pytest/plugins/logging.py,sha256=1zs6Xe54wiaSjabCviaFXwKkoN97CKm3mA5mEoUeJGs,380
|
493
497
|
omlish/testing/pytest/plugins/managermarks.py,sha256=AP3ty-QB-8O5DkulwUOudBlUOvXMHhBfNyY-0yCmejk,1520
|
@@ -504,9 +508,9 @@ omlish/text/glyphsplit.py,sha256=Ug-dPRO7x-OrNNr8g1y6DotSZ2KH0S-VcOmUobwa4B0,329
|
|
504
508
|
omlish/text/indent.py,sha256=6Jj6TFY9unaPa4xPzrnZemJ-fHsV53IamP93XGjSUHs,1274
|
505
509
|
omlish/text/parts.py,sha256=7vPF1aTZdvLVYJ4EwBZVzRSy8XB3YqPd7JwEnNGGAOo,6495
|
506
510
|
omlish/text/random.py,sha256=jNWpqiaKjKyTdMXC-pWAsSC10AAP-cmRRPVhm59ZWLk,194
|
507
|
-
omlish-0.0.0.
|
508
|
-
omlish-0.0.0.
|
509
|
-
omlish-0.0.0.
|
510
|
-
omlish-0.0.0.
|
511
|
-
omlish-0.0.0.
|
512
|
-
omlish-0.0.0.
|
511
|
+
omlish-0.0.0.dev148.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
|
512
|
+
omlish-0.0.0.dev148.dist-info/METADATA,sha256=78h6hQTFkljP4Axca6bTAYdlPCtJVukJDCFxbRrx__4,4264
|
513
|
+
omlish-0.0.0.dev148.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
514
|
+
omlish-0.0.0.dev148.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
|
515
|
+
omlish-0.0.0.dev148.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
|
516
|
+
omlish-0.0.0.dev148.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|