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/EventEmitter.py ADDED
@@ -0,0 +1,522 @@
1
+ """
2
+ Pyjo.EventEmitter - Event emitter base class
3
+ ============================================
4
+ ::
5
+
6
+ from Pyjo.EventEmitter import EventEmitter
7
+
8
+ class Cat(EventEmitter):
9
+ # Emit events
10
+ def poke(self):
11
+ self.emit('roar', 3)
12
+
13
+ # Subscribe to events
14
+ tiger = Cat()
15
+
16
+ @tiger.on('roar')
17
+ def roar(tiger, times):
18
+ for _ in range(times):
19
+ print('RAWR!')
20
+
21
+ tiger.poke()
22
+
23
+ # Or import the module, as in Google Python Style Guide
24
+
25
+ import Pyjo.EventEmitter
26
+
27
+ class Cat(Pyjo.EventEmitter.object):
28
+ # Emit events
29
+ def poke(self):
30
+ self.emit('roar', 3)
31
+
32
+ # Subscribe to events
33
+ tiger = Cat.new()
34
+
35
+ @tiger.on('roar')
36
+ def roar(tiger, times):
37
+ for _ in range(times):
38
+ print('RAWR!')
39
+
40
+ tiger.poke()
41
+
42
+ :mod:`Pyjo.EventEmitter` is a simple base class for event emitting objects.
43
+
44
+ Events
45
+ ------
46
+
47
+ :mod:`Pyjo.EventEmitter` can emit the following events.
48
+
49
+ error
50
+ ~~~~~
51
+ ::
52
+
53
+ e.on('error', lambda e, err: ...)
54
+
55
+ This is a special event for errors, it will not be emitted directly by this
56
+ class, but is fatal if unhandled. Subclasses may choose to emit it, but are
57
+ not required to do so. ::
58
+
59
+ e.on('error', lambda e, err: print(f'This looks bad: {err}'))
60
+
61
+ An unhandled error raises a :mod:`Pyjo.Exception` with the name of the class
62
+ and the error in its message, and an error that is an exception is its cause.
63
+
64
+ Debugging
65
+ ---------
66
+
67
+ You can set the ``PYJO_EVENTEMITTER_DEBUG`` environment variable to get some
68
+ advanced diagnostics information printed to ``sys.stderr``. ::
69
+
70
+ PYJO_EVENTEMITTER_DEBUG=1
71
+
72
+ See also
73
+ --------
74
+
75
+ :mod:`Pyjoyment`, https://mojolicious.org.
76
+
77
+ Ported from `Mojo::EventEmitter <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/EventEmitter.pm>`_
78
+ of Mojolicious 9.49.
79
+ """
80
+
81
+ import Pyjo.Base
82
+ import Pyjo.Exception
83
+
84
+ from Pyjo.Util import _call_hint, warn # pyright: ignore[reportPrivateUsage]
85
+
86
+ import collections
87
+ import os
88
+ import weakref
89
+
90
+ from collections.abc import AsyncIterator, Callable
91
+ from typing import TYPE_CHECKING, Any, Self, cast, overload
92
+
93
+
94
+ if TYPE_CHECKING:
95
+ import Pyjo.Promise
96
+
97
+
98
+ # A subscriber gets the emitter and the arguments of the event
99
+ type Callback = Callable[..., Any]
100
+
101
+ DEBUG = bool(os.environ.get('PYJO_EVENTEMITTER_DEBUG'))
102
+
103
+ # Not in Mojolicious: the emitters whose promises wait, with how many of them, by the id of the emitter. The promise of an
104
+ # emitter, as a socket that a coroutine waits for, may be the only way to it, as the reactor refers to the socket weakly,
105
+ # in a cycle that the garbage collector would free with the promise, as Perl keeps such a cycle
106
+ _holding: dict[int, list[Any]] = {}
107
+
108
+
109
+ class Pyjo_EventEmitter(Pyjo.Base.object):
110
+ """
111
+ :mod:`Pyjo.EventEmitter` inherits all methods from :mod:`Pyjo.Base` and
112
+ implements the following new ones.
113
+ """
114
+
115
+ def __init__(self) -> None:
116
+ self._events: dict[str, list[Callback]] = {}
117
+
118
+ def catch(self, cb: Callback) -> Self:
119
+ """::
120
+
121
+ e = e.catch(lambda e, err: ...)
122
+
123
+ Subscribe to the ``error`` event. ::
124
+
125
+ # Longer version
126
+ e.on('error', lambda e, err: ...)
127
+ """
128
+ self.on('error', cb)
129
+ return self
130
+
131
+ def emit(self, name: str, *args: Any) -> Self:
132
+ """::
133
+
134
+ e = e.emit('foo')
135
+ e = e.emit('foo', 123)
136
+
137
+ Emit event.
138
+ """
139
+ # Unlike Mojolicious: an empty list of subscribers, as subscribers() leaves it, is no subscriber, and Mojo
140
+ # swallows an unhandled error then
141
+ if s := self._events.get(name):
142
+ if DEBUG:
143
+ warn(f'-- Emit {name} in {_name(type(self))} ({len(s)})')
144
+ for cb in s:
145
+ try:
146
+ cb(self, *args)
147
+ except TypeError as e:
148
+ # Not in Mojolicious: the arguments of a callback, which Perl does not check
149
+ _call_hint(e, f'the callbacks of the event "{name}" get the emitter first')
150
+ raise
151
+ else:
152
+ if DEBUG:
153
+ warn(f'-- Emit {name} in {_name(type(self))} (0)')
154
+ if name == 'error':
155
+ err = args[0] if args else ''
156
+ e = Pyjo.Exception.new(f'{_name(type(self))}: {err}')
157
+ # Not in Mojolicious: an error that is an exception is the cause
158
+ if isinstance(err, BaseException):
159
+ raise e from err
160
+ raise e
161
+
162
+ return self
163
+
164
+ # Not in Mojolicious: the events as an asynchronous iterator, as events.on of Node.js
165
+ def events(self, name: str) -> AsyncIterator[tuple[Any, ...]]:
166
+ """::
167
+
168
+ async for args in e.events('foo'):
169
+ ...
170
+
171
+ The arguments of the events as an asynchronous iterator, as a tuple
172
+ for each event, which are queued until they are taken, and which ends
173
+ at the event ``close`` or ``end``, and raises the error of the event
174
+ ``error``. ::
175
+
176
+ async for message, in ws.events('message'):
177
+ print(f'Message: {message}')
178
+ """
179
+ return _Pull(self, name, False).iterate()
180
+
181
+ def has_subscribers(self, name: str) -> bool:
182
+ """::
183
+
184
+ boolean = e.has_subscribers('foo')
185
+
186
+ Check if event has subscribers.
187
+ """
188
+ # Unlike Mojolicious: an empty list of subscribers is no subscriber
189
+ return bool(self._events.get(name))
190
+
191
+ @overload
192
+ def on[F: Callback](self, name: F, cb: None = None) -> F: ...
193
+
194
+ @overload
195
+ def on[F: Callback](self, name: str, cb: None = None) -> Callable[[F], F]: ...
196
+
197
+ @overload
198
+ def on[F: Callback](self, name: str, cb: F) -> F: ...
199
+
200
+ def on(self, name: str | Callback, cb: Callback | None = None) -> Any:
201
+ """::
202
+
203
+ cb = e.on('foo', lambda e, *args: ...)
204
+
205
+ Subscribe to event. ::
206
+
207
+ e.on('foo', lambda e, *args: ...)
208
+
209
+ Without a callback, it is a decorator, with the name of the event or
210
+ of the function. ::
211
+
212
+ @e.on('foo')
213
+ def cb(e, *args):
214
+ ...
215
+
216
+ @e.on
217
+ def foo(e, *args):
218
+ ...
219
+ """
220
+ # Not in Mojolicious: a decorator
221
+ if not isinstance(name, str):
222
+ return self.on(name.__name__, name)
223
+ if cb is None:
224
+ event = name
225
+
226
+ def decorator(cb: Callback) -> Callback:
227
+ return self.on(event, cb)
228
+
229
+ return decorator
230
+
231
+ self._events.setdefault(name, []).append(cb)
232
+ return cb
233
+
234
+ # Not in Mojolicious: a promise of the event, as events.once of Node.js
235
+ def once_p(self, name: str) -> 'Pyjo.Promise.object':
236
+ """::
237
+
238
+ promise = e.once_p('foo')
239
+
240
+ Same as :meth:`once`, but returns a :mod:`Pyjo.Promise` object, which
241
+ is fulfilled with the arguments of the event, and rejected with the
242
+ error of the event ``error``, which comes first. ::
243
+
244
+ code, signal = await child.once_p('close')
245
+ """
246
+ # Imported here, as Pyjo.Promise uses Pyjo.IOLoop, whose classes are event emitters
247
+ import Pyjo.Promise
248
+
249
+ promise = Pyjo.Promise.new()
250
+ failed: Callback | None = None
251
+
252
+ def done(e: Any, *args: Any) -> None:
253
+ if failed is not None:
254
+ self.unsubscribe('error', failed)
255
+ promise.resolve(*args)
256
+
257
+ wrapper = self.once(name, done)
258
+ if name != 'error':
259
+
260
+ def error(e: Any, err: Any) -> None:
261
+ self.unsubscribe(name, wrapper)
262
+ promise.reject(err)
263
+
264
+ failed = self.once('error', error)
265
+ return self._hold(promise)
266
+
267
+ @overload
268
+ def once(self, name: Callback, cb: None = None) -> Callback: ...
269
+
270
+ @overload
271
+ def once(self, name: str, cb: None = None) -> Callable[[Callback], Callback]: ...
272
+
273
+ @overload
274
+ def once(self, name: str, cb: Callback) -> Callback: ...
275
+
276
+ def once(self, name: str | Callback, cb: Callback | None = None) -> Any:
277
+ """::
278
+
279
+ cb = e.once('foo', lambda e, *args: ...)
280
+
281
+ Subscribe to event and unsubscribe again after it has been emitted
282
+ once. ::
283
+
284
+ e.once('foo', lambda e, *args: ...)
285
+
286
+ Without a callback, it is a decorator, as :meth:`on` is.
287
+ """
288
+ # Not in Mojolicious: a decorator
289
+ if not isinstance(name, str):
290
+ return self.once(name.__name__, name)
291
+ event = name
292
+ if cb is None:
293
+
294
+ def once_decorator(cb: Callback) -> Callback:
295
+ return self.once(event, cb)
296
+
297
+ return once_decorator
298
+
299
+ wrapper = _Once(weakref.proxy(self), event, cb)
300
+ self.on(event, wrapper)
301
+
302
+ return wrapper
303
+
304
+ def subscribers(self, name: str) -> list[Callback]:
305
+ """::
306
+
307
+ subscribers = e.subscribers('foo')
308
+
309
+ All subscribers for event. ::
310
+
311
+ # Unsubscribe last subscriber
312
+ e.unsubscribe('foo', e.subscribers('foo')[-1])
313
+
314
+ # Change order of subscribers
315
+ e.subscribers('foo').reverse()
316
+ """
317
+ return self._events.setdefault(name, [])
318
+
319
+ # Not in Mojolicious: the emitter kept while the promise waits, as nothing else may refer to it, which leaves the promise
320
+ # as handled as it was, so that a rejection that nothing handles is still reported
321
+ def _hold(self, promise: 'Pyjo.Promise.object') -> 'Pyjo.Promise.object':
322
+ key = id(self)
323
+ entry = _holding.setdefault(key, [self, 0])
324
+ entry[1] += 1
325
+
326
+ def release(*args: Any) -> None:
327
+ entry[1] -= 1
328
+ if entry[1] <= 0 and _holding.get(key) is entry:
329
+ del _holding[key]
330
+
331
+ handled = promise._handled # pyright: ignore[reportPrivateUsage]
332
+ promise.then(release, release)
333
+ promise._handled = handled # pyright: ignore[reportPrivateUsage]
334
+ return promise
335
+
336
+ # Not in Mojolicious: the values of an event of a subclass that are pulled with promises, one pull for each event, so
337
+ # that the promises and the iterators of a stream take its chunks in order, and with flow, the start and the stop of
338
+ # the reading of a stream, which reads only while a promise waits, and the TimeoutError of its event timeout
339
+ # A value is the first argument of the event, or all of them as a tuple, and an error of the event error, which ends
340
+ # the pull, or only rejects the promises that wait, where the emitter goes on after it, as a datagram socket
341
+ def _pull(self, name: str, flow: bool = False, *, value: bool = True, fatal: bool = True) -> '_Pull':
342
+ pulls: dict[str, _Pull] = self.__dict__.setdefault('_pulls', {})
343
+ if (pull := pulls.get(name)) is None:
344
+ # The methods of a stream, which the subclass has
345
+ stream = cast(Any, self)
346
+ start, stop = (stream.start, stream.stop) if flow else (None, None)
347
+ pull = pulls[name] = _Pull(self, name, value, start, stop, fatal)
348
+ if flow:
349
+ pull.subscribed.append(('timeout', self.on('timeout', pull.timeout)))
350
+ return pull
351
+
352
+ def unsubscribe(self, name: str, cb: Callback | None = None) -> Self:
353
+ """::
354
+
355
+ e = e.unsubscribe('foo')
356
+ e = e.unsubscribe('foo', cb)
357
+
358
+ Unsubscribe from event.
359
+ """
360
+ # One
361
+ if cb is not None:
362
+ self._events[name] = [c for c in self._events.get(name, []) if c != cb]
363
+ if not self._events[name]:
364
+ del self._events[name]
365
+
366
+ # All
367
+ else:
368
+ self._events.pop(name, None)
369
+
370
+ return self
371
+
372
+
373
+ # The wrapper of once, which unsubscribes itself as the argument of its call, as Mojo does with __SUB__: a closure that
374
+ # refers to itself is a cycle, which keeps the callback, and the objects of its closure, until the garbage collector runs
375
+ class _Once:
376
+ __slots__ = ('cb', 'emitter', 'event')
377
+
378
+ def __init__(self, emitter: Pyjo_EventEmitter, event: str, cb: Callback) -> None:
379
+ self.cb, self.emitter, self.event = cb, emitter, event
380
+
381
+ def __call__(self, *args: Any) -> Any:
382
+ self.emitter.unsubscribe(self.event, self)
383
+ return self.cb(*args)
384
+
385
+
386
+ # Not in Mojolicious: the events of an emitter, which are pulled with promises, as the streams of Node.js in their paused
387
+ # mode, and queued until they are, until the event close or end, or the error of the event error. With start and stop, the
388
+ # emitter is a stream, which reads only while a promise waits, so that it reads no more than it is asked for
389
+ class _Pull:
390
+ def __init__(
391
+ self,
392
+ emitter: Pyjo_EventEmitter,
393
+ name: str,
394
+ value: bool,
395
+ start: Callable[[], Any] | None = None,
396
+ stop: Callable[[], Any] | None = None,
397
+ fatal: bool = True,
398
+ ) -> None:
399
+ self.emitter, self.value, self.start, self.stop, self.fatal = emitter, value, start, stop, fatal
400
+ self.queue: collections.deque[Any] = collections.deque()
401
+ self.waiting: collections.deque[Pyjo.Promise.object] = collections.deque()
402
+ self.done = False
403
+ self.error: Any = None
404
+
405
+ # An error that is not fatal, which came while no promise waited, for the next one
406
+ self.missed: Any = None
407
+ self.subscribed: list[tuple[str, Callback]] = [
408
+ (name, emitter.on(name, self._push)),
409
+ ('error', emitter.on('error', self._fail)),
410
+ ('close', emitter.on('close', self._end)),
411
+ ('end', emitter.on('end', self._end)),
412
+ ]
413
+
414
+ def iterate(self, size: int | None = None) -> '_Iterator':
415
+ return _Iterator(self, size)
416
+
417
+ # A promise that waits no more, as one whose time is up, which is rejected
418
+ def cancel(self, promise: Any, err: Any) -> None:
419
+ if promise in self.waiting:
420
+ self.waiting.remove(promise)
421
+ promise.reject(err)
422
+ if not self.waiting and self.stop is not None:
423
+ self.stop()
424
+
425
+ # The end of an emitter that has ended before, as a stream that is closed
426
+ def end(self) -> None:
427
+ if not self.done and self.error is None:
428
+ self._end(self.emitter)
429
+
430
+ def timeout(self, emitter: Any) -> None:
431
+ self._fail(emitter, TimeoutError('Inactivity timeout'))
432
+
433
+ def close(self) -> None:
434
+ for name, cb in self.subscribed:
435
+ self.emitter.unsubscribe(name, cb)
436
+ self.subscribed = []
437
+
438
+ def next_p(self, size: int | None = None) -> 'Pyjo.Promise.object':
439
+ import Pyjo.Promise
440
+
441
+ if self.queue:
442
+ item = self.queue.popleft()
443
+ return Pyjo.Promise.resolve(self._split(item, size) if size is not None else item)
444
+ if self.error is not None:
445
+ return Pyjo.Promise.reject(self.error)
446
+ if (missed := self.missed) is not None:
447
+ self.missed = None
448
+ return Pyjo.Promise.reject(missed)
449
+ if self.done:
450
+ return Pyjo.Promise.resolve(None)
451
+ promise = Pyjo.Promise.new()
452
+ self.waiting.append(promise)
453
+ self.emitter._hold(promise) # pyright: ignore[reportPrivateUsage]
454
+ if size is not None:
455
+
456
+ def split(item: Any = None) -> Any:
457
+ return self._split(item, size)
458
+
459
+ promise = promise.then(split)
460
+ if self.start is not None:
461
+ self.start()
462
+ return promise
463
+
464
+ def _end(self, emitter: Any, *args: Any) -> None:
465
+ self.done = True
466
+ self.close()
467
+ while self.waiting:
468
+ self.waiting.popleft().resolve(None)
469
+
470
+ def _fail(self, emitter: Any, err: Any) -> None:
471
+ if self.fatal:
472
+ self.error = err
473
+ self.close()
474
+ elif not self.waiting:
475
+ self.missed = err
476
+ while self.waiting:
477
+ self.waiting.popleft().reject(err)
478
+ if not self.fatal and self.stop is not None:
479
+ self.stop()
480
+
481
+ def _push(self, emitter: Any, *args: Any) -> None:
482
+ item = args[0] if self.value else args
483
+ if self.waiting:
484
+ self.waiting.popleft().resolve(item)
485
+ else:
486
+ self.queue.append(item)
487
+ if not self.waiting and self.stop is not None:
488
+ self.stop()
489
+
490
+ # A chunk that is longer than what is asked for, whose rest is the next one
491
+ def _split(self, item: Any, size: int) -> Any:
492
+ if isinstance(item, bytes | bytearray) and len(item) > size:
493
+ self.queue.appendleft(item[size:])
494
+ return bytes(item[:size])
495
+ return item
496
+
497
+
498
+ # Not in Mojolicious: an asynchronous iterator of the items of a pull, until None
499
+ class _Iterator:
500
+ def __init__(self, pull: _Pull, size: int | None = None) -> None:
501
+ self.pull, self.size = pull, size
502
+
503
+ def __aiter__(self) -> Self:
504
+ return self
505
+
506
+ async def __anext__(self) -> Any:
507
+ if (item := await self.pull.next_p(self.size)) is None:
508
+ raise StopAsyncIteration
509
+ return item
510
+
511
+ async def aclose(self) -> None:
512
+ self.pull.close()
513
+
514
+
515
+ # The name of a class, as Perl's blessed gives the name of a package
516
+ def _name(cls: type) -> str:
517
+ return f'{cls.__module__}.{cls.__qualname__}'
518
+
519
+
520
+ new = Pyjo_EventEmitter.new
521
+ object = Pyjo_EventEmitter
522
+ EventEmitter = Pyjo_EventEmitter