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
Pyjo/IOLoop/Stdio.py ADDED
@@ -0,0 +1,446 @@
1
+ """
2
+ Pyjo.IOLoop.Stdio - Standard input and output as a stream
3
+ ==========================================================
4
+ ::
5
+
6
+ from Pyjo.IOLoop.Stdio import Stdio
7
+
8
+ # Echo what comes in
9
+ stdio = Stdio()
10
+ stdio.on('read', lambda stdio, chunk: stdio.write(chunk))
11
+
12
+ # Or import the module, as in Google Python Style Guide
13
+
14
+ import Pyjo.IOLoop.Stdio
15
+
16
+ # Echo what comes in, and end once the input has ended and the rest is written
17
+ stdio = Pyjo.IOLoop.Stdio.new()
18
+ stdio.on('read', lambda stdio, chunk: stdio.write(chunk))
19
+ stdio.on('close', lambda stdio: stdio.reactor.stop())
20
+ stdio.start()
21
+
22
+ # Start reactor if necessary
23
+ if not stdio.reactor.is_running:
24
+ stdio.reactor.start()
25
+
26
+ :mod:`Pyjo.IOLoop.Stdio` is the standard input and output of the process as
27
+ one :mod:`Pyjo.IOLoop.Stream`, which reads the file descriptor ``0`` and
28
+ writes the file descriptor ``1``, as a connection of TCP is read and written,
29
+ so that a protocol does not tell them apart: the messages of a protocol, as
30
+ JSON-RPC or a protocol of lines, come in on the standard input and go out on
31
+ the standard output, and the log goes to the standard error.
32
+
33
+ The stream takes the standard input and output for itself while it is open:
34
+ it reads and writes duplicates of them, the file descriptor ``0`` reads
35
+ ``/dev/null``, and the file descriptor ``1`` and :data:`sys.stdout` write to
36
+ the standard error, so that a stray ``print``, a library, or a process that
37
+ inherits them reads and writes nothing of the protocol. Closing the stream
38
+ gives them back.
39
+
40
+ A pipe or a socket is read and written in the non-blocking mode, which belongs
41
+ to the open file description that the process shares with its parent, so the
42
+ stream gives back the mode that it had when it closes; a terminal is read and
43
+ written without it. The reactor watches pipes, sockets and terminals, and a
44
+ file, or ``/dev/null``, which a reactor of epoll does not watch, is read in
45
+ the next cycles of the event loop and written at once, as its reads and writes
46
+ do not block.
47
+
48
+ The stream has no inactivity timeout by default, as the peer of a server of
49
+ standard input and output may send nothing for a long time.
50
+
51
+ The reactor watches pipes on POSIX systems, not on Windows, where the
52
+ constructor raises an exception, and only one stream at a time has the
53
+ standard input and output.
54
+
55
+ The end of a ``with`` block closes the stream.
56
+
57
+ Events
58
+ ------
59
+
60
+ :mod:`Pyjo.IOLoop.Stdio` inherits all events from :mod:`Pyjo.IOLoop.Stream`
61
+ and can emit the following new ones.
62
+
63
+ end
64
+ ~~~
65
+ ::
66
+
67
+ stdio.on('end', lambda stdio: ...)
68
+
69
+ Emitted once the standard input has ended, while the standard output can still
70
+ be written, as the responses of the requests that are in progress. Without a
71
+ subscriber of this event, the stream closes gracefully, once all data has been
72
+ written, as a connection of TCP that the peer closes; with one, the subscriber
73
+ closes it. ::
74
+
75
+ stdio.on('end', lambda stdio: stdio.close_gracefully())
76
+
77
+ See also
78
+ --------
79
+
80
+ :mod:`Pyjo.IOLoop.Stream`, :mod:`Pyjo.IOLoop.ChildProcess`, :mod:`Pyjoyment`,
81
+ https://mojolicious.org.
82
+
83
+ Inspired by ``process.stdin`` and ``process.stdout`` of Node.js, as
84
+ Mojolicious has no stream of the standard input and output.
85
+
86
+ Classes
87
+ -------
88
+ """
89
+
90
+ import Pyjo.Exception
91
+ import Pyjo.IOLoop.Stream
92
+
93
+ import os
94
+ import stat
95
+ import sys
96
+ import weakref
97
+
98
+ from typing import IO, Any, Self
99
+
100
+
101
+ # The stream that has the standard input and output, as there is one of them
102
+ _open: weakref.ref[Any] | None = None
103
+
104
+
105
+ class Pyjo_IOLoop_Stdio(Pyjo.IOLoop.Stream.object):
106
+ """
107
+ :mod:`Pyjo.IOLoop.Stdio` inherits all attributes and methods from
108
+ :mod:`Pyjo.IOLoop.Stream` and implements the following new ones.
109
+ """
110
+
111
+ def __init__(self) -> None:
112
+ """::
113
+
114
+ stdio = Pyjo.IOLoop.Stdio.new()
115
+
116
+ Construct a new :mod:`Pyjo.IOLoop.Stdio` object, which takes the
117
+ standard input and output.
118
+ """
119
+ global _open
120
+ if os.name == 'nt':
121
+ raise Pyjo.Exception.new('Standard input and output need pipes that the reactor watches')
122
+ if _open is not None and (stdio := _open()) is not None and stdio.handle is not None:
123
+ raise Pyjo.Exception.new('Standard input and output are a stream already')
124
+
125
+ # What Python has kept for the file descriptor 1 goes there before it changes
126
+ if sys.stdout is not None:
127
+ sys.stdout.flush()
128
+ input, output = os.dup(0), os.dup(1)
129
+ self._blocking: dict[int, bool] = {}
130
+ for fd in (input, output):
131
+ if _is_pipe(fd):
132
+ self._blocking[fd] = os.get_blocking(fd)
133
+ os.set_blocking(fd, False)
134
+
135
+ # Nothing else reads the input or writes the output of the protocol
136
+ null = os.open(os.devnull, os.O_RDONLY)
137
+ os.dup2(null, 0)
138
+ os.close(null)
139
+ os.dup2(2, 1)
140
+ self._stdout = sys.stdout
141
+ sys.stdout = sys.stderr
142
+
143
+ super().__init__(os.fdopen(input, 'rb', buffering=0))
144
+ self._timeout = 0
145
+ self._ended = False
146
+ self._reading: str | None = None
147
+ self._registered = False
148
+ self._watched = _is_watched(input)
149
+ self._output = _Output(os.fdopen(output, 'wb', buffering=0), _is_watched(output))
150
+ self._output.timeout = 0
151
+ ref = weakref.ref(self)
152
+
153
+ def drain(output: Any) -> None:
154
+ if (stdio := ref()) is not None:
155
+ stdio._drained()
156
+
157
+ def write(output: Any, chunk: bytes) -> None:
158
+ if (stdio := ref()) is not None:
159
+ stdio.emit('write', chunk)._again()
160
+
161
+ def hangup(output: Any) -> None:
162
+ if (stdio := ref()) is not None:
163
+ stdio.close()
164
+
165
+ self._output.on('drain', drain)
166
+ self._output.on('write', write)
167
+ self._output.on('hangup', hangup)
168
+ _open = ref
169
+
170
+ @property
171
+ def bytes_waiting(self) -> int:
172
+ return self._output.bytes_waiting
173
+
174
+ @property
175
+ def bytes_written(self) -> int:
176
+ return self._output.bytes_written
177
+
178
+ @property
179
+ def can_write(self) -> bool:
180
+ return self._handle is not None and self.bytes_waiting < self.high_water_mark
181
+
182
+ def close(self) -> None:
183
+ """::
184
+
185
+ stdio.close()
186
+
187
+ Close stream immediately, and give back the standard input and output.
188
+ """
189
+ if self._handle is None:
190
+ return
191
+ self._restore()
192
+ if (timer := self._reading) is not None:
193
+ self._reading = None
194
+ if self._reactor is not None and (reactor := self._reactor()) is not None:
195
+ reactor.remove(timer)
196
+ self._output.close()
197
+ super().close()
198
+
199
+ def end(self) -> None:
200
+ """::
201
+
202
+ stdio.end()
203
+
204
+ End the standard input before its end, as at its end: stop reading it
205
+ and emit the event ``end``, or close the stream gracefully without a
206
+ subscriber, so that a server stops as when its peer ends. ::
207
+
208
+ # Stop gracefully on a signal
209
+ signal.signal(signal.SIGTERM, lambda signum, frame: stdio.reactor.next_tick(lambda reactor: stdio.end()))
210
+ """
211
+ if self._handle is not None and not self._ended:
212
+ self._end()
213
+
214
+ @property
215
+ def is_writing(self) -> bool:
216
+ if self._handle is None:
217
+ return False
218
+ return bool(self._output.bytes_waiting) or self.has_subscribers('drain')
219
+
220
+ def start(self) -> None:
221
+ """::
222
+
223
+ stdio.start()
224
+
225
+ Start or resume watching for new data on the standard input.
226
+ """
227
+ if (handle := self._handle) is None:
228
+ return
229
+ reactor = self.reactor
230
+ output = self._output
231
+ output.reactor = reactor
232
+ output.start()
233
+ if self.has_subscribers('drain'):
234
+ output.flush()
235
+ self._paused = False
236
+ if self._ended:
237
+ return
238
+ if not self._watched:
239
+ return self._read_later()
240
+ if self._registered:
241
+ reactor.watch(handle, True, False)
242
+ return
243
+ ref = weakref.ref(self)
244
+
245
+ def cb(reactor: Any, writable: bool) -> None:
246
+ if (stdio := ref()) is not None:
247
+ stdio._read_handle()
248
+
249
+ reactor.io(handle, cb)
250
+ self._registered = True
251
+
252
+ def stop(self) -> None:
253
+ """::
254
+
255
+ stdio.stop()
256
+
257
+ Stop watching for new data on the standard input.
258
+ """
259
+ if (handle := self._handle) is None or self._paused:
260
+ return
261
+ self._paused = True
262
+ if self._registered:
263
+ self.reactor.watch(handle, False, False)
264
+
265
+ def write(self, chunk: bytes, cb: Pyjo.IOLoop.Stream.DrainCallback | None = None) -> Self:
266
+ """::
267
+
268
+ stdio = stdio.write(chunk)
269
+ stdio = stdio.write(chunk, lambda stdio: ...)
270
+
271
+ Enqueue data to be written to the standard output as soon as possible,
272
+ the optional drain callback will be executed once all data has been
273
+ written.
274
+ """
275
+ if cb is not None:
276
+ self.once('drain', cb)
277
+ self._output.write(chunk)
278
+ if self.has_subscribers('drain'):
279
+ self._output.flush()
280
+ return self
281
+
282
+ def _drained(self) -> None:
283
+ self.emit('drain')
284
+ if self._graceful and not self.is_writing:
285
+ self.close()
286
+
287
+ def _end(self) -> None:
288
+ self._ended = True
289
+ if self._registered and self._handle is not None:
290
+ self._registered = False
291
+ self.reactor.remove(self._handle)
292
+ if not self.has_subscribers('end'):
293
+ return self.close_gracefully()
294
+ self.emit('end')
295
+
296
+ def _read_handle(self) -> None:
297
+ if (handle := self._handle) is None or self._ended:
298
+ return
299
+ try:
300
+ chunk = os.read(handle.fileno(), 131072)
301
+ except (BlockingIOError, InterruptedError):
302
+ return
303
+ except OSError as e:
304
+ if isinstance(e, ConnectionResetError):
305
+ return self.close()
306
+ return self.emit('error', e).close()
307
+ if not len(chunk):
308
+ return self._end()
309
+ self._read += len(chunk)
310
+ self.emit('read', chunk)._again()
311
+
312
+ # A file, which the reactor may not watch, is read in the next cycles of the event loop, as its reads do not block
313
+ def _read_later(self) -> None:
314
+ if self._reading is not None:
315
+ return
316
+ ref = weakref.ref(self)
317
+
318
+ def read(reactor: Any) -> None:
319
+ if (stdio := ref()) is None:
320
+ return
321
+ stdio._reading = None
322
+ if stdio._paused or stdio._ended or stdio._handle is None:
323
+ return
324
+ stdio._read_handle()
325
+ if not stdio._paused and not stdio._ended and stdio._handle is not None:
326
+ stdio._read_later()
327
+
328
+ self._reading = self.reactor.timer(0, read)
329
+
330
+ def _restore(self) -> None:
331
+ output = self._output.handle
332
+ if output is not None:
333
+ os.dup2(output.fileno(), 1)
334
+ os.dup2(self._handle.fileno(), 0)
335
+ for fd, blocking in self._blocking.items():
336
+ os.set_blocking(fd, blocking)
337
+ if sys.stdout is not None:
338
+ sys.stdout.flush()
339
+ sys.stdout = self._stdout
340
+
341
+
342
+ # The standard output, which the reactor writes and does not read, so that the stream closes when a write fails, where a
343
+ # stream finds an error when it reads, and which is written at once when it is a file, which the reactor may not watch.
344
+ # It does not close itself, as the stream gives back the descriptors and their modes first
345
+ class _Output(Pyjo.IOLoop.Stream.object):
346
+ def __init__(self, handle: IO[bytes], watched: bool) -> None:
347
+ super().__init__(handle)
348
+ self._watched = watched
349
+ self._started = False
350
+ self._writing: str | None = None
351
+
352
+ def close(self) -> None:
353
+ if (timer := self._writing) is not None:
354
+ self._writing = None
355
+ if self._reactor is not None and (reactor := self._reactor()) is not None:
356
+ reactor.remove(timer)
357
+ super().close()
358
+
359
+ def flush(self) -> None:
360
+ if (handle := self._handle) is None or not self._started:
361
+ return
362
+ if not self._watched:
363
+ return self._write_later()
364
+ self.reactor.watch(handle, False, True)
365
+
366
+ def start(self) -> None:
367
+ if self._started:
368
+ return
369
+ self._started = True
370
+ if not self._watched:
371
+ self.timeout = self._timeout
372
+ if self._buffer or self.has_subscribers('drain'):
373
+ self._write_later()
374
+ return
375
+ super().start()
376
+ self.reactor.watch(self._handle, False, self.is_writing)
377
+
378
+ def stop(self) -> None:
379
+ return
380
+
381
+ def write(self, chunk: bytes, cb: Pyjo.IOLoop.Stream.DrainCallback | None = None) -> Self:
382
+ self._buffer += chunk
383
+ if cb is not None:
384
+ self.once('drain', cb)
385
+ if self._buffer:
386
+ self.flush()
387
+ return self
388
+
389
+ # A hang-up or an error of the pipe, which a reactor of poll reports also when it watches for nothing, as the peer
390
+ # has closed its end
391
+ def _read_handle(self) -> None:
392
+ self.emit('hangup')
393
+
394
+ def _write(self) -> None:
395
+ handle = self._handle
396
+ if len(self._buffer):
397
+ try:
398
+ written = os.write(handle.fileno(), self._buffer)
399
+ except (BlockingIOError, InterruptedError):
400
+ return
401
+
402
+ # The peer has closed its end
403
+ except OSError:
404
+ self.emit('hangup')
405
+ return
406
+ self._written += written
407
+ chunk = bytes(self._buffer[:written])
408
+ del self._buffer[:written]
409
+ self.emit('write', chunk)._again()
410
+
411
+ if not len(self._buffer):
412
+ self.emit('drain')
413
+ if self._handle is not None and self._watched and not self.is_writing:
414
+ self.reactor.watch(self._handle, False, False)
415
+
416
+ def _write_later(self) -> None:
417
+ if self._writing is not None:
418
+ return
419
+ ref = weakref.ref(self)
420
+
421
+ def write(reactor: Any) -> None:
422
+ if (output := ref()) is None:
423
+ return
424
+ output._writing = None
425
+ while output._handle is not None and output._buffer:
426
+ output._write()
427
+ if output._handle is not None and output.has_subscribers('drain'):
428
+ output._write()
429
+
430
+ self._writing = self.reactor.timer(0, write)
431
+
432
+
433
+ # A pipe or a socket, whose mode the stream changes
434
+ def _is_pipe(fd: int) -> bool:
435
+ mode = os.fstat(fd).st_mode
436
+ return stat.S_ISFIFO(mode) or stat.S_ISSOCK(mode)
437
+
438
+
439
+ # A pipe, a socket or a terminal, which every reactor watches, where a reactor of epoll watches no file and no device
440
+ def _is_watched(fd: int) -> bool:
441
+ return _is_pipe(fd) or os.isatty(fd)
442
+
443
+
444
+ new = Pyjo_IOLoop_Stdio.new
445
+ object = Pyjo_IOLoop_Stdio
446
+ Stdio = Pyjo_IOLoop_Stdio