Skip to content

Commit eada597

Browse files
committed
rename to asyncWrap, parametrize the test and format the code
1 parent f8bdd58 commit eada597

3 files changed

Lines changed: 59 additions & 57 deletions

File tree

examples/modal_example.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,28 @@
22
import sys
33

44
# from PyQt6.QtWidgets import
5-
from PySide6.QtWidgets import QApplication, QProgressBar, QMessageBox
6-
from qasync import QEventLoop, call_sync
5+
from PySide6.QtWidgets import QApplication, QMessageBox, QProgressBar
6+
7+
from qasync import QEventLoop, asyncWrap
78

89

910
async def master():
1011
progress = QProgressBar()
1112
progress.setRange(0, 99)
1213
progress.show()
1314
await first_50(progress)
14-
15+
1516

1617
async def first_50(progress):
1718
for i in range(50):
1819
progress.setValue(i)
1920
await asyncio.sleep(0.1)
20-
21+
2122
# Schedule the last 50% to run asynchronously
22-
asyncio.create_task(
23-
last_50(progress)
24-
)
25-
23+
asyncio.create_task(last_50(progress))
24+
2625
# create a notification box, use helper to make entering event loop safe.
27-
result = await call_sync(
26+
result = await asyncWrap(
2827
lambda: QMessageBox.information(
2928
None, "Task Completed", "The first 50% of the task is completed."
3029
)

qasync/__init__.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
BSD License
99
"""
1010

11-
__all__ = ["QEventLoop", "QThreadExecutor", "asyncSlot", "asyncClose", "call_sync"]
11+
__all__ = ["QEventLoop", "QThreadExecutor", "asyncSlot", "asyncClose", "asyncWrap"]
1212

1313
import asyncio
1414
import contextlib
@@ -833,8 +833,29 @@ def wrapper(*args, **kwargs):
833833

834834
return outer_decorator
835835

836-
async def call_sync(fn, *args, **kwargs):
837-
"""run a blocking call from the Qt event loop."""
836+
837+
async def asyncWrap(fn, *args, **kwargs):
838+
"""
839+
Wrap a blocking function as an asynchronous and run it on the native Qt event loop.
840+
The function will be scheduled using a one shot QTimer which prevents blocking the
841+
QEventLoop. An example usage of this is raising a modal dialogue inside an asyncSlot.
842+
```python
843+
async def before_shutdown(self):
844+
await asyncio.sleep(2)
845+
846+
@asyncSlot()
847+
async def shutdown_clicked(self):
848+
# do some work async
849+
asyncio.create_task(self.before_shutdown())
850+
851+
# run on the native Qt loop, not blocking the QEventLoop
852+
result = await asyncWrap(
853+
lambda: QMessageBox.information(None, "Done", "It is now safe to shutdown.")
854+
)
855+
if result == QMessageBox.StandardButton.Ok:
856+
app.exit(0)
857+
```
858+
"""
838859
future = asyncio.Future()
839860

840861
@functools.wraps(fn)
@@ -850,6 +871,7 @@ def helper():
850871
QtCore.QTimer.singleShot(0, helper)
851872
return await future
852873

874+
853875
class QEventLoopPolicyMixin:
854876
def new_event_loop(self):
855877
return QEventLoop(QApplication.instance() or QApplication(sys.argv))

tests/test_qeventloop.py

Lines changed: 26 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -791,47 +791,21 @@ async def mycoro():
791791
loop.run_until_complete(mycoro())
792792
assert not loop.is_running()
793793

794-
def test_task_recursion_fails(loop, application):
795-
"""Re-entering the event loop from a Task will fail if there is another
796-
runnable task."""
797-
async_called = False
798-
main_called = False
799-
800-
async def async_job():
801-
nonlocal async_called
802-
async_called = True
803-
804-
def sync_callback():
805-
asyncio.create_task(async_job())
806-
assert not async_called
807-
application.processEvents()
808-
assert not async_called
809-
return 1
810-
811-
async def main():
812-
nonlocal main_called
813-
res = sync_callback()
814-
assert res == 1
815-
main_called = True
816-
817-
exceptions= []
818-
loop.set_exception_handler(lambda loop, context: exceptions.append(context))
819-
820-
loop.run_until_complete(main())
821-
assert main_called, "The main function should have been called"
822-
823-
# We will now have an error in there, because the task 'async_job' could not
824-
# be entered, because the task 'main' was still being executed by the event loop.
825-
assert len(exceptions) == 1
826-
assert isinstance(exceptions[0]["exception"], RuntimeError)
827-
828794

829-
def test_call_sync(loop, application):
830-
"""Re-entering the event loop from a Task will fail if there is another
831-
runnable task."""
795+
@pytest.mark.parametrize(
796+
"async_wrap, expect_async_called, expect_exception",
797+
[(False, False, True), (True, True, False)],
798+
)
799+
def test_async_wrap(
800+
loop, application, async_wrap, expect_async_called, expect_exception
801+
):
802+
"""
803+
Re-entering the event loop from a Task will fail if there is another
804+
runnable task.
805+
"""
832806
async_called = False
833807
main_called = False
834-
808+
835809
async def async_job():
836810
nonlocal async_called
837811
async_called = True
@@ -840,24 +814,31 @@ def sync_callback():
840814
asyncio.create_task(async_job())
841815
assert not async_called
842816
application.processEvents()
843-
assert async_called
817+
assert async_called if expect_async_called else not async_called
844818
return 1
845819

846820
async def main():
847821
nonlocal main_called
848-
res = await qasync.call_sync(sync_callback)
822+
if async_wrap:
823+
res = await qasync.asyncWrap(sync_callback)
824+
else:
825+
res = sync_callback()
849826
assert res == 1
850827
main_called = True
851828

852-
exceptions= []
829+
exceptions = []
853830
loop.set_exception_handler(lambda loop, context: exceptions.append(context))
854831

855832
loop.run_until_complete(main())
856833
assert main_called, "The main function should have been called"
857-
858-
# We will now have an error in there, because the task 'async_job' could not
859-
# be entered, because the task 'main' was still being executed by the event loop.
860-
assert len(exceptions) == 0
834+
835+
if expect_exception:
836+
# We will now have an error in there, because the task 'async_job' could not
837+
# be entered, because the task 'main' was still being executed by the event loop.
838+
assert len(exceptions) == 1
839+
assert isinstance(exceptions[0]["exception"], RuntimeError)
840+
else:
841+
assert len(exceptions) == 0
861842

862843

863844
def test_slow_callback_duration_logging(loop, caplog):

0 commit comments

Comments
 (0)