Skip to content

Commit f59d44b

Browse files
committed
Fix unnecessary deprecation warnings when the widget class is initialized
Because deprecated attributes of the Widget class are static attributes, they will be accessed when initializing this class by traitlets. In that case, since a user did not explicitly try to use these attributes, we do not want to throw a deprecation warning. So we check if the thing calling these static properties is one of the known initialization functions in traitlets.
1 parent 3fd3d90 commit f59d44b

File tree

3 files changed

+59
-22
lines changed

3 files changed

+59
-22
lines changed

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

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@
66
from IPython.core.interactiveshell import InteractiveShell
77
from IPython.display import display
88
from IPython.utils.capture import capture_output
9+
import inspect
10+
import pytest
911

1012
from .. import widget
1113
from ..widget import Widget
1214
from ..widget_button import Button
1315

16+
PYTEST_PATH = inspect.getfile(pytest.Function)
1417

1518
def test_no_widget_view():
1619
# ensure IPython shell is instantiated
@@ -51,7 +54,7 @@ def test_close_all():
5154
widgets = [Button() for i in range(10)]
5255

5356
assert len(widget._instances) > 0, "expect active widgets"
54-
57+
assert widget._instances[widgets[0].model_id] is widgets[0]
5558
# close all the widgets
5659
Widget.close_all()
5760

@@ -60,12 +63,15 @@ def test_close_all():
6063

6164
def test_compatibility():
6265
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
66+
assert widget._instances[button.model_id] is button
67+
with pytest.deprecated_call() as record:
68+
assert widget._instances is widget.Widget.widgets
69+
assert widget._instances is widget.Widget._active_widgets
70+
assert widget._registry is widget.Widget.widget_types
71+
assert widget._registry is widget.Widget._widget_types
72+
73+
Widget.close_all()
74+
assert not widget.Widget.widgets
75+
assert not widget.Widget._active_widgets
76+
assert all(x.filename == PYTEST_PATH for x in record)
77+
assert len(record) == 6

python/ipywidgets/ipywidgets/widgets/utils.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@
66
import inspect
77
import warnings
88

9+
def _get_frame(level):
10+
"""Get the frame at the given stack level."""
11+
# sys._getframe is much faster than inspect.stack, but isn't guaranteed to
12+
# exist in all python implementations, so we fall back to inspect.stack()
13+
14+
# We need to add one to level to account for this get_frame call.
15+
if hasattr(sys, '_getframe'):
16+
frame = sys._getframe(level+1)
17+
else:
18+
frame = inspect.stack(context=0)[level+1].frame
19+
return frame
20+
21+
922
# This function is from https://github.com/python/cpython/issues/67998
1023
# (https://bugs.python.org/file39550/deprecated_module_stacklevel.diff) and
1124
# calculates the appropriate stacklevel for deprecations to target the
@@ -21,13 +34,7 @@ def _external_stacklevel(internal):
2134
"""
2235
# Get the level of my caller's caller
2336
level = 2
24-
25-
# sys._getframe is much faster than inspect.stack, but isn't guaranteed to
26-
# exist in all python implementations, so we fall back to inspect.stack()
27-
if hasattr(sys, '_getframe'):
28-
frame = sys._getframe(level)
29-
else:
30-
frame = inspect.stack(context=0)[level].frame
37+
frame = _get_frame(level)
3138

3239
# Normalize the path separators:
3340
normalized_internal = [str(Path(s)) for s in internal]

python/ipywidgets/ipywidgets/widgets/widget.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import typing
1010
from contextlib import contextmanager
1111
from collections.abc import Iterable
12-
import warnings
1312
from IPython import get_ipython
1413
from ipykernel.comm import Comm
1514
from traitlets import (
@@ -19,8 +18,13 @@
1918

2019
from base64 import standard_b64encode
2120

21+
from .utils import deprecation, _get_frame
22+
2223
from .._version import __protocol_version__, __control_protocol_version__, __jupyter_widgets_base_version__
2324

25+
import inspect
26+
TRAITLETS_FILE = inspect.getfile(HasTraits)
27+
2428
# Based on jupyter_core.paths.envset
2529
def envset(name, default):
2630
"""Return True if the given environment variable is turned on, otherwise False
@@ -302,22 +306,42 @@ class Widget(LoggingHasTraits):
302306

303307
@_staticproperty
304308
def widgets():
305-
warnings.warn("Widget.widgets is deprecated.", DeprecationWarning)
309+
# Because this is a static attribute, it will be accessed when initializing this class. In that case, since a user
310+
# did not explicitly try to use this attribute, we do not want to throw a deprecation warning.
311+
# So we check if the thing calling this static property is one of the known initialization functions in traitlets.
312+
frame = _get_frame(2)
313+
if not (frame.f_code.co_filename == TRAITLETS_FILE and (frame.f_code.co_name in ('getmembers', 'setup_instance'))):
314+
deprecation("Widget.widgets is deprecated.")
306315
return _instances
307316

308317
@_staticproperty
309318
def _active_widgets():
310-
warnings.warn("Widget._active_widgets is deprecated.", DeprecationWarning)
319+
# Because this is a static attribute, it will be accessed when initializing this class. In that case, since a user
320+
# did not explicitly try to use this attribute, we do not want to throw a deprecation warning.
321+
# So we check if the thing calling this static property is one of the known initialization functions in traitlets.
322+
frame = _get_frame(2)
323+
if not (frame.f_code.co_filename == TRAITLETS_FILE and (frame.f_code.co_name in ('getmembers', 'setup_instance'))):
324+
deprecation("Widget._active_widgets is deprecated.")
311325
return _instances
312326

313327
@_staticproperty
314328
def _widget_types():
315-
warnings.warn("Widget._widget_types is deprecated.", DeprecationWarning)
329+
# Because this is a static attribute, it will be accessed when initializing this class. In that case, since a user
330+
# did not explicitly try to use this attribute, we do not want to throw a deprecation warning.
331+
# So we check if the thing calling this static property is one of the known initialization functions in traitlets.
332+
frame = _get_frame(2)
333+
if not (frame.f_code.co_filename == TRAITLETS_FILE and (frame.f_code.co_name in ('getmembers', 'setup_instance'))):
334+
deprecation("Widget._widget_types is deprecated.")
316335
return _registry
317336

318337
@_staticproperty
319338
def widget_types():
320-
warnings.warn("Widget.widget_types is deprecated.", DeprecationWarning)
339+
# Because this is a static attribute, it will be accessed when initializing this class. In that case, since a user
340+
# did not explicitly try to use this attribute, we do not want to throw a deprecation warning.
341+
# So we check if the thing calling this static property is one of the known initialization functions in traitlets.
342+
frame = _get_frame(2)
343+
if not (frame.f_code.co_filename == TRAITLETS_FILE and (frame.f_code.co_name in ('getmembers', 'setup_instance'))):
344+
deprecation("Widget.widget_types is deprecated.")
321345
return _registry
322346

323347
@classmethod

0 commit comments

Comments
 (0)