Skip to content

Commit ca3d0cb

Browse files
author
Alan Fleming
committed
Change _instances from 'global' variable to class variable.
1 parent dec5524 commit ca3d0cb

File tree

6 files changed

+41
-108
lines changed

6 files changed

+41
-108
lines changed

python/ipywidgets/ipywidgets/embed.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import json
1414
import re
15-
from .widgets import Widget, DOMWidget, widget as widget_module
15+
from .widgets import Widget, DOMWidget
1616
from .widgets.widget_link import Link
1717
from .widgets.docutils import doc_subst
1818
from ._version import __html_manager_version__
@@ -129,7 +129,7 @@ def _get_recursive_state(widget, store=None, drop_defaults=False):
129129

130130
def add_resolved_links(store, drop_defaults):
131131
"""Adds the state of any link models between two models in store"""
132-
for widget_id, widget in widget_module._instances.items(): # go over all widgets
132+
for widget_id, widget in Widget._instances.items(): # go over all widgets
133133
if isinstance(widget, Link) and widget_id not in store:
134134
if widget.source[0].model_id in store and widget.target[0].model_id in store:
135135
store[widget.model_id] = widget._get_embed_state(drop_defaults=drop_defaults)
@@ -207,7 +207,7 @@ def embed_data(views, drop_defaults=True, state=None):
207207
view_specs: a list of widget view specs
208208
"""
209209
if views is None:
210-
views = [w for w in widget_module._instances.values() if isinstance(w, DOMWidget)]
210+
views = [w for w in Widget._instances.values() if isinstance(w, DOMWidget)]
211211
else:
212212
try:
213213
views[0]

python/ipywidgets/ipywidgets/tests/test_embed.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import traitlets
1111

12-
from ..widgets import IntSlider, IntText, Text, Widget, jslink, HBox, widget_serialization, widget as widget_module
12+
from ..widgets import IntSlider, IntText, Text, Widget, jslink, HBox, widget_serialization
1313
from ..embed import embed_data, embed_snippet, embed_minimal_html, dependency_state
1414

1515

@@ -29,7 +29,7 @@ class CaseWidget(Widget):
2929
class TestEmbed:
3030

3131
def teardown(self):
32-
for w in tuple(widget_module._instances.values()):
32+
for w in tuple(Widget._instances.values()):
3333
w.close()
3434

3535
def test_embed_data_simple(self):

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

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import copy
77
import gc
8-
import inspect
98
import weakref
109

1110
import pytest
@@ -15,7 +14,6 @@
1514

1615
import ipywidgets as ipw
1716

18-
from .. import widget
1917
from ..widget import Widget
2018
from ..widget_button import Button
2119

@@ -62,29 +60,12 @@ def test_close_all():
6260
# create a couple of widgets
6361
widgets = [Button() for i in range(10)]
6462

65-
assert len(widget._instances) > 0, "expect active widgets"
66-
assert widget._instances[widgets[0].model_id] is widgets[0]
63+
assert len(Widget._instances) > 0, "expect active widgets"
64+
assert Widget._instances[widgets[0].model_id] is widgets[0]
6765
# close all the widgets
6866
Widget.close_all()
6967

70-
assert len(widget._instances) == 0, "active widgets should be cleared"
71-
72-
73-
def test_compatibility():
74-
button = Button()
75-
assert widget._instances[button.model_id] is button
76-
with pytest.deprecated_call() as record:
77-
assert widget._instances is widget.Widget.widgets
78-
assert widget._instances is widget.Widget._active_widgets
79-
assert widget._registry is widget.Widget.widget_types
80-
assert widget._registry is widget.Widget._widget_types
81-
82-
Widget.close_all()
83-
assert not widget.Widget.widgets
84-
assert not widget.Widget._active_widgets
85-
caller_path = inspect.stack(context=0)[1].filename
86-
assert all(x.filename == caller_path for x in record)
87-
assert len(record) == 6
68+
assert len(Widget._instances) == 0, "active widgets should be cleared"
8869

8970

9071
def test_widget_copy():
@@ -98,12 +79,12 @@ def test_widget_copy():
9879
def test_widget_open():
9980
button = Button()
10081
model_id = button.model_id
101-
assert model_id in widget._instances
82+
assert model_id in Widget._instances
10283
spec = button.get_view_spec()
10384
assert list(spec) == ["version_major", "version_minor", "model_id"]
10485
assert spec["model_id"]
10586
button.close()
106-
assert model_id not in widget._instances
87+
assert model_id not in Widget._instances
10788
with pytest.raises(RuntimeError, match="Widget is closed"):
10889
button.open()
10990
with pytest.raises(RuntimeError, match="Widget is closed"):
@@ -225,7 +206,7 @@ def my_click(self, b):
225206
b = TestButton(description="button")
226207
weakref.finalize(b, on_delete)
227208
b_ref = weakref.ref(b)
228-
assert b in widget._instances.values()
209+
assert b in Widget._instances.values()
229210

230211
b.on_click(b.my_click)
231212
b.on_click(lambda x: setattr(x, "clicked", True))
@@ -235,11 +216,11 @@ def my_click(self, b):
235216

236217
if weakref_enabled:
237218
ipw.enable_weakreference()
238-
assert b in widget._instances.values(), "Instances not transferred"
219+
assert b in Widget._instances.values(), "Instances not transferred"
239220
ipw.disable_weakreference()
240-
assert b in widget._instances.values(), "Instances not transferred"
221+
assert b in Widget._instances.values(), "Instances not transferred"
241222
ipw.enable_weakreference()
242-
assert b in widget._instances.values(), "Instances not transferred"
223+
assert b in Widget._instances.values(), "Instances not transferred"
243224

244225
b.click()
245226
assert click_count == 2
@@ -251,7 +232,7 @@ def my_click(self, b):
251232
assert deleted
252233
else:
253234
assert not deleted
254-
assert b_ref() in widget._instances.values()
235+
assert b_ref() in Widget._instances.values()
255236
b_ref().close()
256237
gc.collect()
257238
assert deleted, "Closing should remove the last strong reference."

python/ipywidgets/ipywidgets/widgets/tests/test_widget_box.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ def test_box_validate_mode():
5757

5858

5959
def test_box_gc():
60-
widgets.VBox._active_widgets
6160
widgets.enable_weakreference()
6261
# Test Box gc collected and children lifecycle managed.
6362
try:
@@ -80,6 +79,5 @@ def on_delete():
8079
del b
8180
gc.collect()
8281
assert deleted
83-
widgets.VBox._active_widgets
8482
finally:
8583
widgets.disable_weakreference()

python/ipywidgets/ipywidgets/widgets/widget.py

Lines changed: 24 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919

2020
from base64 import standard_b64encode
2121

22-
from .utils import deprecation, _get_frame
23-
2422
from .._version import __protocol_version__, __control_protocol_version__, __jupyter_widgets_base_version__
2523

2624
import inspect
@@ -43,29 +41,24 @@ def envset(name, default):
4341
JUPYTER_WIDGETS_ECHO = envset('JUPYTER_WIDGETS_ECHO', default=True)
4442
# for a discussion on using weak references see:
4543
# https://github.com/jupyter-widgets/ipywidgets/issues/1345
46-
_instances : typing.MutableMapping[str, "Widget"] = {}
4744

4845
def enable_weakreference():
49-
"""Use a WeakValueDictionary instead of a standard dictionary to map
50-
`comm_id` to `widget` for every widget instance.
46+
"""Use a WeakValueDictionary to store references to Widget instances.
5147
52-
By default widgets are mapped using a standard dictionary. Use this feature
53-
to permit widget garbage collection.
48+
A strong reference must be kept to widgets.
5449
"""
55-
global _instances
56-
if not isinstance(_instances, weakref.WeakValueDictionary):
57-
_instances = weakref.WeakValueDictionary(_instances)
50+
if not isinstance(Widget._instances, weakref.WeakValueDictionary):
51+
Widget._instances = weakref.WeakValueDictionary(Widget._instances)
5852

5953
def disable_weakreference():
60-
"""Use a Dictionary to map `comm_id` to `widget` for every widget instance.
61-
62-
Note: this is the default setting and maintains a strong reference to the
63-
the widget preventing automatic garbage collection. If the close method
64-
is called, the widget will remove itself enabling garbage collection.
54+
"""Use a standard dictionary to store references to Widget instances (default behavior).
55+
56+
Note: this is the default setting and maintains a strong reference to the
57+
the widget preventing automatic garbage collection. When the widget is closed
58+
it can be garbage collected.
6559
"""
66-
global _instances
67-
if isinstance(_instances, weakref.WeakValueDictionary):
68-
_instances = dict(_instances)
60+
if isinstance(Widget._instances, weakref.WeakValueDictionary):
61+
Widget._instances = dict(Widget._instances)
6962

7063
def _widget_to_json(x, obj):
7164
if isinstance(x, Widget):
@@ -82,8 +75,8 @@ def _json_to_widget(x, obj):
8275
return {k: _json_to_widget(v, obj) for k, v in x.items()}
8376
elif isinstance(x, (list, tuple)):
8477
return [_json_to_widget(v, obj) for v in x]
85-
elif isinstance(x, str) and x.startswith('IPY_MODEL_') and x[10:] in _instances:
86-
return _instances[x[10:]]
78+
elif isinstance(x, str) and x.startswith("IPY_MODEL_") and x[10:] in Widget._instances:
79+
return Widget._instances[x[10:]]
8780
else:
8881
return x
8982

@@ -314,50 +307,12 @@ class Widget(LoggingHasTraits):
314307
#-------------------------------------------------------------------------
315308
_widget_construction_callback = None
316309
_control_comm = None
317-
318-
@_staticproperty
319-
def widgets():
320-
# Because this is a static attribute, it will be accessed when initializing this class. In that case, since a user
321-
# did not explicitly try to use this attribute, we do not want to throw a deprecation warning.
322-
# So we check if the thing calling this static property is one of the known initialization functions in traitlets.
323-
frame = _get_frame(2)
324-
if not (frame.f_code.co_filename == TRAITLETS_FILE and (frame.f_code.co_name in ('getmembers', 'setup_instance', 'setup_class'))):
325-
deprecation("Widget.widgets is deprecated.")
326-
return _instances
327-
328-
@_staticproperty
329-
def _active_widgets():
330-
# Because this is a static attribute, it will be accessed when initializing this class. In that case, since a user
331-
# did not explicitly try to use this attribute, we do not want to throw a deprecation warning.
332-
# So we check if the thing calling this static property is one of the known initialization functions in traitlets.
333-
frame = _get_frame(2)
334-
if not (frame.f_code.co_filename == TRAITLETS_FILE and (frame.f_code.co_name in ('getmembers', 'setup_instance', 'setup_class'))):
335-
deprecation("Widget._active_widgets is deprecated.")
336-
return _instances
337-
338-
@_staticproperty
339-
def _widget_types():
340-
# Because this is a static attribute, it will be accessed when initializing this class. In that case, since a user
341-
# did not explicitly try to use this attribute, we do not want to throw a deprecation warning.
342-
# So we check if the thing calling this static property is one of the known initialization functions in traitlets.
343-
frame = _get_frame(2)
344-
if not (frame.f_code.co_filename == TRAITLETS_FILE and (frame.f_code.co_name in ('getmembers', 'setup_instance', 'setup_class'))):
345-
deprecation("Widget._widget_types is deprecated.")
346-
return _registry
347-
348-
@_staticproperty
349-
def widget_types():
350-
# Because this is a static attribute, it will be accessed when initializing this class. In that case, since a user
351-
# did not explicitly try to use this attribute, we do not want to throw a deprecation warning.
352-
# So we check if the thing calling this static property is one of the known initialization functions in traitlets.
353-
frame = _get_frame(2)
354-
if not (frame.f_code.co_filename == TRAITLETS_FILE and (frame.f_code.co_name in ('getmembers', 'setup_instance', 'setup_class'))):
355-
deprecation("Widget.widget_types is deprecated.")
356-
return _registry
310+
311+
_instances: typing.ClassVar[typing.MutableMapping[str, "Widget"]] = {}
357312

358313
@classmethod
359314
def close_all(cls):
360-
for widget in list(_instances.values()):
315+
for widget in list(Widget._instances.values()):
361316
widget.close()
362317

363318
@staticmethod
@@ -399,7 +354,7 @@ def _handle_control_comm_msg(cls, msg):
399354
if method == 'request_states':
400355
# Send back the full widgets state
401356
cls.get_manager_state()
402-
widgets = _instances.values()
357+
widgets = cls._instances.values()
403358
full_state = {}
404359
drop_defaults = False
405360
for widget in widgets:
@@ -440,8 +395,8 @@ def handle_comm_opened(comm, msg):
440395
_put_buffers(state, data['buffer_paths'], msg['buffers'])
441396
widget.set_state(state)
442397

443-
@staticmethod
444-
def get_manager_state(drop_defaults=False, widgets=None):
398+
@classmethod
399+
def get_manager_state(cls, drop_defaults=False, widgets=None):
445400
"""Returns the full state for a widget manager for embedding
446401
447402
:param drop_defaults: when True, it will not include default value
@@ -450,7 +405,7 @@ def get_manager_state(drop_defaults=False, widgets=None):
450405
"""
451406
state = {}
452407
if widgets is None:
453-
widgets = _instances.values()
408+
widgets = cls._instances.values()
454409
for widget in widgets:
455410
state[widget.model_id] = widget._get_embed_state(drop_defaults=drop_defaults)
456411
return {'version_major': 2, 'version_minor': 0, 'state': state}
@@ -561,14 +516,11 @@ def _comm_changed(self, change):
561516
if change['old']:
562517
change['old'].on_msg(None)
563518
change['old'].close()
564-
# On python shutdown _instances can be None
565-
if isinstance(_instances, dict):
566-
_instances.pop(change['old'].comm_id, None)
519+
self._instances.pop(change['old'].comm_id, None)
567520
if change['new']:
568-
if isinstance(_instances, dict):
569-
_instances[change["new"].comm_id] = self
570-
self._model_id = change["new"].comm_id
571-
521+
self._instances[change["new"].comm_id] = self
522+
self._model_id = change["new"].comm_id
523+
572524
# prevent memory leaks by using a weak reference to self.
573525
ref = weakref.ref(self)
574526
def _handle_msg(msg):

python/ipywidgets/setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ classifiers =
2323
Programming Language :: Python :: 3.9
2424
Programming Language :: Python :: 3.10
2525
Programming Language :: Python :: 3.11
26+
Programming Language :: Python :: 3.12
27+
Programming Language :: Python :: 3.13
2628
Programming Language :: Python :: 3 :: Only
2729
Framework :: Jupyter
2830

0 commit comments

Comments
 (0)