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

Skip to content

ENH: Add the ability to block callback signals #20816

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 12, 2021
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
25 changes: 25 additions & 0 deletions doc/users/next_whats_new/callback_blocking.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
``CallbackRegistry`` objects gain a method to temporarily block signals
-----------------------------------------------------------------------

The context manager `~matplotlib.cbook.CallbackRegistry.blocked` can be used
to block callback signals from being processed by the ``CallbackRegistry``.
The optional keyword, *signal*, can be used to block a specific signal
from being processed and let all other signals pass.

.. code-block::

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.imshow([[0, 1], [2, 3]])

# Block all interactivity through the canvas callbacks
with fig.canvas.callbacks.blocked():
plt.show()

fig, ax = plt.subplots()
ax.imshow([[0, 1], [2, 3]])

# Only block key press events
with fig.canvas.callbacks.blocked(signal="key_press_event"):
plt.show()
34 changes: 33 additions & 1 deletion lib/matplotlib/cbook/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ def _weak_or_strong_ref(func, callback):

class CallbackRegistry:
"""
Handle registering and disconnecting for a set of signals and callbacks:
Handle registering, processing, blocking, and disconnecting
for a set of signals and callbacks:

>>> def oneat(x):
... print('eat', x)
Expand All @@ -140,9 +141,15 @@ class CallbackRegistry:
>>> callbacks.process('eat', 456)
eat 456
>>> callbacks.process('be merry', 456) # nothing will be called

>>> callbacks.disconnect(id_eat)
>>> callbacks.process('eat', 456) # nothing will be called

>>> with callbacks.blocked(signal='drink'):
... callbacks.process('drink', 123) # nothing will be called
>>> callbacks.process('drink', 123)
drink 123

In practice, one should always disconnect all callbacks when they are
no longer needed to avoid dangling references (and thus memory leaks).
However, real code in Matplotlib rarely does so, and due to its design,
Expand Down Expand Up @@ -280,6 +287,31 @@ def process(self, s, *args, **kwargs):
else:
raise

@contextlib.contextmanager
def blocked(self, *, signal=None):
"""
Block callback signals from being processed.

A context manager to temporarily block/disable callback signals
from being processed by the registered listeners.

Parameters
----------
signal : str, optional
The callback signal to block. The default is to block all signals.
"""
orig = self.callbacks
try:
if signal is None:
# Empty out the callbacks
self.callbacks = {}
else:
# Only remove the specific signal
self.callbacks = {k: orig[k] for k in orig if k != signal}
yield
finally:
self.callbacks = orig


class silent_list(list):
"""
Expand Down
33 changes: 33 additions & 0 deletions lib/matplotlib/tests/test_cbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,39 @@ def test_callbackregistry_custom_exception_handler(monkeypatch, cb, excp):
cb.process('foo')


def test_callbackregistry_blocking():
# Needs an exception handler for interactive testing environments
# that would only print this out instead of raising the exception
def raise_handler(excp):
raise excp
cb = cbook.CallbackRegistry(exception_handler=raise_handler)
def test_func1():
raise ValueError("1 should be blocked")
def test_func2():
raise ValueError("2 should be blocked")
cb.connect("test1", test_func1)
cb.connect("test2", test_func2)

# block all of the callbacks to make sure they aren't processed
with cb.blocked():
cb.process("test1")
cb.process("test2")

# block individual callbacks to make sure the other is still processed
with cb.blocked(signal="test1"):
# Blocked
cb.process("test1")
# Should raise
with pytest.raises(ValueError, match="2 should be blocked"):
cb.process("test2")

# Make sure the original callback functions are there after blocking
with pytest.raises(ValueError, match="1 should be blocked"):
cb.process("test1")
with pytest.raises(ValueError, match="2 should be blocked"):
cb.process("test2")


def test_sanitize_sequence():
d = {'a': 1, 'b': 2, 'c': 3}
k = ['a', 'b', 'c']
Expand Down