Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1cf6384

Browse files
committed
ENH: Add the ability to block callback signals
CallbackRegistry objects now have a context manager .block() method that can be used to temporarily block callback signals from being processed.
1 parent 3d5027d commit 1cf6384

File tree

3 files changed

+89
-1
lines changed

3 files changed

+89
-1
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
``CallbackRegistry`` objects gain a method to temporarily block signals
2+
-----------------------------------------------------------------------
3+
4+
The context manager `~matplotlib.cbook.CallbackRegistry.blocked` can be used
5+
to block callback signals from being processed by the ``CallbackRegistry``.
6+
The optional keyword, *signal*, can be used to block a specific signal
7+
from being processed and let all other signals pass.
8+
9+
.. code-block::
10+
11+
from matplotlib.cbook import CallbackRegistry
12+
callbacks = CallbackRegistry()
13+
def test_func():
14+
print("Inside test function")
15+
callbacks.connect("test", test_func)
16+
17+
# Pause the callbacks
18+
with callbacks.blocked():
19+
callbacks.process("test") # Nothing printed
20+
21+
# Pause a single callback if there are multiple connected signals
22+
with callbacks.blocked(signal="test"):
23+
callbacks.process("test") # Nothing printed

lib/matplotlib/cbook/__init__.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ def _weak_or_strong_ref(func, callback):
122122

123123
class CallbackRegistry:
124124
"""
125-
Handle registering and disconnecting for a set of signals and callbacks:
125+
Handle registering, processing, blocking, and disconnecting
126+
for a set of signals and callbacks:
126127
127128
>>> def oneat(x):
128129
... print('eat', x)
@@ -140,9 +141,15 @@ class CallbackRegistry:
140141
>>> callbacks.process('eat', 456)
141142
eat 456
142143
>>> callbacks.process('be merry', 456) # nothing will be called
144+
143145
>>> callbacks.disconnect(id_eat)
144146
>>> callbacks.process('eat', 456) # nothing will be called
145147
148+
>>> with callbacks.blocked(signal='drink'):
149+
... callbacks.process('drink', 123) # nothing will be called
150+
>>> callbacks.process('drink', 123)
151+
drink 123
152+
146153
In practice, one should always disconnect all callbacks when they are
147154
no longer needed to avoid dangling references (and thus memory leaks).
148155
However, real code in Matplotlib rarely does so, and due to its design,
@@ -280,6 +287,31 @@ def process(self, s, *args, **kwargs):
280287
else:
281288
raise
282289

290+
@contextlib.contextmanager
291+
def blocked(self, *, signal=None):
292+
"""
293+
Block callback signals from being processed.
294+
295+
A context manager to temporarily block/disable callback signals
296+
from being processed by the registered listeners.
297+
298+
Parameters
299+
----------
300+
signal : str, optional
301+
The callback signal to block. The default is to block all signals.
302+
"""
303+
orig = self.callbacks
304+
try:
305+
if signal is None:
306+
# Empty out the callbacks
307+
self.callbacks = {}
308+
else:
309+
# Only remove the specific signal
310+
self.callbacks = {k: orig[k] for k in orig if k != signal}
311+
yield
312+
finally:
313+
self.callbacks = orig
314+
283315

284316
class silent_list(list):
285317
"""

lib/matplotlib/tests/test_cbook.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,39 @@ def test_callbackregistry_custom_exception_handler(monkeypatch, cb, excp):
361361
cb.process('foo')
362362

363363

364+
def test_callbackregistry_blocking():
365+
# Needs an exception handler for interactive testing environments
366+
# that would only print this out instead of raising the exception
367+
def raise_handler(excp):
368+
raise excp
369+
cb = cbook.CallbackRegistry(exception_handler=raise_handler)
370+
def test_func1():
371+
raise ValueError("1 should be blocked")
372+
def test_func2():
373+
raise ValueError("2 should be blocked")
374+
cb.connect("test1", test_func1)
375+
cb.connect("test2", test_func2)
376+
377+
# block all of the callbacks to make sure they aren't processed
378+
with cb.blocked():
379+
cb.process("test1")
380+
cb.process("test2")
381+
382+
# block individual callbacks to make sure the other is still processed
383+
with cb.blocked(signal="test1"):
384+
# Blocked
385+
cb.process("test1")
386+
# Should raise
387+
with pytest.raises(ValueError, match="2 should be blocked"):
388+
cb.process("test2")
389+
390+
# Make sure the original callback functions are there after blocking
391+
with pytest.raises(ValueError, match="1 should be blocked"):
392+
cb.process("test1")
393+
with pytest.raises(ValueError, match="2 should be blocked"):
394+
cb.process("test2")
395+
396+
364397
def test_sanitize_sequence():
365398
d = {'a': 1, 'b': 2, 'c': 3}
366399
k = ['a', 'b', 'c']

0 commit comments

Comments
 (0)