Package: asyncslot
asyncslotasyncslotis a Python module that allows you to use asyncio-based
libraries in Python for Qt.SynopsisTo use asyncio-based libraries in Python for Qt, wrapapp.exec()insideAsyncSlotRunner, and connect signal to a coroutine function usingasyncSlot.A minimal working GUI example (taken fromexamples/minimal_gui.py):importasynciofromPySide6importQtWidgetsfromasyncslotimportasyncSlot,AsyncSlotRunnerasyncdefsay_hi():awaitasyncio.sleep(1)QtWidgets.QMessageBox.information(None,"Demo","Hi")app=QtWidgets.QApplication()button=QtWidgets.QPushButton()button.setText('Say Hi after one second')button.clicked.connect(asyncSlot(say_hi))# <-- instead of connect(say_hi)button.show()withAsyncSlotRunner():# <-- wrap in Runnerapp.exec()Requirementsasyncslotsupports the following:Python version: 3.7 or higherQt binding: PyQt5, PyQt6, PySide2, PySide6Operating system: Linux, MacOS, WindowsInstallationpip install asyncslotThe above doesnotinstall the Qt bindings.  To install Qt bindings, you maypip install PyQt6Alternatively, you may installasyncslottogether with your Qt binding of
choice, for examplepip install asyncslot[PyQt6]Detailsasyncslotembeds a logical asyncio event loop (AsyncSlotEventLoop)
within a physical Qt event loop (QEventLoop), so that Python libraries
written for asyncio can be used by a Python for Qt application.Running ModesAnAsyncSlotEventLoopmay be run inattached modeornested mode.UseAsyncSlotEventLoopas a context manager to run it in attached mode.This mode only installs the logical asyncio event loop; the physical Qt
event loop must still be run as usual, e.g. byapp.exec().  This is the
preferred workflow as it integrates seamlessly with an existing Qt app.CallAsyncSlotEventLoop.run_foreverto run it in nested mode.  This starts
a (possibly nested) Qt event loop usingQEventLoop.exec()and waits until
it exits.  This is the standard asyncio workflow and is convenient for
unit testing, but it is not recommended for integration with an existing Qt
app as nested event loops are advised against by Qt.For either mode, a (global)QCoreApplication(orQApplication/QGuiApplication) instance must exist before running any coroutine,
as is required by Qt.Clean-upTo properly release the resources of the event loop after it stops, you
should callshutdown_asyncgensandshutdown_default_executor, followed
byclose.  The first two methods are actually coroutines and therefore
must be run from within the event loop.For attached mode, use theasyncslot.AsyncSlotRunnercontext manager,
which handles clean-up automatically.  Note, however, that it actually runs
the first two coroutines in nested mode, i.e. a Qt event loop is started.Your code should be prepared for this.For nested mode,asyncio.run()handles clean-up automatically.TheasyncSlotAdaptorasyncslot.asyncSlotwraps a coroutine function (one defined byasync def)
to make it usable as a Qt slot.  Without wrapping, a coroutine function
(whether decorated withQtCore.Slot/PyQt6.pyqtSlotor not)
generally cannot be used as a slot because calling it merely returns a
coroutine object instead of performing real work.Under the hood,asyncslot.asyncSlotcallsAsyncSlotEventLoop.run_task,
a custom method which creates a Task wrapping the coroutine and executes
it immediately until the first suspension point.This is designed to work with a common pattern where some work has to be
performed immediately in response to a signal.  For example, theclickedhandler of a "Send Order" button normally disables the button on entry
before actually sending the order over network, to avoid sending duplicate
orders.  For this to work correctly, the code until the first suspension
point must be executed synchronously.AnAsyncSlotEventLoopmust be running when a coroutine wrapped byasyncSlotis called, or aRuntimeErrorwill be raised.It is not recommended to decorate a coroutine function withasyncSlotas that would make anasync deffunction into a normal function, which
is confusing.CancellationTo cancel a running coroutine from within itself, raiseasyncio.CancelledError.To retrieve theTaskobject from within the running coroutine and store
it somewhere to be used later, callasyncio.current_task()from within
the running coroutine.Implementation NotesBy embedding a (logical) asyncio event loop inside a (physical) Qt event
loop, what's not changed (from the perspective of the asyncio event loop) is
that all calls (other thancall_soon_threadsafe) are still made from the
same thread.  This frees us from multi-threading complexities.What has changed, however, is that in a standalone asyncio event loop, no
code can run when the scheduler (specifically,_run_once) is blocked inselect(), while in an embedded asyncio event loop, aselect()call
that would otherwise block yields, allowing any code to run while the loop
is "logically" blocked inselect.For example,BaseEventLoop.stop()is implemented by setting the flag_stoppingtoTrue, which is then checked before the next iteration of_run_onceto stop the loop.  This works becausestopcan only ever be
called from a callback, and a callback can only ever be called afterselectreturns and before the next iteration of_run_once.  The behavior
changes ifselectyields andstopis called -- the event loop wait not
wake up until some IO is available.We refer to code that runs (from the Qt event loop) afterselectyields
and before_run_onceis called again asinjected code.  We must
examine and handle the implications of such code.We do this by fitting injected code execution into the standalone asyncio
event loop model.  Specifically, we treat injected code as if they were
scheduled withcall_soon_threadsafe, which wakes up the selector and
executes the code.Withsome loss of generality, we assume no IO event
nor timed callback is ready at the exact same time, so that the scheduler
will be put back into blockingselectimmediately after the code finishes
running (unless the code callsstop).  This simplification is acceptable
because the precise timing of multiple IO or timer events should not be
relied upon.In practice, we cannot actually wake up the asyncio scheduler every time
injected code is executed, firstly because there's no way to detect their
execution and secondly because doing so would be highly inefficient.
Instead, weassumethat injected code which does not access the event loop
object or its selector is benign enough to be treated asindependentfrom the asyncio event loop ecosystem and may be safely ignored.This leaves us to just consider injected code that accesses the event loop
object or its selector and examine its impact on scheduling.  The scheduler
depends on three things:  the_readyqueue for "soon" callbacks, the_scheduledqueue for timer callbacks, and_selectorfor IO events.
If the injected code touches any of these things, it needs to be handled.While the public interface ofAbstractEventLoophas numerous methods, the
methods that modify those three things boil down tocall_soon,call_at,call_later, (arguably)stop, and anything that modifies the selector
(proactor).  When any of these happens, we physically or logically wake up
the selector to simulate acall_soon_threadsafecall.Historyasyncslotis derived fromqasyncbut rewritten from
scratch.  qasync is a fork ofasyncqt, which is a fork ofquamash.LicenseBSD License.