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