omlish 0.0.0.dev133__py3-none-any.whl → 0.0.0.dev177__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (210) hide show
  1. omlish/.manifests.json +265 -7
  2. omlish/__about__.py +5 -3
  3. omlish/antlr/_runtime/__init__.py +0 -22
  4. omlish/antlr/_runtime/_all.py +24 -0
  5. omlish/antlr/_runtime/atn/ParserATNSimulator.py +1 -1
  6. omlish/antlr/_runtime/dfa/DFASerializer.py +1 -1
  7. omlish/antlr/_runtime/error/DiagnosticErrorListener.py +2 -1
  8. omlish/antlr/_runtime/xpath/XPath.py +7 -1
  9. omlish/antlr/_runtime/xpath/XPathLexer.py +1 -1
  10. omlish/antlr/delimit.py +106 -0
  11. omlish/antlr/dot.py +31 -0
  12. omlish/antlr/errors.py +11 -0
  13. omlish/antlr/input.py +96 -0
  14. omlish/antlr/parsing.py +19 -0
  15. omlish/antlr/runtime.py +102 -0
  16. omlish/antlr/utils.py +38 -0
  17. omlish/argparse/all.py +45 -0
  18. omlish/{argparse.py → argparse/cli.py} +112 -107
  19. omlish/asyncs/__init__.py +0 -35
  20. omlish/asyncs/all.py +35 -0
  21. omlish/asyncs/asyncio/all.py +7 -0
  22. omlish/asyncs/asyncio/channels.py +40 -0
  23. omlish/asyncs/asyncio/streams.py +45 -0
  24. omlish/asyncs/asyncio/subprocesses.py +238 -0
  25. omlish/asyncs/asyncio/timeouts.py +16 -0
  26. omlish/asyncs/bluelet/LICENSE +6 -0
  27. omlish/asyncs/bluelet/all.py +67 -0
  28. omlish/asyncs/bluelet/api.py +23 -0
  29. omlish/asyncs/bluelet/core.py +178 -0
  30. omlish/asyncs/bluelet/events.py +78 -0
  31. omlish/asyncs/bluelet/files.py +80 -0
  32. omlish/asyncs/bluelet/runner.py +416 -0
  33. omlish/asyncs/bluelet/sockets.py +214 -0
  34. omlish/bootstrap/sys.py +3 -3
  35. omlish/cached.py +2 -2
  36. omlish/check.py +49 -460
  37. omlish/codecs/__init__.py +72 -0
  38. omlish/codecs/base.py +106 -0
  39. omlish/codecs/bytes.py +119 -0
  40. omlish/codecs/chain.py +23 -0
  41. omlish/codecs/funcs.py +39 -0
  42. omlish/codecs/registry.py +139 -0
  43. omlish/codecs/standard.py +4 -0
  44. omlish/codecs/text.py +217 -0
  45. omlish/collections/cache/impl.py +50 -57
  46. omlish/collections/coerce.py +1 -0
  47. omlish/collections/mappings.py +1 -1
  48. omlish/configs/flattening.py +1 -1
  49. omlish/defs.py +1 -1
  50. omlish/diag/_pycharm/runhack.py +8 -2
  51. omlish/diag/procfs.py +8 -8
  52. omlish/docker/__init__.py +0 -36
  53. omlish/docker/all.py +31 -0
  54. omlish/docker/consts.py +4 -0
  55. omlish/{lite/docker.py → docker/detect.py} +18 -0
  56. omlish/docker/{helpers.py → timebomb.py} +0 -21
  57. omlish/formats/cbor.py +31 -0
  58. omlish/formats/cloudpickle.py +31 -0
  59. omlish/formats/codecs.py +93 -0
  60. omlish/formats/json/codecs.py +29 -0
  61. omlish/formats/json/delimted.py +4 -0
  62. omlish/formats/json/stream/errors.py +2 -0
  63. omlish/formats/json/stream/lex.py +12 -6
  64. omlish/formats/json/stream/parse.py +38 -22
  65. omlish/formats/json5.py +31 -0
  66. omlish/formats/pickle.py +31 -0
  67. omlish/formats/repr.py +25 -0
  68. omlish/formats/toml.py +17 -0
  69. omlish/formats/yaml.py +25 -0
  70. omlish/funcs/__init__.py +0 -0
  71. omlish/{genmachine.py → funcs/genmachine.py} +5 -4
  72. omlish/{matchfns.py → funcs/match.py} +1 -1
  73. omlish/funcs/pairs.py +215 -0
  74. omlish/http/__init__.py +0 -48
  75. omlish/http/all.py +48 -0
  76. omlish/http/coro/__init__.py +0 -0
  77. omlish/{lite/fdio/corohttp.py → http/coro/fdio.py} +21 -19
  78. omlish/{lite/http/coroserver.py → http/coro/server.py} +20 -21
  79. omlish/{lite/http → http}/handlers.py +3 -2
  80. omlish/{lite/http → http}/parsing.py +1 -0
  81. omlish/http/sessions.py +1 -1
  82. omlish/{lite/http → http}/versions.py +1 -0
  83. omlish/inject/managed.py +2 -2
  84. omlish/io/__init__.py +0 -3
  85. omlish/{lite/io.py → io/buffers.py} +8 -9
  86. omlish/io/compress/__init__.py +9 -0
  87. omlish/io/compress/abc.py +104 -0
  88. omlish/io/compress/adapters.py +148 -0
  89. omlish/io/compress/base.py +24 -0
  90. omlish/io/compress/brotli.py +47 -0
  91. omlish/io/compress/bz2.py +61 -0
  92. omlish/io/compress/codecs.py +78 -0
  93. omlish/io/compress/gzip.py +350 -0
  94. omlish/io/compress/lz4.py +91 -0
  95. omlish/io/compress/lzma.py +81 -0
  96. omlish/io/compress/snappy.py +34 -0
  97. omlish/io/compress/zlib.py +74 -0
  98. omlish/io/compress/zstd.py +44 -0
  99. omlish/io/fdio/__init__.py +1 -0
  100. omlish/{lite → io}/fdio/handlers.py +5 -5
  101. omlish/{lite → io}/fdio/kqueue.py +8 -8
  102. omlish/{lite → io}/fdio/manager.py +7 -7
  103. omlish/{lite → io}/fdio/pollers.py +13 -13
  104. omlish/io/generators/__init__.py +56 -0
  105. omlish/io/generators/consts.py +1 -0
  106. omlish/io/generators/direct.py +13 -0
  107. omlish/io/generators/readers.py +189 -0
  108. omlish/io/generators/stepped.py +191 -0
  109. omlish/io/pyio.py +5 -2
  110. omlish/iterators/__init__.py +24 -0
  111. omlish/iterators/iterators.py +132 -0
  112. omlish/iterators/recipes.py +18 -0
  113. omlish/iterators/tools.py +96 -0
  114. omlish/iterators/unique.py +67 -0
  115. omlish/lang/__init__.py +13 -1
  116. omlish/lang/functions.py +11 -2
  117. omlish/lang/generators.py +243 -0
  118. omlish/lang/iterables.py +46 -49
  119. omlish/lang/maybes.py +4 -4
  120. omlish/lite/cached.py +39 -6
  121. omlish/lite/check.py +438 -75
  122. omlish/lite/contextmanagers.py +17 -4
  123. omlish/lite/dataclasses.py +42 -0
  124. omlish/lite/inject.py +28 -45
  125. omlish/lite/logs.py +0 -270
  126. omlish/lite/marshal.py +309 -144
  127. omlish/lite/pycharm.py +47 -0
  128. omlish/lite/reflect.py +33 -0
  129. omlish/lite/resources.py +8 -0
  130. omlish/lite/runtime.py +4 -4
  131. omlish/lite/shlex.py +12 -0
  132. omlish/lite/socketserver.py +2 -2
  133. omlish/lite/strings.py +31 -0
  134. omlish/logs/__init__.py +0 -32
  135. omlish/logs/{_abc.py → abc.py} +0 -1
  136. omlish/logs/all.py +37 -0
  137. omlish/logs/{formatters.py → color.py} +1 -2
  138. omlish/logs/configs.py +7 -38
  139. omlish/logs/filters.py +10 -0
  140. omlish/logs/handlers.py +4 -1
  141. omlish/logs/json.py +56 -0
  142. omlish/logs/proxy.py +99 -0
  143. omlish/logs/standard.py +128 -0
  144. omlish/logs/utils.py +2 -2
  145. omlish/manifests/__init__.py +2 -0
  146. omlish/manifests/load.py +209 -0
  147. omlish/manifests/types.py +17 -0
  148. omlish/marshal/base.py +1 -1
  149. omlish/marshal/factories.py +1 -1
  150. omlish/marshal/forbidden.py +1 -1
  151. omlish/marshal/iterables.py +1 -1
  152. omlish/marshal/literals.py +50 -0
  153. omlish/marshal/mappings.py +1 -1
  154. omlish/marshal/maybes.py +1 -1
  155. omlish/marshal/standard.py +5 -1
  156. omlish/marshal/unions.py +1 -1
  157. omlish/os/__init__.py +0 -0
  158. omlish/os/atomics.py +205 -0
  159. omlish/os/deathsig.py +23 -0
  160. omlish/{os.py → os/files.py} +0 -9
  161. omlish/{lite → os}/journald.py +2 -1
  162. omlish/os/linux.py +484 -0
  163. omlish/os/paths.py +36 -0
  164. omlish/{lite → os}/pidfile.py +1 -0
  165. omlish/os/sizes.py +9 -0
  166. omlish/reflect/__init__.py +3 -0
  167. omlish/reflect/subst.py +2 -1
  168. omlish/reflect/types.py +126 -44
  169. omlish/secrets/pwhash.py +1 -1
  170. omlish/secrets/subprocesses.py +3 -1
  171. omlish/specs/jsonrpc/marshal.py +1 -1
  172. omlish/specs/openapi/marshal.py +1 -1
  173. omlish/sql/alchemy/asyncs.py +1 -1
  174. omlish/sql/queries/__init__.py +9 -1
  175. omlish/sql/queries/building.py +3 -0
  176. omlish/sql/queries/exprs.py +10 -27
  177. omlish/sql/queries/idents.py +48 -10
  178. omlish/sql/queries/names.py +80 -13
  179. omlish/sql/queries/params.py +64 -0
  180. omlish/sql/queries/rendering.py +1 -1
  181. omlish/subprocesses.py +340 -0
  182. omlish/term.py +29 -14
  183. omlish/testing/pytest/marks.py +2 -2
  184. omlish/testing/pytest/plugins/asyncs.py +6 -1
  185. omlish/testing/pytest/plugins/logging.py +1 -1
  186. omlish/testing/pytest/plugins/switches.py +1 -1
  187. {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/METADATA +7 -5
  188. {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/RECORD +200 -117
  189. omlish/fnpairs.py +0 -496
  190. omlish/formats/json/cli/__main__.py +0 -11
  191. omlish/formats/json/cli/cli.py +0 -298
  192. omlish/formats/json/cli/formats.py +0 -71
  193. omlish/formats/json/cli/io.py +0 -74
  194. omlish/formats/json/cli/parsing.py +0 -82
  195. omlish/formats/json/cli/processing.py +0 -48
  196. omlish/formats/json/cli/rendering.py +0 -92
  197. omlish/iterators.py +0 -300
  198. omlish/lite/subprocesses.py +0 -130
  199. /omlish/{formats/json/cli → argparse}/__init__.py +0 -0
  200. /omlish/{lite/fdio → asyncs/asyncio}/__init__.py +0 -0
  201. /omlish/asyncs/{asyncio.py → asyncio/asyncio.py} +0 -0
  202. /omlish/{lite/http → asyncs/bluelet}/__init__.py +0 -0
  203. /omlish/collections/{_abc.py → abc.py} +0 -0
  204. /omlish/{fnpipes.py → funcs/pipes.py} +0 -0
  205. /omlish/io/{_abc.py → abc.py} +0 -0
  206. /omlish/sql/{_abc.py → abc.py} +0 -0
  207. {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/LICENSE +0 -0
  208. {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/WHEEL +0 -0
  209. {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/entry_points.txt +0 -0
  210. {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