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.
- Pyjo/App/ASGI.py +735 -0
- Pyjo/App/CGI.py +773 -0
- Pyjo/App/FastCGI.py +631 -0
- Pyjo/App/Lambda.py +987 -0
- Pyjo/App/WSGI.py +427 -0
- Pyjo/App/__init__.py +409 -0
- Pyjo/Asset/File.py +377 -0
- Pyjo/Asset/Memory.py +235 -0
- Pyjo/Asset/__init__.py +200 -0
- Pyjo/Base.py +215 -0
- Pyjo/Cache.py +119 -0
- Pyjo/Collection.py +570 -0
- Pyjo/Content/MultiPart.py +325 -0
- Pyjo/Content/Single.py +193 -0
- Pyjo/Content/__init__.py +806 -0
- Pyjo/Cookie/Request.py +90 -0
- Pyjo/Cookie/Response.py +294 -0
- Pyjo/Cookie/__init__.py +143 -0
- Pyjo/DOM/CSS.py +1019 -0
- Pyjo/DOM/HTML.py +550 -0
- Pyjo/DOM/__init__.py +1329 -0
- Pyjo/Date.py +294 -0
- Pyjo/EventEmitter.py +522 -0
- Pyjo/Exception.py +494 -0
- Pyjo/File.py +959 -0
- Pyjo/GetOpt.py +605 -0
- Pyjo/Headers.py +760 -0
- Pyjo/HelloWorld.py +77 -0
- Pyjo/Home.py +109 -0
- Pyjo/IOLoop/ChildProcess.py +591 -0
- Pyjo/IOLoop/Client.py +736 -0
- Pyjo/IOLoop/Datagram.py +982 -0
- Pyjo/IOLoop/Messages.py +664 -0
- Pyjo/IOLoop/Server.py +458 -0
- Pyjo/IOLoop/Stdio.py +446 -0
- Pyjo/IOLoop/Stream.py +644 -0
- Pyjo/IOLoop/Subprocess.py +419 -0
- Pyjo/IOLoop/TLS.py +360 -0
- Pyjo/IOLoop/__init__.py +1036 -0
- Pyjo/IOLoop/certs/README.md +41 -0
- Pyjo/IOLoop/certs/ca.crt +20 -0
- Pyjo/IOLoop/certs/server.crt +21 -0
- Pyjo/IOLoop/certs/server.key +28 -0
- Pyjo/JSON/Pointer.py +135 -0
- Pyjo/JSON/RPC.py +772 -0
- Pyjo/JSON/Schema.py +2506 -0
- Pyjo/JSON/__init__.py +573 -0
- Pyjo/Loader.py +342 -0
- Pyjo/Log.py +723 -0
- Pyjo/Message/Request.py +725 -0
- Pyjo/Message/Response.py +476 -0
- Pyjo/Message/__init__.py +830 -0
- Pyjo/Parameters.py +472 -0
- Pyjo/Path.py +419 -0
- Pyjo/Promise.py +894 -0
- Pyjo/Prove.py +1305 -0
- Pyjo/Reactor/Asyncio.py +854 -0
- Pyjo/Reactor/Poll.py +439 -0
- Pyjo/Reactor/__init__.py +525 -0
- Pyjo/SSE.py +119 -0
- Pyjo/Server/ASGI.py +486 -0
- Pyjo/Server/CGI.py +259 -0
- Pyjo/Server/Daemon.py +1007 -0
- Pyjo/Server/FastCGI.py +517 -0
- Pyjo/Server/Hot/__init__.py +687 -0
- Pyjo/Server/Hot/__main__.py +5 -0
- Pyjo/Server/Lambda.py +641 -0
- Pyjo/Server/Prefork.py +917 -0
- Pyjo/Server/Prethread.py +394 -0
- Pyjo/Server/Reload/Backend/Poll.py +114 -0
- Pyjo/Server/Reload/Backend/__init__.py +126 -0
- Pyjo/Server/Reload/__init__.py +482 -0
- Pyjo/Server/Reload/__main__.py +5 -0
- Pyjo/Server/Stdio.py +228 -0
- Pyjo/Server/WSGI.py +349 -0
- Pyjo/Server/__init__.py +396 -0
- Pyjo/String/Bytes.py +532 -0
- Pyjo/String/HTML.py +210 -0
- Pyjo/String/Mixin.py +333 -0
- Pyjo/String/Template.py +470 -0
- Pyjo/String/Unicode.py +553 -0
- Pyjo/String/__init__.py +0 -0
- Pyjo/TOML.py +232 -0
- Pyjo/Template.py +1290 -0
- Pyjo/Test/Pyjo.py +1454 -0
- Pyjo/Test/__init__.py +1233 -0
- Pyjo/Test/pytest_plugin.py +305 -0
- Pyjo/Transaction/HTTP.py +374 -0
- Pyjo/Transaction/WebSocket.py +709 -0
- Pyjo/Transaction/__init__.py +429 -0
- Pyjo/URL/Template.py +459 -0
- Pyjo/URL/__init__.py +651 -0
- Pyjo/Upload.py +139 -0
- Pyjo/UserAgent/CookieJar.py +391 -0
- Pyjo/UserAgent/Proxy.py +138 -0
- Pyjo/UserAgent/Server.py +239 -0
- Pyjo/UserAgent/Transactor.py +766 -0
- Pyjo/UserAgent/__init__.py +1243 -0
- Pyjo/Util.py +3567 -0
- Pyjo/WebSocket.py +332 -0
- Pyjo/YAML.py +1167 -0
- Pyjo/__init__.py +441 -0
- Pyjo/__main__.py +35 -0
- Pyjo/py.typed +0 -0
- Pyjoyment/Command/Author/__init__.py +20 -0
- Pyjoyment/Command/Author/generate/__init__.py +106 -0
- Pyjoyment/Command/Author/generate/app.py +299 -0
- Pyjoyment/Command/Author/generate/dockerfile.py +281 -0
- Pyjoyment/Command/Author/generate/kubernetes.py +514 -0
- Pyjoyment/Command/Author/generate/lambda_zip.py +372 -0
- Pyjoyment/Command/Author/generate/lite_app.py +113 -0
- Pyjoyment/Command/Author/generate/plugin.py +267 -0
- Pyjoyment/Command/Author/generate/pyproject.py +123 -0
- Pyjoyment/Command/Author/inflate.py +101 -0
- Pyjoyment/Command/__init__.py +332 -0
- Pyjoyment/Command/asgi.py +92 -0
- Pyjoyment/Command/cgi.py +90 -0
- Pyjoyment/Command/daemon.py +160 -0
- Pyjoyment/Command/eval.py +128 -0
- Pyjoyment/Command/fastcgi.py +99 -0
- Pyjoyment/Command/get.py +305 -0
- Pyjoyment/Command/lambda.py +117 -0
- Pyjoyment/Command/prefork.py +181 -0
- Pyjoyment/Command/prethread.py +126 -0
- Pyjoyment/Command/routes.py +133 -0
- Pyjoyment/Command/stdio.py +92 -0
- Pyjoyment/Command/version.py +141 -0
- Pyjoyment/Command/wsgi.py +93 -0
- Pyjoyment/Commands.py +546 -0
- Pyjoyment/Controller.py +1307 -0
- Pyjoyment/HelloWorld.py +56 -0
- Pyjoyment/Lite.py +494 -0
- Pyjoyment/Plugin/AllowedHosts.py +205 -0
- Pyjoyment/Plugin/Config.py +248 -0
- Pyjoyment/Plugin/DefaultHelpers.py +1202 -0
- Pyjoyment/Plugin/EPLRenderer.py +134 -0
- Pyjoyment/Plugin/EPRenderer.py +212 -0
- Pyjoyment/Plugin/HeaderCondition.py +98 -0
- Pyjoyment/Plugin/Health.py +325 -0
- Pyjoyment/Plugin/JSONConfig.py +172 -0
- Pyjoyment/Plugin/Mount.py +223 -0
- Pyjoyment/Plugin/PyRenderer.py +209 -0
- Pyjoyment/Plugin/TOMLConfig.py +141 -0
- Pyjoyment/Plugin/TagHelpers.py +959 -0
- Pyjoyment/Plugin/YAMLConfig.py +178 -0
- Pyjoyment/Plugin/__init__.py +80 -0
- Pyjoyment/Plugins.py +215 -0
- Pyjoyment/Renderer.py +673 -0
- Pyjoyment/Routes/Match.py +251 -0
- Pyjoyment/Routes/Pattern.py +443 -0
- Pyjoyment/Routes/Route.py +827 -0
- Pyjoyment/Routes/__init__.py +419 -0
- Pyjoyment/Sessions.py +255 -0
- Pyjoyment/Static.py +421 -0
- Pyjoyment/Types.py +238 -0
- Pyjoyment/Validator/Validation.py +395 -0
- Pyjoyment/Validator/__init__.py +304 -0
- Pyjoyment/__init__.py +1083 -0
- Pyjoyment/__main__.py +5 -0
- Pyjoyment/py.typed +0 -0
- Pyjoyment/resources/public/favicon.ico +0 -0
- Pyjoyment/resources/public/favicon.svg +345 -0
- Pyjoyment/resources/public/pyjo/bootstrap/LICENSE +21 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.bundle.min.js.map +1 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.css +6 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.js +7 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.min.css.map +1 -0
- Pyjoyment/resources/public/pyjo/failunicat.png +0 -0
- Pyjoyment/resources/public/pyjo/failunicat.svg +50 -0
- Pyjoyment/resources/public/pyjo/highlight.js/LICENSE +29 -0
- Pyjoyment/resources/public/pyjo/highlight.js/highlight-mojo-dark.css +56 -0
- Pyjoyment/resources/public/pyjo/highlight.js/highlight.min.js +1262 -0
- Pyjoyment/resources/public/pyjo/highlight.js/pyjoyment.min.js +1 -0
- Pyjoyment/resources/public/pyjo/logo-black-4x.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-black-4x.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo-white-2x.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-white-2x.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo-white-4x.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-white-4x.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo-white.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-white.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo.png +0 -0
- Pyjoyment/resources/public/pyjo/logo.svg +346 -0
- Pyjoyment/resources/public/pyjo/notfound.png +0 -0
- Pyjoyment/resources/public/pyjo/notfound.svg +3 -0
- Pyjoyment/resources/public/pyjo/nounicat.png +0 -0
- Pyjoyment/resources/public/pyjo/nounicat.svg +5 -0
- Pyjoyment/resources/public/pyjo/pinstripe-dark.png +0 -0
- Pyjoyment/resources/public/pyjo/pinstripe-dark.svg +4 -0
- Pyjoyment/resources/public/pyjo/pinstripe-light.png +0 -0
- Pyjoyment/resources/public/pyjo/pinstripe-light.svg +4 -0
- Pyjoyment/resources/public/pyjo/pyjo.css +204 -0
- Pyjoyment/resources/templates/pyjo/debug.html.ep +346 -0
- Pyjoyment/resources/templates/pyjo/exception.html.ep +32 -0
- Pyjoyment/resources/templates/pyjo/not_found.html.ep +45 -0
- pyjoyment-0.1.0.dist-info/METADATA +376 -0
- pyjoyment-0.1.0.dist-info/RECORD +200 -0
- pyjoyment-0.1.0.dist-info/WHEEL +4 -0
- pyjoyment-0.1.0.dist-info/entry_points.txt +8 -0
- 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
|