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