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

Skip to content

Commit b989a67

Browse files
authored
Merge pull request #21238 from anntzer/callbackrestrict
API: Raise when unknown signals are connected to CallbackRegistries.
2 parents dec1e13 + 7f5690e commit b989a67

File tree

11 files changed

+49
-15
lines changed

11 files changed

+49
-15
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
``CallbackRegistry`` raises on unknown signals
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
When Matplotlib instantiates a `.CallbackRegistry`, it now limits callbacks
4+
to the signals that the registry knows about. In practice, this means that
5+
calling `~.FigureCanvasBase.mpl_connect` with an invalid signal name now raises
6+
a `ValueError`.

lib/matplotlib/artist.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ def __init__(self):
167167
# Normally, artist classes need to be queried for mouseover info if and
168168
# only if they override get_cursor_data.
169169
self._mouseover = type(self).get_cursor_data != Artist.get_cursor_data
170-
self._callbacks = cbook.CallbackRegistry()
170+
self._callbacks = cbook.CallbackRegistry(signals=["pchanged"])
171171
try:
172172
self.axes = None
173173
except AttributeError:

lib/matplotlib/axes/_base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1201,7 +1201,8 @@ def cla(self):
12011201
spine.clear()
12021202

12031203
self.ignore_existing_data_limits = True
1204-
self.callbacks = cbook.CallbackRegistry()
1204+
self.callbacks = cbook.CallbackRegistry(
1205+
signals=["xlim_changed", "ylim_changed", "zlim_changed"])
12051206

12061207
if self._sharex is not None:
12071208
self.sharex(self._sharex)

lib/matplotlib/axis.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -655,7 +655,8 @@ def __init__(self, axes, pickradius=15):
655655
self.axes = axes
656656
self.major = Ticker()
657657
self.minor = Ticker()
658-
self.callbacks = cbook.CallbackRegistry()
658+
self.callbacks = cbook.CallbackRegistry(
659+
signals=["units", "units finalize"])
659660

660661
self._autolabelpos = True
661662

@@ -806,7 +807,8 @@ def clear(self):
806807
self._set_scale('linear')
807808

808809
# Clear the callback registry for this axis, or it may "leak"
809-
self.callbacks = cbook.CallbackRegistry()
810+
self.callbacks = cbook.CallbackRegistry(
811+
signals=["units", "units finalize"])
810812

811813
# whether the grids are on
812814
self._major_tick_kw['gridOn'] = (

lib/matplotlib/cbook/__init__.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,13 +175,20 @@ class CallbackRegistry:
175175
The default handler prints the exception (with `traceback.print_exc`) if
176176
an interactive event loop is running; it re-raises the exception if no
177177
interactive event loop is running.
178+
179+
signals : list, optional
180+
If not None, *signals* is a list of signals that this registry handles:
181+
attempting to `process` or to `connect` to a signal not in the list
182+
throws a `ValueError`. The default, None, does not restrict the
183+
handled signals.
178184
"""
179185

180186
# We maintain two mappings:
181187
# callbacks: signal -> {cid -> weakref-to-callback}
182188
# _func_cid_map: signal -> {weakref-to-callback -> cid}
183189

184-
def __init__(self, exception_handler=_exception_printer):
190+
def __init__(self, exception_handler=_exception_printer, *, signals=None):
191+
self._signals = None if signals is None else list(signals) # Copy it.
185192
self.exception_handler = exception_handler
186193
self.callbacks = {}
187194
self._cid_gen = itertools.count()
@@ -217,6 +224,8 @@ def connect(self, signal, func):
217224
if signal == "units finalize":
218225
_api.warn_deprecated(
219226
"3.5", name=signal, obj_type="signal", alternative="units")
227+
if self._signals is not None:
228+
_api.check_in_list(self._signals, signal=signal)
220229
self._func_cid_map.setdefault(signal, {})
221230
proxy = _weak_or_strong_ref(func, self._remove_proxy)
222231
if proxy in self._func_cid_map[signal]:
@@ -280,6 +289,8 @@ def process(self, s, *args, **kwargs):
280289
All of the functions registered to receive callbacks on *s* will be
281290
called with ``*args`` and ``**kwargs``.
282291
"""
292+
if self._signals is not None:
293+
_api.check_in_list(self._signals, signal=s)
283294
for cid, ref in list(self.callbacks.get(s, {}).items()):
284295
func = ref()
285296
if func is not None:

lib/matplotlib/cm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ def __init__(self, norm=None, cmap=None):
359359
self.set_cmap(cmap) # The Colormap instance of this ScalarMappable.
360360
#: The last colorbar associated with this ScalarMappable. May be None.
361361
self.colorbar = None
362-
self.callbacks = cbook.CallbackRegistry()
362+
self.callbacks = cbook.CallbackRegistry(signals=["changed"])
363363

364364
callbacksSM = _api.deprecated("3.5", alternative="callbacks")(
365365
property(lambda self: self.callbacks))

lib/matplotlib/colors.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1130,7 +1130,7 @@ def __init__(self, vmin=None, vmax=None, clip=False):
11301130
self._vmax = _sanitize_extrema(vmax)
11311131
self._clip = clip
11321132
self._scale = None
1133-
self.callbacks = cbook.CallbackRegistry()
1133+
self.callbacks = cbook.CallbackRegistry(signals=["changed"])
11341134

11351135
@property
11361136
def vmin(self):

lib/matplotlib/container.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def __new__(cls, *args, **kwargs):
1818
return tuple.__new__(cls, args[0])
1919

2020
def __init__(self, kl, label=None):
21-
self._callbacks = cbook.CallbackRegistry()
21+
self._callbacks = cbook.CallbackRegistry(signals=["pchanged"])
2222
self._remove_method = None
2323
self.set_label(label)
2424

lib/matplotlib/figure.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2249,11 +2249,12 @@ def __init__(self,
22492249
# everything is None, so use default:
22502250
self.set_layout_engine(layout=layout)
22512251

2252-
self.callbacks = cbook.CallbackRegistry()
2252+
self.callbacks = cbook.CallbackRegistry(signals=["dpi_changed"])
22532253
# Callbacks traditionally associated with the canvas (and exposed with
22542254
# a proxy property), but that actually need to be on the figure for
22552255
# pickling.
2256-
self._canvas_callbacks = cbook.CallbackRegistry()
2256+
self._canvas_callbacks = cbook.CallbackRegistry(
2257+
signals=FigureCanvasBase.events)
22572258
self._button_pick_id = self._canvas_callbacks.connect(
22582259
'button_press_event', lambda event: self.canvas.pick(event))
22592260
self._scroll_pick_id = self._canvas_callbacks.connect(

lib/matplotlib/tests/test_cbook.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,19 @@ def test_callbackregistry_custom_exception_handler(monkeypatch, cb, excp):
363363
cb.process('foo')
364364

365365

366+
def test_callbackregistry_signals():
367+
cr = cbook.CallbackRegistry(signals=["foo"])
368+
results = []
369+
def cb(x): results.append(x)
370+
cr.connect("foo", cb)
371+
with pytest.raises(ValueError):
372+
cr.connect("bar", cb)
373+
cr.process("foo", 1)
374+
with pytest.raises(ValueError):
375+
cr.process("bar", 1)
376+
assert results == [1]
377+
378+
366379
def test_callbackregistry_blocking():
367380
# Needs an exception handler for interactive testing environments
368381
# that would only print this out instead of raising the exception

lib/matplotlib/widgets.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def __init__(self, ax, label, image=None,
184184
horizontalalignment='center',
185185
transform=ax.transAxes)
186186

187-
self._observers = cbook.CallbackRegistry()
187+
self._observers = cbook.CallbackRegistry(signals=["clicked"])
188188

189189
self.connect_event('button_press_event', self._click)
190190
self.connect_event('button_release_event', self._release)
@@ -276,7 +276,7 @@ def __init__(self, ax, orientation, closedmin, closedmax,
276276
self.connect_event("button_release_event", self._update)
277277
if dragging:
278278
self.connect_event("motion_notify_event", self._update)
279-
self._observers = cbook.CallbackRegistry()
279+
self._observers = cbook.CallbackRegistry(signals=["changed"])
280280

281281
def _stepped_value(self, val):
282282
"""Return *val* coerced to closest number in the ``valstep`` grid."""
@@ -1028,7 +1028,7 @@ def __init__(self, ax, labels, actives=None):
10281028

10291029
self.connect_event('button_press_event', self._clicked)
10301030

1031-
self._observers = cbook.CallbackRegistry()
1031+
self._observers = cbook.CallbackRegistry(signals=["clicked"])
10321032

10331033
def _clicked(self, event):
10341034
if self.ignore(event) or event.button != 1 or event.inaxes != self.ax:
@@ -1157,7 +1157,7 @@ def __init__(self, ax, label, initial='',
11571157
verticalalignment='center', horizontalalignment=textalignment,
11581158
parse_math=False)
11591159

1160-
self._observers = cbook.CallbackRegistry()
1160+
self._observers = cbook.CallbackRegistry(signals=["change", "submit"])
11611161

11621162
ax.set(
11631163
xlim=(0, 1), ylim=(0, 1), # s.t. cursor appears from first click.
@@ -1444,7 +1444,7 @@ def __init__(self, ax, labels, active=0, activecolor='blue'):
14441444

14451445
self.connect_event('button_press_event', self._clicked)
14461446

1447-
self._observers = cbook.CallbackRegistry()
1447+
self._observers = cbook.CallbackRegistry(signals=["clicked"])
14481448

14491449
cnt = _api.deprecated("3.4")(property( # Not real, but close enough.
14501450
lambda self: len(self._observers.callbacks['clicked'])))

0 commit comments

Comments
 (0)