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/Stream.py ADDED
@@ -0,0 +1,644 @@
1
+ """
2
+ Pyjo.IOLoop.Stream - Non-blocking I/O stream
3
+ ============================================
4
+ ::
5
+
6
+ from Pyjo.IOLoop.Stream import Stream
7
+
8
+ # Create stream
9
+ stream = Stream(handle)
10
+ stream.on('read', lambda stream, chunk: ...)
11
+ stream.on('close', lambda stream: ...)
12
+ stream.on('error', lambda stream, err: ...)
13
+
14
+ # Or import the module, as in Google Python Style Guide
15
+
16
+ import Pyjo.IOLoop.Stream
17
+
18
+ # Create stream
19
+ stream = Pyjo.IOLoop.Stream.new(handle)
20
+ stream.on('read', lambda stream, chunk: ...)
21
+ stream.on('close', lambda stream: ...)
22
+ stream.on('error', lambda stream, err: ...)
23
+
24
+ # Start and stop watching for new data
25
+ stream.start()
26
+ stream.stop()
27
+
28
+ # Start reactor if necessary
29
+ if not stream.reactor.is_running:
30
+ stream.reactor.start()
31
+
32
+ :mod:`Pyjo.IOLoop.Stream` is a container for I/O streams used by
33
+ :mod:`Pyjo.IOLoop`. A handle is a socket, an :class:`ssl.SSLSocket` too, or
34
+ anything else with a file descriptor, like a pipe.
35
+
36
+ The end of a ``with`` block closes the stream.
37
+
38
+ Events
39
+ ------
40
+
41
+ :mod:`Pyjo.IOLoop.Stream` inherits all events from :mod:`Pyjo.EventEmitter`
42
+ and can emit the following new ones.
43
+
44
+ close
45
+ ~~~~~
46
+ ::
47
+
48
+ stream.on('close', lambda stream: ...)
49
+
50
+ Emitted if the stream gets closed.
51
+
52
+ drain
53
+ ~~~~~
54
+ ::
55
+
56
+ stream.on('drain', lambda stream: ...)
57
+
58
+ Emitted once all data has been written.
59
+
60
+ error
61
+ ~~~~~
62
+ ::
63
+
64
+ stream.on('error', lambda stream, err: ...)
65
+
66
+ Emitted if an error occurs on the stream, fatal if unhandled. The error is
67
+ the :class:`OSError` of the handle.
68
+
69
+ read
70
+ ~~~~
71
+ ::
72
+
73
+ stream.on('read', lambda stream, chunk: ...)
74
+
75
+ Emitted if new data arrives on the stream.
76
+
77
+ timeout
78
+ ~~~~~~~
79
+ ::
80
+
81
+ stream.on('timeout', lambda stream: ...)
82
+
83
+ Emitted if the stream has been inactive for too long and will get closed
84
+ automatically.
85
+
86
+ write
87
+ ~~~~~
88
+ ::
89
+
90
+ stream.on('write', lambda stream, chunk: ...)
91
+
92
+ Emitted if new data has been written to the stream.
93
+
94
+ See also
95
+ --------
96
+
97
+ :mod:`Pyjoyment`, https://mojolicious.org.
98
+
99
+ Ported from `Mojo::IOLoop::Stream <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/IOLoop/Stream.pm>`_
100
+ of Mojolicious 9.49.
101
+ """
102
+
103
+ import Pyjo.EventEmitter
104
+ import Pyjo.IOLoop
105
+ import Pyjo.Reactor
106
+
107
+ import builtins
108
+ import os
109
+ import select
110
+ import ssl
111
+ import sys
112
+ import weakref
113
+
114
+ from collections.abc import AsyncIterator, Callable
115
+ from types import TracebackType
116
+ from typing import TYPE_CHECKING, Any, Self
117
+
118
+
119
+ if TYPE_CHECKING:
120
+ import Pyjo.Promise
121
+
122
+
123
+ type Anything = builtins.object
124
+
125
+ # A callback of a write gets the stream once all data has been written
126
+ type DrainCallback = Callable[[Any], Anything]
127
+
128
+
129
+ class Pyjo_IOLoop_Stream(Pyjo.EventEmitter.object):
130
+ """
131
+ :mod:`Pyjo.IOLoop.Stream` inherits all methods from
132
+ :mod:`Pyjo.EventEmitter` and implements the following new ones.
133
+ """
134
+
135
+ def __init__(self, handle: Any = None) -> None:
136
+ """::
137
+
138
+ stream = Pyjo.IOLoop.Stream.new(handle)
139
+
140
+ Construct a new :mod:`Pyjo.IOLoop.Stream` object.
141
+ """
142
+ super().__init__()
143
+
144
+ self.high_water_mark = 1048576
145
+ """::
146
+
147
+ size = stream.high_water_mark
148
+ stream.high_water_mark = 1024
149
+
150
+ Maximum size of :meth:`write` buffer in bytes before :attr:`can_write`
151
+ is false, defaults to ``1048576`` (1MiB).
152
+ """
153
+
154
+ # A bytearray, whose front is cut without copying the rest, as substr of Perl does
155
+ self._buffer = bytearray()
156
+ self._graceful = False
157
+ self._handle: Any = handle
158
+ self._paused = False
159
+ self._read = 0
160
+ self._reactor: weakref.ref[Pyjo.Reactor.object] | None = None
161
+ self._timeout: float = 15
162
+ self._timer: str | None = None
163
+ self._written = 0
164
+
165
+ def __del__(self) -> None:
166
+ # Module globals may be gone at the end of the interpreter
167
+ try:
168
+ if not sys.is_finalizing() and getattr(self, '_handle', None) is not None:
169
+ reactor = self._reactor() if self._reactor is not None else None
170
+ Pyjo.Reactor._finalize(self.close, reactor) # pyright: ignore[reportPrivateUsage]
171
+ except Exception:
172
+ pass
173
+
174
+ # Not in Mojolicious: a context manager, which closes the stream where the block ends, as PyPy frees the object only
175
+ # when its garbage collector runs
176
+ def __enter__(self) -> Self:
177
+ return self
178
+
179
+ def __exit__(
180
+ self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
181
+ ) -> None:
182
+ self.close()
183
+
184
+ # Not in Mojolicious: an asynchronous iterator of the chunks, as the one of the streams of Node.js
185
+ def __aiter__(self) -> AsyncIterator[bytes]:
186
+ """::
187
+
188
+ async for chunk in stream:
189
+ ...
190
+
191
+ Same as :meth:`iterate`.
192
+ """
193
+ return self.iterate()
194
+
195
+ @property
196
+ def reactor(self) -> Pyjo.Reactor.object:
197
+ """::
198
+
199
+ reactor = stream.reactor
200
+ stream.reactor = Pyjo.Reactor.Poll.new()
201
+
202
+ Low-level event reactor, defaults to the ``reactor`` attribute value of
203
+ the global :mod:`Pyjo.IOLoop` singleton. Note that this attribute is
204
+ weakened.
205
+ """
206
+ if self._reactor is None:
207
+ self._reactor = weakref.ref(Pyjo.IOLoop.singleton.reactor)
208
+ if (reactor := self._reactor()) is None:
209
+ raise ReferenceError('weakly-referenced object no longer exists')
210
+ return reactor
211
+
212
+ @reactor.setter
213
+ def reactor(self, value: Pyjo.Reactor.object) -> None:
214
+ self._reactor = weakref.ref(value)
215
+
216
+ @property
217
+ def bytes_read(self) -> int:
218
+ """::
219
+
220
+ num = stream.bytes_read
221
+
222
+ Number of bytes received.
223
+ """
224
+ return self._read
225
+
226
+ @property
227
+ def bytes_waiting(self) -> int:
228
+ """::
229
+
230
+ num = stream.bytes_waiting
231
+
232
+ Number of bytes that have been enqueued with :meth:`write` and are
233
+ waiting to be written.
234
+ """
235
+ return len(self._buffer)
236
+
237
+ @property
238
+ def bytes_written(self) -> int:
239
+ """::
240
+
241
+ num = stream.bytes_written
242
+
243
+ Number of bytes written.
244
+ """
245
+ return self._written
246
+
247
+ @property
248
+ def can_write(self) -> bool:
249
+ """::
250
+
251
+ boolean = stream.can_write
252
+
253
+ True if calling :meth:`write` is safe.
254
+ """
255
+ return self._handle is not None and self.bytes_waiting < self.high_water_mark
256
+
257
+ def close(self) -> None:
258
+ """::
259
+
260
+ stream.close()
261
+
262
+ Close stream immediately.
263
+ """
264
+ reactor = self._reactor() if self._reactor is not None else Pyjo.IOLoop.singleton.reactor
265
+ if reactor is None:
266
+ # Not in Mojolicious: the handle of a stream whose reactor is gone is closed, as Perl frees it with the
267
+ # stream
268
+ if (handle := self._handle) is not None:
269
+ self._handle = None
270
+ handle.close()
271
+ return
272
+ self.timeout = 0
273
+ if (handle := self._handle) is None:
274
+ return
275
+ self._handle = None
276
+ reactor.remove(handle)
277
+ # Perl closes the handle it frees at once, Python later, as PyPy does when its garbage collector runs
278
+ handle.close()
279
+ self.emit('close')
280
+
281
+ def close_gracefully(self) -> None:
282
+ """::
283
+
284
+ stream.close_gracefully()
285
+
286
+ Close stream gracefully.
287
+ """
288
+ if self.is_writing:
289
+ self._graceful = True
290
+ else:
291
+ self.close()
292
+
293
+ # Not in Mojolicious: the end of a graceful close, as end of promise-writable, which subscribes the event close before
294
+ # the close, which comes at once when nothing is waiting to be written
295
+ def close_gracefully_p(self) -> 'Pyjo.Promise.object':
296
+ """::
297
+
298
+ promise = stream.close_gracefully_p()
299
+
300
+ Same as :meth:`close_gracefully`, but returns a :mod:`Pyjo.Promise`
301
+ object, which is fulfilled once the stream has been closed, also when it
302
+ is closed already. ::
303
+
304
+ await stream.write_p(b'Bye!')
305
+ await stream.close_gracefully_p()
306
+ """
307
+ import Pyjo.Promise
308
+
309
+ if self._handle is None:
310
+ return Pyjo.Promise.resolve()
311
+ promise = self.once_p('close')
312
+ self.close_gracefully()
313
+ return promise
314
+
315
+ @property
316
+ def handle(self) -> Any:
317
+ """::
318
+
319
+ handle = stream.handle
320
+
321
+ Get handle for stream, usually a :class:`socket.socket` or
322
+ :class:`ssl.SSLSocket` object.
323
+ """
324
+ return self._handle
325
+
326
+ @property
327
+ def is_readable(self) -> bool:
328
+ """::
329
+
330
+ boolean = stream.is_readable
331
+
332
+ Quick non-blocking check if stream is readable, useful for identifying
333
+ tainted sockets.
334
+ """
335
+ self._again()
336
+ if (handle := self._handle) is None:
337
+ return False
338
+ return bool(select.select([handle], [], [], 0)[0])
339
+
340
+ @property
341
+ def is_writing(self) -> bool:
342
+ """::
343
+
344
+ boolean = stream.is_writing
345
+
346
+ Check if stream is writing.
347
+ """
348
+ if self._handle is None:
349
+ return False
350
+ return bool(len(self._buffer)) or self.has_subscribers('drain')
351
+
352
+ # Not in Mojolicious: the chunks as an asynchronous iterator, as iterate of promise-readable
353
+ def iterate(self, size: int | None = None) -> AsyncIterator[bytes]:
354
+ """::
355
+
356
+ async for chunk in stream.iterate():
357
+ ...
358
+ async for chunk in stream.iterate(1024):
359
+ ...
360
+
361
+ The chunks as an asynchronous iterator, at most ``size`` bytes each,
362
+ until the end of the stream, which :meth:`read_p` takes. ::
363
+
364
+ # Echo in capitals
365
+ async for chunk in stream:
366
+ await stream.write_p(chunk.upper())
367
+ """
368
+ return self._chunks().iterate(size)
369
+
370
+ # Not in Mojolicious: the event close of a stream that is closed already, as once of promise-writable
371
+ def once_p(self, name: str) -> 'Pyjo.Promise.object':
372
+ """::
373
+
374
+ promise = stream.once_p('close')
375
+
376
+ Same as :meth:`~Pyjo.EventEmitter.Pyjo_EventEmitter.once_p` of
377
+ :mod:`Pyjo.EventEmitter`, but fulfilled at once for the event
378
+ ``close`` of a stream that is closed already, as it comes no more.
379
+ """
380
+ import Pyjo.Promise
381
+
382
+ if name == 'close' and self._handle is None:
383
+ return Pyjo.Promise.resolve()
384
+ return super().once_p(name)
385
+
386
+ # Not in Mojolicious: everything until the end, as readAll of promise-readable
387
+ def read_all_p(self) -> 'Pyjo.Promise.object':
388
+ """::
389
+
390
+ promise = stream.read_all_p()
391
+
392
+ Return a :mod:`Pyjo.Promise` object, which is fulfilled with all the
393
+ data of the stream until its end as a :mod:`Pyjo.String.Bytes`
394
+ object, as :meth:`read_p` takes it. ::
395
+
396
+ data = await stream.read_all_p()
397
+ """
398
+ # Imported here, as Pyjo.Promise uses Pyjo.IOLoop
399
+ import Pyjo.Promise
400
+ import Pyjo.String.Bytes
401
+
402
+ # The first chunk is asked for at once, as the reactor of asyncio runs a coroutine later, when the stream may have
403
+ # read it already
404
+ first = self.read_p()
405
+
406
+ async def read() -> Pyjo.String.Bytes.object:
407
+ chunks: list[bytes] = []
408
+ chunk = await first
409
+ while chunk is not None:
410
+ chunks.append(chunk)
411
+ chunk = await self.read_p()
412
+ return Pyjo.String.Bytes.new(b''.join(chunks))
413
+
414
+ return Pyjo.Promise.new(read())
415
+
416
+ # Not in Mojolicious: the next chunk, as read of promise-readable, which the stream reads only while a promise waits
417
+ def read_p(self, size: int | None = None) -> 'Pyjo.Promise.object':
418
+ """::
419
+
420
+ promise = stream.read_p()
421
+ promise = stream.read_p(1024)
422
+
423
+ Return a :mod:`Pyjo.Promise` object, which is fulfilled with the next
424
+ chunk of the stream, at most ``size`` bytes, or ``None`` at its end,
425
+ the event ``close``, or ``end`` of :mod:`Pyjo.IOLoop.Stdio`, or when it
426
+ is closed already, and rejected with the error of the event ``error``,
427
+ or a :class:`TimeoutError` when the stream has been inactive for too
428
+ long. The stream reads only
429
+ while a promise waits, and stops in between, so that a chunk that
430
+ comes before is kept for the next promise, and a stream that is read
431
+ no more stops reading, as the streams of Node.js in their paused mode.
432
+ The subscribers of the event ``read`` get the chunks that the stream
433
+ reads then. ::
434
+
435
+ # Read until the end
436
+ while (chunk := await stream.read_p()) is not None:
437
+ print(chunk)
438
+ """
439
+ return self._chunks().next_p(size)
440
+
441
+ def start(self) -> None:
442
+ """::
443
+
444
+ stream.start()
445
+
446
+ Start or resume watching for new data on the stream.
447
+ """
448
+ # Resume
449
+ if (handle := self._handle) is None:
450
+ return
451
+ reactor = self.reactor
452
+ if self._paused:
453
+ self._paused = False
454
+ reactor.watch(handle, True, self.is_writing)
455
+ return
456
+
457
+ ref = weakref.ref(self)
458
+
459
+ def cb(reactor: Any, writable: bool) -> None:
460
+ if (stream := ref()) is not None:
461
+ stream._write() if writable else stream._read_handle()
462
+
463
+ self.timeout = self._timeout
464
+ reactor.io(handle, cb)
465
+
466
+ def steal_handle(self) -> Any:
467
+ """::
468
+
469
+ handle = stream.steal_handle()
470
+
471
+ Steal :attr:`handle` and prevent it from getting closed automatically.
472
+ """
473
+ handle, self._handle = self._handle, None
474
+ if handle is not None:
475
+ self.reactor.remove(handle)
476
+ return handle
477
+
478
+ def stop(self) -> None:
479
+ """::
480
+
481
+ stream.stop()
482
+
483
+ Stop watching for new data on the stream.
484
+ """
485
+ if self._handle is not None and not self._paused:
486
+ self._paused = True
487
+ self.reactor.watch(self._handle, False, self.is_writing)
488
+
489
+ @property
490
+ def timeout(self) -> float:
491
+ """::
492
+
493
+ timeout = stream.timeout
494
+ stream.timeout = 45
495
+
496
+ Maximum amount of time in seconds stream can be inactive before getting
497
+ closed automatically, defaults to ``15``. Setting the value to ``0``
498
+ will allow this stream to be inactive indefinitely.
499
+ """
500
+ return self._timeout
501
+
502
+ @timeout.setter
503
+ def timeout(self, value: float) -> None:
504
+ self._timeout = value
505
+
506
+ reactor = self.reactor
507
+ if self._timer is not None:
508
+ if not self._timeout:
509
+ timer, self._timer = self._timer, None
510
+ reactor.remove(timer)
511
+ else:
512
+ reactor.again(self._timer, self._timeout)
513
+
514
+ elif self._timeout:
515
+ ref = weakref.ref(self)
516
+
517
+ def timeout(reactor: Any) -> None:
518
+ if (stream := ref()) is not None and stream._timer is not None:
519
+ stream._timer = None
520
+ stream.emit('timeout').close()
521
+
522
+ self._timer = reactor.timer(value, timeout)
523
+
524
+ def write(self, chunk: bytes, cb: DrainCallback | None = None) -> Self:
525
+ """::
526
+
527
+ stream = stream.write(chunk)
528
+ stream = stream.write(chunk, lambda stream: ...)
529
+
530
+ Enqueue data to be written to the stream as soon as possible, the
531
+ optional drain callback will be executed once all data has been
532
+ written.
533
+ """
534
+ # Left out from Mojolicious: utf8::downgrade, as the chunk is bytes already
535
+ self._buffer += chunk
536
+ if cb is not None:
537
+ self.once('drain', cb)
538
+ elif not len(self._buffer):
539
+ return self
540
+ if self._handle is not None:
541
+ self.reactor.watch(self._handle, not self._paused, True)
542
+
543
+ return self
544
+
545
+ # Not in Mojolicious: the end of a write, as write of promise-writable
546
+ def write_p(self, chunk: bytes) -> 'Pyjo.Promise.object':
547
+ """::
548
+
549
+ promise = stream.write_p(chunk)
550
+
551
+ Same as :meth:`write`, but returns a :mod:`Pyjo.Promise` object, which
552
+ is fulfilled once all data has been written, and rejected if the stream
553
+ closes before, or is closed already, or with a :class:`TimeoutError`
554
+ when it has been inactive for too long. ::
555
+
556
+ await stream.write_p(b'Hello World!')
557
+ """
558
+ import Pyjo.Exception
559
+ import Pyjo.Promise
560
+
561
+ if self._handle is None:
562
+ return Pyjo.Promise.reject(Pyjo.Exception.new('Stream closed before all data has been written'))
563
+ promise = Pyjo.Promise.new()
564
+
565
+ def closed(stream: Any) -> None:
566
+ self.unsubscribe('timeout', inactive)
567
+ promise.reject(Pyjo.Exception.new('Stream closed before all data has been written'))
568
+
569
+ def timeout(stream: Any) -> None:
570
+ self.unsubscribe('close', wrapper)
571
+ promise.reject(TimeoutError('Inactivity timeout'))
572
+
573
+ wrapper = self.once('close', closed)
574
+ inactive = self.once('timeout', timeout)
575
+
576
+ def drained(stream: Any) -> None:
577
+ self.unsubscribe('close', wrapper).unsubscribe('timeout', inactive)
578
+ promise.resolve()
579
+
580
+ self.write(chunk, drained)
581
+ return self._hold(promise)
582
+
583
+ # Not in Mojolicious: the pull of the chunks, which a stream that is closed already has ended
584
+ def _chunks(self) -> Any:
585
+ pull = self._pull('read', True)
586
+ if self._handle is None:
587
+ pull.end()
588
+ return pull
589
+
590
+ def _again(self) -> None:
591
+ if self._timer is not None:
592
+ self.reactor.again(self._timer)
593
+
594
+ # Renamed from Mojolicious: _read, the attribute _read is the number of bytes read
595
+ def _read_handle(self) -> None:
596
+ handle = self._handle
597
+ try:
598
+ chunk = handle.recv(131072) if hasattr(handle, 'recv') else os.read(handle.fileno(), 131072)
599
+ except OSError as e:
600
+ # Retry
601
+ if isinstance(e, (BlockingIOError, InterruptedError, ssl.SSLWantReadError, ssl.SSLWantWriteError)):
602
+ return
603
+
604
+ # Closed (maybe real error)
605
+ # Unlike Mojolicious: EPIPE is a close too, as ECONNRESET is, as a read of TLS writes the session tickets
606
+ # of TLS 1.3 with OpenSSL 3.5, which fails when the peer is gone
607
+ if isinstance(e, (ConnectionResetError, BrokenPipeError)):
608
+ return self.close()
609
+ return self.emit('error', e).close()
610
+
611
+ self._read += len(chunk)
612
+ if not len(chunk):
613
+ return self.close()
614
+ self.emit('read', chunk)._again()
615
+
616
+ def _write(self) -> None:
617
+ # Handle errors only when reading (to avoid timing problems)
618
+ handle = self._handle
619
+ if len(self._buffer):
620
+ # A copy of the front of the buffer, as the socket of TLS of PyPy keeps the data that it sends exported, so
621
+ # that the buffer cannot change its size, and at most as much as a read, so that a write copies no more
622
+ data = bytes(self._buffer[:131072])
623
+ try:
624
+ written = handle.send(data) if hasattr(handle, 'send') else os.write(handle.fileno(), data)
625
+ except OSError:
626
+ return
627
+ self._written += written
628
+ chunk = data[:written]
629
+ del self._buffer[:written]
630
+ self.emit('write', chunk)._again()
631
+
632
+ if not len(self._buffer):
633
+ self.emit('drain')
634
+ if self.is_writing:
635
+ return
636
+ if self._graceful:
637
+ return self.close()
638
+ if self._handle is not None:
639
+ self.reactor.watch(self._handle, not self._paused, False)
640
+
641
+
642
+ new = Pyjo_IOLoop_Stream.new
643
+ object = Pyjo_IOLoop_Stream
644
+ Stream = Pyjo_IOLoop_Stream