commit python-ipywidgets for openSUSE:Factory
![](https://seccdn.libravatar.org/avatar/128a7b98d536a9cf9b4d4d5a90d63475.jpg?s=120&d=mm&r=g)
Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-ipywidgets for openSUSE:Factory checked in at 2022-12-26 23:27:57
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-ipywidgets (Old)
and /work/SRC/openSUSE:Factory/.python-ipywidgets.new.1563 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-ipywidgets"
Mon Dec 26 23:27:57 2022 rev:12 rq:1045321 version:8.0.4
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-ipywidgets/python-ipywidgets.changes 2022-09-10 20:17:50.324913421 +0200
+++ /work/SRC/openSUSE:Factory/.python-ipywidgets.new.1563/python-ipywidgets.changes 2022-12-26 23:28:10.768974086 +0100
@@ -1,0 +2,14 @@
+Sun Dec 25 19:30:47 UTC 2022 - Ben Greiner
+
+- Update to 8.0.4
+ * Fix: slider change event issue with tapping: #3597, #3617
+ * Fix: unintentional deprecation warnings: #3648, #3650
+ * Fix: registry state lookup failed, making is impossible to
+ create widgets from the frontend: #3653
+- Release 8.0.3
+ * Fix: be backwards compatibel with 7.x, where we re-instroduced
+ .widget and .widget_types #3567
+ * Fix: be backwards compatibel with 7.x, revert hold_sync during
+ set_state #3642
+
+-------------------------------------------------------------------
Old:
----
ipywidgets-8.0.2.tar.gz
New:
----
ipywidgets-8.0.4.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-ipywidgets.spec ++++++
--- /var/tmp/diff_new_pack.foefzb/_old 2022-12-26 23:28:11.244976866 +0100
+++ /var/tmp/diff_new_pack.foefzb/_new 2022-12-26 23:28:11.248976889 +0100
@@ -16,9 +16,8 @@
#
-%{?!python_module:%define python_module() python-%{**} python3-%{**}}
Name: python-ipywidgets
-Version: 8.0.2
+Version: 8.0.4
Release: 0
Summary: IPython HTML widgets for Jupyter
License: BSD-3-Clause
@@ -26,16 +25,18 @@
URL: https://github.com/jupyter-widgets/ipywidgets
Source0: https://files.pythonhosted.org/packages/source/i/ipywidgets/ipywidgets-%{version}.tar.gz
BuildRequires: %{python_module base >= 3.7}
+BuildRequires: %{python_module pip}
BuildRequires: %{python_module setuptools}
+BuildRequires: %{python_module wheel}
BuildRequires: fdupes
BuildRequires: python-rpm-macros
BuildRequires: unzip
Requires: python-ipykernel >= 4.5.1
Requires: python-ipython >= 6.1.0
Requires: python-ipython_genutils >= 0.2
-Requires: python-jupyterlab_widgets >= 3.0
Requires: python-traitlets >= 4.3.1
-Requires: python-widgetsnbextension >= 4.0
+Requires: (python-jupyterlab_widgets >= 3.0 with python-jupyterlab_widgets < 4)
+Requires: (python-widgetsnbextension >= 4.0 with python-widgetsnbextension < 5)
Provides: python-jupyter_ipywidgets = %{version}
Obsoletes: python-jupyter_ipywidgets < %{version}
BuildArch: noarch
@@ -44,11 +45,11 @@
BuildRequires: %{python_module ipython >= 6.1.0}
BuildRequires: %{python_module ipython_genutils >= 0.2}
BuildRequires: %{python_module jsonschema}
-BuildRequires: %{python_module jupyterlab_widgets >= 3}
+BuildRequires: %{python_module jupyterlab_widgets >= 3 with %python-jupyterlab_widgets < 4}
BuildRequires: %{python_module pytest >= 3.6.0}
BuildRequires: %{python_module pytz}
BuildRequires: %{python_module traitlets >= 4.3.1}
-BuildRequires: %{python_module widgetsnbextension >= 4.0}
+BuildRequires: %{python_module widgetsnbextension >= 4.0 with %python-widgetsnbextension < 5}
# /SECTION
%if "%{python_flavor}" == "python3" || "%{?python_provides}" == "python3"
Provides: jupyter-ipywidgets = %{version}
@@ -60,12 +61,14 @@
%prep
%autosetup -p1 -n ipywidgets-%{version}
+# remove shebangs from test modules. Those are not standalone scripts.
+sed -i '1{/env python/d}' ipywidgets/widgets/tests/*.py
%build
-%python_build
+%pyproject_wheel
%install
-%python_install
+%pyproject_install
%python_expand %fdupes %{buildroot}%{$python_sitelib}
%check
++++++ ipywidgets-8.0.2.tar.gz -> ipywidgets-8.0.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipywidgets-8.0.2/PKG-INFO new/ipywidgets-8.0.4/PKG-INFO
--- old/ipywidgets-8.0.2/PKG-INFO 2022-09-02 20:40:07.634295700 +0200
+++ new/ipywidgets-8.0.4/PKG-INFO 2022-12-22 10:14:32.496301000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: ipywidgets
-Version: 8.0.2
+Version: 8.0.4
Summary: Jupyter interactive widgets
Home-page: http://jupyter.org
Author: Jupyter Development Team
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipywidgets-8.0.2/ipywidgets/_version.py new/ipywidgets-8.0.4/ipywidgets/_version.py
--- old/ipywidgets-8.0.2/ipywidgets/_version.py 2022-09-02 20:39:55.000000000 +0200
+++ new/ipywidgets-8.0.4/ipywidgets/_version.py 2022-12-22 10:14:04.000000000 +0100
@@ -1,7 +1,7 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
-__version__ = '8.0.2'
+__version__ = '8.0.4'
__protocol_version__ = '2.1.0'
__control_protocol_version__ = '1.0.0'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipywidgets-8.0.2/ipywidgets/embed.py new/ipywidgets-8.0.4/ipywidgets/embed.py
--- old/ipywidgets-8.0.2/ipywidgets/embed.py 2022-09-02 20:27:45.000000000 +0200
+++ new/ipywidgets-8.0.4/ipywidgets/embed.py 2022-12-22 09:55:44.000000000 +0100
@@ -12,7 +12,7 @@
import json
import re
-from .widgets import Widget, DOMWidget
+from .widgets import Widget, DOMWidget, widget as widget_module
from .widgets.widget_link import Link
from .widgets.docutils import doc_subst
from ._version import __html_manager_version__
@@ -129,7 +129,7 @@
def add_resolved_links(store, drop_defaults):
"""Adds the state of any link models between two models in store"""
- for widget_id, widget in Widget._active_widgets.items(): # go over all widgets
+ for widget_id, widget in widget_module._instances.items(): # go over all widgets
if isinstance(widget, Link) and widget_id not in store:
if widget.source[0].model_id in store and widget.target[0].model_id in store:
store[widget.model_id] = widget._get_embed_state(drop_defaults=drop_defaults)
@@ -207,7 +207,7 @@
view_specs: a list of widget view specs
"""
if views is None:
- views = [w for w in Widget._active_widgets.values() if isinstance(w, DOMWidget)]
+ views = [w for w in widget_module._instances.values() if isinstance(w, DOMWidget)]
else:
try:
views[0]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipywidgets-8.0.2/ipywidgets/tests/test_embed.py new/ipywidgets-8.0.4/ipywidgets/tests/test_embed.py
--- old/ipywidgets-8.0.2/ipywidgets/tests/test_embed.py 2022-09-02 20:27:45.000000000 +0200
+++ new/ipywidgets-8.0.4/ipywidgets/tests/test_embed.py 2022-12-22 09:55:44.000000000 +0100
@@ -9,7 +9,7 @@
import traitlets
-from ..widgets import IntSlider, IntText, Text, Widget, jslink, HBox, widget_serialization
+from ..widgets import IntSlider, IntText, Text, Widget, jslink, HBox, widget_serialization, widget as widget_module
from ..embed import embed_data, embed_snippet, embed_minimal_html, dependency_state
@@ -29,7 +29,7 @@
class TestEmbed:
def teardown(self):
- for w in tuple(Widget._active_widgets.values()):
+ for w in tuple(widget_module._instances.values()):
w.close()
def test_embed_data_simple(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipywidgets-8.0.2/ipywidgets/widgets/tests/test_send_state.py new/ipywidgets-8.0.4/ipywidgets/widgets/tests/test_send_state.py
--- old/ipywidgets-8.0.2/ipywidgets/widgets/tests/test_send_state.py 2022-09-02 20:27:45.000000000 +0200
+++ new/ipywidgets-8.0.4/ipywidgets/widgets/tests/test_send_state.py 2022-12-22 09:55:44.000000000 +0100
@@ -12,29 +12,20 @@
# A widget with simple traits
class SimpleWidget(Widget):
a = Bool().tag(sync=True)
- b = Tuple(Bool(), Bool(), Bool(), default_value=(False, False, False)).tag(sync=True)
+ b = Tuple(Bool(), Bool(), Bool(), default_value=(False, False, False)).tag(
+ sync=True
+ )
c = List(Bool()).tag(sync=True)
+
def test_empty_send_state():
w = SimpleWidget()
w.send_state([])
assert w.comm.messages == []
+
def test_empty_hold_sync():
w = SimpleWidget()
with w.hold_sync():
pass
assert w.comm.messages == []
-
-
-def test_control():
- comm = DummyComm()
- Widget.close_all()
- w = SimpleWidget()
- Widget.handle_control_comm_opened(
- comm, dict(metadata={'version': __control_protocol_version__})
- )
- Widget._handle_control_comm_msg(dict(content=dict(
- data={'method': 'request_states'}
- )))
- assert comm.messages
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipywidgets-8.0.2/ipywidgets/widgets/tests/test_set_state.py new/ipywidgets-8.0.4/ipywidgets/widgets/tests/test_set_state.py
--- old/ipywidgets-8.0.2/ipywidgets/widgets/tests/test_set_state.py 2022-09-02 20:27:45.000000000 +0200
+++ new/ipywidgets-8.0.4/ipywidgets/widgets/tests/test_set_state.py 2022-12-22 09:55:44.000000000 +0100
@@ -67,14 +67,14 @@
return DataInstance( memoryview(json_data['data']).tobytes() if json_data else None )
class DataWidget(SimpleWidget):
- d = Instance(DataInstance).tag(sync=True, to_json=mview_serializer, from_json=deserializer)
+ d = Instance(DataInstance, args=()).tag(sync=True, to_json=mview_serializer, from_json=deserializer)
# A widget that has a buffer that might be changed on reception:
def truncate_deserializer(json_data, widget):
return DataInstance( json_data['data'][:20].tobytes() if json_data else None )
class TruncateDataWidget(SimpleWidget):
- d = Instance(DataInstance).tag(sync=True, to_json=bytes_serializer, from_json=truncate_deserializer)
+ d = Instance(DataInstance, args=()).tag(sync=True, to_json=bytes_serializer, from_json=truncate_deserializer)
#
@@ -287,10 +287,13 @@
msg = {'method': 'echo_update', 'state': {'value': 42.0}, 'buffer_paths': []}
call42 = mock.call(msg, buffers=[])
- msg = {'method': 'update', 'state': {'value': 2.0, 'other': 11.0}, 'buffer_paths': []}
+ msg = {'method': 'update', 'state': {'value': 2.0}, 'buffer_paths': []}
call2 = mock.call(msg, buffers=[])
- calls = [call42, call2] if echo else [call2]
+ msg = {'method': 'update', 'state': {'other': 11.0}, 'buffer_paths': []}
+ call11 = mock.call(msg, buffers=[])
+
+ calls = [call42, call2, call11] if echo else [call2, call11]
widget._send.assert_has_calls(calls)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipywidgets-8.0.2/ipywidgets/widgets/tests/test_utils.py new/ipywidgets-8.0.4/ipywidgets/widgets/tests/test_utils.py
--- old/ipywidgets-8.0.2/ipywidgets/widgets/tests/test_utils.py 1970-01-01 01:00:00.000000000 +0100
+++ new/ipywidgets-8.0.4/ipywidgets/widgets/tests/test_utils.py 2022-12-22 09:55:44.000000000 +0100
@@ -0,0 +1,72 @@
+# Copyright (c) Jupyter Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+import inspect
+import pytest
+
+from ..utils import deprecation
+from .utils import call_method
+
+CALL_PATH = inspect.getfile(call_method)
+
+def test_deprecation():
+ caller_path = inspect.stack(context=0)[1].filename
+ with pytest.deprecated_call() as record:
+ deprecation('Deprecated call')
+ # Make sure the deprecation pointed to the external function calling this test function
+ assert len(record) == 1
+ assert record[0].filename == caller_path
+
+ with pytest.deprecated_call() as record:
+ deprecation('Deprecated call', ['ipywidgets/widgets/tests'])
+ # Make sure the deprecation pointed to the external function calling this test function
+ assert len(record) == 1
+ assert record[0].filename == caller_path
+
+ with pytest.deprecated_call() as record:
+ deprecation('Deprecated call', 'ipywidgets/widgets/tests')
+ # Make sure the deprecation pointed to the external function calling this test function
+ assert len(record) == 1
+ assert record[0].filename == caller_path
+
+ with pytest.deprecated_call() as record:
+ deprecation('Deprecated call', [])
+ # Make sure the deprecation pointed to *this* file
+ assert len(record) == 1
+ assert record[0].filename == __file__
+
+def test_deprecation_indirect():
+ # If the line that calls "deprecation" is not internal, it is considered the source:
+ with pytest.warns(DeprecationWarning) as record:
+ call_method(deprecation, "test message", [])
+ assert len(record) == 1
+ assert record[0].filename == CALL_PATH
+
+def test_deprecation_indirect_internal():
+ # If the line that calls "deprecation" is internal, it is not considered the source:
+ with pytest.warns(DeprecationWarning) as record:
+ call_method(deprecation, "test message", [CALL_PATH])
+ assert len(record) == 1
+ assert record[0].filename == __file__
+
+def test_deprecation_nested1():
+ def level1():
+ deprecation("test message", [])
+
+ with pytest.warns(DeprecationWarning) as record:
+ call_method(level1)
+
+ assert len(record) == 1
+ assert record[0].filename == __file__
+
+def test_deprecation_nested2():
+ def level2():
+ deprecation("test message", [])
+ def level1():
+ level2()
+
+ with pytest.warns(DeprecationWarning) as record:
+ call_method(level1)
+
+ assert len(record) == 1
+ assert record[0].filename == __file__
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipywidgets-8.0.2/ipywidgets/widgets/tests/test_widget.py new/ipywidgets-8.0.4/ipywidgets/widgets/tests/test_widget.py
--- old/ipywidgets-8.0.2/ipywidgets/widgets/tests/test_widget.py 2022-09-02 20:27:45.000000000 +0200
+++ new/ipywidgets-8.0.4/ipywidgets/widgets/tests/test_widget.py 2022-12-22 09:55:44.000000000 +0100
@@ -3,10 +3,14 @@
"""Test Widget."""
+import inspect
+
+import pytest
from IPython.core.interactiveshell import InteractiveShell
from IPython.display import display
from IPython.utils.capture import capture_output
+from .. import widget
from ..widget import Widget
from ..widget_button import Button
@@ -22,10 +26,12 @@
assert len(cap.outputs) == 1, "expect 1 output"
mime_bundle = cap.outputs[0].data
- assert mime_bundle['text/plain'] == repr(w), "expected plain text output"
- assert 'application/vnd.jupyter.widget-view+json' not in mime_bundle, "widget has no view"
- assert cap.stdout == '', repr(cap.stdout)
- assert cap.stderr == '', repr(cap.stderr)
+ assert mime_bundle["text/plain"] == repr(w), "expected plain text output"
+ assert (
+ "application/vnd.jupyter.widget-view+json" not in mime_bundle
+ ), "widget has no view"
+ assert cap.stdout == "", repr(cap.stdout)
+ assert cap.stderr == "", repr(cap.stderr)
def test_widget_view():
@@ -39,19 +45,38 @@
assert len(cap.outputs) == 1, "expect 1 output"
mime_bundle = cap.outputs[0].data
- assert mime_bundle['text/plain'] == repr(w), "expected plain text output"
- assert 'application/vnd.jupyter.widget-view+json' in mime_bundle, "widget should have have a view"
- assert cap.stdout == '', repr(cap.stdout)
- assert cap.stderr == '', repr(cap.stderr)
+ assert mime_bundle["text/plain"] == repr(w), "expected plain text output"
+ assert (
+ "application/vnd.jupyter.widget-view+json" in mime_bundle
+ ), "widget should have have a view"
+ assert cap.stdout == "", repr(cap.stdout)
+ assert cap.stderr == "", repr(cap.stderr)
def test_close_all():
# create a couple of widgets
widgets = [Button() for i in range(10)]
- assert len(Widget._active_widgets) > 0, "expect active widgets"
-
+ assert len(widget._instances) > 0, "expect active widgets"
+ assert widget._instances[widgets[0].model_id] is widgets[0]
# close all the widgets
Widget.close_all()
- assert len(Widget._active_widgets) == 0, "active widgets should be cleared"
+ assert len(widget._instances) == 0, "active widgets should be cleared"
+
+
+def test_compatibility():
+ button = Button()
+ assert widget._instances[button.model_id] is button
+ with pytest.deprecated_call() as record:
+ assert widget._instances is widget.Widget.widgets
+ assert widget._instances is widget.Widget._active_widgets
+ assert widget._registry is widget.Widget.widget_types
+ assert widget._registry is widget.Widget._widget_types
+
+ Widget.close_all()
+ assert not widget.Widget.widgets
+ assert not widget.Widget._active_widgets
+ caller_path = inspect.stack(context=0)[1].filename
+ assert all(x.filename == caller_path for x in record)
+ assert len(record) == 6
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipywidgets-8.0.2/ipywidgets/widgets/tests/test_widget_button.py new/ipywidgets-8.0.4/ipywidgets/widgets/tests/test_widget_button.py
--- old/ipywidgets-8.0.2/ipywidgets/widgets/tests/test_widget_button.py 1970-01-01 01:00:00.000000000 +0100
+++ new/ipywidgets-8.0.4/ipywidgets/widgets/tests/test_widget_button.py 2022-12-22 09:55:44.000000000 +0100
@@ -0,0 +1,12 @@
+# Copyright (c) Jupyter Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+import inspect
+import pytest
+from ipywidgets import Button
+
+def test_deprecation_fa_icons():
+ with pytest.deprecated_call() as record:
+ Button(icon='fa-home')
+ assert len(record) == 1
+ assert record[0].filename == inspect.stack(context=0)[1].filename
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipywidgets-8.0.2/ipywidgets/widgets/tests/test_widget_string.py new/ipywidgets-8.0.4/ipywidgets/widgets/tests/test_widget_string.py
--- old/ipywidgets-8.0.2/ipywidgets/widgets/tests/test_widget_string.py 2022-09-02 20:27:45.000000000 +0200
+++ new/ipywidgets-8.0.4/ipywidgets/widgets/tests/test_widget_string.py 2022-12-22 09:55:44.000000000 +0100
@@ -1,8 +1,10 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
-from ..widget_string import Combobox
+import inspect
+import pytest
+from ..widget_string import Combobox, Text
def test_combobox_creation_blank():
w = Combobox()
@@ -32,3 +34,32 @@
"Vanilla",
)
assert w.ensure_option == True
+
+def test_tooltip_deprecation():
+ caller_path = inspect.stack(context=0)[1].filename
+ with pytest.deprecated_call() as record:
+ w = Text(description_tooltip="testing")
+ assert len(record) == 1
+ assert record[0].filename == caller_path
+
+ with pytest.deprecated_call() as record:
+ w.description_tooltip
+ assert len(record) == 1
+ assert record[0].filename == caller_path
+
+ with pytest.deprecated_call() as record:
+ w.description_tooltip == "testing"
+ assert len(record) == 1
+ assert record[0].filename == caller_path
+
+ with pytest.deprecated_call() as record:
+ w.description_tooltip = "second value"
+ assert len(record) == 1
+ assert record[0].filename == caller_path
+ assert w.tooltip == "second value"
+
+def test_on_submit_deprecation():
+ with pytest.deprecated_call() as record:
+ Text().on_submit(lambda *args: ...)
+ assert len(record) == 1
+ assert record[0].filename == inspect.stack(context=0)[1].filename
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipywidgets-8.0.2/ipywidgets/widgets/tests/utils.py new/ipywidgets-8.0.4/ipywidgets/widgets/tests/utils.py
--- old/ipywidgets-8.0.2/ipywidgets/widgets/tests/utils.py 2022-09-02 20:27:45.000000000 +0200
+++ new/ipywidgets-8.0.4/ipywidgets/widgets/tests/utils.py 2022-12-22 09:55:45.000000000 +0100
@@ -3,6 +3,7 @@
from ipykernel.comm import Comm
from ipywidgets import Widget
+import ipywidgets.widgets.widget
class DummyComm(Comm):
comm_id = 'a-b-c-d'
@@ -25,14 +26,16 @@
undefined = object()
def setup_test_comm():
- _widget_attrs['_comm_default'] = getattr(Widget, '_comm_default', undefined)
- Widget._comm_default = lambda self: DummyComm()
+ Widget.comm.klass = DummyComm
+ ipywidgets.widgets.widget.Comm = DummyComm
_widget_attrs['_repr_mimebundle_'] = Widget._repr_mimebundle_
def raise_not_implemented(*args, **kwargs):
raise NotImplementedError()
Widget._repr_mimebundle_ = raise_not_implemented
def teardown_test_comm():
+ Widget.comm.klass = Comm
+ ipywidgets.widgets.widget.Comm = Comm
for attr, value in _widget_attrs.items():
if value is undefined:
delattr(Widget, attr)
@@ -45,3 +48,6 @@
def teardown():
teardown_test_comm()
+
+def call_method(method, *args, **kwargs):
+ method(*args, **kwargs)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipywidgets-8.0.2/ipywidgets/widgets/utils.py new/ipywidgets-8.0.4/ipywidgets/widgets/utils.py
--- old/ipywidgets-8.0.2/ipywidgets/widgets/utils.py 1970-01-01 01:00:00.000000000 +0100
+++ new/ipywidgets-8.0.4/ipywidgets/widgets/utils.py 2022-12-22 09:55:45.000000000 +0100
@@ -0,0 +1,64 @@
+# Copyright (c) Jupyter Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+from pathlib import Path
+import sys
+import inspect
+import warnings
+
+def _get_frame(level):
+ """Get the frame at the given stack level."""
+ # sys._getframe is much faster than inspect.stack, but isn't guaranteed to
+ # exist in all python implementations, so we fall back to inspect.stack()
+
+ # We need to add one to level to account for this get_frame call.
+ if hasattr(sys, '_getframe'):
+ frame = sys._getframe(level+1)
+ else:
+ frame = inspect.stack(context=0)[level+1].frame
+ return frame
+
+
+# This function is from https://github.com/python/cpython/issues/67998
+# (https://bugs.python.org/file39550/deprecated_module_stacklevel.diff) and
+# calculates the appropriate stacklevel for deprecations to target the
+# deprecation for the caller, no matter how many internal stack frames we have
+# added in the process. For example, with the deprecation warning in the
+# __init__ below, the appropriate stacklevel will change depending on how deep
+# the inheritance hierarchy is.
+def _external_stacklevel(internal):
+ """Find the stacklevel of the first frame that doesn't contain any of the given internal strings
+
+ The depth will be 1 at minimum in order to start checking at the caller of
+ the function that called this utility method.
+ """
+ # Get the level of my caller's caller
+ level = 2
+ frame = _get_frame(level)
+
+ # Normalize the path separators:
+ normalized_internal = [str(Path(s)) for s in internal]
+
+ # climb the stack frames while we see internal frames
+ while frame and any(s in str(Path(frame.f_code.co_filename)) for s in normalized_internal):
+ level +=1
+ frame = frame.f_back
+
+ # Return the stack level from the perspective of whoever called us (i.e., one level up)
+ return level-1
+
+def deprecation(message, internal='ipywidgets/widgets/'):
+ """Generate a deprecation warning targeting the first frame that is not 'internal'
+
+ internal is a string or list of strings, which if they appear in filenames in the
+ frames, the frames will be considered internal. Changing this can be useful if, for examnple,
+ we know that ipywidgets is calling out to traitlets internally.
+ """
+ if isinstance(internal, str):
+ internal = [internal]
+
+ # stack level of the first external frame from here
+ stacklevel = _external_stacklevel(internal)
+
+ # The call to .warn adds one frame, so bump the stacklevel up by one
+ warnings.warn(message, DeprecationWarning, stacklevel=stacklevel+1)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipywidgets-8.0.2/ipywidgets/widgets/widget.py new/ipywidgets-8.0.4/ipywidgets/widgets/widget.py
--- old/ipywidgets-8.0.2/ipywidgets/widgets/widget.py 2022-09-02 20:27:45.000000000 +0200
+++ new/ipywidgets-8.0.4/ipywidgets/widgets/widget.py 2022-12-22 09:55:45.000000000 +0100
@@ -6,6 +6,7 @@
in the Jupyter notebook front-end.
"""
import os
+import typing
from contextlib import contextmanager
from collections.abc import Iterable
from IPython import get_ipython
@@ -17,8 +18,13 @@
from base64 import standard_b64encode
+from .utils import deprecation, _get_frame
+
from .._version import __protocol_version__, __control_protocol_version__, __jupyter_widgets_base_version__
+import inspect
+TRAITLETS_FILE = inspect.getfile(HasTraits)
+
# Based on jupyter_core.paths.envset
def envset(name, default):
"""Return True if the given environment variable is turned on, otherwise False
@@ -34,6 +40,9 @@
PROTOCOL_VERSION_MAJOR = __protocol_version__.split('.')[0]
CONTROL_PROTOCOL_VERSION_MAJOR = __control_protocol_version__.split('.')[0]
JUPYTER_WIDGETS_ECHO = envset('JUPYTER_WIDGETS_ECHO', default=True)
+# we keep a strong reference for every widget created, for a discussion on using weak references see:
+# https://github.com/jupyter-widgets/ipywidgets/issues/1345
+_instances : typing.MutableMapping[str, "Widget"] = {}
def _widget_to_json(x, obj):
if isinstance(x, dict):
@@ -50,8 +59,8 @@
return {k: _json_to_widget(v, obj) for k, v in x.items()}
elif isinstance(x, (list, tuple)):
return [_json_to_widget(v, obj) for v in x]
- elif isinstance(x, str) and x.startswith('IPY_MODEL_') and x[10:] in Widget._active_widgets:
- return Widget._active_widgets[x[10:]]
+ elif isinstance(x, str) and x.startswith('IPY_MODEL_') and x[10:] in _instances:
+ return _instances[x[10:]]
else:
return x
@@ -259,10 +268,16 @@
for view_name, widget in sorted(vn.items()):
yield (model_module, model_version, model_name, view_module, view_version, view_name), widget
+
+
+# a registry of widgets by module, version, and name so we can create a Python model from widgets
+# that are constructed from the frontend.
+_registry = WidgetRegistry()
+
def register(widget):
"""A decorator registering a widget class in the widget registry."""
w = widget.class_traits()
- Widget._widget_types.register(w['_model_module'].default_value,
+ _registry.register(w['_model_module'].default_value,
w['_model_module_version'].default_value,
w['_model_name'].default_value,
w['_view_module'].default_value,
@@ -272,6 +287,16 @@
return widget
+class _staticproperty(object):
+ def __init__(self, fget):
+ self.fget = fget
+
+ def __get__(self, owner_self, owner_cls):
+ assert owner_self is None
+ return self.fget()
+
+
+
class Widget(LoggingHasTraits):
#-------------------------------------------------------------------------
# Class attributes
@@ -279,15 +304,49 @@
_widget_construction_callback = None
_control_comm = None
- # _active_widgets is a dictionary of all active widget objects
- _active_widgets = {}
-
- # _widget_types is a registry of widgets by module, version, and name:
- _widget_types = WidgetRegistry()
+ @_staticproperty
+ def widgets():
+ # Because this is a static attribute, it will be accessed when initializing this class. In that case, since a user
+ # did not explicitly try to use this attribute, we do not want to throw a deprecation warning.
+ # So we check if the thing calling this static property is one of the known initialization functions in traitlets.
+ frame = _get_frame(2)
+ if not (frame.f_code.co_filename == TRAITLETS_FILE and (frame.f_code.co_name in ('getmembers', 'setup_instance', 'setup_class'))):
+ deprecation("Widget.widgets is deprecated.")
+ return _instances
+
+ @_staticproperty
+ def _active_widgets():
+ # Because this is a static attribute, it will be accessed when initializing this class. In that case, since a user
+ # did not explicitly try to use this attribute, we do not want to throw a deprecation warning.
+ # So we check if the thing calling this static property is one of the known initialization functions in traitlets.
+ frame = _get_frame(2)
+ if not (frame.f_code.co_filename == TRAITLETS_FILE and (frame.f_code.co_name in ('getmembers', 'setup_instance', 'setup_class'))):
+ deprecation("Widget._active_widgets is deprecated.")
+ return _instances
+
+ @_staticproperty
+ def _widget_types():
+ # Because this is a static attribute, it will be accessed when initializing this class. In that case, since a user
+ # did not explicitly try to use this attribute, we do not want to throw a deprecation warning.
+ # So we check if the thing calling this static property is one of the known initialization functions in traitlets.
+ frame = _get_frame(2)
+ if not (frame.f_code.co_filename == TRAITLETS_FILE and (frame.f_code.co_name in ('getmembers', 'setup_instance', 'setup_class'))):
+ deprecation("Widget._widget_types is deprecated.")
+ return _registry
+
+ @_staticproperty
+ def widget_types():
+ # Because this is a static attribute, it will be accessed when initializing this class. In that case, since a user
+ # did not explicitly try to use this attribute, we do not want to throw a deprecation warning.
+ # So we check if the thing calling this static property is one of the known initialization functions in traitlets.
+ frame = _get_frame(2)
+ if not (frame.f_code.co_filename == TRAITLETS_FILE and (frame.f_code.co_name in ('getmembers', 'setup_instance', 'setup_class'))):
+ deprecation("Widget.widget_types is deprecated.")
+ return _registry
@classmethod
def close_all(cls):
- for widget in list(cls._active_widgets.values()):
+ for widget in list(_instances.values()):
widget.close()
@staticmethod
@@ -329,7 +388,7 @@
if method == 'request_states':
# Send back the full widgets state
cls.get_manager_state()
- widgets = cls._active_widgets.values()
+ widgets = _instances.values()
full_state = {}
drop_defaults = False
for widget in widgets:
@@ -359,7 +418,7 @@
state = data['state']
# Find the widget class to instantiate in the registered widgets
- widget_class = Widget._widget_types.get(state['_model_module'],
+ widget_class = _registry.get(state['_model_module'],
state['_model_module_version'],
state['_model_name'],
state['_view_module'],
@@ -380,7 +439,7 @@
"""
state = {}
if widgets is None:
- widgets = Widget._active_widgets.values()
+ widgets = _instances.values()
for widget in widgets:
state[widget.model_id] = widget._get_embed_state(drop_defaults=drop_defaults)
return {'version_major': 2, 'version_minor': 0, 'state': state}
@@ -476,7 +535,7 @@
self._model_id = self.model_id
self.comm.on_msg(self._handle_msg)
- Widget._active_widgets[self.model_id] = self
+ _instances[self.model_id] = self
@property
def model_id(self):
@@ -496,7 +555,7 @@
When the comm is closed, all of the widget views are automatically
removed from the front-end."""
if self.comm is not None:
- Widget._active_widgets.pop(self.model_id, None)
+ _instances.pop(self.model_id, None)
self.comm.close()
self.comm = None
self._repr_mimebundle_ = None
@@ -581,7 +640,7 @@
# The order of these context managers is important. Properties must
# be locked when the hold_trait_notification context manager is
# released and notifications are fired.
- with self.hold_sync(), self._lock_property(**sync_data), self.hold_trait_notifications():
+ with self._lock_property(**sync_data), self.hold_trait_notifications():
for name in sync_data:
if name in self.keys:
from_json = self.trait_metadata(name, 'from_json',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipywidgets-8.0.2/ipywidgets/widgets/widget_button.py new/ipywidgets-8.0.4/ipywidgets/widgets/widget_button.py
--- old/ipywidgets-8.0.2/ipywidgets/widgets/widget_button.py 2022-09-02 20:27:45.000000000 +0200
+++ new/ipywidgets-8.0.4/ipywidgets/widgets/widget_button.py 2022-12-22 09:55:45.000000000 +0100
@@ -7,6 +7,7 @@
click events on the button and trigger backend code when the clicks are fired.
"""
+from .utils import deprecation
from .domwidget import DOMWidget
from .widget import CallbackDispatcher, register, widget_serialization
from .widget_core import CoreWidget
@@ -14,7 +15,6 @@
from .trait_types import Color, InstanceDict
from traitlets import Unicode, Bool, CaselessStrEnum, Instance, validate, default
-import warnings
@register
@@ -70,8 +70,9 @@
"""Strip 'fa-' if necessary'"""
value = proposal['value']
if 'fa-' in value:
- warnings.warn("icons names no longer need 'fa-', "
- "just use the class names themselves (for example, 'gear spin' instead of 'fa-gear fa-spin')", DeprecationWarning)
+ deprecation("icons names no longer need 'fa-', "
+ "just use the class names themselves (for example, 'gear spin' instead of 'fa-gear fa-spin')",
+ internal=['ipywidgets/widgets/', 'traitlets/traitlets.py', '/contextlib.py'])
value = value.replace('fa-', '')
return value
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipywidgets-8.0.2/ipywidgets/widgets/widget_description.py new/ipywidgets-8.0.4/ipywidgets/widgets/widget_description.py
--- old/ipywidgets-8.0.2/ipywidgets/widgets/widget_description.py 2022-09-02 20:27:45.000000000 +0200
+++ new/ipywidgets-8.0.4/ipywidgets/widgets/widget_description.py 2022-12-22 09:55:45.000000000 +0100
@@ -9,6 +9,8 @@
from .widget_style import Style
from .widget_core import CoreWidget
from .domwidget import DOMWidget
+from .utils import deprecation
+
import warnings
@register
@@ -27,7 +29,7 @@
def __init__(self, *args, **kwargs):
if 'description_tooltip' in kwargs:
- warnings.warn("the description_tooltip argument is deprecated, use tooltip instead", DeprecationWarning)
+ deprecation("the description_tooltip argument is deprecated, use tooltip instead")
kwargs.setdefault('tooltip', kwargs['description_tooltip'])
del kwargs['description_tooltip']
super().__init__(*args, **kwargs)
@@ -47,10 +49,10 @@
.. deprecated :: 8.0.0
Use tooltip attribute instead.
"""
- warnings.warn(".description_tooltip is deprecated, use .tooltip instead", DeprecationWarning)
+ deprecation(".description_tooltip is deprecated, use .tooltip instead")
return self.tooltip
@description_tooltip.setter
def description_tooltip(self, tooltip):
- warnings.warn(".description_tooltip is deprecated, use .tooltip instead", DeprecationWarning)
+ deprecation(".description_tooltip is deprecated, use .tooltip instead")
self.tooltip = tooltip
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipywidgets-8.0.2/ipywidgets/widgets/widget_string.py new/ipywidgets-8.0.4/ipywidgets/widgets/widget_string.py
--- old/ipywidgets-8.0.2/ipywidgets/widgets/widget_string.py 2022-09-02 20:27:45.000000000 +0200
+++ new/ipywidgets-8.0.4/ipywidgets/widgets/widget_string.py 2022-12-22 09:55:45.000000000 +0100
@@ -11,8 +11,8 @@
from .widget import CallbackDispatcher, register, widget_serialization
from .widget_core import CoreWidget
from .trait_types import Color, InstanceDict, TypedTuple
+from .utils import deprecation
from traitlets import Unicode, Bool, Int
-from warnings import warn
class _StringStyle(DescriptionStyle, CoreWidget):
@@ -142,8 +142,7 @@
remove: bool (optional)
Whether to unregister the callback
"""
- import warnings
- warnings.warn("on_submit is deprecated. Instead, set the .continuous_update attribute to False and observe the value changing with: mywidget.observe(callback, 'value').", DeprecationWarning)
+ deprecation("on_submit is deprecated. Instead, set the .continuous_update attribute to False and observe the value changing with: mywidget.observe(callback, 'value').")
self._submission_callbacks.register_callback(callback, remove=remove)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipywidgets-8.0.2/ipywidgets.egg-info/PKG-INFO new/ipywidgets-8.0.4/ipywidgets.egg-info/PKG-INFO
--- old/ipywidgets-8.0.2/ipywidgets.egg-info/PKG-INFO 2022-09-02 20:40:07.000000000 +0200
+++ new/ipywidgets-8.0.4/ipywidgets.egg-info/PKG-INFO 2022-12-22 10:14:32.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: ipywidgets
-Version: 8.0.2
+Version: 8.0.4
Summary: Jupyter interactive widgets
Home-page: http://jupyter.org
Author: Jupyter Development Team
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipywidgets-8.0.2/ipywidgets.egg-info/SOURCES.txt new/ipywidgets-8.0.4/ipywidgets.egg-info/SOURCES.txt
--- old/ipywidgets-8.0.2/ipywidgets.egg-info/SOURCES.txt 2022-09-02 20:40:07.000000000 +0200
+++ new/ipywidgets-8.0.4/ipywidgets.egg-info/SOURCES.txt 2022-12-22 10:14:32.000000000 +0100
@@ -22,6 +22,7 @@
ipywidgets/widgets/domwidget.py
ipywidgets/widgets/interaction.py
ipywidgets/widgets/trait_types.py
+ipywidgets/widgets/utils.py
ipywidgets/widgets/valuewidget.py
ipywidgets/widgets/widget.py
ipywidgets/widgets/widget_bool.py
@@ -56,8 +57,10 @@
ipywidgets/widgets/tests/test_send_state.py
ipywidgets/widgets/tests/test_set_state.py
ipywidgets/widgets/tests/test_traits.py
+ipywidgets/widgets/tests/test_utils.py
ipywidgets/widgets/tests/test_widget.py
ipywidgets/widgets/tests/test_widget_box.py
+ipywidgets/widgets/tests/test_widget_button.py
ipywidgets/widgets/tests/test_widget_datetime.py
ipywidgets/widgets/tests/test_widget_float.py
ipywidgets/widgets/tests/test_widget_image.py
participants (1)
-
Source-Sync