Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/psygnal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"SignalInstance",
"__version__",
"_compiled",
"clear_queued",
"debounced",
"emit_queued",
"evented",
Expand Down Expand Up @@ -57,7 +58,7 @@
from ._exceptions import EmitLoopError
from ._group import EmissionInfo, PathStep, SignalGroup
from ._group_descriptor import SignalGroupDescriptor, get_evented_namespace, is_evented
from ._queue import emit_queued
from ._queue import clear_queued, emit_queued
from ._signal import Signal, SignalInstance, _compiled
from ._throttler import debounced, throttled

Expand Down
29 changes: 28 additions & 1 deletion src/psygnal/_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from collections import defaultdict
from collections.abc import Callable
from queue import Queue
from queue import Empty, Queue
from threading import Thread, current_thread, main_thread
from typing import Any, ClassVar, Literal

Expand Down Expand Up @@ -108,3 +108,30 @@ def emit_queued(thread: Thread | None = None) -> None:
cb(args)
except Exception as e: # pragma: no cover
raise EmitLoopError(exc=e) from e


def clear_queued(thread: Thread | None = None) -> int:
"""Discard all callbacks queued for `thread`, without invoking them.

Parameters
----------
thread : Thread, optional
The thread whose queue should be cleared. If not provided, the current
thread will be used.

Returns
-------
int
The number of queued callbacks that were discarded.
"""
_thread = current_thread() if thread is None else thread
if (queue := QueuedCallback._GLOBAL_QUEUE.get(_thread)) is None:
return 0

count = 0
while True:
try:
queue.get_nowait()
except Empty:
return count
count += 1
21 changes: 20 additions & 1 deletion tests/test_psygnal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
from contextlib import suppress
from functools import partial, wraps
from inspect import Signature
from threading import Thread
from typing import Literal
from unittest.mock import MagicMock, Mock, call

import pytest

import psygnal
from psygnal import EmitLoopError, Signal, SignalInstance
from psygnal import EmitLoopError, Signal, SignalInstance, clear_queued, emit_queued
from psygnal._signal import ReemissionMode, ReemissionVal
from psygnal._weak_callback import WeakCallback

Expand Down Expand Up @@ -975,6 +976,24 @@ def cb3(arg):
this_thread_mock.assert_called_once_with(2, this_thread)


def test_clear_queued():

emitter = Emitter()
mock = Mock()
emitter.one_int.connect(mock, thread="main")

thread = Thread(target=lambda: [emitter.one_int.emit(i) for i in range(3)])
thread.start()
thread.join()

assert clear_queued() == 3
emit_queued()
mock.assert_not_called()
assert clear_queued() == 0
# a thread that never had anything queued
assert clear_queued(Thread()) == 0


def test_deepcopy():
from copy import deepcopy

Expand Down
Loading