omlish 0.0.0.dev147__py3-none-any.whl → 0.0.0.dev149__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/argparse/all.py +45 -0
- omlish/{argparse.py → argparse/cli.py} +71 -107
- omlish/check.py +2 -0
- omlish/io/__init__.py +0 -3
- omlish/{lite/io.py → io/buffers.py} +8 -7
- omlish/io/fdio/__init__.py +0 -0
- omlish/{lite → io}/fdio/corohttp.py +9 -9
- omlish/{lite → io}/fdio/handlers.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/check.py +6 -1
- omlish/lite/deathsig.py +22 -0
- omlish/lite/subprocesses.py +71 -18
- omlish/testing/pytest/plugins/asyncs.py +6 -1
- {omlish-0.0.0.dev147.dist-info → omlish-0.0.0.dev149.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev147.dist-info → omlish-0.0.0.dev149.dist-info}/RECORD +27 -21
- /omlish/{lite/fdio → argparse}/__init__.py +0 -0
- /omlish/{lite → io}/fdio/kqueue.py +0 -0
- /omlish/{lite → io}/fdio/manager.py +0 -0
- /omlish/{lite → io}/fdio/pollers.py +0 -0
- {omlish-0.0.0.dev147.dist-info → omlish-0.0.0.dev149.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev147.dist-info → omlish-0.0.0.dev149.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev147.dist-info → omlish-0.0.0.dev149.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev147.dist-info → omlish-0.0.0.dev149.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
omlish/argparse/all.py
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# ruff: noqa: I001
|
2
|
+
import argparse
|
3
|
+
|
4
|
+
from .cli import ( # noqa
|
5
|
+
ArgparseArg as Arg,
|
6
|
+
argparse_arg as arg,
|
7
|
+
|
8
|
+
ArgparseCommandFn as CommandFn,
|
9
|
+
ArgparseCommand as Command,
|
10
|
+
argparse_command as command,
|
11
|
+
|
12
|
+
ArgparseCli as Cli,
|
13
|
+
)
|
14
|
+
|
15
|
+
|
16
|
+
##
|
17
|
+
|
18
|
+
|
19
|
+
SUPPRESS = argparse.SUPPRESS
|
20
|
+
|
21
|
+
OPTIONAL = argparse.OPTIONAL
|
22
|
+
ZERO_OR_MORE = argparse.ZERO_OR_MORE
|
23
|
+
ONE_OR_MORE = argparse.ONE_OR_MORE
|
24
|
+
PARSER = argparse.PARSER
|
25
|
+
REMAINDER = argparse.REMAINDER
|
26
|
+
|
27
|
+
HelpFormatter = argparse.HelpFormatter
|
28
|
+
RawDescriptionHelpFormatter = argparse.RawDescriptionHelpFormatter
|
29
|
+
RawTextHelpFormatter = argparse.RawTextHelpFormatter
|
30
|
+
ArgumentDefaultsHelpFormatter = argparse.ArgumentDefaultsHelpFormatter
|
31
|
+
|
32
|
+
MetavarTypeHelpFormatter = argparse.MetavarTypeHelpFormatter
|
33
|
+
|
34
|
+
ArgumentError = argparse.ArgumentError
|
35
|
+
ArgumentTypeError = argparse.ArgumentTypeError
|
36
|
+
|
37
|
+
Action = argparse.Action
|
38
|
+
BooleanOptionalAction = argparse.BooleanOptionalAction
|
39
|
+
SubParsersAction = argparse._SubParsersAction # noqa
|
40
|
+
|
41
|
+
FileType = argparse.FileType
|
42
|
+
|
43
|
+
Namespace = argparse.Namespace
|
44
|
+
|
45
|
+
ArgumentParser = argparse.ArgumentParser
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
1
3
|
"""
|
2
4
|
TODO:
|
3
5
|
- default command
|
@@ -9,8 +11,11 @@ import functools
|
|
9
11
|
import sys
|
10
12
|
import typing as ta
|
11
13
|
|
12
|
-
from . import
|
13
|
-
from . import
|
14
|
+
from ..lite.check import check_arg
|
15
|
+
from ..lite.check import check_isinstance
|
16
|
+
from ..lite.check import check_not_empty
|
17
|
+
from ..lite.check import check_not_in
|
18
|
+
from ..lite.check import check_not_isinstance
|
14
19
|
|
15
20
|
|
16
21
|
T = ta.TypeVar('T')
|
@@ -19,43 +24,11 @@ T = ta.TypeVar('T')
|
|
19
24
|
##
|
20
25
|
|
21
26
|
|
22
|
-
SUPPRESS = argparse.SUPPRESS
|
23
|
-
|
24
|
-
OPTIONAL = argparse.OPTIONAL
|
25
|
-
ZERO_OR_MORE = argparse.ZERO_OR_MORE
|
26
|
-
ONE_OR_MORE = argparse.ONE_OR_MORE
|
27
|
-
PARSER = argparse.PARSER
|
28
|
-
REMAINDER = argparse.REMAINDER
|
29
|
-
|
30
|
-
HelpFormatter = argparse.HelpFormatter
|
31
|
-
RawDescriptionHelpFormatter = argparse.RawDescriptionHelpFormatter
|
32
|
-
RawTextHelpFormatter = argparse.RawTextHelpFormatter
|
33
|
-
ArgumentDefaultsHelpFormatter = argparse.ArgumentDefaultsHelpFormatter
|
34
|
-
|
35
|
-
MetavarTypeHelpFormatter = argparse.MetavarTypeHelpFormatter
|
36
|
-
|
37
|
-
ArgumentError = argparse.ArgumentError
|
38
|
-
ArgumentTypeError = argparse.ArgumentTypeError
|
39
|
-
|
40
|
-
Action = argparse.Action
|
41
|
-
BooleanOptionalAction = argparse.BooleanOptionalAction
|
42
|
-
SubParsersAction = argparse._SubParsersAction # noqa
|
43
|
-
|
44
|
-
FileType = argparse.FileType
|
45
|
-
|
46
|
-
Namespace = argparse.Namespace
|
47
|
-
|
48
|
-
ArgumentParser = argparse.ArgumentParser
|
49
|
-
|
50
|
-
|
51
|
-
##
|
52
|
-
|
53
|
-
|
54
27
|
@dc.dataclass(eq=False)
|
55
|
-
class
|
28
|
+
class ArgparseArg:
|
56
29
|
args: ta.Sequence[ta.Any]
|
57
30
|
kwargs: ta.Mapping[str, ta.Any]
|
58
|
-
dest: str
|
31
|
+
dest: ta.Optional[str] = None
|
59
32
|
|
60
33
|
def __get__(self, instance, owner=None):
|
61
34
|
if instance is None:
|
@@ -63,42 +36,42 @@ class Arg:
|
|
63
36
|
return getattr(instance.args, self.dest) # type: ignore
|
64
37
|
|
65
38
|
|
66
|
-
def
|
67
|
-
return
|
39
|
+
def argparse_arg(*args, **kwargs) -> ArgparseArg:
|
40
|
+
return ArgparseArg(args, kwargs)
|
68
41
|
|
69
42
|
|
70
43
|
#
|
71
44
|
|
72
45
|
|
73
|
-
|
46
|
+
ArgparseCommandFn = ta.Callable[[], ta.Optional[int]] # ta.TypeAlias
|
74
47
|
|
75
48
|
|
76
49
|
@dc.dataclass(eq=False)
|
77
|
-
class
|
50
|
+
class ArgparseCommand:
|
78
51
|
name: str
|
79
|
-
fn:
|
80
|
-
args: ta.Sequence[
|
52
|
+
fn: ArgparseCommandFn
|
53
|
+
args: ta.Sequence[ArgparseArg] = () # noqa
|
81
54
|
|
82
|
-
_: dc.KW_ONLY
|
55
|
+
# _: dc.KW_ONLY
|
83
56
|
|
84
|
-
aliases: ta.Sequence[str]
|
85
|
-
parent: ta.Optional['
|
57
|
+
aliases: ta.Optional[ta.Sequence[str]] = None
|
58
|
+
parent: ta.Optional['ArgparseCommand'] = None
|
86
59
|
accepts_unknown: bool = False
|
87
60
|
|
88
61
|
def __post_init__(self) -> None:
|
89
62
|
def check_name(s: str) -> None:
|
90
|
-
|
91
|
-
|
92
|
-
|
63
|
+
check_isinstance(s, str)
|
64
|
+
check_not_in('_', s)
|
65
|
+
check_not_empty(s)
|
93
66
|
check_name(self.name)
|
94
|
-
|
67
|
+
check_not_isinstance(self.aliases, str)
|
95
68
|
for a in self.aliases or []:
|
96
69
|
check_name(a)
|
97
70
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
71
|
+
check_arg(callable(self.fn))
|
72
|
+
check_arg(all(isinstance(a, ArgparseArg) for a in self.args))
|
73
|
+
check_isinstance(self.parent, (ArgparseCommand, type(None)))
|
74
|
+
check_isinstance(self.accepts_unknown, bool)
|
102
75
|
|
103
76
|
functools.update_wrapper(self, self.fn)
|
104
77
|
|
@@ -107,25 +80,25 @@ class Command:
|
|
107
80
|
return self
|
108
81
|
return dc.replace(self, fn=self.fn.__get__(instance, owner)) # noqa
|
109
82
|
|
110
|
-
def __call__(self, *args, **kwargs) -> int
|
83
|
+
def __call__(self, *args, **kwargs) -> ta.Optional[int]:
|
111
84
|
return self.fn(*args, **kwargs)
|
112
85
|
|
113
86
|
|
114
|
-
def
|
115
|
-
*args:
|
116
|
-
name: str
|
117
|
-
aliases: ta.Iterable[str]
|
118
|
-
parent:
|
87
|
+
def argparse_command(
|
88
|
+
*args: ArgparseArg,
|
89
|
+
name: ta.Optional[str] = None,
|
90
|
+
aliases: ta.Optional[ta.Iterable[str]] = None,
|
91
|
+
parent: ta.Optional[ArgparseCommand] = None,
|
119
92
|
accepts_unknown: bool = False,
|
120
|
-
) -> ta.Any: # ta.Callable[[
|
93
|
+
) -> ta.Any: # ta.Callable[[ArgparseCommandFn], ArgparseCommand]: # FIXME
|
121
94
|
for arg in args:
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
95
|
+
check_isinstance(arg, ArgparseArg)
|
96
|
+
check_isinstance(name, (str, type(None)))
|
97
|
+
check_isinstance(parent, (ArgparseCommand, type(None)))
|
98
|
+
check_not_isinstance(aliases, str)
|
126
99
|
|
127
100
|
def inner(fn):
|
128
|
-
return
|
101
|
+
return ArgparseCommand(
|
129
102
|
(name if name is not None else fn.__name__).replace('_', '-'),
|
130
103
|
fn,
|
131
104
|
args,
|
@@ -140,7 +113,7 @@ def command(
|
|
140
113
|
##
|
141
114
|
|
142
115
|
|
143
|
-
def
|
116
|
+
def _get_argparse_arg_ann_kwargs(ann: ta.Any) -> ta.Mapping[str, ta.Any]:
|
144
117
|
if ann is str:
|
145
118
|
return {}
|
146
119
|
elif ann is int:
|
@@ -153,48 +126,51 @@ def get_arg_ann_kwargs(ann: ta.Any) -> ta.Mapping[str, ta.Any]:
|
|
153
126
|
raise TypeError(ann)
|
154
127
|
|
155
128
|
|
156
|
-
class
|
157
|
-
|
129
|
+
class _ArgparseCliAnnotationBox:
|
158
130
|
def __init__(self, annotations: ta.Mapping[str, ta.Any]) -> None:
|
159
131
|
super().__init__()
|
160
132
|
self.__annotations__ = annotations # type: ignore
|
161
133
|
|
162
134
|
|
163
|
-
class
|
135
|
+
class ArgparseCli:
|
136
|
+
def __init__(self, argv: ta.Optional[ta.Sequence[str]] = None) -> None:
|
137
|
+
super().__init__()
|
138
|
+
|
139
|
+
self._argv = argv if argv is not None else sys.argv[1:]
|
164
140
|
|
165
|
-
|
166
|
-
if not bases:
|
167
|
-
return super().__new__(mcls, name, tuple(bases), dict(namespace))
|
141
|
+
self._args, self._unknown_args = self.get_parser().parse_known_args(self._argv)
|
168
142
|
|
169
|
-
|
170
|
-
|
143
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
144
|
+
super().__init_subclass__(**kwargs)
|
145
|
+
|
146
|
+
ns = cls.__dict__
|
171
147
|
|
172
148
|
objs = {}
|
173
|
-
mro =
|
174
|
-
for bns in [bcls.__dict__ for bcls in reversed(mro)] + [
|
149
|
+
mro = cls.__mro__[::-1]
|
150
|
+
for bns in [bcls.__dict__ for bcls in reversed(mro)] + [ns]:
|
175
151
|
bseen = set() # type: ignore
|
176
152
|
for k, v in bns.items():
|
177
|
-
if isinstance(v, (
|
178
|
-
|
153
|
+
if isinstance(v, (ArgparseCommand, ArgparseArg)):
|
154
|
+
check_not_in(v, bseen)
|
179
155
|
bseen.add(v)
|
180
156
|
objs[k] = v
|
181
157
|
elif k in objs:
|
182
158
|
del [k]
|
183
159
|
|
184
|
-
anns = ta.get_type_hints(
|
160
|
+
anns = ta.get_type_hints(_ArgparseCliAnnotationBox({
|
185
161
|
**{k: v for bcls in reversed(mro) for k, v in getattr(bcls, '__annotations__', {}).items()},
|
186
|
-
**
|
187
|
-
}), globalns=
|
162
|
+
**ns.get('__annotations__', {}),
|
163
|
+
}), globalns=ns.get('__globals__', {}))
|
188
164
|
|
189
|
-
if '
|
190
|
-
parser =
|
165
|
+
if '_parser' in ns:
|
166
|
+
parser = check_isinstance(ns['_parser'], argparse.ArgumentParser)
|
191
167
|
else:
|
192
|
-
parser = ArgumentParser()
|
193
|
-
|
168
|
+
parser = argparse.ArgumentParser()
|
169
|
+
setattr(cls, '_parser', parser)
|
194
170
|
|
195
171
|
subparsers = parser.add_subparsers()
|
196
172
|
for att, obj in objs.items():
|
197
|
-
if isinstance(obj,
|
173
|
+
if isinstance(obj, ArgparseCommand):
|
198
174
|
if obj.parent is not None:
|
199
175
|
raise NotImplementedError
|
200
176
|
for cn in [obj.name, *(obj.aliases or [])]:
|
@@ -203,7 +179,7 @@ class _CliMeta(type):
|
|
203
179
|
if (
|
204
180
|
len(arg.args) == 1 and
|
205
181
|
isinstance(arg.args[0], str) and
|
206
|
-
not (n :=
|
182
|
+
not (n := check_isinstance(arg.args[0], str)).startswith('-') and
|
207
183
|
'metavar' not in arg.kwargs
|
208
184
|
):
|
209
185
|
cparser.add_argument(
|
@@ -215,9 +191,9 @@ class _CliMeta(type):
|
|
215
191
|
cparser.add_argument(*arg.args, **arg.kwargs)
|
216
192
|
cparser.set_defaults(_cmd=obj)
|
217
193
|
|
218
|
-
elif isinstance(obj,
|
194
|
+
elif isinstance(obj, ArgparseArg):
|
219
195
|
if att in anns:
|
220
|
-
akwargs =
|
196
|
+
akwargs = _get_argparse_arg_ann_kwargs(anns[att])
|
221
197
|
obj.kwargs = {**akwargs, **obj.kwargs}
|
222
198
|
if not obj.dest:
|
223
199
|
if 'dest' in obj.kwargs:
|
@@ -229,22 +205,10 @@ class _CliMeta(type):
|
|
229
205
|
else:
|
230
206
|
raise TypeError(obj)
|
231
207
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
class Cli(metaclass=_CliMeta):
|
236
|
-
|
237
|
-
def __init__(self, argv: ta.Sequence[str] | None = None) -> None:
|
238
|
-
super().__init__()
|
239
|
-
|
240
|
-
self._argv = argv if argv is not None else sys.argv[1:]
|
241
|
-
|
242
|
-
self._args, self._unknown_args = self.get_parser().parse_known_args(self._argv)
|
243
|
-
|
244
|
-
_parser: ta.ClassVar[ArgumentParser]
|
208
|
+
_parser: ta.ClassVar[argparse.ArgumentParser]
|
245
209
|
|
246
210
|
@classmethod
|
247
|
-
def get_parser(cls) -> ArgumentParser:
|
211
|
+
def get_parser(cls) -> argparse.ArgumentParser:
|
248
212
|
return cls._parser
|
249
213
|
|
250
214
|
@property
|
@@ -252,17 +216,17 @@ class Cli(metaclass=_CliMeta):
|
|
252
216
|
return self._argv
|
253
217
|
|
254
218
|
@property
|
255
|
-
def args(self) -> Namespace:
|
219
|
+
def args(self) -> argparse.Namespace:
|
256
220
|
return self._args
|
257
221
|
|
258
222
|
@property
|
259
223
|
def unknown_args(self) -> ta.Sequence[str]:
|
260
224
|
return self._unknown_args
|
261
225
|
|
262
|
-
def _run_cmd(self, cmd:
|
226
|
+
def _run_cmd(self, cmd: ArgparseCommand) -> ta.Optional[int]:
|
263
227
|
return cmd.__get__(self, type(self))()
|
264
228
|
|
265
|
-
def __call__(self) -> int
|
229
|
+
def __call__(self) -> ta.Optional[int]:
|
266
230
|
cmd = getattr(self.args, '_cmd', None)
|
267
231
|
|
268
232
|
if self._unknown_args and not (cmd is not None and cmd.accepts_unknown):
|
@@ -270,7 +234,7 @@ class Cli(metaclass=_CliMeta):
|
|
270
234
|
if (parser := self.get_parser()).exit_on_error: # type: ignore
|
271
235
|
parser.error(msg)
|
272
236
|
else:
|
273
|
-
raise ArgumentError(None, msg)
|
237
|
+
raise argparse.ArgumentError(None, msg)
|
274
238
|
|
275
239
|
if cmd is None:
|
276
240
|
self.get_parser().print_help()
|
omlish/check.py
CHANGED
omlish/io/__init__.py
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
# ruff: noqa: UP007
|
2
|
+
# @omlish-lite
|
2
3
|
import io
|
3
4
|
import typing as ta
|
4
5
|
|
5
|
-
from .check import check_isinstance
|
6
|
-
from .check import
|
7
|
-
from .check import check_not_none
|
8
|
-
from .strings import attr_repr
|
6
|
+
from ..lite.check import check_isinstance
|
7
|
+
from ..lite.check import check_not_empty
|
8
|
+
from ..lite.check import check_not_none
|
9
|
+
from ..lite.strings import attr_repr
|
9
10
|
|
10
11
|
|
11
12
|
class DelimitingBuffer:
|
@@ -192,7 +193,7 @@ class IncrementalWriteBuffer:
|
|
192
193
|
) -> None:
|
193
194
|
super().__init__()
|
194
195
|
|
195
|
-
|
196
|
+
check_not_empty(data)
|
196
197
|
self._len = len(data)
|
197
198
|
self._write_size = write_size
|
198
199
|
|
@@ -207,11 +208,11 @@ class IncrementalWriteBuffer:
|
|
207
208
|
return self._len - self._pos
|
208
209
|
|
209
210
|
def write(self, fn: ta.Callable[[bytes], int]) -> int:
|
210
|
-
lst =
|
211
|
+
lst = check_not_empty(self._lst)
|
211
212
|
|
212
213
|
t = 0
|
213
214
|
for i, d in enumerate(lst): # noqa
|
214
|
-
n = fn(
|
215
|
+
n = fn(check_not_empty(d))
|
215
216
|
if not n:
|
216
217
|
break
|
217
218
|
t += n
|
File without changes
|
@@ -2,15 +2,15 @@
|
|
2
2
|
import socket
|
3
3
|
import typing as ta
|
4
4
|
|
5
|
-
from
|
6
|
-
from
|
7
|
-
from
|
8
|
-
from
|
9
|
-
from
|
10
|
-
from
|
11
|
-
from
|
12
|
-
from ..
|
13
|
-
from ..
|
5
|
+
from ...lite.check import check_isinstance
|
6
|
+
from ...lite.check import check_none
|
7
|
+
from ...lite.check import check_not_none
|
8
|
+
from ...lite.check import check_state
|
9
|
+
from ...lite.http.coroserver import CoroHttpServer
|
10
|
+
from ...lite.http.handlers import HttpHandler
|
11
|
+
from ...lite.socket import SocketAddress
|
12
|
+
from ..buffers import IncrementalWriteBuffer
|
13
|
+
from ..buffers import ReadableListBuffer
|
14
14
|
from .handlers import SocketFdioHandler
|
15
15
|
|
16
16
|
|
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/check.py
CHANGED
@@ -41,6 +41,11 @@ def check_non_empty_str(v: ta.Optional[str]) -> str:
|
|
41
41
|
return v
|
42
42
|
|
43
43
|
|
44
|
+
def check_arg(v: bool, msg: str = 'Illegal argument') -> None:
|
45
|
+
if not v:
|
46
|
+
raise ValueError(msg)
|
47
|
+
|
48
|
+
|
44
49
|
def check_state(v: bool, msg: str = 'Illegal state') -> None:
|
45
50
|
if not v:
|
46
51
|
raise ValueError(msg)
|
@@ -93,7 +98,7 @@ def check_empty(v: SizedT) -> SizedT:
|
|
93
98
|
return v
|
94
99
|
|
95
100
|
|
96
|
-
def
|
101
|
+
def check_not_empty(v: SizedT) -> SizedT:
|
97
102
|
if not len(v):
|
98
103
|
raise ValueError(v)
|
99
104
|
return v
|
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/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]:
|
@@ -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,10 +1,9 @@
|
|
1
1
|
omlish/.manifests.json,sha256=RX24SRc6DCEg77PUVnaXOKCWa5TF_c9RQJdGIf7gl9c,1135
|
2
|
-
omlish/__about__.py,sha256=
|
2
|
+
omlish/__about__.py,sha256=l0_vBxdzereK5v8M0TtiX5ahNihp0_HGdpH8Oq5x5Ag,3409
|
3
3
|
omlish/__init__.py,sha256=SsyiITTuK0v74XpKV8dqNaCmjOlan1JZKrHQv5rWKPA,253
|
4
|
-
omlish/argparse.py,sha256=cqKGAqcxuxv_s62z0gq29L9KAvg_3-_rFvXKjVpRJjo,8126
|
5
4
|
omlish/c3.py,sha256=ubu7lHwss5V4UznbejAI0qXhXahrU01MysuHOZI9C4U,8116
|
6
5
|
omlish/cached.py,sha256=UI-XTFBwA6YXWJJJeBn-WkwBkfzDjLBBaZf4nIJA9y0,510
|
7
|
-
omlish/check.py,sha256=
|
6
|
+
omlish/check.py,sha256=KWS5IRrBCjzfK_nTRQeF0sRlbo46G-6w4dvkeJDUw8I,10651
|
8
7
|
omlish/datetimes.py,sha256=HajeM1kBvwlTa-uR1TTZHmZ3zTPnnUr1uGGQhiO1XQ0,2152
|
9
8
|
omlish/defs.py,sha256=9uUjJuVIbCBL3g14fyzAp-9gH935MFofvlfOGwcBIaM,4913
|
10
9
|
omlish/dynamic.py,sha256=35C_cCX_Vq2HrHzGk5T-zbrMvmUdiIiwDzDNixczoDo,6541
|
@@ -75,6 +74,9 @@ omlish/antlr/_runtime/tree/__init__.py,sha256=Jn5lqTVbeUQXD5a4IxDHKibOatAQWVTlaQ
|
|
75
74
|
omlish/antlr/_runtime/xpath/XPath.py,sha256=CbS0Fpnd2aRt_nQUBJlTpoHpxCyT9qbVW8ldj1aQJKY,9643
|
76
75
|
omlish/antlr/_runtime/xpath/XPathLexer.py,sha256=xFtdr4ZXMZxb2dnB_ggWyhvlQiC7RXQlDS5ePhTyOGg,3505
|
77
76
|
omlish/antlr/_runtime/xpath/__init__.py,sha256=lMd_BbXYdlDhZQN_q0TKN978XW5G0pq618F0NaLkpFE,71
|
77
|
+
omlish/argparse/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
78
|
+
omlish/argparse/all.py,sha256=EfUSf27vFWqa4Q93AycU5YRsrHt-Nx3pU3uNVapb-EE,1054
|
79
|
+
omlish/argparse/cli.py,sha256=SULYDE7BDGcU_dfKLguIcBQ0k_ttsyCjPjeqdHBN3Qc,7607
|
78
80
|
omlish/asyncs/__init__.py,sha256=uUz9ziKh4_QrgmdhKFMgq6j7mFbiZd3LiogguDCQsGI,587
|
79
81
|
omlish/asyncs/anyio.py,sha256=gfpx-D8QGmUfhnQxHEaHXcAP8zSMQjcGw4COFTGNnHI,8021
|
80
82
|
omlish/asyncs/asyncio.py,sha256=JfM59QgB3asgEbrps0zoVbNjWD4kL2XdsEkRMEIoFos,971
|
@@ -256,8 +258,9 @@ omlish/inject/impl/privates.py,sha256=alpCYyk5VJ9lJknbRH2nLVNFYVvFhkj-VC1Vco3zCF
|
|
256
258
|
omlish/inject/impl/providers.py,sha256=QnwhsujJFIHC0JTgd2Wlo1kP53i3CWTrj1nKU2DNxwg,2375
|
257
259
|
omlish/inject/impl/proxy.py,sha256=1ko0VaKqzu9UG8bIldp9xtUrAVUOFTKWKTjOCqIGr4s,1636
|
258
260
|
omlish/inject/impl/scopes.py,sha256=hKnzNieB-fJSFEXDP_QG1mCfIKoVFIfFlf9LiIt5tk4,5920
|
259
|
-
omlish/io/__init__.py,sha256=
|
261
|
+
omlish/io/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
260
262
|
omlish/io/abc.py,sha256=Cxs8KB1B_69rxpUYxI-MTsilAmNooJJn3w07DKqYKkE,1255
|
263
|
+
omlish/io/buffers.py,sha256=JoifktnJxnNfL8WdhqbcDwEzBcOm-3jJbFk6CooE6aQ,5378
|
261
264
|
omlish/io/pyio.py,sha256=q4RBFVpBE5PYjnGPGT-_4pcZb7dFJmLJ4LtI8OoDRQY,95433
|
262
265
|
omlish/io/trampoline.py,sha256=oUKTQg1F5xQS1431Kt7MbK-NZpX509ubcXU-s86xJr8,7171
|
263
266
|
omlish/io/compress/__init__.py,sha256=qV-aDfPWykTMYcoQmE8THZ4KFDRzqwN3QPPNEJVarXY,86
|
@@ -272,6 +275,12 @@ omlish/io/compress/lzma.py,sha256=8qxi7TniLN00LyJIJLyp6W7UUU50JBaPxxoXYg2j2XQ,22
|
|
272
275
|
omlish/io/compress/snappy.py,sha256=kCPgZ7PTBAxAnmYzpQCq4HKUIJ4APeAEXsU3Vg2CaDU,411
|
273
276
|
omlish/io/compress/zlib.py,sha256=MtnVGfzDlRU1LPl2J8Sa3wwgqnTVBx2uclZygWpH9xI,2115
|
274
277
|
omlish/io/compress/zstd.py,sha256=LrYWVHzk-TqWJA_Bnci2i8QOtrqnFFpppLQhLqanDWM,668
|
278
|
+
omlish/io/fdio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
279
|
+
omlish/io/fdio/corohttp.py,sha256=fJKNpED19W_goXdZ6CN_supOyeaMQNgps-E0TQxxjlo,4132
|
280
|
+
omlish/io/fdio/handlers.py,sha256=ZtJ6MOVVRjLuKim9sfGMJsVCRvOTMvFYNPfzp1Cyn7M,1353
|
281
|
+
omlish/io/fdio/kqueue.py,sha256=YgGBQibkAUYODYDiGl7Enjtx1oQsJXuDsBLBXgqlLQw,3832
|
282
|
+
omlish/io/fdio/manager.py,sha256=q4wWf7nKrNtjx6yPEvrVnFt4UtK_BTvVlquEGw7poEo,1250
|
283
|
+
omlish/io/fdio/pollers.py,sha256=yNadAt3W5wd90PFmd3vD77bq5QwoVb2A6SM2JjZpKRs,5507
|
275
284
|
omlish/io/generators/__init__.py,sha256=40DTZUqyss40dlgm68yKAtiAlqeIlYylTi8zaFrUW40,1135
|
276
285
|
omlish/io/generators/consts.py,sha256=4r6IMLBMic6MJHVn9UiORIkkPAuxsqtzFT3KV0fatC0,33
|
277
286
|
omlish/io/generators/direct.py,sha256=A9VJB1rNKU3l-NatpYIwyCLI3R_ybGglmdx6sAtoTo4,324
|
@@ -311,12 +320,12 @@ omlish/lifecycles/manager.py,sha256=Au66KaO-fI-SEJALaPUJsCHYW2GE20xextk1wKn2BEU,
|
|
311
320
|
omlish/lifecycles/states.py,sha256=zqMOU2ZU-MDNnWuwauM3_anIAiXM8LoBDElDEraptFg,1292
|
312
321
|
omlish/lifecycles/transitions.py,sha256=qQtFby-h4VzbvgaUqT2NnbNumlcOx9FVVADP9t83xj4,1939
|
313
322
|
omlish/lite/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
|
314
|
-
omlish/lite/cached.py,sha256=
|
315
|
-
omlish/lite/check.py,sha256=
|
323
|
+
omlish/lite/cached.py,sha256=hBW77-F7ZLtFqbLwVrlqaJ4-iFHMQleMWZXaZN1IubA,1308
|
324
|
+
omlish/lite/check.py,sha256=VOf_skD5hRzny_aqxykihQRs0PYd3SwkuihXJ2Bfaps,1874
|
316
325
|
omlish/lite/contextmanagers.py,sha256=DRarS2gx15tbse1YzyI8ZLdBmWYjFgmKPe-i4CSNDYg,1458
|
326
|
+
omlish/lite/deathsig.py,sha256=Etz04WX6R2PXQ-BgqJyVJ0C5Pqym6Ds6PAG7p1cqr5o,626
|
317
327
|
omlish/lite/docker.py,sha256=Dj_7lQjs2sFPc_SmUn5CpJF3LnQQnckEBYGBKz8u5tE,392
|
318
328
|
omlish/lite/inject.py,sha256=aRRmFb6azTKF208ogYwVCEopNZx7496Ta1GZmL_IKBA,23716
|
319
|
-
omlish/lite/io.py,sha256=3ECgUXdRnXyS6pGTSoVr6oB4moI38EpWxTq08zaTM-U,5339
|
320
329
|
omlish/lite/journald.py,sha256=f5Y2Q6-6O3iK_7MoGiwZwoQEOcP7LfkxxQNUR9tMjJM,3882
|
321
330
|
omlish/lite/json.py,sha256=7-02Ny4fq-6YAu5ynvqoijhuYXWpLmfCI19GUeZnb1c,740
|
322
331
|
omlish/lite/logs.py,sha256=1pcGu0ekhVCcLUckLSP16VccnAoprjtl5Vkdfm7y1Wg,6184
|
@@ -331,14 +340,11 @@ omlish/lite/secrets.py,sha256=3Mz3V2jf__XU9qNHcH56sBSw95L3U2UPL24bjvobG0c,816
|
|
331
340
|
omlish/lite/socket.py,sha256=7OYgkXTcQv0wq7TQuLnl9y6dJA1ZT6Vbc1JH59QlxgY,1792
|
332
341
|
omlish/lite/socketserver.py,sha256=Esy9dAo9dPnNavNx5hW52YZi5hv504a8XQUudMrPs2A,1595
|
333
342
|
omlish/lite/strings.py,sha256=QURcE4-1pKVW8eT_5VCJpXaHDWR2dW2pYOChTJnZDiQ,1504
|
334
|
-
omlish/lite/subprocesses.py,sha256=
|
343
|
+
omlish/lite/subprocesses.py,sha256=RTs8HJ1Lz8YOZTHw12Ja8KW7Eq4oyDFJZDiG0PSUBKY,4918
|
335
344
|
omlish/lite/typing.py,sha256=U3-JaEnkDSYxK4tsu_MzUn3RP6qALBe5FXQXpD-licE,1090
|
336
|
-
omlish/lite/
|
337
|
-
omlish/lite/
|
338
|
-
omlish/lite/
|
339
|
-
omlish/lite/fdio/kqueue.py,sha256=YgGBQibkAUYODYDiGl7Enjtx1oQsJXuDsBLBXgqlLQw,3832
|
340
|
-
omlish/lite/fdio/manager.py,sha256=q4wWf7nKrNtjx6yPEvrVnFt4UtK_BTvVlquEGw7poEo,1250
|
341
|
-
omlish/lite/fdio/pollers.py,sha256=yNadAt3W5wd90PFmd3vD77bq5QwoVb2A6SM2JjZpKRs,5507
|
345
|
+
omlish/lite/asyncio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
346
|
+
omlish/lite/asyncio/asyncio.py,sha256=tsqQSLl5rG4GZHPYOBqI7V2yuw45ZVuGZEHe-J4QEhE,1320
|
347
|
+
omlish/lite/asyncio/subprocesses.py,sha256=crG4FlkVIA19sO2QtIagOCtXk8ak9rLaHgbTjGDy3mk,8274
|
342
348
|
omlish/lite/http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
343
349
|
omlish/lite/http/coroserver.py,sha256=aBaYjP80yQHQxPxwi7PTYHub-fdRDKsMnB-tM8lBc2o,18095
|
344
350
|
omlish/lite/http/handlers.py,sha256=Yu0P3nqz-frklwCM2PbiWvoJNE-NqeTFLBvpNpqcdtA,753
|
@@ -487,7 +493,7 @@ omlish/testing/pytest/inject/__init__.py,sha256=pdRKv1HcDmJ_yArKJbYITPXXZthRSGgB
|
|
487
493
|
omlish/testing/pytest/inject/harness.py,sha256=v4DaKJ0KL8oQjzIMK43Gh8GHP4hiI6-lY37O9lyOHRk,5724
|
488
494
|
omlish/testing/pytest/plugins/__init__.py,sha256=ys1zXrYrNm7Uo6YOIVJ6Bd3dQo6kv387k7MbTYlqZSI,467
|
489
495
|
omlish/testing/pytest/plugins/_registry.py,sha256=IK04KlBgiOJxKAyCCgjpX2R-9tE-btalYJkgjLc8Te8,77
|
490
|
-
omlish/testing/pytest/plugins/asyncs.py,sha256=
|
496
|
+
omlish/testing/pytest/plugins/asyncs.py,sha256=CG-cWWxCtxVIyKJKEjxfFV0MVwYBHPo1mb-umCGz9X8,5532
|
491
497
|
omlish/testing/pytest/plugins/depskip.py,sha256=xithY-OMtjwhv8mcRNkv-WI_PSQtHldQ8H1s60MIXkk,2673
|
492
498
|
omlish/testing/pytest/plugins/logging.py,sha256=1zs6Xe54wiaSjabCviaFXwKkoN97CKm3mA5mEoUeJGs,380
|
493
499
|
omlish/testing/pytest/plugins/managermarks.py,sha256=AP3ty-QB-8O5DkulwUOudBlUOvXMHhBfNyY-0yCmejk,1520
|
@@ -504,9 +510,9 @@ omlish/text/glyphsplit.py,sha256=Ug-dPRO7x-OrNNr8g1y6DotSZ2KH0S-VcOmUobwa4B0,329
|
|
504
510
|
omlish/text/indent.py,sha256=6Jj6TFY9unaPa4xPzrnZemJ-fHsV53IamP93XGjSUHs,1274
|
505
511
|
omlish/text/parts.py,sha256=7vPF1aTZdvLVYJ4EwBZVzRSy8XB3YqPd7JwEnNGGAOo,6495
|
506
512
|
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.
|
513
|
+
omlish-0.0.0.dev149.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
|
514
|
+
omlish-0.0.0.dev149.dist-info/METADATA,sha256=z0AUwvKglpElbQTqOlIdPh5J21U5sNjvUuYimEhJncE,4264
|
515
|
+
omlish-0.0.0.dev149.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
516
|
+
omlish-0.0.0.dev149.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
|
517
|
+
omlish-0.0.0.dev149.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
|
518
|
+
omlish-0.0.0.dev149.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|