Skip to content

Commit ca91f89

Browse files
fix: make .widget and .widget_types deprecated
In jupyter-widgets#3122 we renamed .widget and .widget_types to ._active_widgets and ._widget_types. That breaks code, and we did not have a deprecation period. This PR makes the dict and registry non-members of the Widget class and puts in a backwards compatible way the deprecated these members.
1 parent 74774da commit ca91f89

File tree

4 files changed

+69
-18
lines changed

4 files changed

+69
-18
lines changed

python/ipywidgets/ipywidgets/embed.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import json
1414
import re
15+
import ipywidgets.widgets.widget
1516
from .widgets import Widget, DOMWidget
1617
from .widgets.widget_link import Link
1718
from .widgets.docutils import doc_subst
@@ -129,7 +130,7 @@ def _get_recursive_state(widget, store=None, drop_defaults=False):
129130

130131
def add_resolved_links(store, drop_defaults):
131132
"""Adds the state of any link models between two models in store"""
132-
for widget_id, widget in Widget._active_widgets.items(): # go over all widgets
133+
for widget_id, widget in ipywidgets.widgets.widget.instances.items(): # go over all widgets
133134
if isinstance(widget, Link) and widget_id not in store:
134135
if widget.source[0].model_id in store and widget.target[0].model_id in store:
135136
store[widget.model_id] = widget._get_embed_state(drop_defaults=drop_defaults)
@@ -207,7 +208,7 @@ def embed_data(views, drop_defaults=True, state=None):
207208
view_specs: a list of widget view specs
208209
"""
209210
if views is None:
210-
views = [w for w in Widget._active_widgets.values() if isinstance(w, DOMWidget)]
211+
views = [w for w in ipywidgets.widgets.widget.instances.values() if isinstance(w, DOMWidget)]
211212
else:
212213
try:
213214
views[0]

python/ipywidgets/ipywidgets/tests/test_embed.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import traitlets
1111

12+
import ipywidgets.widgets.widget
1213
from ..widgets import IntSlider, IntText, Text, Widget, jslink, HBox, widget_serialization
1314
from ..embed import embed_data, embed_snippet, embed_minimal_html, dependency_state
1415

@@ -29,7 +30,7 @@ class CaseWidget(Widget):
2930
class TestEmbed:
3031

3132
def teardown(self):
32-
for w in tuple(Widget._active_widgets.values()):
33+
for w in tuple(ipywidgets.widgets.widget.instances.values()):
3334
w.close()
3435

3536
def test_embed_data_simple(self):

python/ipywidgets/ipywidgets/widgets/tests/test_widget.py

+16-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from IPython.display import display
88
from IPython.utils.capture import capture_output
99

10+
from .. import widget
1011
from ..widget import Widget
1112
from ..widget_button import Button
1213

@@ -49,9 +50,22 @@ def test_close_all():
4950
# create a couple of widgets
5051
widgets = [Button() for i in range(10)]
5152

52-
assert len(Widget._active_widgets) > 0, "expect active widgets"
53+
assert len(widget.instances) > 0, "expect active widgets"
5354

5455
# close all the widgets
5556
Widget.close_all()
5657

57-
assert len(Widget._active_widgets) == 0, "active widgets should be cleared"
58+
assert len(widget.instances) == 0, "active widgets should be cleared"
59+
60+
61+
def test_compatibility():
62+
button = Button()
63+
assert button in widget.Widget.widgets.values()
64+
assert widget.instances is widget.Widget.widgets
65+
assert widget.instances is widget.Widget._active_widgets
66+
Widget.close_all()
67+
assert not widget.Widget.widgets
68+
assert not widget.Widget._active_widgets
69+
70+
assert widget.Widget.widget_types is widget.registry
71+
assert widget.Widget._widget_types is widget.registry

python/ipywidgets/ipywidgets/widgets/widget.py

+48-13
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
in the Jupyter notebook front-end.
77
"""
88
import os
9+
import typing
910
from contextlib import contextmanager
1011
from collections.abc import Iterable
12+
import warnings
1113
from IPython import get_ipython
1214
from ipykernel.comm import Comm
1315
from traitlets import (
@@ -34,6 +36,9 @@ def envset(name, default):
3436
PROTOCOL_VERSION_MAJOR = __protocol_version__.split('.')[0]
3537
CONTROL_PROTOCOL_VERSION_MAJOR = __control_protocol_version__.split('.')[0]
3638
JUPYTER_WIDGETS_ECHO = envset('JUPYTER_WIDGETS_ECHO', default=True)
39+
# we keep a strong reference for every widget created, for a discussion on using weak references see:
40+
# https://github.com/jupyter-widgets/ipywidgets/issues/1345
41+
instances : typing.MutableMapping[str, "Widget"] = {}
3742

3843
def _widget_to_json(x, obj):
3944
if isinstance(x, dict):
@@ -50,8 +55,8 @@ def _json_to_widget(x, obj):
5055
return {k: _json_to_widget(v, obj) for k, v in x.items()}
5156
elif isinstance(x, (list, tuple)):
5257
return [_json_to_widget(v, obj) for v in x]
53-
elif isinstance(x, str) and x.startswith('IPY_MODEL_') and x[10:] in Widget._active_widgets:
54-
return Widget._active_widgets[x[10:]]
58+
elif isinstance(x, str) and x.startswith('IPY_MODEL_') and x[10:] in instances:
59+
return instances[x[10:]]
5560
else:
5661
return x
5762

@@ -259,10 +264,16 @@ def items(self):
259264
for view_name, widget in sorted(vn.items()):
260265
yield (model_module, model_version, model_name, view_module, view_version, view_name), widget
261266

267+
268+
269+
# a registry of widgets by module, version, and name so we can create a Python model from widgets
270+
# that are constructed from the frontend.
271+
registry = WidgetRegistry()
272+
262273
def register(widget):
263274
"""A decorator registering a widget class in the widget registry."""
264275
w = widget.class_traits()
265-
Widget._widget_types.register(w['_model_module'].default_value,
276+
registry.register(w['_model_module'].default_value,
266277
w['_model_module_version'].default_value,
267278
w['_model_name'].default_value,
268279
w['_view_module'].default_value,
@@ -272,22 +283,46 @@ def register(widget):
272283
return widget
273284

274285

286+
class _staticproperty(object):
287+
def __init__(self, fget):
288+
self.fget = fget
289+
290+
def __get__(self, owner_self, owner_cls):
291+
assert owner_self is None
292+
return self.fget()
293+
294+
295+
275296
class Widget(LoggingHasTraits):
276297
#-------------------------------------------------------------------------
277298
# Class attributes
278299
#-------------------------------------------------------------------------
279300
_widget_construction_callback = None
280301
_control_comm = None
281302

282-
# _active_widgets is a dictionary of all active widget objects
283-
_active_widgets = {}
303+
@_staticproperty
304+
def widgets():
305+
warnings.warn("Widget.widgets is deprecated, use ipywidgets.widgets.widget.instances", DeprecationWarning)
306+
return instances
307+
308+
@_staticproperty
309+
def _active_widgets():
310+
warnings.warn("Widget._active_widgets is deprecated, use ipywidgets.widgets.widget.instances", DeprecationWarning)
311+
return instances
312+
313+
@_staticproperty
314+
def _widget_types():
315+
warnings.warn("Widget._widget_types is deprecated, use ipywidgets.widgets.widget.register", DeprecationWarning)
316+
return registry
284317

285-
# _widget_types is a registry of widgets by module, version, and name:
286-
_widget_types = WidgetRegistry()
318+
@_staticproperty
319+
def widget_types():
320+
warnings.warn("Widget.widget_types is deprecated, use ipywidgets.widgets.widget.register", DeprecationWarning)
321+
return registry
287322

288323
@classmethod
289324
def close_all(cls):
290-
for widget in list(cls._active_widgets.values()):
325+
for widget in list(instances.values()):
291326
widget.close()
292327

293328
@staticmethod
@@ -329,7 +364,7 @@ def _handle_control_comm_msg(cls, msg):
329364
if method == 'request_states':
330365
# Send back the full widgets state
331366
cls.get_manager_state()
332-
widgets = cls._active_widgets.values()
367+
widgets = instances.values()
333368
full_state = {}
334369
drop_defaults = False
335370
for widget in widgets:
@@ -359,7 +394,7 @@ def handle_comm_opened(comm, msg):
359394
state = data['state']
360395

361396
# Find the widget class to instantiate in the registered widgets
362-
widget_class = Widget._widget_types.get(state['_model_module'],
397+
widget_class = register.get(state['_model_module'],
363398
state['_model_module_version'],
364399
state['_model_name'],
365400
state['_view_module'],
@@ -380,7 +415,7 @@ def get_manager_state(drop_defaults=False, widgets=None):
380415
"""
381416
state = {}
382417
if widgets is None:
383-
widgets = Widget._active_widgets.values()
418+
widgets = instances.values()
384419
for widget in widgets:
385420
state[widget.model_id] = widget._get_embed_state(drop_defaults=drop_defaults)
386421
return {'version_major': 2, 'version_minor': 0, 'state': state}
@@ -476,7 +511,7 @@ def _comm_changed(self, change):
476511
self._model_id = self.model_id
477512

478513
self.comm.on_msg(self._handle_msg)
479-
Widget._active_widgets[self.model_id] = self
514+
instances[self.model_id] = self
480515

481516
@property
482517
def model_id(self):
@@ -496,7 +531,7 @@ def close(self):
496531
When the comm is closed, all of the widget views are automatically
497532
removed from the front-end."""
498533
if self.comm is not None:
499-
Widget._active_widgets.pop(self.model_id, None)
534+
instances.pop(self.model_id, None)
500535
self.comm.close()
501536
self.comm = None
502537
self._repr_mimebundle_ = None

0 commit comments

Comments
 (0)