Skip to content

Commit ab2fb48

Browse files
authored
Merge pull request #157 from CabbageDevelopment/asyncslot-without-qobject
Allow asyncSlot to decorate top level functions, outside of a QObject
2 parents a4048ab + 53dc434 commit ab2fb48

2 files changed

Lines changed: 52 additions & 9 deletions

File tree

src/qasync/__init__.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -824,8 +824,7 @@ def wrapper(*args, **kwargs):
824824
# Qt ignores trailing args from a signal but python does
825825
# not so inspect the slot signature and if it's not
826826
# callable try removing args until it is.
827-
task = None
828-
while len(args):
827+
while True:
829828
try:
830829
inspect.signature(fn).bind(*args, **kwargs)
831830
except TypeError:
@@ -834,15 +833,14 @@ def wrapper(*args, **kwargs):
834833
args = list(args)
835834
args.pop()
836835
continue
836+
else:
837+
raise TypeError(
838+
"asyncSlot was not callable from Signal. Potential signature mismatch."
839+
)
837840
else:
838841
task = asyncio.ensure_future(fn(*args, **kwargs))
839842
task.add_done_callback(_error_handler)
840-
break
841-
if task is None:
842-
raise TypeError(
843-
"asyncSlot was not callable from Signal. Potential signature mismatch."
844-
)
845-
return task
843+
return task
846844

847845
return wrapper
848846

tests/test_qeventloop.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,51 @@ async def mycoro():
793793
assert not loop.is_running()
794794

795795

796+
def test_async_slot(loop):
797+
no_args_called = asyncio.Event()
798+
with_args_called = asyncio.Event()
799+
trailing_args_called = asyncio.Event()
800+
801+
async def slot_no_args():
802+
no_args_called.set()
803+
804+
async def slot_with_args(flag: bool):
805+
assert flag
806+
with_args_called.set()
807+
808+
async def slot_trailing_args(flag: bool):
809+
assert flag
810+
trailing_args_called.set()
811+
812+
async def slot_signature_mismatch(_: bool): ...
813+
814+
async def main():
815+
# passing kwargs to the underlying Slot such as name, arguments, return
816+
sig = qasync._make_signaller(qasync.QtCore)
817+
sig.signal.connect(qasync.asyncSlot(name="slot")(slot_no_args))
818+
sig.signal.emit()
819+
820+
sig1 = qasync._make_signaller(qasync.QtCore, bool)
821+
sig1.signal.connect(qasync.asyncSlot(bool)(slot_with_args))
822+
sig1.signal.emit(True)
823+
824+
# when a signal produces more arguments than a slot, trailing args are removed
825+
sig2 = qasync._make_signaller(qasync.QtCore, bool, bool)
826+
sig2.signal.connect(qasync.asyncSlot()(slot_trailing_args))
827+
sig2.signal.emit(True, False)
828+
829+
# signature mismatch when called
830+
with pytest.raises(TypeError):
831+
qasync.asyncSlot(bool)(slot_signature_mismatch)()
832+
833+
all_done = asyncio.gather(
834+
no_args_called.wait(), with_args_called.wait(), trailing_args_called.wait()
835+
)
836+
await asyncio.wait_for(all_done, timeout=1.0)
837+
838+
loop.run_until_complete(main())
839+
840+
796841
@pytest.mark.parametrize(
797842
"async_wrap, expect_async_called, expect_exception",
798843
[(False, False, True), (True, True, False)],
@@ -829,7 +874,6 @@ async def main():
829874
await coro # avoid warnings about unawaited coroutines
830875
assert res == 1
831876
main_called = True
832-
833877

834878
exceptions = []
835879
loop.set_exception_handler(lambda loop, context: exceptions.append(context))
@@ -917,6 +961,7 @@ async def coro():
917961

918962
thread.join() # Ensure thread cleanup
919963

964+
920965
def teardown_module(module):
921966
"""
922967
Remove handlers from all loggers

0 commit comments

Comments
 (0)