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/Reactor/Poll.py
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pyjo.Reactor.Poll - Low-level event reactor with poll support
|
|
3
|
+
=============================================================
|
|
4
|
+
::
|
|
5
|
+
|
|
6
|
+
from Pyjo.Reactor.Poll import Poll
|
|
7
|
+
|
|
8
|
+
# Watch if handle becomes readable or writable
|
|
9
|
+
reactor = Poll()
|
|
10
|
+
reactor.io(first, lambda reactor, writable: print(
|
|
11
|
+
'First handle is writable' if writable else 'First handle is readable'
|
|
12
|
+
))
|
|
13
|
+
|
|
14
|
+
# Or import the module, as in Google Python Style Guide
|
|
15
|
+
|
|
16
|
+
import Pyjo.Reactor.Poll
|
|
17
|
+
|
|
18
|
+
# Watch if handle becomes readable or writable
|
|
19
|
+
reactor = Pyjo.Reactor.Poll.new()
|
|
20
|
+
reactor.io(first, lambda reactor, writable: print(
|
|
21
|
+
'First handle is writable' if writable else 'First handle is readable'
|
|
22
|
+
))
|
|
23
|
+
|
|
24
|
+
# Change to watching only if handle becomes writable
|
|
25
|
+
reactor.watch(first, False, True)
|
|
26
|
+
|
|
27
|
+
# Watch a file descriptor if it becomes readable
|
|
28
|
+
reactor.io(fd, lambda reactor, writable: print(
|
|
29
|
+
'Second handle is writable' if writable else 'Second handle is readable'
|
|
30
|
+
)).watch(fd, True, False)
|
|
31
|
+
|
|
32
|
+
# Add a timer
|
|
33
|
+
def timeout(reactor):
|
|
34
|
+
reactor.remove(first)
|
|
35
|
+
reactor.remove(fd)
|
|
36
|
+
print('Timeout!')
|
|
37
|
+
|
|
38
|
+
reactor.timer(15, timeout)
|
|
39
|
+
|
|
40
|
+
# Start reactor if necessary
|
|
41
|
+
if not reactor.is_running:
|
|
42
|
+
reactor.start()
|
|
43
|
+
|
|
44
|
+
:mod:`Pyjo.Reactor.Poll` is a low-level event reactor based on
|
|
45
|
+
:func:`select.poll`, without asyncio. Windows has no :func:`select.poll`,
|
|
46
|
+
and :func:`select.select` takes its place there, which watches sockets only,
|
|
47
|
+
not pipes or files, up to 512 of them. ``PYJO_REACTOR`` selects it for
|
|
48
|
+
:mod:`Pyjo.IOLoop`::
|
|
49
|
+
|
|
50
|
+
PYJO_REACTOR=Pyjo.Reactor.Poll
|
|
51
|
+
|
|
52
|
+
Events
|
|
53
|
+
------
|
|
54
|
+
|
|
55
|
+
:mod:`Pyjo.Reactor.Poll` inherits all events from :mod:`Pyjo.Reactor`.
|
|
56
|
+
|
|
57
|
+
See also
|
|
58
|
+
--------
|
|
59
|
+
|
|
60
|
+
:mod:`Pyjoyment`, https://mojolicious.org.
|
|
61
|
+
|
|
62
|
+
Ported from `Mojo::Reactor::Poll <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/Reactor/Poll.pm>`_
|
|
63
|
+
of Mojolicious 9.49.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
import Pyjo.Exception
|
|
67
|
+
import Pyjo.Reactor
|
|
68
|
+
|
|
69
|
+
from Pyjo.Reactor import Handle, IOCallback, TimerCallback
|
|
70
|
+
from Pyjo.Util import _call_hint, md5_sum # pyright: ignore[reportPrivateUsage]
|
|
71
|
+
|
|
72
|
+
import collections
|
|
73
|
+
import random
|
|
74
|
+
import select
|
|
75
|
+
import sys
|
|
76
|
+
import threading
|
|
77
|
+
import time
|
|
78
|
+
|
|
79
|
+
from typing import Any, Self, TypedDict
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class _IO(TypedDict):
|
|
83
|
+
cb: IOCallback
|
|
84
|
+
mode: int
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class _Timer(TypedDict):
|
|
88
|
+
cb: TimerCallback | None
|
|
89
|
+
after: float
|
|
90
|
+
recurring: bool
|
|
91
|
+
time: float
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class Pyjo_Reactor_Poll(Pyjo.Reactor.object):
|
|
95
|
+
"""
|
|
96
|
+
:mod:`Pyjo.Reactor.Poll` inherits all methods from :mod:`Pyjo.Reactor`
|
|
97
|
+
and implements the following new ones.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
def __init__(self) -> None:
|
|
101
|
+
"""::
|
|
102
|
+
|
|
103
|
+
reactor = Pyjo.Reactor.Poll.new()
|
|
104
|
+
|
|
105
|
+
Construct a new :mod:`Pyjo.Reactor.Poll` object.
|
|
106
|
+
"""
|
|
107
|
+
super().__init__()
|
|
108
|
+
self._io: dict[int, _IO] = {}
|
|
109
|
+
# A deque, whose popleft takes the first callback as shift of Perl does, where pop(0) of a list moves all the others
|
|
110
|
+
self._next_tick: collections.deque[TimerCallback] = collections.deque()
|
|
111
|
+
self._next_timer: str | None = None
|
|
112
|
+
self._running = 0
|
|
113
|
+
self._timers: dict[str, _Timer] = {}
|
|
114
|
+
|
|
115
|
+
def again(self, id: str, after: float | None = None) -> None:
|
|
116
|
+
"""::
|
|
117
|
+
|
|
118
|
+
reactor.again(id)
|
|
119
|
+
reactor.again(id, 0.5)
|
|
120
|
+
|
|
121
|
+
Restart timer and optionally change the invocation time. Note that this
|
|
122
|
+
method requires an active timer.
|
|
123
|
+
"""
|
|
124
|
+
if (timer := self._timers.get(id)) is None:
|
|
125
|
+
Pyjo.Exception.object.throw('Timer not active')
|
|
126
|
+
if after is not None:
|
|
127
|
+
timer['after'] = after
|
|
128
|
+
timer['time'] = time.monotonic() + timer['after']
|
|
129
|
+
|
|
130
|
+
def io(self, handle: Handle, cb: IOCallback) -> Self:
|
|
131
|
+
"""::
|
|
132
|
+
|
|
133
|
+
reactor = reactor.io(handle, cb)
|
|
134
|
+
|
|
135
|
+
Watch handle for I/O events, invoking the callback whenever handle
|
|
136
|
+
becomes readable or writable. ::
|
|
137
|
+
|
|
138
|
+
# Callback will be executed twice if handle becomes readable and writable
|
|
139
|
+
reactor.io(handle, lambda reactor, writable: print('Handle is writable' if writable else 'Handle is readable'))
|
|
140
|
+
"""
|
|
141
|
+
self._io[_fileno(handle)] = {'cb': cb, 'mode': 0}
|
|
142
|
+
return self.watch(handle, True, True)
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def is_running(self) -> bool:
|
|
146
|
+
"""::
|
|
147
|
+
|
|
148
|
+
boolean = reactor.is_running
|
|
149
|
+
|
|
150
|
+
Check if reactor is running.
|
|
151
|
+
"""
|
|
152
|
+
return bool(self._running)
|
|
153
|
+
|
|
154
|
+
def next_tick(self, cb: TimerCallback) -> None:
|
|
155
|
+
"""::
|
|
156
|
+
|
|
157
|
+
none = reactor.next_tick(cb)
|
|
158
|
+
|
|
159
|
+
Execute callback as soon as possible, but not before returning or other
|
|
160
|
+
callbacks that have been registered with this method, always returns
|
|
161
|
+
``None``.
|
|
162
|
+
"""
|
|
163
|
+
self._next_tick.append(cb)
|
|
164
|
+
if self._next_timer is None:
|
|
165
|
+
self._next_timer = self.timer(0, Pyjo_Reactor_Poll._next)
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
def one_tick(self) -> None:
|
|
169
|
+
"""::
|
|
170
|
+
|
|
171
|
+
reactor.one_tick()
|
|
172
|
+
|
|
173
|
+
Run reactor until an event occurs or no events are being watched
|
|
174
|
+
anymore. ::
|
|
175
|
+
|
|
176
|
+
# Don't block longer than 0.5 seconds
|
|
177
|
+
id = reactor.timer(0.5, lambda reactor: None)
|
|
178
|
+
reactor.one_tick()
|
|
179
|
+
reactor.remove(id)
|
|
180
|
+
"""
|
|
181
|
+
# Just one tick
|
|
182
|
+
running = self._running
|
|
183
|
+
if not running:
|
|
184
|
+
self._running = 1
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
# Wait for one event
|
|
188
|
+
i = 0
|
|
189
|
+
while not i and self._running:
|
|
190
|
+
# Not in Mojolicious: the cleanups that finalizers deferred, before the reactor waits
|
|
191
|
+
if Pyjo.Reactor._DEFERRED.get(threading.get_ident()): # pyright: ignore[reportPrivateUsage]
|
|
192
|
+
Pyjo.Reactor._drain() # pyright: ignore[reportPrivateUsage]
|
|
193
|
+
|
|
194
|
+
# Stop automatically if there is nothing to watch
|
|
195
|
+
if not len(self._timers) and not len(self._io):
|
|
196
|
+
return self.stop()
|
|
197
|
+
|
|
198
|
+
# Calculate ideal timeout based on timers and round up to next millisecond
|
|
199
|
+
# Not in Mojolicious: a copy of the timers, as a signal handler that calls next_tick adds one in the middle
|
|
200
|
+
first = min((t['time'] for t in list(self._timers.values())), default=None)
|
|
201
|
+
timeout = first - time.monotonic() if first is not None else 0.5
|
|
202
|
+
timeout = 0 if timeout <= 0 else int(timeout * 1000) + 1
|
|
203
|
+
|
|
204
|
+
# I/O
|
|
205
|
+
if len(self._io):
|
|
206
|
+
for fd, mode in _poll({fd: io['mode'] for fd, io in self._io.items()}, timeout):
|
|
207
|
+
if mode & (_POLLIN | _POLLPRI | _POLLNVAL | _POLLHUP | _POLLERR):
|
|
208
|
+
if (io := self._io.get(fd)) is None:
|
|
209
|
+
continue
|
|
210
|
+
i += 1
|
|
211
|
+
self._try('I/O watcher', io['cb'], False)
|
|
212
|
+
if not mode & _POLLOUT or (io := self._io.get(fd)) is None:
|
|
213
|
+
continue
|
|
214
|
+
i += 1
|
|
215
|
+
self._try('I/O watcher', io['cb'], True)
|
|
216
|
+
|
|
217
|
+
# Wait for timeout if poll can't be used
|
|
218
|
+
elif timeout:
|
|
219
|
+
time.sleep(timeout / 1000)
|
|
220
|
+
|
|
221
|
+
# Timers (time should not change in between timers)
|
|
222
|
+
now = time.monotonic()
|
|
223
|
+
for id in list(self._timers):
|
|
224
|
+
if (t := self._timers.get(id)) is None:
|
|
225
|
+
continue
|
|
226
|
+
if t['time'] > now:
|
|
227
|
+
continue
|
|
228
|
+
|
|
229
|
+
# Recurring timer
|
|
230
|
+
if t['recurring']:
|
|
231
|
+
t['time'] = now + t['after']
|
|
232
|
+
|
|
233
|
+
# Normal timer
|
|
234
|
+
else:
|
|
235
|
+
self.remove(id)
|
|
236
|
+
|
|
237
|
+
if (cb := t['cb']) is not None:
|
|
238
|
+
i += 1
|
|
239
|
+
self._try('Timer', cb)
|
|
240
|
+
finally:
|
|
241
|
+
if not running:
|
|
242
|
+
self._running = running
|
|
243
|
+
|
|
244
|
+
def recurring(self, after: float, cb: TimerCallback) -> str:
|
|
245
|
+
"""::
|
|
246
|
+
|
|
247
|
+
id = reactor.recurring(0.25, cb)
|
|
248
|
+
|
|
249
|
+
Create a new recurring timer, invoking the callback repeatedly after a
|
|
250
|
+
given amount of time in seconds.
|
|
251
|
+
"""
|
|
252
|
+
return self._timer(True, after, cb)
|
|
253
|
+
|
|
254
|
+
def remove(self, remove: Handle | str) -> bool:
|
|
255
|
+
"""::
|
|
256
|
+
|
|
257
|
+
boolean = reactor.remove(handle)
|
|
258
|
+
boolean = reactor.remove(id)
|
|
259
|
+
|
|
260
|
+
Remove handle or timer.
|
|
261
|
+
"""
|
|
262
|
+
if isinstance(remove, str):
|
|
263
|
+
return self._timers.pop(remove, None) is not None
|
|
264
|
+
return self._io.pop(_fileno(remove), None) is not None
|
|
265
|
+
|
|
266
|
+
def reset(self) -> None:
|
|
267
|
+
"""::
|
|
268
|
+
|
|
269
|
+
reactor.reset()
|
|
270
|
+
|
|
271
|
+
Remove all handles and timers.
|
|
272
|
+
"""
|
|
273
|
+
self._events = {}
|
|
274
|
+
self._io = {}
|
|
275
|
+
self._next_tick.clear()
|
|
276
|
+
self._next_timer = None
|
|
277
|
+
self._running = 0
|
|
278
|
+
self._timers = {}
|
|
279
|
+
|
|
280
|
+
def start(self) -> None:
|
|
281
|
+
"""::
|
|
282
|
+
|
|
283
|
+
reactor.start()
|
|
284
|
+
|
|
285
|
+
Start watching for I/O and timer events, this will block until
|
|
286
|
+
:meth:`stop` is called or no events are being watched anymore. ::
|
|
287
|
+
|
|
288
|
+
# Start reactor only if it is not running already
|
|
289
|
+
if not reactor.is_running:
|
|
290
|
+
reactor.start()
|
|
291
|
+
"""
|
|
292
|
+
running = self._running
|
|
293
|
+
self._running = running + 1
|
|
294
|
+
try:
|
|
295
|
+
while self._running:
|
|
296
|
+
self.one_tick()
|
|
297
|
+
finally:
|
|
298
|
+
self._running = running
|
|
299
|
+
|
|
300
|
+
def stop(self) -> None:
|
|
301
|
+
"""::
|
|
302
|
+
|
|
303
|
+
reactor.stop()
|
|
304
|
+
|
|
305
|
+
Stop watching for I/O and timer events.
|
|
306
|
+
"""
|
|
307
|
+
self._running = 0
|
|
308
|
+
|
|
309
|
+
def timer(self, after: float, cb: TimerCallback) -> str:
|
|
310
|
+
"""::
|
|
311
|
+
|
|
312
|
+
id = reactor.timer(0.5, cb)
|
|
313
|
+
|
|
314
|
+
Create a new timer, invoking the callback after a given amount of time
|
|
315
|
+
in seconds.
|
|
316
|
+
"""
|
|
317
|
+
return self._timer(False, after, cb)
|
|
318
|
+
|
|
319
|
+
def watch(self, handle: Handle, read: bool, write: bool) -> Self:
|
|
320
|
+
"""::
|
|
321
|
+
|
|
322
|
+
reactor = reactor.watch(handle, readable, writable)
|
|
323
|
+
|
|
324
|
+
Change I/O events to watch handle for with true and false values. Note
|
|
325
|
+
that this method requires an active I/O watcher. ::
|
|
326
|
+
|
|
327
|
+
# Watch only for readable events
|
|
328
|
+
reactor.watch(handle, True, False)
|
|
329
|
+
|
|
330
|
+
# Watch only for writable events
|
|
331
|
+
reactor.watch(handle, False, True)
|
|
332
|
+
|
|
333
|
+
# Watch for readable and writable events
|
|
334
|
+
reactor.watch(handle, True, True)
|
|
335
|
+
|
|
336
|
+
# Pause watching for events
|
|
337
|
+
reactor.watch(handle, False, False)
|
|
338
|
+
"""
|
|
339
|
+
if (io := self._io.get(_fileno(handle))) is None:
|
|
340
|
+
Pyjo.Exception.object.throw('I/O watcher not active')
|
|
341
|
+
io['mode'] = 0
|
|
342
|
+
if read:
|
|
343
|
+
io['mode'] |= _POLLIN | _POLLPRI
|
|
344
|
+
if write:
|
|
345
|
+
io['mode'] |= _POLLOUT
|
|
346
|
+
return self
|
|
347
|
+
|
|
348
|
+
def _id(self) -> str:
|
|
349
|
+
while True:
|
|
350
|
+
id = md5_sum(f't{time.monotonic()}{random.random()}'.encode())
|
|
351
|
+
if id not in self._timers:
|
|
352
|
+
return id
|
|
353
|
+
|
|
354
|
+
def _next(self) -> None:
|
|
355
|
+
self._next_timer = None
|
|
356
|
+
while len(self._next_tick):
|
|
357
|
+
self._next_tick.popleft()(self)
|
|
358
|
+
|
|
359
|
+
def _timer(self, recurring: bool, after: float, cb: TimerCallback) -> str:
|
|
360
|
+
id = self._id()
|
|
361
|
+
self._timers[id] = {'cb': cb, 'after': after, 'recurring': recurring, 'time': time.monotonic() + after}
|
|
362
|
+
return id
|
|
363
|
+
|
|
364
|
+
def _try(self, what: str, cb: Any, *args: Any) -> None:
|
|
365
|
+
try:
|
|
366
|
+
cb(self, *args)
|
|
367
|
+
except Exception as e:
|
|
368
|
+
# Not in Mojolicious: the arguments of a callback, which Perl does not check
|
|
369
|
+
if isinstance(e, TypeError):
|
|
370
|
+
_call_hint(e, 'the callbacks of a reactor get the reactor first')
|
|
371
|
+
# Unlike Mojolicious: the message of Python has no file and line, the exception keeps the frames
|
|
372
|
+
err = Pyjo.Exception.new(e)
|
|
373
|
+
err.message = f'{what} failed: {err.message}'
|
|
374
|
+
err.__cause__ = e
|
|
375
|
+
self.emit('error', err)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def _fileno(handle: Handle) -> int:
|
|
379
|
+
fd = handle if isinstance(handle, int) else handle.fileno()
|
|
380
|
+
if fd < 0:
|
|
381
|
+
Pyjo.Exception.object.throw('Handle is closed')
|
|
382
|
+
return fd
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
# The events of poll, which Windows does not have, with the values of Linux there
|
|
386
|
+
if sys.platform == 'win32':
|
|
387
|
+
_POLLIN, _POLLPRI, _POLLOUT, _POLLERR, _POLLHUP, _POLLNVAL = 0x001, 0x002, 0x004, 0x008, 0x010, 0x020
|
|
388
|
+
else:
|
|
389
|
+
_POLLIN, _POLLPRI, _POLLOUT = select.POLLIN, select.POLLPRI, select.POLLOUT
|
|
390
|
+
_POLLERR, _POLLHUP, _POLLNVAL = select.POLLERR, select.POLLHUP, select.POLLNVAL
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
# The events of the handles that are ready, in the time in milliseconds
|
|
394
|
+
def _poll(modes: dict[int, int], timeout: int) -> list[tuple[int, int]]:
|
|
395
|
+
if sys.platform == 'win32':
|
|
396
|
+
return _select(modes, timeout)
|
|
397
|
+
poll = select.poll()
|
|
398
|
+
for fd, mode in modes.items():
|
|
399
|
+
poll.register(fd, mode)
|
|
400
|
+
return poll.poll(timeout)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
# Not in Mojolicious: Windows has no poll, and its select, which watches sockets only, takes the place of it; a
|
|
404
|
+
# connection that failed is an exceptional condition there, which poll reports as an error and writable, and the
|
|
405
|
+
# selector of asyncio as writable
|
|
406
|
+
def _select(modes: dict[int, int], timeout: int) -> list[tuple[int, int]]:
|
|
407
|
+
readers = [fd for fd, mode in modes.items() if mode & (_POLLIN | _POLLPRI)]
|
|
408
|
+
writers = [fd for fd, mode in modes.items() if mode & _POLLOUT]
|
|
409
|
+
if not len(readers) and not len(writers):
|
|
410
|
+
time.sleep(timeout / 1000)
|
|
411
|
+
return []
|
|
412
|
+
try:
|
|
413
|
+
readable, writable, failed = select.select(readers, writers, writers, timeout / 1000)
|
|
414
|
+
except OSError:
|
|
415
|
+
# A handle that is closed, which poll reports as invalid
|
|
416
|
+
if not len(invalid := [fd for fd in modes if not _is_valid(fd)]):
|
|
417
|
+
raise
|
|
418
|
+
return [(fd, _POLLNVAL) for fd in invalid]
|
|
419
|
+
events: dict[int, int] = {}
|
|
420
|
+
for fd in readable:
|
|
421
|
+
events[fd] = events.get(fd, 0) | _POLLIN
|
|
422
|
+
for fd in writable:
|
|
423
|
+
events[fd] = events.get(fd, 0) | _POLLOUT
|
|
424
|
+
for fd in failed:
|
|
425
|
+
events[fd] = events.get(fd, 0) | _POLLERR | _POLLOUT
|
|
426
|
+
return list(events.items())
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _is_valid(fd: int) -> bool:
|
|
430
|
+
try:
|
|
431
|
+
select.select([fd], [], [], 0)
|
|
432
|
+
except OSError:
|
|
433
|
+
return False
|
|
434
|
+
return True
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
new = Pyjo_Reactor_Poll.new
|
|
438
|
+
object = Pyjo_Reactor_Poll
|
|
439
|
+
Poll = Pyjo_Reactor_Poll
|