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