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