diff --git a/README.md b/README.md index d5743f2..b4a347f 100644 --- a/README.md +++ b/README.md @@ -46,17 +46,15 @@ class MainWindow(QWidget): if __name__ == "__main__": app = QApplication(sys.argv) - event_loop = QEventLoop(app) - asyncio.set_event_loop(event_loop) - app_close_event = asyncio.Event() app.aboutToQuit.connect(app_close_event.set) main_window = MainWindow() main_window.show() - with event_loop: - event_loop.run_until_complete(app_close_event.wait()) + # for 3.11 or older use qasync.run instead of asyncio.run + # qasync.run(app_close_event.wait()) + asyncio.run(app_close_event.wait(), loop_factory=QEventLoop) ``` More detailed examples can be found [here](https://github.com/CabbageDevelopment/qasync/tree/master/examples). diff --git a/examples/aiohttp_fetch.py b/examples/aiohttp_fetch.py index 1fdcc4a..1e4c2ee 100644 --- a/examples/aiohttp_fetch.py +++ b/examples/aiohttp_fetch.py @@ -13,6 +13,7 @@ QVBoxLayout, QWidget, ) + from qasync import QEventLoop, asyncClose, asyncSlot @@ -68,15 +69,16 @@ async def on_btn_fetch_clicked(self): if __name__ == "__main__": app = QApplication(sys.argv) - event_loop = QEventLoop(app) - asyncio.set_event_loop(event_loop) - app_close_event = asyncio.Event() app.aboutToQuit.connect(app_close_event.set) - + main_window = MainWindow() main_window.show() - event_loop.create_task(main_window.boot()) - event_loop.run_until_complete(app_close_event.wait()) - event_loop.close() + async def async_main(): + asyncio.create_task(main_window.boot()) + await app_close_event.wait() + + # for 3.11 or older use qasync.run instead of asyncio.run + # qasync.run(async_main()) + asyncio.run(async_main(), loop_factory=QEventLoop) diff --git a/examples/executor_example.py b/examples/executor_example.py index f208912..2dee234 100644 --- a/examples/executor_example.py +++ b/examples/executor_example.py @@ -1,10 +1,11 @@ -import functools import asyncio -import time +import functools import sys +import time # from PyQt6.QtWidgets import from PySide6.QtWidgets import QApplication, QProgressBar + from qasync import QEventLoop, QThreadExecutor @@ -34,8 +35,6 @@ def last_50(progress, loop): if __name__ == "__main__": app = QApplication(sys.argv) - event_loop = QEventLoop(app) - asyncio.set_event_loop(event_loop) - - event_loop.run_until_complete(master()) - event_loop.close() + # for 3.11 or older use qasync.run instead of asyncio.run + # qasync.run(master()) + asyncio.run(master(), loop_factory=QEventLoop) diff --git a/qasync/__init__.py b/qasync/__init__.py index 732f7a2..f6bbfcb 100644 --- a/qasync/__init__.py +++ b/qasync/__init__.py @@ -331,11 +331,8 @@ class _QEventLoop: ... assert x + y == 4 ... await asyncio.sleep(.1) >>> - >>> loop = QEventLoop(app) - >>> asyncio.set_event_loop(loop) - >>> with loop: - ... loop.run_until_complete(xplusy(2, 2)) - + >>> asyncio.run(xplusy(2, 2), loop_factory=lambda:QEventLoop(app)) + If the event loop shall be used with an existing and already running QApplication it must be specified in the constructor via already_running=True In this case the user is responsible for loop cleanup with stop() and close() diff --git a/tests/test_qeventloop.py b/tests/test_qeventloop.py index 9eefc72..d2feb96 100644 --- a/tests/test_qeventloop.py +++ b/tests/test_qeventloop.py @@ -808,6 +808,72 @@ async def mycoro(): assert "seconds" in msg +@pytest.mark.skipif(sys.version_info < (3, 12), reason="Requires Python 3.12+") +def test_asyncio_run(application): + """Test that QEventLoop is compatible with asyncio.run()""" + done = False + loop = None + + async def main(): + nonlocal done, loop + assert loop.is_running() + assert asyncio.get_running_loop() is loop + await asyncio.sleep(0.01) + done = True + + def factory(): + nonlocal loop + loop = qasync.QEventLoop(application) + return loop + + asyncio.run(main(), loop_factory=factory) + assert done + assert loop.is_closed() + assert not loop.is_running() + + +@pytest.mark.skipif(sys.version_info < (3, 12), reason="Requires Python 3.12+") +def test_asyncio_run_cleanup(application): + """Test that running tasks are cleaned up""" + task = None + cancelled = False + + async def main(): + nonlocal task, cancelled + + async def long_task(): + nonlocal cancelled + try: + await asyncio.sleep(10) + except asyncio.CancelledError: + cancelled = True + + task = asyncio.create_task(long_task()) + await asyncio.sleep(0.01) + + asyncio.run(main(), loop_factory=lambda: qasync.QEventLoop(application)) + assert cancelled + + +def test_qasync_run(application): + """Test running with qasync.run()""" + done = False + loop = None + + async def main(): + nonlocal done, loop + loop = asyncio.get_running_loop() + assert loop.is_running() + await asyncio.sleep(0.01) + done = True + + # qasync.run uses an EventLoopPolicy to create the loop + qasync.run(main()) + assert done + assert loop.is_closed() + assert not loop.is_running() + + def teardown_module(module): """ Remove handlers from all loggers