omlish 0.0.0.dev147__py3-none-any.whl → 0.0.0.dev149__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev147'
2
- __revision__ = 'd937c5e9830e9f6a67d18d09f079b827659e3339'
1
+ __version__ = '0.0.0.dev149'
2
+ __revision__ = '78e7c7e35dc2bc806db7fd199c1ce42b0f41cbe8'
3
3
 
4
4
 
5
5
  #
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 c3
13
- from . import check
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 Arg:
28
+ class ArgparseArg:
56
29
  args: ta.Sequence[ta.Any]
57
30
  kwargs: ta.Mapping[str, ta.Any]
58
- dest: str | None = None
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 arg(*args, **kwargs) -> Arg:
67
- return Arg(args, kwargs)
39
+ def argparse_arg(*args, **kwargs) -> ArgparseArg:
40
+ return ArgparseArg(args, kwargs)
68
41
 
69
42
 
70
43
  #
71
44
 
72
45
 
73
- CommandFn = ta.Callable[[], int | None]
46
+ ArgparseCommandFn = ta.Callable[[], ta.Optional[int]] # ta.TypeAlias
74
47
 
75
48
 
76
49
  @dc.dataclass(eq=False)
77
- class Command:
50
+ class ArgparseCommand:
78
51
  name: str
79
- fn: CommandFn
80
- args: ta.Sequence[Arg] = () # noqa
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] | None = None
85
- parent: ta.Optional['Command'] = None
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
- check.isinstance(s, str)
91
- check.not_in('_', s)
92
- check.not_empty(s)
63
+ check_isinstance(s, str)
64
+ check_not_in('_', s)
65
+ check_not_empty(s)
93
66
  check_name(self.name)
94
- check.not_isinstance(self.aliases, str)
67
+ check_not_isinstance(self.aliases, str)
95
68
  for a in self.aliases or []:
96
69
  check_name(a)
97
70
 
98
- check.callable(self.fn)
99
- check.arg(all(isinstance(a, Arg) for a in self.args))
100
- check.isinstance(self.parent, (Command, None))
101
- check.isinstance(self.accepts_unknown, bool)
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 | None:
83
+ def __call__(self, *args, **kwargs) -> ta.Optional[int]:
111
84
  return self.fn(*args, **kwargs)
112
85
 
113
86
 
114
- def command(
115
- *args: Arg,
116
- name: str | None = None,
117
- aliases: ta.Iterable[str] | None = None,
118
- parent: Command | None = None,
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[[CommandFn], Command]: # FIXME
93
+ ) -> ta.Any: # ta.Callable[[ArgparseCommandFn], ArgparseCommand]: # FIXME
121
94
  for arg in args:
122
- check.isinstance(arg, Arg)
123
- check.isinstance(name, (str, None))
124
- check.isinstance(parent, (Command, None))
125
- check.not_isinstance(aliases, str)
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 Command(
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 get_arg_ann_kwargs(ann: ta.Any) -> ta.Mapping[str, ta.Any]:
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 _AnnotationBox:
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 _CliMeta(type):
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
- def __new__(mcls, name: str, bases: ta.Sequence[type], namespace: ta.Mapping[str, ta.Any]) -> type:
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
- bases = list(bases)
170
- namespace = dict(namespace)
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 = c3.merge([list(b.__mro__) for b in bases])
174
- for bns in [bcls.__dict__ for bcls in reversed(mro)] + [namespace]:
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, (Command, Arg)):
178
- check.not_in(v, bseen)
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(_AnnotationBox({
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
- **namespace.get('__annotations__', {}),
187
- }), globalns=namespace.get('__globals__', {}))
162
+ **ns.get('__annotations__', {}),
163
+ }), globalns=ns.get('__globals__', {}))
188
164
 
189
- if 'parser' in namespace:
190
- parser = check.isinstance(namespace.pop('parser'), ArgumentParser)
165
+ if '_parser' in ns:
166
+ parser = check_isinstance(ns['_parser'], argparse.ArgumentParser)
191
167
  else:
192
- parser = ArgumentParser()
193
- namespace['_parser'] = parser
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, Command):
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 := check.isinstance(arg.args[0], str)).startswith('-') and
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, Arg):
194
+ elif isinstance(obj, ArgparseArg):
219
195
  if att in anns:
220
- akwargs = get_arg_ann_kwargs(anns[att])
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
- return super().__new__(mcls, name, tuple(bases), namespace)
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: Command) -> int | None:
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 | None:
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
@@ -1,6 +1,8 @@
1
1
  """
2
2
  TODO:
3
3
  - def maybe(v: lang.Maybe[T])
4
+ - patch / override lite.check ?
5
+ - checker interface?
4
6
  """
5
7
  import collections
6
8
  import threading
omlish/io/__init__.py CHANGED
@@ -1,3 +0,0 @@
1
- from ..lite.io import ( # noqa
2
- DelimitingBuffer,
3
- )
@@ -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 check_non_empty
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
- check_non_empty(data)
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 = check_non_empty(self._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(check_non_empty(d))
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 ..check import check_isinstance
6
- from ..check import check_none
7
- from ..check import check_not_none
8
- from ..check import check_state
9
- from ..http.coroserver import CoroHttpServer
10
- from ..http.handlers import HttpHandler
11
- from ..io import IncrementalWriteBuffer
12
- from ..io import ReadableListBuffer
13
- from ..socket import SocketAddress
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
 
@@ -3,8 +3,8 @@ import abc
3
3
  import socket
4
4
  import typing as ta
5
5
 
6
- from ..check import check_not_none
7
- from ..socket import SocketAddress
6
+ from ...lite.check import check_not_none
7
+ from ...lite.socket import SocketAddress
8
8
 
9
9
 
10
10
  class FdioHandler(abc.ABC):
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
- class _cached_nullary: # noqa
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
- if self._value is self._missing:
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 _cached_nullary(fn)
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 check_non_empty(v: SizedT) -> SizedT:
101
+ def check_not_empty(v: SizedT) -> SizedT:
97
102
  if not len(v):
98
103
  raise ValueError(v)
99
104
  return v
@@ -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
@@ -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 _prepare_subprocess_invocation(
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
- def subprocess_check_call(*args: str, stdout=sys.stderr, **kwargs: ta.Any) -> None:
73
- args, kwargs = _prepare_subprocess_invocation(*args, stdout=stdout, **kwargs)
74
- return subprocess.check_call(args, **kwargs) # type: ignore
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(*args: str, **kwargs: ta.Any) -> bytes:
78
- args, kwargs = _prepare_subprocess_invocation(*args, **kwargs)
79
- return subprocess.check_output(args, **kwargs)
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 subprocess_try_call(
96
- *args: str,
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
- ) -> bool:
135
+ ) -> ta.Union[T, Exception]:
100
136
  try:
101
- subprocess_check_call(*args, **kwargs)
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
- try:
116
- return subprocess_check_output(*args, **kwargs)
117
- except try_exceptions as e: # noqa
118
- if log.isEnabledFor(logging.DEBUG):
119
- log.exception('command failed')
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
- raise Exception(f'{item.nodeid}: async def function and no async plugin specified')
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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omlish
3
- Version: 0.0.0.dev147
3
+ Version: 0.0.0.dev149
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -1,10 +1,9 @@
1
1
  omlish/.manifests.json,sha256=RX24SRc6DCEg77PUVnaXOKCWa5TF_c9RQJdGIf7gl9c,1135
2
- omlish/__about__.py,sha256=cjS87eUIaMvfyFbRNKoCGNxC70RIFRVnspSCKl5DPLA,3409
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=CBOCfl6ANZ7CKke2bGfQfUew9m22_ke0GvfEDO4Sjug,10595
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=aaIEsXTSfytW-oEkUWczdUJ_ifFY7ihIpyidIbfjkwY,56
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=rrc_JEv3sKJIEmCBB6g7DwPvkb1hNFmhg0mxkvuXDJw,848
315
- omlish/lite/check.py,sha256=pQC412ffe_Zh7eHa4C1UYn6fA71Ls1vpVM0ZIOroPAY,1765
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=1we1S-YQ9kbH36hPWLoh6zKZUARRnq2__ewtX_dVdWU,3633
343
+ omlish/lite/subprocesses.py,sha256=RTs8HJ1Lz8YOZTHw12Ja8KW7Eq4oyDFJZDiG0PSUBKY,4918
335
344
  omlish/lite/typing.py,sha256=U3-JaEnkDSYxK4tsu_MzUn3RP6qALBe5FXQXpD-licE,1090
336
- omlish/lite/fdio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
337
- omlish/lite/fdio/corohttp.py,sha256=FHdakDTGI2UYbCihahuwleyailclxQMUGhpkz3suww4,4080
338
- omlish/lite/fdio/handlers.py,sha256=Wr0O2cvIC8NuLs3yoDHj9ZG4n1g_oVEeT87B0WDsg0Y,1341
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=SV6oKCy50CGkzLGYX-CT4MfWNqsrH8ONEbIWC3tFcHA,5324
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.dev147.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
508
- omlish-0.0.0.dev147.dist-info/METADATA,sha256=nS6HmxhGs_7YPKXtA-OutWPomt4JOFvPmLAFwYYT7nk,4264
509
- omlish-0.0.0.dev147.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
510
- omlish-0.0.0.dev147.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
511
- omlish-0.0.0.dev147.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
512
- omlish-0.0.0.dev147.dist-info/RECORD,,
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