Skip to content

Commit 9042d87

Browse files
authored
Merge pull request #3726 from martinRenou/backport_move_comm
Backport #3720 and #3533 to 7.x
2 parents 5f2e97a + d9375b9 commit 9042d87

File tree

9 files changed

+113
-28
lines changed

9 files changed

+113
-28
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_output.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -202,14 +202,14 @@ def test_append_display_data():
202202

203203
# Now try appending an Image.
204204
image_data = b"foobar"
205-
image_data_b64 = image_data if sys.version_info[0] < 3 else 'Zm9vYmFy\n'
206205

207206
widget.append_display_data(Image(image_data, width=123, height=456))
208-
expected += (
207+
# Old ipykernel/IPython
208+
expected1 = expected + (
209209
{
210210
'output_type': 'display_data',
211211
'data': {
212-
'image/png': image_data_b64,
212+
'image/png': image_data if sys.version_info[0] < 3 else 'Zm9vYmFy\n',
213213
'text/plain': '<IPython.core.display.Image object>'
214214
},
215215
'metadata': {
@@ -220,4 +220,20 @@ def test_append_display_data():
220220
}
221221
},
222222
)
223-
assert widget.outputs == expected, repr(widget.outputs)
223+
# Latest ipykernel/IPython
224+
expected2 = expected + (
225+
{
226+
'output_type': 'display_data',
227+
'data': {
228+
'image/png': image_data if sys.version_info[0] < 3 else 'Zm9vYmFy',
229+
'text/plain': '<IPython.core.display.Image object>'
230+
},
231+
'metadata': {
232+
'image/png': {
233+
'width': 123,
234+
'height': 456
235+
}
236+
}
237+
},
238+
)
239+
assert widget.outputs == expected1 or widget.outputs == expected2

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: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,74 @@
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+
class DummyCommManager():
38+
39+
def unregister_comm(self, comm):
40+
pass
41+
42+
43+
def dummy_create_comm(**kwargs):
44+
return DummyComm()
45+
46+
47+
def dummy_get_comm_manager(**kwargs):
48+
return DummyCommManager()
49+
50+
2551
_widget_attrs = {}
2652
undefined = object()
2753

54+
if NEW_COMM_PACKAGE:
55+
orig_comm = ipykernel.comm.comm.BaseComm
56+
else:
57+
orig_comm = ipykernel.comm.Comm
58+
orig_create_comm = None
59+
orig_get_comm_manager = None
60+
61+
if NEW_COMM_PACKAGE:
62+
orig_create_comm = comm.create_comm
63+
orig_get_comm_manager = comm.get_comm_manager
64+
2865
def setup_test_comm():
66+
if NEW_COMM_PACKAGE:
67+
comm.create_comm = dummy_create_comm
68+
comm.get_comm_manager = dummy_get_comm_manager
69+
ipykernel.comm.comm.BaseComm = DummyComm
70+
else:
71+
ipykernel.comm.Comm = DummyComm
2972
Widget.comm.klass = DummyComm
3073
ipywidgets.widgets.widget.Comm = DummyComm
3174
_widget_attrs['_ipython_display_'] = Widget._ipython_display_
@@ -34,8 +77,14 @@ def raise_not_implemented(*args, **kwargs):
3477
Widget._ipython_display_ = raise_not_implemented
3578

3679
def teardown_test_comm():
37-
Widget.comm.klass = Comm
38-
ipywidgets.widgets.widget.Comm = Comm
80+
if NEW_COMM_PACKAGE:
81+
comm.create_comm = orig_create_comm
82+
comm.get_comm_manager = orig_get_comm_manager
83+
ipykernel.comm.comm.BaseComm = orig_comm
84+
else:
85+
ipykernel.comm.Comm = orig_comm
86+
Widget.comm.klass = orig_comm
87+
ipywidgets.widgets.widget.Comm = orig_comm
3988
for attr, value in _widget_attrs.items():
4089
if value is undefined:
4190
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)