Pyjoyment 0.1.0__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 (200) hide show
  1. Pyjo/App/ASGI.py +735 -0
  2. Pyjo/App/CGI.py +773 -0
  3. Pyjo/App/FastCGI.py +631 -0
  4. Pyjo/App/Lambda.py +987 -0
  5. Pyjo/App/WSGI.py +427 -0
  6. Pyjo/App/__init__.py +409 -0
  7. Pyjo/Asset/File.py +377 -0
  8. Pyjo/Asset/Memory.py +235 -0
  9. Pyjo/Asset/__init__.py +200 -0
  10. Pyjo/Base.py +215 -0
  11. Pyjo/Cache.py +119 -0
  12. Pyjo/Collection.py +570 -0
  13. Pyjo/Content/MultiPart.py +325 -0
  14. Pyjo/Content/Single.py +193 -0
  15. Pyjo/Content/__init__.py +806 -0
  16. Pyjo/Cookie/Request.py +90 -0
  17. Pyjo/Cookie/Response.py +294 -0
  18. Pyjo/Cookie/__init__.py +143 -0
  19. Pyjo/DOM/CSS.py +1019 -0
  20. Pyjo/DOM/HTML.py +550 -0
  21. Pyjo/DOM/__init__.py +1329 -0
  22. Pyjo/Date.py +294 -0
  23. Pyjo/EventEmitter.py +522 -0
  24. Pyjo/Exception.py +494 -0
  25. Pyjo/File.py +959 -0
  26. Pyjo/GetOpt.py +605 -0
  27. Pyjo/Headers.py +760 -0
  28. Pyjo/HelloWorld.py +77 -0
  29. Pyjo/Home.py +109 -0
  30. Pyjo/IOLoop/ChildProcess.py +591 -0
  31. Pyjo/IOLoop/Client.py +736 -0
  32. Pyjo/IOLoop/Datagram.py +982 -0
  33. Pyjo/IOLoop/Messages.py +664 -0
  34. Pyjo/IOLoop/Server.py +458 -0
  35. Pyjo/IOLoop/Stdio.py +446 -0
  36. Pyjo/IOLoop/Stream.py +644 -0
  37. Pyjo/IOLoop/Subprocess.py +419 -0
  38. Pyjo/IOLoop/TLS.py +360 -0
  39. Pyjo/IOLoop/__init__.py +1036 -0
  40. Pyjo/IOLoop/certs/README.md +41 -0
  41. Pyjo/IOLoop/certs/ca.crt +20 -0
  42. Pyjo/IOLoop/certs/server.crt +21 -0
  43. Pyjo/IOLoop/certs/server.key +28 -0
  44. Pyjo/JSON/Pointer.py +135 -0
  45. Pyjo/JSON/RPC.py +772 -0
  46. Pyjo/JSON/Schema.py +2506 -0
  47. Pyjo/JSON/__init__.py +573 -0
  48. Pyjo/Loader.py +342 -0
  49. Pyjo/Log.py +723 -0
  50. Pyjo/Message/Request.py +725 -0
  51. Pyjo/Message/Response.py +476 -0
  52. Pyjo/Message/__init__.py +830 -0
  53. Pyjo/Parameters.py +472 -0
  54. Pyjo/Path.py +419 -0
  55. Pyjo/Promise.py +894 -0
  56. Pyjo/Prove.py +1305 -0
  57. Pyjo/Reactor/Asyncio.py +854 -0
  58. Pyjo/Reactor/Poll.py +439 -0
  59. Pyjo/Reactor/__init__.py +525 -0
  60. Pyjo/SSE.py +119 -0
  61. Pyjo/Server/ASGI.py +486 -0
  62. Pyjo/Server/CGI.py +259 -0
  63. Pyjo/Server/Daemon.py +1007 -0
  64. Pyjo/Server/FastCGI.py +517 -0
  65. Pyjo/Server/Hot/__init__.py +687 -0
  66. Pyjo/Server/Hot/__main__.py +5 -0
  67. Pyjo/Server/Lambda.py +641 -0
  68. Pyjo/Server/Prefork.py +917 -0
  69. Pyjo/Server/Prethread.py +394 -0
  70. Pyjo/Server/Reload/Backend/Poll.py +114 -0
  71. Pyjo/Server/Reload/Backend/__init__.py +126 -0
  72. Pyjo/Server/Reload/__init__.py +482 -0
  73. Pyjo/Server/Reload/__main__.py +5 -0
  74. Pyjo/Server/Stdio.py +228 -0
  75. Pyjo/Server/WSGI.py +349 -0
  76. Pyjo/Server/__init__.py +396 -0
  77. Pyjo/String/Bytes.py +532 -0
  78. Pyjo/String/HTML.py +210 -0
  79. Pyjo/String/Mixin.py +333 -0
  80. Pyjo/String/Template.py +470 -0
  81. Pyjo/String/Unicode.py +553 -0
  82. Pyjo/String/__init__.py +0 -0
  83. Pyjo/TOML.py +232 -0
  84. Pyjo/Template.py +1290 -0
  85. Pyjo/Test/Pyjo.py +1454 -0
  86. Pyjo/Test/__init__.py +1233 -0
  87. Pyjo/Test/pytest_plugin.py +305 -0
  88. Pyjo/Transaction/HTTP.py +374 -0
  89. Pyjo/Transaction/WebSocket.py +709 -0
  90. Pyjo/Transaction/__init__.py +429 -0
  91. Pyjo/URL/Template.py +459 -0
  92. Pyjo/URL/__init__.py +651 -0
  93. Pyjo/Upload.py +139 -0
  94. Pyjo/UserAgent/CookieJar.py +391 -0
  95. Pyjo/UserAgent/Proxy.py +138 -0
  96. Pyjo/UserAgent/Server.py +239 -0
  97. Pyjo/UserAgent/Transactor.py +766 -0
  98. Pyjo/UserAgent/__init__.py +1243 -0
  99. Pyjo/Util.py +3567 -0
  100. Pyjo/WebSocket.py +332 -0
  101. Pyjo/YAML.py +1167 -0
  102. Pyjo/__init__.py +441 -0
  103. Pyjo/__main__.py +35 -0
  104. Pyjo/py.typed +0 -0
  105. Pyjoyment/Command/Author/__init__.py +20 -0
  106. Pyjoyment/Command/Author/generate/__init__.py +106 -0
  107. Pyjoyment/Command/Author/generate/app.py +299 -0
  108. Pyjoyment/Command/Author/generate/dockerfile.py +281 -0
  109. Pyjoyment/Command/Author/generate/kubernetes.py +514 -0
  110. Pyjoyment/Command/Author/generate/lambda_zip.py +372 -0
  111. Pyjoyment/Command/Author/generate/lite_app.py +113 -0
  112. Pyjoyment/Command/Author/generate/plugin.py +267 -0
  113. Pyjoyment/Command/Author/generate/pyproject.py +123 -0
  114. Pyjoyment/Command/Author/inflate.py +101 -0
  115. Pyjoyment/Command/__init__.py +332 -0
  116. Pyjoyment/Command/asgi.py +92 -0
  117. Pyjoyment/Command/cgi.py +90 -0
  118. Pyjoyment/Command/daemon.py +160 -0
  119. Pyjoyment/Command/eval.py +128 -0
  120. Pyjoyment/Command/fastcgi.py +99 -0
  121. Pyjoyment/Command/get.py +305 -0
  122. Pyjoyment/Command/lambda.py +117 -0
  123. Pyjoyment/Command/prefork.py +181 -0
  124. Pyjoyment/Command/prethread.py +126 -0
  125. Pyjoyment/Command/routes.py +133 -0
  126. Pyjoyment/Command/stdio.py +92 -0
  127. Pyjoyment/Command/version.py +141 -0
  128. Pyjoyment/Command/wsgi.py +93 -0
  129. Pyjoyment/Commands.py +546 -0
  130. Pyjoyment/Controller.py +1307 -0
  131. Pyjoyment/HelloWorld.py +56 -0
  132. Pyjoyment/Lite.py +494 -0
  133. Pyjoyment/Plugin/AllowedHosts.py +205 -0
  134. Pyjoyment/Plugin/Config.py +248 -0
  135. Pyjoyment/Plugin/DefaultHelpers.py +1202 -0
  136. Pyjoyment/Plugin/EPLRenderer.py +134 -0
  137. Pyjoyment/Plugin/EPRenderer.py +212 -0
  138. Pyjoyment/Plugin/HeaderCondition.py +98 -0
  139. Pyjoyment/Plugin/Health.py +325 -0
  140. Pyjoyment/Plugin/JSONConfig.py +172 -0
  141. Pyjoyment/Plugin/Mount.py +223 -0
  142. Pyjoyment/Plugin/PyRenderer.py +209 -0
  143. Pyjoyment/Plugin/TOMLConfig.py +141 -0
  144. Pyjoyment/Plugin/TagHelpers.py +959 -0
  145. Pyjoyment/Plugin/YAMLConfig.py +178 -0
  146. Pyjoyment/Plugin/__init__.py +80 -0
  147. Pyjoyment/Plugins.py +215 -0
  148. Pyjoyment/Renderer.py +673 -0
  149. Pyjoyment/Routes/Match.py +251 -0
  150. Pyjoyment/Routes/Pattern.py +443 -0
  151. Pyjoyment/Routes/Route.py +827 -0
  152. Pyjoyment/Routes/__init__.py +419 -0
  153. Pyjoyment/Sessions.py +255 -0
  154. Pyjoyment/Static.py +421 -0
  155. Pyjoyment/Types.py +238 -0
  156. Pyjoyment/Validator/Validation.py +395 -0
  157. Pyjoyment/Validator/__init__.py +304 -0
  158. Pyjoyment/__init__.py +1083 -0
  159. Pyjoyment/__main__.py +5 -0
  160. Pyjoyment/py.typed +0 -0
  161. Pyjoyment/resources/public/favicon.ico +0 -0
  162. Pyjoyment/resources/public/favicon.svg +345 -0
  163. Pyjoyment/resources/public/pyjo/bootstrap/LICENSE +21 -0
  164. Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.bundle.min.js.map +1 -0
  165. Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.css +6 -0
  166. Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.js +7 -0
  167. Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.min.css.map +1 -0
  168. Pyjoyment/resources/public/pyjo/failunicat.png +0 -0
  169. Pyjoyment/resources/public/pyjo/failunicat.svg +50 -0
  170. Pyjoyment/resources/public/pyjo/highlight.js/LICENSE +29 -0
  171. Pyjoyment/resources/public/pyjo/highlight.js/highlight-mojo-dark.css +56 -0
  172. Pyjoyment/resources/public/pyjo/highlight.js/highlight.min.js +1262 -0
  173. Pyjoyment/resources/public/pyjo/highlight.js/pyjoyment.min.js +1 -0
  174. Pyjoyment/resources/public/pyjo/logo-black-4x.png +0 -0
  175. Pyjoyment/resources/public/pyjo/logo-black-4x.svg +346 -0
  176. Pyjoyment/resources/public/pyjo/logo-white-2x.png +0 -0
  177. Pyjoyment/resources/public/pyjo/logo-white-2x.svg +346 -0
  178. Pyjoyment/resources/public/pyjo/logo-white-4x.png +0 -0
  179. Pyjoyment/resources/public/pyjo/logo-white-4x.svg +346 -0
  180. Pyjoyment/resources/public/pyjo/logo-white.png +0 -0
  181. Pyjoyment/resources/public/pyjo/logo-white.svg +346 -0
  182. Pyjoyment/resources/public/pyjo/logo.png +0 -0
  183. Pyjoyment/resources/public/pyjo/logo.svg +346 -0
  184. Pyjoyment/resources/public/pyjo/notfound.png +0 -0
  185. Pyjoyment/resources/public/pyjo/notfound.svg +3 -0
  186. Pyjoyment/resources/public/pyjo/nounicat.png +0 -0
  187. Pyjoyment/resources/public/pyjo/nounicat.svg +5 -0
  188. Pyjoyment/resources/public/pyjo/pinstripe-dark.png +0 -0
  189. Pyjoyment/resources/public/pyjo/pinstripe-dark.svg +4 -0
  190. Pyjoyment/resources/public/pyjo/pinstripe-light.png +0 -0
  191. Pyjoyment/resources/public/pyjo/pinstripe-light.svg +4 -0
  192. Pyjoyment/resources/public/pyjo/pyjo.css +204 -0
  193. Pyjoyment/resources/templates/pyjo/debug.html.ep +346 -0
  194. Pyjoyment/resources/templates/pyjo/exception.html.ep +32 -0
  195. Pyjoyment/resources/templates/pyjo/not_found.html.ep +45 -0
  196. pyjoyment-0.1.0.dist-info/METADATA +376 -0
  197. pyjoyment-0.1.0.dist-info/RECORD +200 -0
  198. pyjoyment-0.1.0.dist-info/WHEEL +4 -0
  199. pyjoyment-0.1.0.dist-info/entry_points.txt +8 -0
  200. pyjoyment-0.1.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,419 @@
1
+ """
2
+ Pyjo.IOLoop.Subprocess - Subprocesses
3
+ =====================================
4
+ ::
5
+
6
+ from Pyjo.IOLoop.Subprocess import Subprocess
7
+
8
+ import time
9
+
10
+ # Operation that would block the event loop for 5 seconds
11
+ subprocess = Subprocess()
12
+
13
+
14
+ def blocking(subprocess):
15
+ time.sleep(5)
16
+ return '♥', 'Pyjoyment'
17
+
18
+
19
+ def done(subprocess, err, *results):
20
+ if err:
21
+ print(f'Subprocess error: {err}')
22
+ return
23
+ print(f'I {results[0]} {results[1]}!')
24
+
25
+
26
+ subprocess.run(blocking, done)
27
+
28
+ # Or import the module, as in Google Python Style Guide
29
+
30
+ import Pyjo.IOLoop.Subprocess
31
+
32
+ import time
33
+
34
+ # Operation that would block the event loop for 5 seconds
35
+ subprocess = Pyjo.IOLoop.Subprocess.new()
36
+
37
+
38
+ def blocking(subprocess):
39
+ time.sleep(5)
40
+ return '♥', 'Pyjoyment'
41
+
42
+
43
+ def done(subprocess, err, *results):
44
+ if err:
45
+ print(f'Subprocess error: {err}')
46
+ return
47
+ print(f'I {results[0]} {results[1]}!')
48
+
49
+
50
+ subprocess.run(blocking, done)
51
+
52
+ # Operation that would block the event loop for 5 seconds (with promise)
53
+ subprocess.run_p(blocking).then(lambda *results: print(f'I {results[0]} {results[1]}!')).catch(
54
+ lambda err: print(f'Subprocess error: {err}')
55
+ )
56
+
57
+ # Start event loop if necessary
58
+ if not subprocess.ioloop.is_running:
59
+ subprocess.ioloop.start()
60
+
61
+ :mod:`Pyjo.IOLoop.Subprocess` allows :mod:`Pyjo.IOLoop` to perform
62
+ computationally expensive operations in subprocesses, without blocking the
63
+ event loop.
64
+
65
+ The subprocess is a fork of the process, made with :func:`os.fork`, so the
66
+ first callback of :meth:`~Pyjo_IOLoop_Subprocess.run` can be any function,
67
+ a closure too, and it has the state of the parent. It runs where
68
+ :func:`os.fork` exists, not on Windows.
69
+
70
+ Events
71
+ ------
72
+
73
+ :mod:`Pyjo.IOLoop.Subprocess` inherits all events from :mod:`Pyjo.EventEmitter`
74
+ and can emit the following new ones.
75
+
76
+ cleanup
77
+ ~~~~~~~
78
+ ::
79
+
80
+ subprocess.on('cleanup', lambda subprocess: ...)
81
+
82
+ Emitted in the subprocess right before the process will exit. ::
83
+
84
+ subprocess.on('cleanup', lambda subprocess: print(f'Process {os.getpid()} is about to exit'))
85
+
86
+ progress
87
+ ~~~~~~~~
88
+ ::
89
+
90
+ subprocess.on('progress', lambda subprocess, *data: ...)
91
+
92
+ Emitted in the parent process when the subprocess calls the
93
+ :meth:`~Pyjo_IOLoop_Subprocess.progress` method.
94
+
95
+ spawn
96
+ ~~~~~
97
+ ::
98
+
99
+ subprocess.on('spawn', lambda subprocess: ...)
100
+
101
+ Emitted in the parent process when the subprocess has been spawned. ::
102
+
103
+ subprocess.on('spawn', lambda subprocess: print(f'Performing work in process {subprocess.pid}'))
104
+
105
+ See also
106
+ --------
107
+
108
+ :mod:`Pyjoyment`, https://mojolicious.org.
109
+
110
+ Ported from `Mojo::IOLoop::Subprocess <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/IOLoop/Subprocess.pm>`_
111
+ of Mojolicious 9.49.
112
+
113
+ Classes
114
+ -------
115
+ """
116
+
117
+ import Pyjo.EventEmitter
118
+ import Pyjo.Exception
119
+ import Pyjo.IOLoop
120
+ import Pyjo.IOLoop.Stream
121
+ import Pyjo.JSON
122
+ import Pyjo.Promise
123
+
124
+ from Pyjo.Util import warn
125
+
126
+ import builtins
127
+ import os
128
+ import re
129
+ import sys
130
+ import traceback
131
+ import weakref
132
+
133
+ from collections.abc import Callable
134
+ from typing import IO, Any, NoReturn, Self
135
+
136
+
137
+ type Anything = builtins.object
138
+
139
+ # The first callback of run runs in the subprocess, the second one in the parent process with the error and the results
140
+ type Child = Callable[[Any], Any]
141
+ type Parent = Callable[..., Anything]
142
+
143
+
144
+ class Pyjo_IOLoop_Subprocess(Pyjo.EventEmitter.object):
145
+ """
146
+ :mod:`Pyjo.IOLoop.Subprocess` inherits all methods from
147
+ :mod:`Pyjo.EventEmitter` and implements the following new ones.
148
+ """
149
+
150
+ def __init__(
151
+ self,
152
+ *,
153
+ deserialize: Callable[[bytes], Any] | None = None,
154
+ ioloop: Pyjo.IOLoop.object | None = None,
155
+ serialize: Callable[[Any], bytes] | None = None,
156
+ ) -> None:
157
+ """::
158
+
159
+ subprocess = Pyjo.IOLoop.Subprocess.new()
160
+ subprocess = Pyjo.IOLoop.Subprocess.new(ioloop=Pyjo.IOLoop.new())
161
+
162
+ Construct a new :mod:`Pyjo.IOLoop.Subprocess` object.
163
+ """
164
+ super().__init__()
165
+
166
+ self.deserialize: Callable[[bytes], Any] = deserialize or Pyjo.JSON.decode_json
167
+ """::
168
+
169
+ cb = subprocess.deserialize
170
+ subprocess.deserialize = lambda data: []
171
+
172
+ A callback used to deserialize subprocess return values, defaults to
173
+ using :mod:`Pyjo.JSON`.
174
+ """
175
+
176
+ self.serialize: Callable[[Any], bytes] = serialize or Pyjo.JSON.encode_json
177
+ """::
178
+
179
+ cb = subprocess.serialize
180
+ subprocess.serialize = lambda array: b''
181
+
182
+ A callback used to serialize subprocess return values, defaults to
183
+ using :mod:`Pyjo.JSON`.
184
+ """
185
+
186
+ self._exit_code: int | None = None
187
+ self._ioloop: weakref.ref[Pyjo.IOLoop.object] | None = None
188
+ self._pid: int | None = None
189
+ self._writer: IO[bytes] | None = None
190
+ if ioloop is not None:
191
+ self.ioloop = ioloop
192
+
193
+ @property
194
+ def ioloop(self) -> Pyjo.IOLoop.object:
195
+ """::
196
+
197
+ loop = subprocess.ioloop
198
+ subprocess.ioloop = Pyjo.IOLoop.new()
199
+
200
+ Event loop object to control, defaults to the global :mod:`Pyjo.IOLoop`
201
+ singleton. Note that this attribute is weakened.
202
+ """
203
+ if self._ioloop is None:
204
+ self._ioloop = weakref.ref(Pyjo.IOLoop.singleton)
205
+ if (loop := self._ioloop()) is None:
206
+ raise ReferenceError('weakly-referenced object no longer exists')
207
+ return loop
208
+
209
+ @ioloop.setter
210
+ def ioloop(self, value: Pyjo.IOLoop.object) -> None:
211
+ self._ioloop = weakref.ref(value)
212
+
213
+ @property
214
+ def exit_code(self) -> int | None:
215
+ """::
216
+
217
+ code = subprocess.exit_code
218
+
219
+ Returns the subprocess exit code, or ``None`` if the subprocess is
220
+ still running.
221
+ """
222
+ return self._exit_code
223
+
224
+ @property
225
+ def pid(self) -> int | None:
226
+ """::
227
+
228
+ pid = subprocess.pid
229
+
230
+ Process id of the spawned subprocess if available.
231
+ """
232
+ return self._pid
233
+
234
+ def progress(self, *data: Anything) -> None:
235
+ """::
236
+
237
+ subprocess.progress(*data)
238
+
239
+ Send data serialized with :mod:`Pyjo.JSON` to the parent process at any
240
+ time during the subprocess's execution. Must be called by the subprocess
241
+ and emits the ``progress`` event in the parent process with the data. ::
242
+
243
+ # Send progress information to the parent process
244
+ def blocking(subprocess):
245
+ subprocess.progress('0%')
246
+ time.sleep(5)
247
+ subprocess.progress('50%')
248
+ time.sleep(5)
249
+ return 'Hello Pyjo!'
250
+
251
+
252
+ def done(subprocess, err, *results):
253
+ print('Progress is 100%')
254
+ print(results[0])
255
+
256
+
257
+ subprocess.run(blocking, done)
258
+ subprocess.on('progress', lambda subprocess, *data: print(f'Progress is {data[0]}'))
259
+ """
260
+ serialized = self.serialize(list(data))
261
+ if (writer := self._writer) is None:
262
+ Pyjo.Exception.object.throw('Progress can only be sent by the subprocess')
263
+ writer.write(f'{len(serialized)}-'.encode() + serialized)
264
+ writer.flush()
265
+
266
+ def run(self, child: Child, parent: Parent) -> Self:
267
+ """::
268
+
269
+ subprocess = subprocess.run(lambda subprocess: ..., lambda subprocess, err, *results: ...)
270
+
271
+ Execute the first callback in a child process and wait for it to return
272
+ a value, a tuple for several ones and ``None`` for none, without
273
+ blocking :attr:`ioloop` in the parent process. Then execute the second
274
+ callback in the parent process with the error and the results. The
275
+ return value of the first callback and the exception raised by it will
276
+ be serialized with :mod:`Pyjo.JSON`, so they can be shared between
277
+ processes.
278
+ """
279
+ self.ioloop.next_tick(lambda loop: self._start(child, parent))
280
+ return self
281
+
282
+ def run_p(self, child: Child) -> Pyjo.Promise.object:
283
+ """::
284
+
285
+ promise = subprocess.run_p(lambda subprocess: ...)
286
+
287
+ Same as :meth:`run`, but returns a :mod:`Pyjo.Promise` object instead of
288
+ accepting a second callback. ::
289
+
290
+ subprocess.run_p(blocking).then(lambda *results: print(f'I {results[0]} {results[1]}!')).catch(
291
+ lambda err: print(f'Subprocess error: {err}')
292
+ ).wait()
293
+ """
294
+ p = Pyjo.Promise.new()
295
+
296
+ def parent(self: Pyjo_IOLoop_Subprocess, err: Any, *results: Any) -> None:
297
+ p.reject(err) if err else p.resolve(*results)
298
+
299
+ self.ioloop.next_tick(lambda loop: self._start(child, parent))
300
+ return p
301
+
302
+ def _start(self, child: Child, parent: Parent) -> None:
303
+ # Unlike Mojolicious: no fork at all, where Mojo has no fork emulation of Perl on Windows
304
+ if not hasattr(os, 'fork'):
305
+ parent(self, 'Subprocesses need os.fork')
306
+ return
307
+
308
+ # Pipe for subprocess communication
309
+ try:
310
+ reader, writer = os.pipe()
311
+ except OSError as e:
312
+ parent(self, f"Can't create pipe: {e.strerror or e}")
313
+ return
314
+
315
+ # Child
316
+ # Not in Mojolicious: the buffers of the standard streams are written before, so that the child does not write
317
+ # them again, as multiprocessing does it
318
+ _flush()
319
+ try:
320
+ pid = self._pid = os.fork()
321
+ except OSError as e:
322
+ os.close(reader)
323
+ os.close(writer)
324
+ parent(self, f"Can't fork: {e.strerror or e}")
325
+ return
326
+ if not pid:
327
+ self._child(child, reader, writer)
328
+
329
+ # Parent
330
+ me = os.getpid()
331
+ os.close(writer)
332
+ os.set_blocking(reader, False)
333
+ stream = Pyjo.IOLoop.Stream.new(open(reader, 'rb', buffering=0))
334
+ stream.timeout = 0
335
+ self.emit('spawn').ioloop.stream(stream)
336
+ buffer = bytearray()
337
+
338
+ def read(stream: Pyjo.IOLoop.Stream.object, chunk: bytes) -> None:
339
+ buffer.extend(chunk)
340
+ while True:
341
+ m = re.match(rb'([0-9]+)-', buffer)
342
+ if not m or not int(m[1]) or len(buffer) < int(m[1]) + m.end():
343
+ break
344
+ snippet = bytes(buffer[: int(m[1]) + m.end()])
345
+ del buffer[: int(m[1]) + m.end()]
346
+ args = self.deserialize(snippet[m.end() :])
347
+ self.emit('progress', *args)
348
+
349
+ def close(stream: Pyjo.IOLoop.Stream.object) -> None:
350
+ if os.getpid() != me:
351
+ return
352
+ _, status = os.waitpid(pid, 0)
353
+ self._exit_code = status >> 8
354
+ del buffer[:2]
355
+ err: Any = None
356
+ try:
357
+ results = list(self.deserialize(bytes(buffer)))
358
+ except Exception as e:
359
+ results, err = [], _error(e)
360
+ first = results.pop(0) if len(results) else None
361
+ parent(self, first if first is not None else err, *results)
362
+
363
+ stream.on('read', read)
364
+ stream.on('close', close)
365
+
366
+ # The subprocess, which runs the first callback, writes its results and exits
367
+ def _child(self, child: Child, reader: int, writer: int) -> NoReturn:
368
+ code = 0
369
+ try:
370
+ os.close(reader)
371
+ self._writer = open(writer, 'wb')
372
+ self.ioloop.reset(freeze=True)
373
+ err: Any = None
374
+ try:
375
+ results = _results(child(self))
376
+ except Exception as e:
377
+ results, err = [], _error(e)
378
+ self._writer.write(b'0-' + self.serialize([err, *results]))
379
+ self._writer.flush()
380
+ self.emit('cleanup')
381
+
382
+ # Not in Mojolicious: sys.exit() is the exit of the subprocess, as exit is in Perl
383
+ except SystemExit as e:
384
+ code = e.code if isinstance(e.code, int) else 0 if e.code is None else 1
385
+ except BaseException as e:
386
+ warn(''.join(traceback.format_exception(e)))
387
+
388
+ # Not in Mojolicious: the buffers of the standard streams, which os._exit leaves
389
+ _flush()
390
+ os._exit(code)
391
+
392
+
393
+ def _flush() -> None:
394
+ for stream in (sys.stdout, sys.stderr):
395
+ try:
396
+ stream.flush()
397
+ except Exception:
398
+ pass
399
+
400
+
401
+ # The error of the subprocess, as the string of the exception that Perl has in $@
402
+ def _error(e: BaseException) -> str:
403
+ if isinstance(e, Pyjo.Exception.object):
404
+ return str(e)
405
+ return ''.join(traceback.format_exception_only(e)).strip()
406
+
407
+
408
+ # The results of the first callback: none, one, or the values of a tuple
409
+ def _results(value: Any) -> list[Any]:
410
+ if value is None:
411
+ return []
412
+ if isinstance(value, tuple):
413
+ return list(value) # pyright: ignore[reportUnknownArgumentType]
414
+ return [value]
415
+
416
+
417
+ new = Pyjo_IOLoop_Subprocess.new
418
+ object = Pyjo_IOLoop_Subprocess
419
+ Subprocess = Pyjo_IOLoop_Subprocess