Skip to content

Commit d1e5bb8

Browse files
fix: allow use of comm package (jupyter-widgets#3533)
* Move comm package * Fix wrongly written test This test was supposed to fail, as setting the bottom_left value will trigger a ipywidgets.Layout.send_state on `child.layout.grid_area = position` (ipywidgets.widgets.widget_template.py L419) and on `self.layout.grid_template_areas = grid_template_areas_css` (ipywidgets.widgets.widget_template.py L448) * Fix Python 3.7 test Co-authored-by: Maarten Breddels <[email protected]> (cherry picked from commit 785d159)
1 parent 5f2e97a commit d1e5bb8

File tree

8 files changed

+88
-24
lines changed

8 files changed

+88
-24
lines changed

ipywidgets/__init__.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,36 @@
2020

2121
import os
2222

23+
from traitlets import link, dlink
2324
from IPython import get_ipython
25+
26+
try:
27+
from comm import get_comm_manager
28+
except ImportError:
29+
def get_comm_manager():
30+
ip = get_ipython()
31+
32+
if ip is not None and ip.kernel is not None:
33+
return get_ipython().kernel.comm_manager
34+
2435
from ._version import version_info, __version__, __protocol_version__, __jupyter_widgets_controls_version__, __jupyter_widgets_base_version__
2536
from .widgets import *
26-
from traitlets import link, dlink
37+
2738

2839

2940
def load_ipython_extension(ip):
3041
"""Set up IPython to work with widgets"""
3142
if not hasattr(ip, 'kernel'):
3243
return
33-
register_comm_target(ip.kernel)
44+
register_comm_target()
3445

3546

3647
def register_comm_target(kernel=None):
3748
"""Register the jupyter.widget comm target"""
38-
if kernel is None:
39-
kernel = get_ipython().kernel
40-
kernel.comm_manager.register_target('jupyter.widget', Widget.handle_comm_opened)
41-
kernel.comm_manager.register_target('jupyter.widget.control', Widget.handle_control_comm_opened)
49+
comm_manager = get_comm_manager()
50+
51+
comm_manager.register_target('jupyter.widget', Widget.handle_comm_opened)
52+
comm_manager.register_target('jupyter.widget.control', Widget.handle_control_comm_opened)
4253

4354
# deprecated alias
4455
handle_kernel = register_comm_target
@@ -48,6 +59,6 @@ def _handle_ipython():
4859
ip = get_ipython()
4960
if ip is None:
5061
return
51-
load_ipython_extension(ip)
62+
register_comm_target()
5263

5364
_handle_ipython()

ipywidgets/tests/test_embed.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77

88
import traitlets
99

10-
from ..widgets import IntSlider, IntText, Text, Widget, jslink, HBox, widget_serialization
10+
# This has a byproduct of setting up the comms
11+
import ipykernel.ipkernel
12+
13+
from ..widgets import IntSlider, IntText, Text, Widget, jslink, HBox, widget_serialization, widget
1114
from ..embed import embed_data, embed_snippet, embed_minimal_html, dependency_state
1215

1316
try:

ipywidgets/widgets/tests/test_interaction.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -734,4 +734,3 @@ def test_state_schema():
734734
with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../../', 'state.schema.json')) as f:
735735
schema = json.load(f)
736736
jsonschema.validate(state, schema)
737-

ipywidgets/widgets/tests/test_widget_templates.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ def test_update_dynamically(self, send_state): #pylint: disable=no-self-use
233233
assert box.layout.grid_template_areas == ('"top-left top-right"\n' +
234234
'"bottom-left bottom-right"')
235235
# check whether frontend was informed
236-
send_state.assert_called_once_with(key="grid_template_areas")
236+
send_state.assert_called_with(key="grid_template_areas")
237237

238238
box = widgets.TwoByTwoLayout(top_left=button1, top_right=button3,
239239
bottom_left=None, bottom_right=button4)
@@ -244,7 +244,7 @@ def test_update_dynamically(self, send_state): #pylint: disable=no-self-use
244244
box.merge = False
245245
assert box.layout.grid_template_areas == ('"top-left top-right"\n' +
246246
'"bottom-left bottom-right"')
247-
send_state.assert_called_once_with(key="grid_template_areas")
247+
send_state.assert_called_with(key="grid_template_areas")
248248

249249

250250
class TestAppLayout(TestCase):

ipywidgets/widgets/tests/utils.py

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,69 @@
11
# Copyright (c) Jupyter Development Team.
22
# Distributed under the terms of the Modified BSD License.
33

4-
from ipykernel.comm import Comm
54
from ipywidgets import Widget
65
import ipywidgets.widgets.widget
76

8-
class DummyComm(Comm):
7+
# The new comm package is not available in our Python 3.7 CI (older ipykernel version)
8+
try:
9+
import comm
10+
NEW_COMM_PACKAGE = True
11+
except ImportError:
12+
NEW_COMM_PACKAGE = False
13+
14+
import ipykernel.comm
15+
16+
17+
class DummyComm():
918
comm_id = 'a-b-c-d'
1019
kernel = 'Truthy'
1120

1221
def __init__(self, *args, **kwargs):
13-
super(DummyComm, self).__init__(*args, **kwargs)
22+
super().__init__()
1423
self.messages = []
1524

1625
def open(self, *args, **kwargs):
1726
pass
1827

28+
def on_msg(self, *args, **kwargs):
29+
pass
30+
1931
def send(self, *args, **kwargs):
2032
self.messages.append((args, kwargs))
2133

2234
def close(self, *args, **kwargs):
2335
pass
2436

37+
38+
def dummy_create_comm(**kwargs):
39+
return DummyComm()
40+
41+
42+
def dummy_get_comm_manager(**kwargs):
43+
return {}
44+
45+
2546
_widget_attrs = {}
2647
undefined = object()
2748

49+
if NEW_COMM_PACKAGE:
50+
orig_comm = ipykernel.comm.comm.BaseComm
51+
else:
52+
orig_comm = ipykernel.comm.Comm
53+
orig_create_comm = None
54+
orig_get_comm_manager = None
55+
56+
if NEW_COMM_PACKAGE:
57+
orig_create_comm = comm.create_comm
58+
orig_get_comm_manager = comm.get_comm_manager
59+
2860
def setup_test_comm():
61+
if NEW_COMM_PACKAGE:
62+
comm.create_comm = dummy_create_comm
63+
comm.get_comm_manager = dummy_get_comm_manager
64+
ipykernel.comm.comm.BaseComm = DummyComm
65+
else:
66+
ipykernel.comm.Comm = DummyComm
2967
Widget.comm.klass = DummyComm
3068
ipywidgets.widgets.widget.Comm = DummyComm
3169
_widget_attrs['_ipython_display_'] = Widget._ipython_display_
@@ -34,8 +72,14 @@ def raise_not_implemented(*args, **kwargs):
3472
Widget._ipython_display_ = raise_not_implemented
3573

3674
def teardown_test_comm():
37-
Widget.comm.klass = Comm
38-
ipywidgets.widgets.widget.Comm = Comm
75+
if NEW_COMM_PACKAGE:
76+
comm.create_comm = orig_create_comm
77+
comm.get_comm_manager = orig_get_comm_manager
78+
ipykernel.comm.comm.BaseComm = orig_comm
79+
else:
80+
ipykernel.comm.Comm = orig_comm
81+
Widget.comm.klass = orig_comm
82+
ipywidgets.widgets.widget.Comm = orig_comm
3983
for attr, value in _widget_attrs.items():
4084
if value is undefined:
4185
delattr(Widget, attr)

ipywidgets/widgets/widget.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from IPython.core.getipython import get_ipython
1919
from ipykernel.comm import Comm
2020
from traitlets import (
21-
HasTraits, Unicode, Dict, Instance, List, Int, Set, Bytes, observe, default, Container,
21+
Any, HasTraits, Unicode, Dict, Instance, List, Int, Set, Bytes, observe, default, Container,
2222
Undefined)
2323
from ipython_genutils.py3compat import string_types, PY3
2424
from IPython.display import display
@@ -454,7 +454,7 @@ def get_view_spec(self):
454454

455455
_view_count = Int(None, allow_none=True,
456456
help="EXPERIMENTAL: The number of views of the model displayed in the frontend. This attribute is experimental and may change or be removed in the future. None signifies that views will not be tracked. Set this to 0 to start tracking view creation/deletion.").tag(sync=True)
457-
comm = Instance('ipykernel.comm.Comm', allow_none=True)
457+
comm = Any(allow_none=True)
458458

459459
keys = List(help="The traits which are synced.")
460460

@@ -500,7 +500,15 @@ def open(self):
500500
if self._model_id is not None:
501501
args['comm_id'] = self._model_id
502502

503-
self.comm = Comm(**args)
503+
try:
504+
from comm import create_comm
505+
except ImportError:
506+
def create_comm(**kwargs):
507+
from ipykernel.comm import Comm
508+
509+
return Comm(**kwargs)
510+
511+
self.comm = create_comm(**args)
504512

505513
@observe('comm')
506514
def _comm_changed(self, change):
@@ -678,7 +686,7 @@ def notify_change(self, change):
678686
# Send the state to the frontend before the user-registered callbacks
679687
# are called.
680688
name = change['name']
681-
if self.comm is not None and self.comm.kernel is not None:
689+
if self.comm is not None and getattr(self.comm, 'kernel', True) is not None:
682690
# Make sure this isn't information that the front-end just sent us.
683691
if name in self.keys and self._should_send_property(name, getattr(self, name)):
684692
# Send new state to front-end
@@ -813,7 +821,7 @@ def _ipython_display_(self, **kwargs):
813821

814822
def _send(self, msg, buffers=None):
815823
"""Sends a message to the model in the front-end."""
816-
if self.comm is not None and self.comm.kernel is not None:
824+
if self.comm is not None and (self.comm.kernel is not None if hasattr(self.comm, "kernel") else True):
817825
self.comm.send(data=msg, buffers=buffers)
818826

819827
def _repr_keys(self):

ipywidgets/widgets/widget_output.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class Output(DOMWidget):
3131
context will be captured and displayed in the widget instead of the standard output
3232
area.
3333
34-
You can also use the .capture() method to decorate a function or a method. Any output
34+
You can also use the .capture() method to decorate a function or a method. Any output
3535
produced by the function will then go to the output widget. This is useful for
3636
debugging widget callbacks, for example.
3737

setup.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@
110110

111111
setuptools_args = {}
112112
install_requires = setuptools_args['install_requires'] = [
113-
'ipykernel>=4.5.1',
114113
'ipython_genutils~=0.2.0',
115114
'traitlets>=4.3.1',
116115
# TODO: Dynamically add this dependency
@@ -125,7 +124,7 @@
125124
':python_version>="3.3"': ['ipython>=4.0.0'],
126125
':python_version>="3.6"': ['jupyterlab_widgets>=1.0.0,<3'],
127126
'test:python_version=="2.7"': ['mock'],
128-
'test': ['pytest>=3.6.0', 'pytest-cov'],
127+
'test': ['pytest>=3.6.0', 'pytest-cov', 'ipykernel'],
129128
}
130129

131130
if 'setuptools' in sys.modules:

0 commit comments

Comments
 (0)