omlish 0.0.0.dev133__py3-none-any.whl → 0.0.0.dev177__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/.manifests.json +265 -7
- omlish/__about__.py +5 -3
- omlish/antlr/_runtime/__init__.py +0 -22
- omlish/antlr/_runtime/_all.py +24 -0
- omlish/antlr/_runtime/atn/ParserATNSimulator.py +1 -1
- omlish/antlr/_runtime/dfa/DFASerializer.py +1 -1
- omlish/antlr/_runtime/error/DiagnosticErrorListener.py +2 -1
- omlish/antlr/_runtime/xpath/XPath.py +7 -1
- omlish/antlr/_runtime/xpath/XPathLexer.py +1 -1
- omlish/antlr/delimit.py +106 -0
- omlish/antlr/dot.py +31 -0
- omlish/antlr/errors.py +11 -0
- omlish/antlr/input.py +96 -0
- omlish/antlr/parsing.py +19 -0
- omlish/antlr/runtime.py +102 -0
- omlish/antlr/utils.py +38 -0
- omlish/argparse/all.py +45 -0
- omlish/{argparse.py → argparse/cli.py} +112 -107
- omlish/asyncs/__init__.py +0 -35
- omlish/asyncs/all.py +35 -0
- omlish/asyncs/asyncio/all.py +7 -0
- omlish/asyncs/asyncio/channels.py +40 -0
- omlish/asyncs/asyncio/streams.py +45 -0
- omlish/asyncs/asyncio/subprocesses.py +238 -0
- omlish/asyncs/asyncio/timeouts.py +16 -0
- omlish/asyncs/bluelet/LICENSE +6 -0
- omlish/asyncs/bluelet/all.py +67 -0
- omlish/asyncs/bluelet/api.py +23 -0
- omlish/asyncs/bluelet/core.py +178 -0
- omlish/asyncs/bluelet/events.py +78 -0
- omlish/asyncs/bluelet/files.py +80 -0
- omlish/asyncs/bluelet/runner.py +416 -0
- omlish/asyncs/bluelet/sockets.py +214 -0
- omlish/bootstrap/sys.py +3 -3
- omlish/cached.py +2 -2
- omlish/check.py +49 -460
- omlish/codecs/__init__.py +72 -0
- omlish/codecs/base.py +106 -0
- omlish/codecs/bytes.py +119 -0
- omlish/codecs/chain.py +23 -0
- omlish/codecs/funcs.py +39 -0
- omlish/codecs/registry.py +139 -0
- omlish/codecs/standard.py +4 -0
- omlish/codecs/text.py +217 -0
- omlish/collections/cache/impl.py +50 -57
- omlish/collections/coerce.py +1 -0
- omlish/collections/mappings.py +1 -1
- omlish/configs/flattening.py +1 -1
- omlish/defs.py +1 -1
- omlish/diag/_pycharm/runhack.py +8 -2
- omlish/diag/procfs.py +8 -8
- omlish/docker/__init__.py +0 -36
- omlish/docker/all.py +31 -0
- omlish/docker/consts.py +4 -0
- omlish/{lite/docker.py → docker/detect.py} +18 -0
- omlish/docker/{helpers.py → timebomb.py} +0 -21
- omlish/formats/cbor.py +31 -0
- omlish/formats/cloudpickle.py +31 -0
- omlish/formats/codecs.py +93 -0
- omlish/formats/json/codecs.py +29 -0
- omlish/formats/json/delimted.py +4 -0
- omlish/formats/json/stream/errors.py +2 -0
- omlish/formats/json/stream/lex.py +12 -6
- omlish/formats/json/stream/parse.py +38 -22
- omlish/formats/json5.py +31 -0
- omlish/formats/pickle.py +31 -0
- omlish/formats/repr.py +25 -0
- omlish/formats/toml.py +17 -0
- omlish/formats/yaml.py +25 -0
- omlish/funcs/__init__.py +0 -0
- omlish/{genmachine.py → funcs/genmachine.py} +5 -4
- omlish/{matchfns.py → funcs/match.py} +1 -1
- omlish/funcs/pairs.py +215 -0
- omlish/http/__init__.py +0 -48
- omlish/http/all.py +48 -0
- omlish/http/coro/__init__.py +0 -0
- omlish/{lite/fdio/corohttp.py → http/coro/fdio.py} +21 -19
- omlish/{lite/http/coroserver.py → http/coro/server.py} +20 -21
- omlish/{lite/http → http}/handlers.py +3 -2
- omlish/{lite/http → http}/parsing.py +1 -0
- omlish/http/sessions.py +1 -1
- omlish/{lite/http → http}/versions.py +1 -0
- omlish/inject/managed.py +2 -2
- omlish/io/__init__.py +0 -3
- omlish/{lite/io.py → io/buffers.py} +8 -9
- omlish/io/compress/__init__.py +9 -0
- omlish/io/compress/abc.py +104 -0
- omlish/io/compress/adapters.py +148 -0
- omlish/io/compress/base.py +24 -0
- omlish/io/compress/brotli.py +47 -0
- omlish/io/compress/bz2.py +61 -0
- omlish/io/compress/codecs.py +78 -0
- omlish/io/compress/gzip.py +350 -0
- omlish/io/compress/lz4.py +91 -0
- omlish/io/compress/lzma.py +81 -0
- omlish/io/compress/snappy.py +34 -0
- omlish/io/compress/zlib.py +74 -0
- omlish/io/compress/zstd.py +44 -0
- omlish/io/fdio/__init__.py +1 -0
- omlish/{lite → io}/fdio/handlers.py +5 -5
- omlish/{lite → io}/fdio/kqueue.py +8 -8
- omlish/{lite → io}/fdio/manager.py +7 -7
- omlish/{lite → io}/fdio/pollers.py +13 -13
- omlish/io/generators/__init__.py +56 -0
- omlish/io/generators/consts.py +1 -0
- omlish/io/generators/direct.py +13 -0
- omlish/io/generators/readers.py +189 -0
- omlish/io/generators/stepped.py +191 -0
- omlish/io/pyio.py +5 -2
- omlish/iterators/__init__.py +24 -0
- omlish/iterators/iterators.py +132 -0
- omlish/iterators/recipes.py +18 -0
- omlish/iterators/tools.py +96 -0
- omlish/iterators/unique.py +67 -0
- omlish/lang/__init__.py +13 -1
- omlish/lang/functions.py +11 -2
- omlish/lang/generators.py +243 -0
- omlish/lang/iterables.py +46 -49
- omlish/lang/maybes.py +4 -4
- omlish/lite/cached.py +39 -6
- omlish/lite/check.py +438 -75
- omlish/lite/contextmanagers.py +17 -4
- omlish/lite/dataclasses.py +42 -0
- omlish/lite/inject.py +28 -45
- omlish/lite/logs.py +0 -270
- omlish/lite/marshal.py +309 -144
- omlish/lite/pycharm.py +47 -0
- omlish/lite/reflect.py +33 -0
- omlish/lite/resources.py +8 -0
- omlish/lite/runtime.py +4 -4
- omlish/lite/shlex.py +12 -0
- omlish/lite/socketserver.py +2 -2
- omlish/lite/strings.py +31 -0
- omlish/logs/__init__.py +0 -32
- omlish/logs/{_abc.py → abc.py} +0 -1
- omlish/logs/all.py +37 -0
- omlish/logs/{formatters.py → color.py} +1 -2
- omlish/logs/configs.py +7 -38
- omlish/logs/filters.py +10 -0
- omlish/logs/handlers.py +4 -1
- omlish/logs/json.py +56 -0
- omlish/logs/proxy.py +99 -0
- omlish/logs/standard.py +128 -0
- omlish/logs/utils.py +2 -2
- omlish/manifests/__init__.py +2 -0
- omlish/manifests/load.py +209 -0
- omlish/manifests/types.py +17 -0
- omlish/marshal/base.py +1 -1
- omlish/marshal/factories.py +1 -1
- omlish/marshal/forbidden.py +1 -1
- omlish/marshal/iterables.py +1 -1
- omlish/marshal/literals.py +50 -0
- omlish/marshal/mappings.py +1 -1
- omlish/marshal/maybes.py +1 -1
- omlish/marshal/standard.py +5 -1
- omlish/marshal/unions.py +1 -1
- omlish/os/__init__.py +0 -0
- omlish/os/atomics.py +205 -0
- omlish/os/deathsig.py +23 -0
- omlish/{os.py → os/files.py} +0 -9
- omlish/{lite → os}/journald.py +2 -1
- omlish/os/linux.py +484 -0
- omlish/os/paths.py +36 -0
- omlish/{lite → os}/pidfile.py +1 -0
- omlish/os/sizes.py +9 -0
- omlish/reflect/__init__.py +3 -0
- omlish/reflect/subst.py +2 -1
- omlish/reflect/types.py +126 -44
- omlish/secrets/pwhash.py +1 -1
- omlish/secrets/subprocesses.py +3 -1
- omlish/specs/jsonrpc/marshal.py +1 -1
- omlish/specs/openapi/marshal.py +1 -1
- omlish/sql/alchemy/asyncs.py +1 -1
- omlish/sql/queries/__init__.py +9 -1
- omlish/sql/queries/building.py +3 -0
- omlish/sql/queries/exprs.py +10 -27
- omlish/sql/queries/idents.py +48 -10
- omlish/sql/queries/names.py +80 -13
- omlish/sql/queries/params.py +64 -0
- omlish/sql/queries/rendering.py +1 -1
- omlish/subprocesses.py +340 -0
- omlish/term.py +29 -14
- omlish/testing/pytest/marks.py +2 -2
- omlish/testing/pytest/plugins/asyncs.py +6 -1
- omlish/testing/pytest/plugins/logging.py +1 -1
- omlish/testing/pytest/plugins/switches.py +1 -1
- {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/METADATA +7 -5
- {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/RECORD +200 -117
- omlish/fnpairs.py +0 -496
- omlish/formats/json/cli/__main__.py +0 -11
- omlish/formats/json/cli/cli.py +0 -298
- omlish/formats/json/cli/formats.py +0 -71
- omlish/formats/json/cli/io.py +0 -74
- omlish/formats/json/cli/parsing.py +0 -82
- omlish/formats/json/cli/processing.py +0 -48
- omlish/formats/json/cli/rendering.py +0 -92
- omlish/iterators.py +0 -300
- omlish/lite/subprocesses.py +0 -130
- /omlish/{formats/json/cli → argparse}/__init__.py +0 -0
- /omlish/{lite/fdio → asyncs/asyncio}/__init__.py +0 -0
- /omlish/asyncs/{asyncio.py → asyncio/asyncio.py} +0 -0
- /omlish/{lite/http → asyncs/bluelet}/__init__.py +0 -0
- /omlish/collections/{_abc.py → abc.py} +0 -0
- /omlish/{fnpipes.py → funcs/pipes.py} +0 -0
- /omlish/io/{_abc.py → abc.py} +0 -0
- /omlish/sql/{_abc.py → abc.py} +0 -0
- {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import asyncio
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
|
7
|
+
ASYNCIO_DEFAULT_BUFFER_LIMIT = 2 ** 16
|
8
|
+
|
9
|
+
|
10
|
+
async def asyncio_open_stream_reader(
|
11
|
+
f: ta.IO,
|
12
|
+
loop: ta.Any = None,
|
13
|
+
*,
|
14
|
+
limit: int = ASYNCIO_DEFAULT_BUFFER_LIMIT,
|
15
|
+
) -> asyncio.StreamReader:
|
16
|
+
if loop is None:
|
17
|
+
loop = asyncio.get_running_loop()
|
18
|
+
|
19
|
+
reader = asyncio.StreamReader(limit=limit, loop=loop)
|
20
|
+
await loop.connect_read_pipe(
|
21
|
+
lambda: asyncio.StreamReaderProtocol(reader, loop=loop),
|
22
|
+
f,
|
23
|
+
)
|
24
|
+
|
25
|
+
return reader
|
26
|
+
|
27
|
+
|
28
|
+
async def asyncio_open_stream_writer(
|
29
|
+
f: ta.IO,
|
30
|
+
loop: ta.Any = None,
|
31
|
+
) -> asyncio.StreamWriter:
|
32
|
+
if loop is None:
|
33
|
+
loop = asyncio.get_running_loop()
|
34
|
+
|
35
|
+
writer_transport, writer_protocol = await loop.connect_write_pipe(
|
36
|
+
lambda: asyncio.streams.FlowControlMixin(loop=loop),
|
37
|
+
f,
|
38
|
+
)
|
39
|
+
|
40
|
+
return asyncio.streams.StreamWriter(
|
41
|
+
writer_transport,
|
42
|
+
writer_protocol,
|
43
|
+
None,
|
44
|
+
loop,
|
45
|
+
)
|
@@ -0,0 +1,238 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import asyncio.base_subprocess
|
4
|
+
import asyncio.subprocess
|
5
|
+
import contextlib
|
6
|
+
import dataclasses as dc
|
7
|
+
import functools
|
8
|
+
import logging
|
9
|
+
import subprocess
|
10
|
+
import sys
|
11
|
+
import typing as ta
|
12
|
+
|
13
|
+
from ...lite.check import check
|
14
|
+
from ...subprocesses import AbstractAsyncSubprocesses
|
15
|
+
from .timeouts import asyncio_maybe_timeout
|
16
|
+
|
17
|
+
|
18
|
+
T = ta.TypeVar('T')
|
19
|
+
|
20
|
+
|
21
|
+
##
|
22
|
+
|
23
|
+
|
24
|
+
class AsyncioProcessCommunicator:
|
25
|
+
def __init__(
|
26
|
+
self,
|
27
|
+
proc: asyncio.subprocess.Process,
|
28
|
+
loop: ta.Optional[ta.Any] = None,
|
29
|
+
*,
|
30
|
+
log: ta.Optional[logging.Logger] = None,
|
31
|
+
) -> None:
|
32
|
+
super().__init__()
|
33
|
+
|
34
|
+
if loop is None:
|
35
|
+
loop = asyncio.get_running_loop()
|
36
|
+
|
37
|
+
self._proc = proc
|
38
|
+
self._loop = loop
|
39
|
+
self._log = log
|
40
|
+
|
41
|
+
self._transport: asyncio.base_subprocess.BaseSubprocessTransport = check.isinstance(
|
42
|
+
proc._transport, # type: ignore # noqa
|
43
|
+
asyncio.base_subprocess.BaseSubprocessTransport,
|
44
|
+
)
|
45
|
+
|
46
|
+
@property
|
47
|
+
def _debug(self) -> bool:
|
48
|
+
return self._loop.get_debug()
|
49
|
+
|
50
|
+
async def _feed_stdin(self, input: bytes) -> None: # noqa
|
51
|
+
stdin = check.not_none(self._proc.stdin)
|
52
|
+
try:
|
53
|
+
if input is not None:
|
54
|
+
stdin.write(input)
|
55
|
+
if self._debug and self._log is not None:
|
56
|
+
self._log.debug('%r communicate: feed stdin (%s bytes)', self, len(input))
|
57
|
+
|
58
|
+
await stdin.drain()
|
59
|
+
|
60
|
+
except (BrokenPipeError, ConnectionResetError) as exc:
|
61
|
+
# communicate() ignores BrokenPipeError and ConnectionResetError. write() and drain() can raise these
|
62
|
+
# exceptions.
|
63
|
+
if self._debug and self._log is not None:
|
64
|
+
self._log.debug('%r communicate: stdin got %r', self, exc)
|
65
|
+
|
66
|
+
if self._debug and self._log is not None:
|
67
|
+
self._log.debug('%r communicate: close stdin', self)
|
68
|
+
|
69
|
+
stdin.close()
|
70
|
+
|
71
|
+
async def _noop(self) -> None:
|
72
|
+
return None
|
73
|
+
|
74
|
+
async def _read_stream(self, fd: int) -> bytes:
|
75
|
+
transport: ta.Any = check.not_none(self._transport.get_pipe_transport(fd))
|
76
|
+
|
77
|
+
if fd == 2:
|
78
|
+
stream = check.not_none(self._proc.stderr)
|
79
|
+
else:
|
80
|
+
check.equal(fd, 1)
|
81
|
+
stream = check.not_none(self._proc.stdout)
|
82
|
+
|
83
|
+
if self._debug and self._log is not None:
|
84
|
+
name = 'stdout' if fd == 1 else 'stderr'
|
85
|
+
self._log.debug('%r communicate: read %s', self, name)
|
86
|
+
|
87
|
+
output = await stream.read()
|
88
|
+
|
89
|
+
if self._debug and self._log is not None:
|
90
|
+
name = 'stdout' if fd == 1 else 'stderr'
|
91
|
+
self._log.debug('%r communicate: close %s', self, name)
|
92
|
+
|
93
|
+
transport.close()
|
94
|
+
|
95
|
+
return output
|
96
|
+
|
97
|
+
class Communication(ta.NamedTuple):
|
98
|
+
stdout: ta.Optional[bytes]
|
99
|
+
stderr: ta.Optional[bytes]
|
100
|
+
|
101
|
+
async def _communicate(
|
102
|
+
self,
|
103
|
+
input: ta.Any = None, # noqa
|
104
|
+
) -> Communication:
|
105
|
+
stdin_fut: ta.Any
|
106
|
+
if self._proc.stdin is not None:
|
107
|
+
stdin_fut = self._feed_stdin(input)
|
108
|
+
else:
|
109
|
+
stdin_fut = self._noop()
|
110
|
+
|
111
|
+
stdout_fut: ta.Any
|
112
|
+
if self._proc.stdout is not None:
|
113
|
+
stdout_fut = self._read_stream(1)
|
114
|
+
else:
|
115
|
+
stdout_fut = self._noop()
|
116
|
+
|
117
|
+
stderr_fut: ta.Any
|
118
|
+
if self._proc.stderr is not None:
|
119
|
+
stderr_fut = self._read_stream(2)
|
120
|
+
else:
|
121
|
+
stderr_fut = self._noop()
|
122
|
+
|
123
|
+
stdin_res, stdout_res, stderr_res = await asyncio.gather(stdin_fut, stdout_fut, stderr_fut)
|
124
|
+
|
125
|
+
await self._proc.wait()
|
126
|
+
|
127
|
+
return AsyncioProcessCommunicator.Communication(stdout_res, stderr_res)
|
128
|
+
|
129
|
+
async def communicate(
|
130
|
+
self,
|
131
|
+
input: ta.Any = None, # noqa
|
132
|
+
timeout: ta.Optional[float] = None,
|
133
|
+
) -> Communication:
|
134
|
+
return await asyncio_maybe_timeout(self._communicate(input), timeout)
|
135
|
+
|
136
|
+
|
137
|
+
##
|
138
|
+
|
139
|
+
|
140
|
+
class AsyncioSubprocesses(AbstractAsyncSubprocesses):
|
141
|
+
async def communicate(
|
142
|
+
self,
|
143
|
+
proc: asyncio.subprocess.Process,
|
144
|
+
input: ta.Any = None, # noqa
|
145
|
+
timeout: ta.Optional[float] = None,
|
146
|
+
) -> ta.Tuple[ta.Optional[bytes], ta.Optional[bytes]]:
|
147
|
+
return await AsyncioProcessCommunicator(proc).communicate(input, timeout) # noqa
|
148
|
+
|
149
|
+
#
|
150
|
+
|
151
|
+
@contextlib.asynccontextmanager
|
152
|
+
async def popen(
|
153
|
+
self,
|
154
|
+
*cmd: str,
|
155
|
+
shell: bool = False,
|
156
|
+
timeout: ta.Optional[float] = None,
|
157
|
+
**kwargs: ta.Any,
|
158
|
+
) -> ta.AsyncGenerator[asyncio.subprocess.Process, None]:
|
159
|
+
fac: ta.Any
|
160
|
+
if shell:
|
161
|
+
fac = functools.partial(
|
162
|
+
asyncio.create_subprocess_shell,
|
163
|
+
check.single(cmd),
|
164
|
+
)
|
165
|
+
else:
|
166
|
+
fac = functools.partial(
|
167
|
+
asyncio.create_subprocess_exec,
|
168
|
+
*cmd,
|
169
|
+
)
|
170
|
+
|
171
|
+
with self.prepare_and_wrap( *cmd, shell=shell, **kwargs) as (cmd, kwargs): # noqa
|
172
|
+
proc: asyncio.subprocess.Process = await fac(**kwargs)
|
173
|
+
try:
|
174
|
+
yield proc
|
175
|
+
|
176
|
+
finally:
|
177
|
+
await asyncio_maybe_timeout(proc.wait(), timeout)
|
178
|
+
|
179
|
+
#
|
180
|
+
|
181
|
+
@dc.dataclass(frozen=True)
|
182
|
+
class RunOutput:
|
183
|
+
proc: asyncio.subprocess.Process
|
184
|
+
stdout: ta.Optional[bytes]
|
185
|
+
stderr: ta.Optional[bytes]
|
186
|
+
|
187
|
+
async def run(
|
188
|
+
self,
|
189
|
+
*cmd: str,
|
190
|
+
input: ta.Any = None, # noqa
|
191
|
+
timeout: ta.Optional[float] = None,
|
192
|
+
check: bool = False, # noqa
|
193
|
+
capture_output: ta.Optional[bool] = None,
|
194
|
+
**kwargs: ta.Any,
|
195
|
+
) -> RunOutput:
|
196
|
+
if capture_output:
|
197
|
+
kwargs.setdefault('stdout', subprocess.PIPE)
|
198
|
+
kwargs.setdefault('stderr', subprocess.PIPE)
|
199
|
+
|
200
|
+
proc: asyncio.subprocess.Process
|
201
|
+
async with self.popen(*cmd, **kwargs) as proc:
|
202
|
+
stdout, stderr = await self.communicate(proc, input, timeout)
|
203
|
+
|
204
|
+
if check and proc.returncode:
|
205
|
+
raise subprocess.CalledProcessError(
|
206
|
+
proc.returncode,
|
207
|
+
cmd,
|
208
|
+
output=stdout,
|
209
|
+
stderr=stderr,
|
210
|
+
)
|
211
|
+
|
212
|
+
return self.RunOutput(
|
213
|
+
proc,
|
214
|
+
stdout,
|
215
|
+
stderr,
|
216
|
+
)
|
217
|
+
|
218
|
+
#
|
219
|
+
|
220
|
+
async def check_call(
|
221
|
+
self,
|
222
|
+
*cmd: str,
|
223
|
+
stdout: ta.Any = sys.stderr,
|
224
|
+
**kwargs: ta.Any,
|
225
|
+
) -> None:
|
226
|
+
with self.prepare_and_wrap(*cmd, stdout=stdout, check=True, **kwargs) as (cmd, kwargs): # noqa
|
227
|
+
await self.run(*cmd, **kwargs)
|
228
|
+
|
229
|
+
async def check_output(
|
230
|
+
self,
|
231
|
+
*cmd: str,
|
232
|
+
**kwargs: ta.Any,
|
233
|
+
) -> bytes:
|
234
|
+
with self.prepare_and_wrap(*cmd, stdout=subprocess.PIPE, check=True, **kwargs) as (cmd, kwargs): # noqa
|
235
|
+
return check.not_none((await self.run(*cmd, **kwargs)).stdout)
|
236
|
+
|
237
|
+
|
238
|
+
asyncio_subprocesses = AsyncioSubprocesses()
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import asyncio
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
|
7
|
+
AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
|
8
|
+
|
9
|
+
|
10
|
+
def asyncio_maybe_timeout(
|
11
|
+
fut: AwaitableT,
|
12
|
+
timeout: ta.Optional[float] = None,
|
13
|
+
) -> AwaitableT:
|
14
|
+
if timeout is not None:
|
15
|
+
fut = asyncio.wait_for(fut, timeout) # type: ignore
|
16
|
+
return fut
|
@@ -0,0 +1,6 @@
|
|
1
|
+
Based on bluelet ( https://github.com/sampsyo/bluelet ) by Adrian Sampson, original license:
|
2
|
+
|
3
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
4
|
+
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
5
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
6
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# ruff: noqa: I001
|
2
|
+
from .api import ( # noqa
|
3
|
+
bluelet,
|
4
|
+
)
|
5
|
+
|
6
|
+
from .core import ( # noqa
|
7
|
+
BlueletCoro as Coro,
|
8
|
+
BlueletExcInfo as ExcInfo,
|
9
|
+
CoreBlueletEvent as CoreEvent,
|
10
|
+
DelegationBlueletEvent as DelegationEvent,
|
11
|
+
ExceptionBlueletEvent as ExceptionEvent,
|
12
|
+
JoinBlueletEvent as JoinEvent,
|
13
|
+
KillBlueletEvent as KillEvent,
|
14
|
+
ReturnBlueletEvent as ReturnEvent,
|
15
|
+
SleepBlueletEvent as SleepEvent,
|
16
|
+
SpawnBlueletEvent as SpawnEvent,
|
17
|
+
ValueBlueletEvent as ValueEvent,
|
18
|
+
)
|
19
|
+
|
20
|
+
from .events import ( # noqa
|
21
|
+
BlueletEvent as Event,
|
22
|
+
BlueletFuture as Future,
|
23
|
+
BlueletHasFileno as HasFileno,
|
24
|
+
BlueletWaitable as Waitable,
|
25
|
+
BlueletWaitables as Waitables,
|
26
|
+
WaitableBlueletEvent as WaitableEvent,
|
27
|
+
)
|
28
|
+
|
29
|
+
from .files import ( # noqa
|
30
|
+
FileBlueletEvent as FileEvent,
|
31
|
+
ReadBlueletEvent as ReadEvent,
|
32
|
+
WriteBlueletEvent as WriteEvent,
|
33
|
+
)
|
34
|
+
|
35
|
+
from .runner import ( # noqa
|
36
|
+
BlueletCoroException as CoroException,
|
37
|
+
)
|
38
|
+
|
39
|
+
from .sockets import ( # noqa
|
40
|
+
AcceptBlueletEvent as AcceptEvent,
|
41
|
+
BlueletConnection as Connection,
|
42
|
+
BlueletListener as Listener,
|
43
|
+
ReceiveBlueletEvent as ReceiveEvent,
|
44
|
+
SendBlueletEvent as SendEvent,
|
45
|
+
SocketBlueletEvent as SocketEvent,
|
46
|
+
SocketClosedBlueletError as SocketClosedError,
|
47
|
+
)
|
48
|
+
|
49
|
+
|
50
|
+
##
|
51
|
+
|
52
|
+
|
53
|
+
call = bluelet.call
|
54
|
+
end = bluelet.end
|
55
|
+
join = bluelet.join
|
56
|
+
kill = bluelet.kill
|
57
|
+
null = bluelet.null
|
58
|
+
sleep = bluelet.sleep
|
59
|
+
spawn = bluelet.spawn
|
60
|
+
|
61
|
+
read = bluelet.read
|
62
|
+
write = bluelet.write
|
63
|
+
|
64
|
+
run = bluelet.run
|
65
|
+
|
66
|
+
connect = bluelet.connect
|
67
|
+
server = bluelet.server
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
# Based on bluelet ( https://github.com/sampsyo/bluelet ) by Adrian Sampson, original license:
|
4
|
+
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
5
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
6
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
7
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
8
|
+
from .core import _CoreBlueletApi
|
9
|
+
from .files import _FilesBlueletApi
|
10
|
+
from .runner import _RunnerBlueletApi
|
11
|
+
from .sockets import _SocketsBlueletApi
|
12
|
+
|
13
|
+
|
14
|
+
class BlueletApi(
|
15
|
+
_RunnerBlueletApi,
|
16
|
+
_SocketsBlueletApi,
|
17
|
+
_FilesBlueletApi,
|
18
|
+
_CoreBlueletApi,
|
19
|
+
):
|
20
|
+
pass
|
21
|
+
|
22
|
+
|
23
|
+
bluelet = BlueletApi()
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
# Based on bluelet ( https://github.com/sampsyo/bluelet ) by Adrian Sampson, original license:
|
4
|
+
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
5
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
6
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
7
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
8
|
+
import abc
|
9
|
+
import dataclasses as dc
|
10
|
+
import time
|
11
|
+
import types
|
12
|
+
import typing as ta
|
13
|
+
|
14
|
+
from .events import BlueletEvent
|
15
|
+
from .events import BlueletFuture
|
16
|
+
from .events import WaitableBlueletEvent
|
17
|
+
|
18
|
+
|
19
|
+
T = ta.TypeVar('T')
|
20
|
+
|
21
|
+
BlueletExcInfo = ta.Tuple[ta.Type[BaseException], BaseException, types.TracebackType] # ta.TypeAlias
|
22
|
+
|
23
|
+
BlueletCoro = ta.Generator[ta.Union['BlueletEvent', 'BlueletCoro'], ta.Any, None] # ta.TypeAlias
|
24
|
+
|
25
|
+
BlueletSpawnable = ta.Union[BlueletCoro, ta.Awaitable] # ta.TypeAlias
|
26
|
+
|
27
|
+
|
28
|
+
##
|
29
|
+
|
30
|
+
|
31
|
+
@dc.dataclass(frozen=True, eq=False)
|
32
|
+
class _BlueletAwaitableDriver:
|
33
|
+
a: ta.Awaitable
|
34
|
+
|
35
|
+
def __call__(self) -> BlueletCoro:
|
36
|
+
g = self.a.__await__()
|
37
|
+
gi = iter(g)
|
38
|
+
while True:
|
39
|
+
try:
|
40
|
+
f = gi.send(None)
|
41
|
+
except StopIteration as e:
|
42
|
+
yield ReturnBlueletEvent(e.value)
|
43
|
+
break
|
44
|
+
else:
|
45
|
+
if not isinstance(f, BlueletFuture):
|
46
|
+
raise TypeError(f)
|
47
|
+
res = yield f.event
|
48
|
+
f.done = True
|
49
|
+
f.result = res
|
50
|
+
|
51
|
+
|
52
|
+
##
|
53
|
+
|
54
|
+
|
55
|
+
class CoreBlueletEvent(BlueletEvent, abc.ABC): # noqa
|
56
|
+
pass
|
57
|
+
|
58
|
+
|
59
|
+
@dc.dataclass(frozen=True, eq=False)
|
60
|
+
class ValueBlueletEvent(CoreBlueletEvent, ta.Generic[T]):
|
61
|
+
"""An event that does nothing but return a fixed value."""
|
62
|
+
|
63
|
+
value: T
|
64
|
+
|
65
|
+
|
66
|
+
@dc.dataclass(frozen=True, eq=False)
|
67
|
+
class ExceptionBlueletEvent(CoreBlueletEvent):
|
68
|
+
"""Raise an exception at the yield point. Used internally."""
|
69
|
+
|
70
|
+
exc_info: BlueletExcInfo
|
71
|
+
|
72
|
+
|
73
|
+
@dc.dataclass(frozen=True, eq=False)
|
74
|
+
class SpawnBlueletEvent(CoreBlueletEvent):
|
75
|
+
"""Add a new coroutine coro to the scheduler."""
|
76
|
+
|
77
|
+
spawned: BlueletSpawnable
|
78
|
+
|
79
|
+
|
80
|
+
@dc.dataclass(frozen=True, eq=False)
|
81
|
+
class JoinBlueletEvent(CoreBlueletEvent):
|
82
|
+
"""Suspend the coro until the specified child coro has completed."""
|
83
|
+
|
84
|
+
child: BlueletCoro
|
85
|
+
|
86
|
+
|
87
|
+
@dc.dataclass(frozen=True, eq=False)
|
88
|
+
class KillBlueletEvent(CoreBlueletEvent):
|
89
|
+
"""Unschedule a child coro."""
|
90
|
+
|
91
|
+
child: BlueletCoro
|
92
|
+
|
93
|
+
|
94
|
+
@dc.dataclass(frozen=True, eq=False)
|
95
|
+
class DelegationBlueletEvent(CoreBlueletEvent):
|
96
|
+
"""
|
97
|
+
Suspend execution of the current coro, start a new coro and, once the child coro finished, return control to
|
98
|
+
the parent coro.
|
99
|
+
"""
|
100
|
+
|
101
|
+
spawned: BlueletCoro
|
102
|
+
|
103
|
+
|
104
|
+
@dc.dataclass(frozen=True, eq=False)
|
105
|
+
class ReturnBlueletEvent(CoreBlueletEvent, ta.Generic[T]):
|
106
|
+
"""Return a value the current coro's delegator at the point of delegation. Ends the current (delegate) coro."""
|
107
|
+
|
108
|
+
value: ta.Optional[T]
|
109
|
+
|
110
|
+
|
111
|
+
@dc.dataclass(frozen=True, eq=False)
|
112
|
+
class SleepBlueletEvent(WaitableBlueletEvent, CoreBlueletEvent):
|
113
|
+
"""Suspend the coro for a given duration."""
|
114
|
+
|
115
|
+
wakeup_time: float
|
116
|
+
|
117
|
+
def time_left(self) -> float:
|
118
|
+
return max(self.wakeup_time - time.time(), 0.)
|
119
|
+
|
120
|
+
|
121
|
+
##
|
122
|
+
|
123
|
+
|
124
|
+
class _CoreBlueletApi:
|
125
|
+
def value(self, v: T) -> ValueBlueletEvent[T]:
|
126
|
+
"""Event: yield a value."""
|
127
|
+
|
128
|
+
return ValueBlueletEvent(v)
|
129
|
+
|
130
|
+
def null(self) -> ValueBlueletEvent[None]:
|
131
|
+
"""Event: yield to the scheduler without doing anything special."""
|
132
|
+
|
133
|
+
return ValueBlueletEvent(None)
|
134
|
+
|
135
|
+
def spawn(self, spawned: BlueletSpawnable) -> SpawnBlueletEvent:
|
136
|
+
"""Event: add another coroutine to the scheduler. Both the parent and child coroutines run concurrently."""
|
137
|
+
|
138
|
+
if isinstance(spawned, types.CoroutineType):
|
139
|
+
spawned = _BlueletAwaitableDriver(spawned)()
|
140
|
+
|
141
|
+
if not isinstance(spawned, types.GeneratorType):
|
142
|
+
raise TypeError(f'{spawned} is not spawnable')
|
143
|
+
|
144
|
+
return SpawnBlueletEvent(spawned)
|
145
|
+
|
146
|
+
def join(self, coro: BlueletCoro) -> JoinBlueletEvent:
|
147
|
+
"""Suspend the coro until another, previously `spawn`ed coro completes."""
|
148
|
+
|
149
|
+
return JoinBlueletEvent(coro)
|
150
|
+
|
151
|
+
def kill(self, coro: BlueletCoro) -> KillBlueletEvent:
|
152
|
+
"""Halt the execution of a different `spawn`ed coro."""
|
153
|
+
|
154
|
+
return KillBlueletEvent(coro)
|
155
|
+
|
156
|
+
def call(self, spawned: BlueletSpawnable) -> DelegationBlueletEvent:
|
157
|
+
"""
|
158
|
+
Event: delegate to another coroutine. The current coroutine is resumed once the sub-coroutine finishes. If the
|
159
|
+
sub-coroutine returns a value using end(), then this event returns that value.
|
160
|
+
"""
|
161
|
+
|
162
|
+
if isinstance(spawned, types.CoroutineType):
|
163
|
+
spawned = _BlueletAwaitableDriver(spawned)()
|
164
|
+
|
165
|
+
if not isinstance(spawned, types.GeneratorType):
|
166
|
+
raise TypeError(f'{spawned} is not spawnable')
|
167
|
+
|
168
|
+
return DelegationBlueletEvent(spawned)
|
169
|
+
|
170
|
+
def end(self, value: ta.Optional[T] = None) -> ReturnBlueletEvent[T]:
|
171
|
+
"""Event: ends the coroutine and returns a value to its delegator."""
|
172
|
+
|
173
|
+
return ReturnBlueletEvent(value)
|
174
|
+
|
175
|
+
def sleep(self, duration: float) -> SleepBlueletEvent:
|
176
|
+
"""Event: suspend the coro for ``duration`` seconds."""
|
177
|
+
|
178
|
+
return SleepBlueletEvent(time.time() + duration)
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# ruff: noqa: UP007
|
2
|
+
# @omlish-lite
|
3
|
+
# Based on bluelet ( https://github.com/sampsyo/bluelet ) by Adrian Sampson, original license:
|
4
|
+
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
5
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
6
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
7
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
8
|
+
import abc
|
9
|
+
import dataclasses as dc
|
10
|
+
import typing as ta
|
11
|
+
|
12
|
+
|
13
|
+
R = ta.TypeVar('R')
|
14
|
+
|
15
|
+
BlueletEventT = ta.TypeVar('BlueletEventT', bound='BlueletEvent') # ta.TypeAlias
|
16
|
+
|
17
|
+
BlueletWaitable = ta.Union[int, 'BlueletHasFileno'] # ta.TypeAlias
|
18
|
+
|
19
|
+
|
20
|
+
##
|
21
|
+
|
22
|
+
|
23
|
+
class BlueletEvent(abc.ABC): # noqa
|
24
|
+
"""
|
25
|
+
Just a base class identifying Bluelet events. An event is an object yielded from a Bluelet coro coroutine to
|
26
|
+
suspend operation and communicate with the scheduler.
|
27
|
+
"""
|
28
|
+
|
29
|
+
def __await__(self):
|
30
|
+
return BlueletFuture(self).__await__()
|
31
|
+
|
32
|
+
|
33
|
+
##
|
34
|
+
|
35
|
+
|
36
|
+
class BlueletHasFileno(ta.Protocol):
|
37
|
+
def fileno(self) -> int: ...
|
38
|
+
|
39
|
+
|
40
|
+
@dc.dataclass(frozen=True)
|
41
|
+
class BlueletWaitables:
|
42
|
+
r: ta.Sequence[BlueletWaitable] = ()
|
43
|
+
w: ta.Sequence[BlueletWaitable] = ()
|
44
|
+
x: ta.Sequence[BlueletWaitable] = ()
|
45
|
+
|
46
|
+
|
47
|
+
class WaitableBlueletEvent(BlueletEvent, abc.ABC): # noqa
|
48
|
+
"""
|
49
|
+
A waitable event is one encapsulating an action that can be waited for using a select() call. That is, it's an event
|
50
|
+
with an associated file descriptor.
|
51
|
+
"""
|
52
|
+
|
53
|
+
def waitables(self) -> BlueletWaitables:
|
54
|
+
"""
|
55
|
+
Return "waitable" objects to pass to select(). Should return three iterables for input readiness, output
|
56
|
+
readiness, and exceptional conditions (i.e., the three lists passed to select()).
|
57
|
+
"""
|
58
|
+
return BlueletWaitables()
|
59
|
+
|
60
|
+
def fire(self) -> ta.Any:
|
61
|
+
"""Called when an associated file descriptor becomes ready (i.e., is returned from a select() call)."""
|
62
|
+
|
63
|
+
|
64
|
+
##
|
65
|
+
|
66
|
+
|
67
|
+
@dc.dataclass(eq=False)
|
68
|
+
class BlueletFuture(ta.Generic[BlueletEventT, R]):
|
69
|
+
event: BlueletEventT
|
70
|
+
done: bool = False
|
71
|
+
result: ta.Optional[R] = None
|
72
|
+
|
73
|
+
def __await__(self):
|
74
|
+
if not self.done:
|
75
|
+
yield self
|
76
|
+
if not self.done:
|
77
|
+
raise RuntimeError("await wasn't used with event future")
|
78
|
+
return self.result
|