urwid 2.6.0.post0__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.
Potentially problematic release.
This version of urwid might be problematic. Click here for more details.
- urwid/__init__.py +333 -0
- urwid/canvas.py +1413 -0
- urwid/command_map.py +137 -0
- urwid/container.py +59 -0
- urwid/decoration.py +65 -0
- urwid/display/__init__.py +97 -0
- urwid/display/_posix_raw_display.py +413 -0
- urwid/display/_raw_display_base.py +914 -0
- urwid/display/_web.css +12 -0
- urwid/display/_web.js +462 -0
- urwid/display/_win32.py +171 -0
- urwid/display/_win32_raw_display.py +269 -0
- urwid/display/common.py +1219 -0
- urwid/display/curses.py +690 -0
- urwid/display/escape.py +624 -0
- urwid/display/html_fragment.py +251 -0
- urwid/display/lcd.py +518 -0
- urwid/display/raw.py +37 -0
- urwid/display/web.py +636 -0
- urwid/event_loop/__init__.py +55 -0
- urwid/event_loop/abstract_loop.py +175 -0
- urwid/event_loop/asyncio_loop.py +231 -0
- urwid/event_loop/glib_loop.py +294 -0
- urwid/event_loop/main_loop.py +721 -0
- urwid/event_loop/select_loop.py +230 -0
- urwid/event_loop/tornado_loop.py +206 -0
- urwid/event_loop/trio_loop.py +302 -0
- urwid/event_loop/twisted_loop.py +269 -0
- urwid/event_loop/zmq_loop.py +275 -0
- urwid/font.py +695 -0
- urwid/graphics.py +96 -0
- urwid/highlight.css +19 -0
- urwid/listbox.py +1899 -0
- urwid/monitored_list.py +522 -0
- urwid/numedit.py +376 -0
- urwid/signals.py +330 -0
- urwid/split_repr.py +130 -0
- urwid/str_util.py +358 -0
- urwid/text_layout.py +632 -0
- urwid/treetools.py +515 -0
- urwid/util.py +557 -0
- urwid/version.py +16 -0
- urwid/vterm.py +1806 -0
- urwid/widget/__init__.py +181 -0
- urwid/widget/attr_map.py +161 -0
- urwid/widget/attr_wrap.py +140 -0
- urwid/widget/bar_graph.py +649 -0
- urwid/widget/big_text.py +77 -0
- urwid/widget/box_adapter.py +126 -0
- urwid/widget/columns.py +1145 -0
- urwid/widget/constants.py +574 -0
- urwid/widget/container.py +227 -0
- urwid/widget/divider.py +110 -0
- urwid/widget/edit.py +718 -0
- urwid/widget/filler.py +403 -0
- urwid/widget/frame.py +539 -0
- urwid/widget/grid_flow.py +539 -0
- urwid/widget/line_box.py +194 -0
- urwid/widget/overlay.py +829 -0
- urwid/widget/padding.py +597 -0
- urwid/widget/pile.py +971 -0
- urwid/widget/popup.py +170 -0
- urwid/widget/progress_bar.py +141 -0
- urwid/widget/scrollable.py +597 -0
- urwid/widget/solid_fill.py +44 -0
- urwid/widget/text.py +354 -0
- urwid/widget/widget.py +852 -0
- urwid/widget/widget_decoration.py +166 -0
- urwid/widget/wimp.py +792 -0
- urwid/wimp.py +23 -0
- urwid-2.6.0.post0.dist-info/COPYING +504 -0
- urwid-2.6.0.post0.dist-info/METADATA +332 -0
- urwid-2.6.0.post0.dist-info/RECORD +75 -0
- urwid-2.6.0.post0.dist-info/WHEEL +5 -0
- urwid-2.6.0.post0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Urwid main loop code
|
|
2
|
+
# Copyright (C) 2004-2012 Ian Ward
|
|
3
|
+
# Copyright (C) 2008 Walter Mundt
|
|
4
|
+
# Copyright (C) 2009 Andrew Psaltis
|
|
5
|
+
#
|
|
6
|
+
# This library is free software; you can redistribute it and/or
|
|
7
|
+
# modify it under the terms of the GNU Lesser General Public
|
|
8
|
+
# License as published by the Free Software Foundation; either
|
|
9
|
+
# version 2.1 of the License, or (at your option) any later version.
|
|
10
|
+
#
|
|
11
|
+
# This library is distributed in the hope that it will be useful,
|
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
14
|
+
# Lesser General Public License for more details.
|
|
15
|
+
#
|
|
16
|
+
# You should have received a copy of the GNU Lesser General Public
|
|
17
|
+
# License along with this library; if not, write to the Free Software
|
|
18
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
19
|
+
#
|
|
20
|
+
# Urwid web site: https://urwid.org/
|
|
21
|
+
|
|
22
|
+
"""Select based urwid EventLoop implementation."""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import contextlib
|
|
27
|
+
import heapq
|
|
28
|
+
import logging
|
|
29
|
+
import selectors
|
|
30
|
+
import time
|
|
31
|
+
import typing
|
|
32
|
+
from contextlib import suppress
|
|
33
|
+
from itertools import count
|
|
34
|
+
|
|
35
|
+
from .abstract_loop import EventLoop, ExitMainLoop
|
|
36
|
+
|
|
37
|
+
if typing.TYPE_CHECKING:
|
|
38
|
+
from collections.abc import Callable, Iterator
|
|
39
|
+
from concurrent.futures import Executor, Future
|
|
40
|
+
|
|
41
|
+
from typing_extensions import Literal, ParamSpec
|
|
42
|
+
|
|
43
|
+
_T = typing.TypeVar("_T")
|
|
44
|
+
_Spec = ParamSpec("_Spec")
|
|
45
|
+
|
|
46
|
+
__all__ = ("SelectEventLoop",)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class SelectEventLoop(EventLoop):
|
|
50
|
+
"""
|
|
51
|
+
Event loop based on :func:`selectors.DefaultSelector.select`
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self) -> None:
|
|
55
|
+
super().__init__()
|
|
56
|
+
self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__)
|
|
57
|
+
self._alarms: list[tuple[float, int, Callable[[], typing.Any]]] = []
|
|
58
|
+
self._watch_files: dict[int, Callable[[], typing.Any]] = {}
|
|
59
|
+
self._idle_handle: int = 0
|
|
60
|
+
self._idle_callbacks: dict[int, Callable[[], typing.Any]] = {}
|
|
61
|
+
self._tie_break: Iterator[int] = count()
|
|
62
|
+
self._did_something: bool = False
|
|
63
|
+
|
|
64
|
+
def run_in_executor(
|
|
65
|
+
self,
|
|
66
|
+
executor: Executor,
|
|
67
|
+
func: Callable[_Spec, _T],
|
|
68
|
+
*args: _Spec.args,
|
|
69
|
+
**kwargs: _Spec.kwargs,
|
|
70
|
+
) -> Future[_T]:
|
|
71
|
+
"""Run callable in executor.
|
|
72
|
+
|
|
73
|
+
:param executor: Executor to use for running the function
|
|
74
|
+
:type executor: concurrent.futures.Executor
|
|
75
|
+
:param func: function to call
|
|
76
|
+
:type func: Callable
|
|
77
|
+
:param args: positional arguments to function
|
|
78
|
+
:type args: object
|
|
79
|
+
:param kwargs: keyword arguments to function
|
|
80
|
+
:type kwargs: object
|
|
81
|
+
:return: future object for the function call outcome.
|
|
82
|
+
:rtype: concurrent.futures.Future
|
|
83
|
+
"""
|
|
84
|
+
return executor.submit(func, *args, **kwargs)
|
|
85
|
+
|
|
86
|
+
def alarm(
|
|
87
|
+
self,
|
|
88
|
+
seconds: float,
|
|
89
|
+
callback: Callable[[], typing.Any],
|
|
90
|
+
) -> tuple[float, int, Callable[[], typing.Any]]:
|
|
91
|
+
"""
|
|
92
|
+
Call callback() a given time from now. No parameters are
|
|
93
|
+
passed to callback.
|
|
94
|
+
|
|
95
|
+
Returns a handle that may be passed to remove_alarm()
|
|
96
|
+
|
|
97
|
+
seconds -- floating point time to wait before calling callback
|
|
98
|
+
callback -- function to call from event loop
|
|
99
|
+
"""
|
|
100
|
+
tm = time.time() + seconds
|
|
101
|
+
handle = (tm, next(self._tie_break), callback)
|
|
102
|
+
heapq.heappush(self._alarms, handle)
|
|
103
|
+
return handle
|
|
104
|
+
|
|
105
|
+
def remove_alarm(self, handle: tuple[float, int, Callable[[], typing.Any]]) -> bool:
|
|
106
|
+
"""
|
|
107
|
+
Remove an alarm.
|
|
108
|
+
|
|
109
|
+
Returns True if the alarm exists, False otherwise
|
|
110
|
+
"""
|
|
111
|
+
try:
|
|
112
|
+
self._alarms.remove(handle)
|
|
113
|
+
heapq.heapify(self._alarms)
|
|
114
|
+
|
|
115
|
+
except ValueError:
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
return True
|
|
119
|
+
|
|
120
|
+
def watch_file(self, fd: int, callback: Callable[[], typing.Any]) -> int:
|
|
121
|
+
"""
|
|
122
|
+
Call callback() when fd has some data to read. No parameters
|
|
123
|
+
are passed to callback.
|
|
124
|
+
|
|
125
|
+
Returns a handle that may be passed to remove_watch_file()
|
|
126
|
+
|
|
127
|
+
fd -- file descriptor to watch for input
|
|
128
|
+
callback -- function to call when input is available
|
|
129
|
+
"""
|
|
130
|
+
self._watch_files[fd] = callback
|
|
131
|
+
return fd
|
|
132
|
+
|
|
133
|
+
def remove_watch_file(self, handle: int) -> bool:
|
|
134
|
+
"""
|
|
135
|
+
Remove an input file.
|
|
136
|
+
|
|
137
|
+
Returns True if the input file exists, False otherwise
|
|
138
|
+
"""
|
|
139
|
+
if handle in self._watch_files:
|
|
140
|
+
del self._watch_files[handle]
|
|
141
|
+
return True
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
def enter_idle(self, callback: Callable[[], typing.Any]) -> int:
|
|
145
|
+
"""
|
|
146
|
+
Add a callback for entering idle.
|
|
147
|
+
|
|
148
|
+
Returns a handle that may be passed to remove_idle()
|
|
149
|
+
"""
|
|
150
|
+
self._idle_handle += 1
|
|
151
|
+
self._idle_callbacks[self._idle_handle] = callback
|
|
152
|
+
return self._idle_handle
|
|
153
|
+
|
|
154
|
+
def remove_enter_idle(self, handle: int) -> bool:
|
|
155
|
+
"""
|
|
156
|
+
Remove an idle callback.
|
|
157
|
+
|
|
158
|
+
Returns True if the handle was removed.
|
|
159
|
+
"""
|
|
160
|
+
try:
|
|
161
|
+
del self._idle_callbacks[handle]
|
|
162
|
+
except KeyError:
|
|
163
|
+
return False
|
|
164
|
+
return True
|
|
165
|
+
|
|
166
|
+
def _entering_idle(self) -> None:
|
|
167
|
+
"""
|
|
168
|
+
Call all the registered idle callbacks.
|
|
169
|
+
"""
|
|
170
|
+
for callback in self._idle_callbacks.values():
|
|
171
|
+
callback()
|
|
172
|
+
|
|
173
|
+
def run(self) -> None:
|
|
174
|
+
"""
|
|
175
|
+
Start the event loop. Exit the loop when any callback raises
|
|
176
|
+
an exception. If ExitMainLoop is raised, exit cleanly.
|
|
177
|
+
"""
|
|
178
|
+
with contextlib.suppress(ExitMainLoop):
|
|
179
|
+
self._did_something = True
|
|
180
|
+
while True:
|
|
181
|
+
with suppress(InterruptedError):
|
|
182
|
+
self._loop()
|
|
183
|
+
|
|
184
|
+
def _loop(self) -> None:
|
|
185
|
+
"""
|
|
186
|
+
A single iteration of the event loop
|
|
187
|
+
"""
|
|
188
|
+
tm: float | Literal["idle"] | None = None
|
|
189
|
+
|
|
190
|
+
with selectors.DefaultSelector() as selector:
|
|
191
|
+
for fd, callback in self._watch_files.items():
|
|
192
|
+
selector.register(fd, selectors.EVENT_READ, callback)
|
|
193
|
+
|
|
194
|
+
if self._alarms or self._did_something:
|
|
195
|
+
timeout = 0.0
|
|
196
|
+
|
|
197
|
+
if self._alarms:
|
|
198
|
+
timeout_ = self._alarms[0][0]
|
|
199
|
+
tm = timeout_
|
|
200
|
+
timeout = max(timeout, timeout_ - time.time())
|
|
201
|
+
|
|
202
|
+
if self._did_something and (not self._alarms or (self._alarms and timeout > 0)):
|
|
203
|
+
timeout = 0.0
|
|
204
|
+
tm = "idle"
|
|
205
|
+
|
|
206
|
+
self.logger.debug(f"Waiting for input: timeout={timeout!r}")
|
|
207
|
+
ready = [event for event, _ in selector.select(timeout)]
|
|
208
|
+
|
|
209
|
+
elif self._watch_files:
|
|
210
|
+
self.logger.debug("Waiting for input: timeout")
|
|
211
|
+
ready = [event for event, _ in selector.select()]
|
|
212
|
+
else:
|
|
213
|
+
ready = []
|
|
214
|
+
|
|
215
|
+
if not ready:
|
|
216
|
+
if tm == "idle":
|
|
217
|
+
self.logger.debug("No input, entering IDLE")
|
|
218
|
+
self._entering_idle()
|
|
219
|
+
self._did_something = False
|
|
220
|
+
elif tm is not None:
|
|
221
|
+
# must have been a timeout
|
|
222
|
+
tm, _tie_break, alarm_callback = heapq.heappop(self._alarms)
|
|
223
|
+
self.logger.debug(f"No input in timeout, calling scheduled {alarm_callback!r}")
|
|
224
|
+
alarm_callback()
|
|
225
|
+
self._did_something = True
|
|
226
|
+
|
|
227
|
+
self.logger.debug("Processing input")
|
|
228
|
+
for record in ready:
|
|
229
|
+
record.data()
|
|
230
|
+
self._did_something = True
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Urwid main loop code
|
|
2
|
+
# Copyright (C) 2004-2012 Ian Ward
|
|
3
|
+
# Copyright (C) 2008 Walter Mundt
|
|
4
|
+
# Copyright (C) 2009 Andrew Psaltis
|
|
5
|
+
#
|
|
6
|
+
# This library is free software; you can redistribute it and/or
|
|
7
|
+
# modify it under the terms of the GNU Lesser General Public
|
|
8
|
+
# License as published by the Free Software Foundation; either
|
|
9
|
+
# version 2.1 of the License, or (at your option) any later version.
|
|
10
|
+
#
|
|
11
|
+
# This library is distributed in the hope that it will be useful,
|
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
14
|
+
# Lesser General Public License for more details.
|
|
15
|
+
#
|
|
16
|
+
# You should have received a copy of the GNU Lesser General Public
|
|
17
|
+
# License along with this library; if not, write to the Free Software
|
|
18
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
19
|
+
#
|
|
20
|
+
# Urwid web site: https://urwid.org/
|
|
21
|
+
|
|
22
|
+
"""Tornado IOLoop based urwid EventLoop implementation.
|
|
23
|
+
|
|
24
|
+
Tornado library is required.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import functools
|
|
30
|
+
import logging
|
|
31
|
+
import typing
|
|
32
|
+
from contextlib import suppress
|
|
33
|
+
|
|
34
|
+
from tornado import ioloop
|
|
35
|
+
|
|
36
|
+
from .abstract_loop import EventLoop, ExitMainLoop
|
|
37
|
+
|
|
38
|
+
if typing.TYPE_CHECKING:
|
|
39
|
+
import asyncio
|
|
40
|
+
from collections.abc import Callable
|
|
41
|
+
from concurrent.futures import Executor
|
|
42
|
+
|
|
43
|
+
from typing_extensions import Literal, ParamSpec
|
|
44
|
+
|
|
45
|
+
_Spec = ParamSpec("_Spec")
|
|
46
|
+
_T = typing.TypeVar("_T")
|
|
47
|
+
|
|
48
|
+
__all__ = ("TornadoEventLoop",)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class TornadoEventLoop(EventLoop):
|
|
52
|
+
"""This is an Urwid-specific event loop to plug into its MainLoop.
|
|
53
|
+
It acts as an adaptor for Tornado's IOLoop which does all
|
|
54
|
+
heavy lifting except idle-callbacks.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, loop: ioloop.IOLoop | None = None) -> None:
|
|
58
|
+
super().__init__()
|
|
59
|
+
self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__)
|
|
60
|
+
if loop:
|
|
61
|
+
self._loop: ioloop.IOLoop = loop
|
|
62
|
+
else:
|
|
63
|
+
self._loop = ioloop.IOLoop.current() # TODO(Aleksei): Switch to the asyncio.EventLoop as tornado >= 6.0 !
|
|
64
|
+
|
|
65
|
+
self._pending_alarms: dict[object, int] = {}
|
|
66
|
+
self._watch_handles: dict[int, int] = {} # {<watch_handle> : <file_descriptor>}
|
|
67
|
+
self._max_watch_handle: int = 0
|
|
68
|
+
self._exc: BaseException | None = None
|
|
69
|
+
|
|
70
|
+
self._idle_asyncio_handle: object | None = None
|
|
71
|
+
self._idle_handle: int = 0
|
|
72
|
+
self._idle_callbacks: dict[int, Callable[[], typing.Any]] = {}
|
|
73
|
+
|
|
74
|
+
def _also_call_idle(self, callback: Callable[_Spec, _T]) -> Callable[_Spec, _T]:
|
|
75
|
+
"""
|
|
76
|
+
Wrap the callback to also call _entering_idle.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
@functools.wraps(callback)
|
|
80
|
+
def wrapper(*args: _Spec.args, **kwargs: _Spec.kwargs) -> _T:
|
|
81
|
+
if not self._idle_asyncio_handle:
|
|
82
|
+
self._idle_asyncio_handle = self._loop.call_later(0, self._entering_idle)
|
|
83
|
+
return callback(*args, **kwargs)
|
|
84
|
+
|
|
85
|
+
return wrapper
|
|
86
|
+
|
|
87
|
+
def _entering_idle(self) -> None:
|
|
88
|
+
"""
|
|
89
|
+
Call all the registered idle callbacks.
|
|
90
|
+
"""
|
|
91
|
+
try:
|
|
92
|
+
for callback in self._idle_callbacks.values():
|
|
93
|
+
callback()
|
|
94
|
+
finally:
|
|
95
|
+
self._idle_asyncio_handle = None
|
|
96
|
+
|
|
97
|
+
def run_in_executor(
|
|
98
|
+
self,
|
|
99
|
+
executor: Executor,
|
|
100
|
+
func: Callable[_Spec, _T],
|
|
101
|
+
*args: _Spec.args,
|
|
102
|
+
) -> asyncio.Future[_T]:
|
|
103
|
+
"""Run callable in executor.
|
|
104
|
+
|
|
105
|
+
:param executor: Executor to use for running the function
|
|
106
|
+
:type executor: concurrent.futures.Executor
|
|
107
|
+
:param func: function to call
|
|
108
|
+
:type func: Callable
|
|
109
|
+
:param args: arguments to function (positional only)
|
|
110
|
+
:type args: object
|
|
111
|
+
:return: future object for the function call outcome.
|
|
112
|
+
:rtype: asyncio.Future
|
|
113
|
+
"""
|
|
114
|
+
return self._loop.run_in_executor(executor, func, *args)
|
|
115
|
+
|
|
116
|
+
def alarm(self, seconds: float, callback: Callable[[], typing.Any]):
|
|
117
|
+
@self._also_call_idle
|
|
118
|
+
@functools.wraps(callback)
|
|
119
|
+
def wrapped() -> None:
|
|
120
|
+
with suppress(KeyError):
|
|
121
|
+
del self._pending_alarms[handle]
|
|
122
|
+
|
|
123
|
+
self.handle_exit(callback)()
|
|
124
|
+
|
|
125
|
+
handle = self._loop.add_timeout(self._loop.time() + seconds, wrapped)
|
|
126
|
+
self._pending_alarms[handle] = 1
|
|
127
|
+
return handle
|
|
128
|
+
|
|
129
|
+
def remove_alarm(self, handle: object) -> bool:
|
|
130
|
+
self._loop.remove_timeout(handle)
|
|
131
|
+
try:
|
|
132
|
+
del self._pending_alarms[handle]
|
|
133
|
+
except KeyError:
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
return True
|
|
137
|
+
|
|
138
|
+
def watch_file(self, fd: int, callback: Callable[[], _T]) -> int:
|
|
139
|
+
@self._also_call_idle
|
|
140
|
+
def handler(_fd: int, _events: int) -> None:
|
|
141
|
+
self.handle_exit(callback)()
|
|
142
|
+
|
|
143
|
+
self._loop.add_handler(fd, handler, ioloop.IOLoop.READ)
|
|
144
|
+
self._max_watch_handle += 1
|
|
145
|
+
handle = self._max_watch_handle
|
|
146
|
+
self._watch_handles[handle] = fd
|
|
147
|
+
return handle
|
|
148
|
+
|
|
149
|
+
def remove_watch_file(self, handle: int) -> bool:
|
|
150
|
+
fd = self._watch_handles.pop(handle, None)
|
|
151
|
+
if fd is None:
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
self._loop.remove_handler(fd)
|
|
155
|
+
return True
|
|
156
|
+
|
|
157
|
+
def enter_idle(self, callback: Callable[[], typing.Any]) -> int:
|
|
158
|
+
"""
|
|
159
|
+
Add a callback for entering idle.
|
|
160
|
+
|
|
161
|
+
Returns a handle that may be passed to remove_idle()
|
|
162
|
+
"""
|
|
163
|
+
# XXX there's no such thing as "idle" in most event loops; this fakes
|
|
164
|
+
# it by adding extra callback to the timer and file watch callbacks.
|
|
165
|
+
self._idle_handle += 1
|
|
166
|
+
self._idle_callbacks[self._idle_handle] = callback
|
|
167
|
+
return self._idle_handle
|
|
168
|
+
|
|
169
|
+
def remove_enter_idle(self, handle: int) -> bool:
|
|
170
|
+
"""
|
|
171
|
+
Remove an idle callback.
|
|
172
|
+
|
|
173
|
+
Returns True if the handle was removed.
|
|
174
|
+
"""
|
|
175
|
+
try:
|
|
176
|
+
del self._idle_callbacks[handle]
|
|
177
|
+
except KeyError:
|
|
178
|
+
return False
|
|
179
|
+
return True
|
|
180
|
+
|
|
181
|
+
def handle_exit(self, f: Callable[_Spec, _T]) -> Callable[_Spec, _T | Literal[False]]:
|
|
182
|
+
@functools.wraps(f)
|
|
183
|
+
def wrapper(*args: _Spec.args, **kwargs: _Spec.kwargs) -> _T | Literal[False]:
|
|
184
|
+
try:
|
|
185
|
+
return f(*args, **kwargs)
|
|
186
|
+
except ExitMainLoop:
|
|
187
|
+
pass # handled later
|
|
188
|
+
except Exception as exc:
|
|
189
|
+
self._exc = exc
|
|
190
|
+
|
|
191
|
+
if self._idle_asyncio_handle:
|
|
192
|
+
# clean it up to prevent old callbacks
|
|
193
|
+
# from messing things up if loop is restarted
|
|
194
|
+
self._loop.remove_timeout(self._idle_asyncio_handle)
|
|
195
|
+
self._idle_asyncio_handle = None
|
|
196
|
+
|
|
197
|
+
self._loop.stop()
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
return wrapper
|
|
201
|
+
|
|
202
|
+
def run(self) -> None:
|
|
203
|
+
self._loop.start()
|
|
204
|
+
if self._exc:
|
|
205
|
+
exc, self._exc = self._exc, None
|
|
206
|
+
raise exc.with_traceback(exc.__traceback__)
|