From fda42c5c8a4718401f505580403022a41f8c0ddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eugenio=20Parodi=20=F0=9F=8C=B6=EF=B8=8F?= Date: Wed, 22 Jan 2025 18:06:23 +0000 Subject: [PATCH 1/9] Prototyping the coroutine support --- TermTk/TTkCore/signal.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/TermTk/TTkCore/signal.py b/TermTk/TTkCore/signal.py index 67fef876..d011711e 100644 --- a/TermTk/TTkCore/signal.py +++ b/TermTk/TTkCore/signal.py @@ -58,9 +58,10 @@ Methods __all__ = ['pyTTkSlot', 'pyTTkSignal'] # from typing import TypeVar, TypeVarTuple, Generic, List -from inspect import getfullargspec +from inspect import getfullargspec, iscoroutinefunction from types import LambdaType from threading import Lock +import asyncio def pyTTkSlot(*args): def pyTTkSlot_d(func): @@ -72,8 +73,9 @@ def pyTTkSlot(*args): # Ts = TypeVarTuple("Ts") # class pyTTkSignal(Generic[*Ts]): class pyTTkSignal(): + _asyncio_event_loop = asyncio.new_event_loop() _signals = [] - __slots__ = ('_types', '_connected_slots', '_mutex') + __slots__ = ('_types', '_connected_slots', '_connected_async_slots', '_mutex') def __init__(self, *args, **kwargs) -> None: # ref: http://pyqt.sourceforge.net/Docs/PyQt5/signals_slots.html#PyQt5.QtCore.pyqtSignal @@ -87,6 +89,7 @@ class pyTTkSignal(): # an unbound signal self._types = args self._connected_slots = {} + self._connected_async_slots = {} self._mutex = Lock() pyTTkSignal._signals.append(self) @@ -122,8 +125,12 @@ class pyTTkSignal(): if a!=b and not issubclass(a,b): error = "Decorated slot has no signature compatible: "+slot.__name__+str(slot._TTkslot_attr)+" != signal"+str(self._types) raise TypeError(error) - if slot not in self._connected_slots: - self._connected_slots[slot]=slice(nargs) + if iscoroutinefunction(slot): + if slot not in self._connected_async_slots: + self._connected_async_slots[slot]=slice(nargs) + else: + if slot not in self._connected_slots: + self._connected_slots[slot]=slice(nargs) def disconnect(self, *args, **kwargs) -> None: for slot in args: @@ -137,6 +144,14 @@ class pyTTkSignal(): raise TypeError(error) for slot,sl in self._connected_slots.copy().items(): slot(*args[sl], **kwargs) + if self._connected_async_slots: + asyncio.set_event_loop(_loop:=pyTTkSignal._asyncio_event_loop) + # asyncio.set_event_loop(_loop:=asyncio.new_event_loop()) + for slot,sl in self._connected_async_slots.copy().items(): + asyncio.run_coroutine_threadsafe(slot(*args[sl], **kwargs), _loop) + # should I call the future results? + self.future.result() + self._mutex.release() def clear(self): From ea3349c281f428ea0aaa10022e616a0f9f5fcc8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eugenio=20Parodi=20=F0=9F=8C=B6=EF=B8=8F?= Date: Wed, 22 Jan 2025 18:10:48 +0000 Subject: [PATCH 2/9] fix --- TermTk/TTkCore/signal.py | 1 - 1 file changed, 1 deletion(-) diff --git a/TermTk/TTkCore/signal.py b/TermTk/TTkCore/signal.py index d011711e..dacfaa66 100644 --- a/TermTk/TTkCore/signal.py +++ b/TermTk/TTkCore/signal.py @@ -150,7 +150,6 @@ class pyTTkSignal(): for slot,sl in self._connected_async_slots.copy().items(): asyncio.run_coroutine_threadsafe(slot(*args[sl], **kwargs), _loop) # should I call the future results? - self.future.result() self._mutex.release() From ef40e068f3cb52ffee6798f4f436d11705a03563 Mon Sep 17 00:00:00 2001 From: Shlomo Zalman Rabinowitz <106286969+SZRabinowitz@users.noreply.github.com> Date: Wed, 22 Jan 2025 23:13:03 -0500 Subject: [PATCH 3/9] Adjustments for pr #313 - Creates executor to run async slots - All async slots run in dedicated thread --- TermTk/TTkCore/signal.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/TermTk/TTkCore/signal.py b/TermTk/TTkCore/signal.py index dacfaa66..3f540cb1 100644 --- a/TermTk/TTkCore/signal.py +++ b/TermTk/TTkCore/signal.py @@ -59,6 +59,7 @@ __all__ = ['pyTTkSlot', 'pyTTkSignal'] # from typing import TypeVar, TypeVarTuple, Generic, List from inspect import getfullargspec, iscoroutinefunction +from concurrent.futures import ThreadPoolExecutor from types import LambdaType from threading import Lock import asyncio @@ -75,7 +76,7 @@ def pyTTkSlot(*args): class pyTTkSignal(): _asyncio_event_loop = asyncio.new_event_loop() _signals = [] - __slots__ = ('_types', '_connected_slots', '_connected_async_slots', '_mutex') + __slots__ = ('_types', '_connected_slots', '_connected_async_slots', '_mutex', '_async_executor') def __init__(self, *args, **kwargs) -> None: # ref: http://pyqt.sourceforge.net/Docs/PyQt5/signals_slots.html#PyQt5.QtCore.pyqtSignal @@ -91,6 +92,7 @@ class pyTTkSignal(): self._connected_slots = {} self._connected_async_slots = {} self._mutex = Lock() + self._async_executor = None pyTTkSignal._signals.append(self) def connect(self, slot): @@ -137,6 +139,14 @@ class pyTTkSignal(): if slot in self._connected_slots: del self._connected_slots[slot] + def async_runner(self, coros): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.run_until_complete(asyncio.gather(*coros)) + loop.close() + + + def emit(self, *args, **kwargs) -> None: if not self._mutex.acquire(False): return if len(args) != len(self._types): @@ -145,12 +155,10 @@ class pyTTkSignal(): for slot,sl in self._connected_slots.copy().items(): slot(*args[sl], **kwargs) if self._connected_async_slots: - asyncio.set_event_loop(_loop:=pyTTkSignal._asyncio_event_loop) - # asyncio.set_event_loop(_loop:=asyncio.new_event_loop()) - for slot,sl in self._connected_async_slots.copy().items(): - asyncio.run_coroutine_threadsafe(slot(*args[sl], **kwargs), _loop) - # should I call the future results? - + if self._async_executor is None: + self._async_executor = ThreadPoolExecutor(max_workers=1) # async dont need so many workers + coros = [slot(*args[sl], **kwargs) for slot,sl in self._connected_async_slots.copy().items()] + self._async_executor.submit(self.async_runner, coros) self._mutex.release() def clear(self): From 82ca2c5995824af72b1a68bbce3e762d2f02a185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eugenio=20Parodi=20=F0=9F=8C=B6=EF=B8=8F?= Date: Thu, 23 Jan 2025 10:34:56 +0000 Subject: [PATCH 4/9] Improved the async runner thanks to @SZRabinowitz --- TermTk/TTkCore/signal.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/TermTk/TTkCore/signal.py b/TermTk/TTkCore/signal.py index dacfaa66..bff42780 100644 --- a/TermTk/TTkCore/signal.py +++ b/TermTk/TTkCore/signal.py @@ -60,7 +60,7 @@ __all__ = ['pyTTkSlot', 'pyTTkSignal'] # from typing import TypeVar, TypeVarTuple, Generic, List from inspect import getfullargspec, iscoroutinefunction from types import LambdaType -from threading import Lock +from threading import Lock, Thread import asyncio def pyTTkSlot(*args): @@ -73,7 +73,6 @@ def pyTTkSlot(*args): # Ts = TypeVarTuple("Ts") # class pyTTkSignal(Generic[*Ts]): class pyTTkSignal(): - _asyncio_event_loop = asyncio.new_event_loop() _signals = [] __slots__ = ('_types', '_connected_slots', '_connected_async_slots', '_mutex') def __init__(self, *args, **kwargs) -> None: @@ -137,6 +136,12 @@ class pyTTkSignal(): if slot in self._connected_slots: del self._connected_slots[slot] + def _async_runner(self, coros): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.run_until_complete(asyncio.gather(*coros)) + loop.close() + def emit(self, *args, **kwargs) -> None: if not self._mutex.acquire(False): return if len(args) != len(self._types): @@ -145,12 +150,8 @@ class pyTTkSignal(): for slot,sl in self._connected_slots.copy().items(): slot(*args[sl], **kwargs) if self._connected_async_slots: - asyncio.set_event_loop(_loop:=pyTTkSignal._asyncio_event_loop) - # asyncio.set_event_loop(_loop:=asyncio.new_event_loop()) - for slot,sl in self._connected_async_slots.copy().items(): - asyncio.run_coroutine_threadsafe(slot(*args[sl], **kwargs), _loop) - # should I call the future results? - + coros = [slot(*args[sl], **kwargs) for slot,sl in self._connected_async_slots.copy().items()] + Thread(target=self._async_runner, args=(coros,)).start() self._mutex.release() def clear(self): From 81e258c722e77fccc56fbb4c73fda4e025aef9ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eugenio=20Parodi=20=F0=9F=8C=B6=EF=B8=8F?= Date: Thu, 23 Jan 2025 11:26:00 +0000 Subject: [PATCH 5/9] Moved the coroutines helpers outsize the Signal class --- TermTk/TTkCore/signal.py | 29 ++++++++++++------ tests/t.ui/test.ui.034.async.01.py | 49 ++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 9 deletions(-) create mode 100755 tests/t.ui/test.ui.034.async.01.py diff --git a/TermTk/TTkCore/signal.py b/TermTk/TTkCore/signal.py index bff42780..3f6325d6 100644 --- a/TermTk/TTkCore/signal.py +++ b/TermTk/TTkCore/signal.py @@ -60,9 +60,26 @@ __all__ = ['pyTTkSlot', 'pyTTkSignal'] # from typing import TypeVar, TypeVarTuple, Generic, List from inspect import getfullargspec, iscoroutinefunction from types import LambdaType -from threading import Lock, Thread +from threading import Lock import asyncio +import importlib.util + +if importlib.util.find_spec('pyodideProxy'): + pass +else: + from threading import Thread + import asyncio + + def _async_runner(coros): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.run_until_complete(asyncio.gather(*coros)) + loop.close() + + def _run_coroutines(coros): + Thread(target=_async_runner, args=(coros,)).start() + def pyTTkSlot(*args): def pyTTkSlot_d(func): # Add signature attributes to the function @@ -136,12 +153,6 @@ class pyTTkSignal(): if slot in self._connected_slots: del self._connected_slots[slot] - def _async_runner(self, coros): - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - loop.run_until_complete(asyncio.gather(*coros)) - loop.close() - def emit(self, *args, **kwargs) -> None: if not self._mutex.acquire(False): return if len(args) != len(self._types): @@ -151,7 +162,7 @@ class pyTTkSignal(): slot(*args[sl], **kwargs) if self._connected_async_slots: coros = [slot(*args[sl], **kwargs) for slot,sl in self._connected_async_slots.copy().items()] - Thread(target=self._async_runner, args=(coros,)).start() + _run_coroutines(coros) self._mutex.release() def clear(self): @@ -165,4 +176,4 @@ class pyTTkSignal(): def forward(self): def _ret(*args, **kwargs) -> None: self.emit(*args, **kwargs) - return _ret + return _ret \ No newline at end of file diff --git a/tests/t.ui/test.ui.034.async.01.py b/tests/t.ui/test.ui.034.async.01.py new file mode 100755 index 00000000..d326ae88 --- /dev/null +++ b/tests/t.ui/test.ui.034.async.01.py @@ -0,0 +1,49 @@ +import asyncio +import inspect +import sys, os +import time + +sys.path.append(os.path.join(sys.path[0],'..')) +import TermTk as ttk + +root = ttk.TTk(layout=ttk.TTkVBoxLayout()) +res = ttk.TTkLabel(parent=root) +num = ttk.TTkLabel(parent=root, text=1) +button_add = ttk.TTkButton(parent=root, text="+") +button_add.clicked.connect(lambda: num.setText(int(num.text()) + 1)) +button_call = ttk.TTkButton(parent=root, text="Call") +ttk.TTkButton(parent=root, text="Quit").clicked.connect(ttk.TTkHelper.quit) + +@ttk.pyTTkSlot() +def call0(): + res.setText("0 Calling...") + time.sleep(1) + res.setText("0 Calling... - DONE") + +async def call1(): + res.setText("1 Calling...") + await asyncio.sleep(3) + res.setText("1 Calling... - DONE") + +@ttk.pyTTkSlot() +async def call2(): + res.setText("2 Calling...") + await asyncio.sleep(5) + res.setText("2 Calling... - DONE") + +@ttk.pyTTkSlot(int) +async def call3(val): + res.setText(f"3 Calling... {val}") + await asyncio.sleep(5) + res.setText(f"3 Calling... {val} - DONE") + +print(inspect.iscoroutinefunction(call0)) +print(inspect.iscoroutinefunction(call1)) +print(inspect.iscoroutinefunction(call2)) +print(inspect.iscoroutinefunction(call3)) + +button_call.clicked.connect(call0) +button_call.clicked.connect(call1) +button_call.clicked.connect(call2) + +root.mainloop() \ No newline at end of file From b5698dee630342fed5a55016d592b7227642c090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eugenio=20Parodi=20=F0=9F=8C=B6=EF=B8=8F?= Date: Thu, 23 Jan 2025 11:48:04 +0000 Subject: [PATCH 6/9] Added async test --- tests/t.ui/test.ui.034.async.01.py | 63 +++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/tests/t.ui/test.ui.034.async.01.py b/tests/t.ui/test.ui.034.async.01.py index d326ae88..8f0633ea 100755 --- a/tests/t.ui/test.ui.034.async.01.py +++ b/tests/t.ui/test.ui.034.async.01.py @@ -1,41 +1,81 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2025 Eugenio Parodi +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + import asyncio import inspect import sys, os import time -sys.path.append(os.path.join(sys.path[0],'..')) +sys.path.append(os.path.join(sys.path[0],'../..')) import TermTk as ttk -root = ttk.TTk(layout=ttk.TTkVBoxLayout()) -res = ttk.TTkLabel(parent=root) -num = ttk.TTkLabel(parent=root, text=1) -button_add = ttk.TTkButton(parent=root, text="+") +root = ttk.TTk(layout=(rgl:=ttk.TTkGridLayout())) + +rgl.addWidget(button_add := ttk.TTkButton(maxSize=(8,3), border=True, text="+") , 0, 0) +rgl.addWidget(button_call := ttk.TTkButton(maxSize=(8,3), border=True, text="Call") , 1, 0) +rgl.addWidget(button_call2 := ttk.TTkButton(maxSize=(8,3), border=True, text="Call2", checkable=True) , 2, 0) button_add.clicked.connect(lambda: num.setText(int(num.text()) + 1)) -button_call = ttk.TTkButton(parent=root, text="Call") -ttk.TTkButton(parent=root, text="Quit").clicked.connect(ttk.TTkHelper.quit) + +rgl.addWidget(res := ttk.TTkLabel(test='out...'), 0 , 1) +rgl.addWidget(num := ttk.TTkLabel(text='1') , 1 , 1) + +rgl.addWidget(qb := ttk.TTkButton(border=True, maxWidth=8, text="Quit"), 0,2,3,1) +qb.clicked.connect(ttk.TTkHelper.quit) + +rgl.addWidget(ttk.TTkLogViewer(), 3, 0, 1, 3) + @ttk.pyTTkSlot() def call0(): res.setText("0 Calling...") + ttk.TTkLog.info("0 Calling...") time.sleep(1) res.setText("0 Calling... - DONE") + ttk.TTkLog.info("0 Calling... - DONE") async def call1(): res.setText("1 Calling...") + ttk.TTkLog.info("1 Calling...") await asyncio.sleep(3) res.setText("1 Calling... - DONE") + ttk.TTkLog.info("1 Calling... - DONE") @ttk.pyTTkSlot() async def call2(): res.setText("2 Calling...") - await asyncio.sleep(5) + ttk.TTkLog.info("2 Calling...") + await asyncio.sleep(4) res.setText("2 Calling... - DONE") + ttk.TTkLog.info("2 Calling... - DONE") -@ttk.pyTTkSlot(int) +@ttk.pyTTkSlot(bool) async def call3(val): res.setText(f"3 Calling... {val}") + ttk.TTkLog.info(f"3 Calling... {val}") await asyncio.sleep(5) res.setText(f"3 Calling... {val} - DONE") + ttk.TTkLog.info(f"3 Calling... {val} - DONE") print(inspect.iscoroutinefunction(call0)) print(inspect.iscoroutinefunction(call1)) @@ -46,4 +86,9 @@ button_call.clicked.connect(call0) button_call.clicked.connect(call1) button_call.clicked.connect(call2) +# button_call2.toggled.connect(call0) +button_call2.toggled.connect(call1) +button_call2.toggled.connect(call2) +button_call2.toggled.connect(call3) + root.mainloop() \ No newline at end of file From df9c39d85a596850572e705fe1944b1d95bd3f1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eugenio=20Parodi=20=F0=9F=8C=B6=EF=B8=8F?= Date: Thu, 23 Jan 2025 12:05:28 +0000 Subject: [PATCH 7/9] Added WASM support for async calls, thanks to @SZRabinowitz --- TermTk/TTkCore/signal.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/TermTk/TTkCore/signal.py b/TermTk/TTkCore/signal.py index 3f6325d6..b69acf32 100644 --- a/TermTk/TTkCore/signal.py +++ b/TermTk/TTkCore/signal.py @@ -66,7 +66,9 @@ import asyncio import importlib.util if importlib.util.find_spec('pyodideProxy'): - pass + def _run_coroutines(coros): + for call in coros: + asyncio.create_task(call) else: from threading import Thread import asyncio From b15a0d74155e1cd2074acc2aca9504763cd17f5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eugenio=20Parodi=20=F0=9F=8C=B6=EF=B8=8F?= Date: Thu, 23 Jan 2025 12:15:15 +0000 Subject: [PATCH 8/9] Refined the async test --- tests/t.ui/test.ui.034.async.01.py | 41 ++++++++++++++++++------------ 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/tests/t.ui/test.ui.034.async.01.py b/tests/t.ui/test.ui.034.async.01.py index 8f0633ea..d89f7b8c 100755 --- a/tests/t.ui/test.ui.034.async.01.py +++ b/tests/t.ui/test.ui.034.async.01.py @@ -26,6 +26,7 @@ import asyncio import inspect import sys, os import time +from datetime import datetime sys.path.append(os.path.join(sys.path[0],'../..')) import TermTk as ttk @@ -46,36 +47,44 @@ qb.clicked.connect(ttk.TTkHelper.quit) rgl.addWidget(ttk.TTkLogViewer(), 3, 0, 1, 3) +# normal slot with a bolocking call @ttk.pyTTkSlot() def call0(): - res.setText("0 Calling...") - ttk.TTkLog.info("0 Calling...") + now = datetime.now().strftime("[%Y-%m-%d]-[%H:%M:%S]") + res.setText(f"{now} 0 Calling...") + ttk.TTkLog.info(f"{now} 0 Calling...") time.sleep(1) - res.setText("0 Calling... - DONE") - ttk.TTkLog.info("0 Calling... - DONE") + res.setText(f"{now} 0 Calling... - DONE") + ttk.TTkLog.info(f"{now} 0 Calling... - DONE") +# async call wothout slot decorator async def call1(): - res.setText("1 Calling...") - ttk.TTkLog.info("1 Calling...") + now = datetime.now().strftime("[%Y-%m-%d]-[%H:%M:%S]") + res.setText(f"{now} 1 Calling...") + ttk.TTkLog.info(f"{now} 1 Calling...") await asyncio.sleep(3) - res.setText("1 Calling... - DONE") - ttk.TTkLog.info("1 Calling... - DONE") + res.setText(f"{now} 1 Calling... - DONE") + ttk.TTkLog.info(f"{now} 1 Calling... - DONE") +# async call with slot decorator @ttk.pyTTkSlot() async def call2(): - res.setText("2 Calling...") - ttk.TTkLog.info("2 Calling...") + now = datetime.now().strftime("[%Y-%m-%d]-[%H:%M:%S]") + res.setText(f"{now} 2 Calling...") + ttk.TTkLog.info(f"{now} 2 Calling...") await asyncio.sleep(4) - res.setText("2 Calling... - DONE") - ttk.TTkLog.info("2 Calling... - DONE") + res.setText(f"{now} 2 Calling... - DONE") + ttk.TTkLog.info(f"{now} 2 Calling... - DONE") +# async call with slot decorator and arguments @ttk.pyTTkSlot(bool) async def call3(val): - res.setText(f"3 Calling... {val}") - ttk.TTkLog.info(f"3 Calling... {val}") + now = datetime.now().strftime("[%Y-%m-%d]-[%H:%M:%S]") + res.setText(f"{now} 3 Calling... {val}") + ttk.TTkLog.info(f"{now} 3 Calling... {val}") await asyncio.sleep(5) - res.setText(f"3 Calling... {val} - DONE") - ttk.TTkLog.info(f"3 Calling... {val} - DONE") + res.setText(f"{now} 3 Calling... {val} - DONE") + ttk.TTkLog.info(f"{now} 3 Calling... {val} - DONE") print(inspect.iscoroutinefunction(call0)) print(inspect.iscoroutinefunction(call1)) From 4683726bde1933b79e8080e2944c4db13916d72d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eugenio=20Parodi=20=F0=9F=8C=B6=EF=B8=8F?= Date: Thu, 23 Jan 2025 12:16:35 +0000 Subject: [PATCH 9/9] Fix test --- tools/check.import.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/check.import.sh b/tools/check.import.sh index c5ff7a6b..ffc975f8 100755 --- a/tools/check.import.sh +++ b/tools/check.import.sh @@ -11,6 +11,8 @@ __check(){ -e "signal.py:from inspect import getfullargspec" \ -e "signal.py:from types import LambdaType" \ -e "signal.py:from threading import Lock" \ + -e "signal.py:import asyncio" \ + -e "signal.py:import importlib.util" \ -e "colors.py:from .colors_ansi_map" \ -e "log.py:import inspect" \ -e "log.py:import logging" \