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.
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