From 822f93ff4d6f1b36e7aeabbf38a69e48f40e4cd0 Mon Sep 17 00:00:00 2001 From: martinRenou Date: Mon, 1 Aug 2022 15:34:04 +0200 Subject: [PATCH] Move comm package --- .github/workflows/tests.yml | 2 +- python/ipywidgets/ipywidgets/__init__.py | 25 +++++++++++++----- .../ipywidgets/ipywidgets/tests/test_embed.py | 3 +++ .../widgets/tests/test_interaction.py | 1 - .../widgets/tests/test_widget_upload.py | 2 +- .../ipywidgets/widgets/tests/utils.py | 26 +++++++++++++++++-- .../ipywidgets/ipywidgets/widgets/widget.py | 19 +++++++++----- .../ipywidgets/widgets/widget_output.py | 8 +++--- python/ipywidgets/setup.cfg | 2 +- 9 files changed, 66 insertions(+), 22 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 37c873e947..e8867ae112 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -131,7 +131,7 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - pip install file://$PWD/python/ipywidgets#egg=ipywidgets + pip install file://$PWD/python/ipywidgets#egg=ipywidgets[test] - name: Install JS dependencies run: | yarn diff --git a/python/ipywidgets/ipywidgets/__init__.py b/python/ipywidgets/ipywidgets/__init__.py index 4eb6942356..9e40f66034 100644 --- a/python/ipywidgets/ipywidgets/__init__.py +++ b/python/ipywidgets/ipywidgets/__init__.py @@ -21,28 +21,39 @@ from ._version import __version__, __protocol_version__, __jupyter_widgets_controls_version__, __jupyter_widgets_base_version__ import os + +from traitlets import link, dlink from IPython import get_ipython +try: + from comm import get_comm_manager +except ImportError: + def get_comm_manager(): + ip = get_ipython() + + if ip is not None and ip.kernel is not None: + return get_ipython().kernel.comm_manager + from .widgets import * -from traitlets import link, dlink + def load_ipython_extension(ip): """Set up Jupyter to work with widgets""" if not hasattr(ip, 'kernel'): return - register_comm_target(ip.kernel) + register_comm_target() def register_comm_target(kernel=None): """Register the jupyter.widget comm target""" - if kernel is None: - kernel = get_ipython().kernel - kernel.comm_manager.register_target('jupyter.widget', Widget.handle_comm_opened) - kernel.comm_manager.register_target('jupyter.widget.control', Widget.handle_control_comm_opened) + comm_manager = get_comm_manager() + + comm_manager.register_target('jupyter.widget', Widget.handle_comm_opened) + comm_manager.register_target('jupyter.widget.control', Widget.handle_control_comm_opened) def _handle_ipython(): """Register with the comm target at import if running in Jupyter""" ip = get_ipython() if ip is None: return - load_ipython_extension(ip) + register_comm_target() _handle_ipython() diff --git a/python/ipywidgets/ipywidgets/tests/test_embed.py b/python/ipywidgets/ipywidgets/tests/test_embed.py index a295442455..83a0788451 100644 --- a/python/ipywidgets/ipywidgets/tests/test_embed.py +++ b/python/ipywidgets/ipywidgets/tests/test_embed.py @@ -9,6 +9,9 @@ import traitlets +# This has a byproduct of setting up the comms +import ipykernel.ipkernel + 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 diff --git a/python/ipywidgets/ipywidgets/widgets/tests/test_interaction.py b/python/ipywidgets/ipywidgets/widgets/tests/test_interaction.py index 0821eb6c20..0dc7e5fcfc 100644 --- a/python/ipywidgets/ipywidgets/widgets/tests/test_interaction.py +++ b/python/ipywidgets/ipywidgets/widgets/tests/test_interaction.py @@ -624,4 +624,3 @@ def test_state_schema(): with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../../', 'state.schema.json')) as f: schema = json.load(f) jsonschema.validate(state, schema) - diff --git a/python/ipywidgets/ipywidgets/widgets/tests/test_widget_upload.py b/python/ipywidgets/ipywidgets/widgets/tests/test_widget_upload.py index f8dc1a8cee..5a06f31b57 100644 --- a/python/ipywidgets/ipywidgets/widgets/tests/test_widget_upload.py +++ b/python/ipywidgets/ipywidgets/widgets/tests/test_widget_upload.py @@ -76,8 +76,8 @@ def test_serialization_deserialization_integrity(self): from ipykernel.comm import Comm uploader = FileUpload() mock_comm = MagicMock(spec=Comm) - mock_comm.kernel = 'does not matter' mock_comm.send = MagicMock() + mock_comm.kernel = 'does not matter' uploader.comm = mock_comm message = {'value': [FILE_UPLOAD_FRONTEND_CONTENT]} uploader.set_state(message) diff --git a/python/ipywidgets/ipywidgets/widgets/tests/utils.py b/python/ipywidgets/ipywidgets/widgets/tests/utils.py index 7b9b8fcc29..efb572fd56 100644 --- a/python/ipywidgets/ipywidgets/widgets/tests/utils.py +++ b/python/ipywidgets/ipywidgets/widgets/tests/utils.py @@ -1,11 +1,14 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. -from ipykernel.comm import Comm from ipywidgets import Widget import ipywidgets.widgets.widget -class DummyComm(Comm): +import comm +from ipykernel.comm import Comm + + +class DummyComm(): comm_id = 'a-b-c-d' kernel = 'Truthy' @@ -16,16 +19,33 @@ def __init__(self, *args, **kwargs): def open(self, *args, **kwargs): pass + def on_msg(self, *args, **kwargs): + pass + def send(self, *args, **kwargs): self.messages.append((args, kwargs)) def close(self, *args, **kwargs): pass + +def dummy_create_comm(**kwargs): + return DummyComm() + + +def dummy_get_comm_manager(**kwargs): + return {} + + _widget_attrs = {} undefined = object() +orig_create_comm = comm.create_comm +orig_get_comm_manager = comm.get_comm_manager + def setup_test_comm(): + comm.create_comm = dummy_create_comm + comm.get_comm_manager = dummy_get_comm_manager Widget.comm.klass = DummyComm ipywidgets.widgets.widget.Comm = DummyComm _widget_attrs['_repr_mimebundle_'] = Widget._repr_mimebundle_ @@ -34,6 +54,8 @@ def raise_not_implemented(*args, **kwargs): Widget._repr_mimebundle_ = raise_not_implemented def teardown_test_comm(): + comm.create_comm = orig_create_comm + comm.get_comm_manager = orig_get_comm_manager Widget.comm.klass = Comm ipywidgets.widgets.widget.Comm = Comm for attr, value in _widget_attrs.items(): diff --git a/python/ipywidgets/ipywidgets/widgets/widget.py b/python/ipywidgets/ipywidgets/widgets/widget.py index 030fdc45ce..52afa5881c 100644 --- a/python/ipywidgets/ipywidgets/widgets/widget.py +++ b/python/ipywidgets/ipywidgets/widgets/widget.py @@ -10,9 +10,8 @@ from contextlib import contextmanager from collections.abc import Iterable from IPython import get_ipython -from ipykernel.comm import Comm from traitlets import ( - HasTraits, Unicode, Dict, Instance, List, Int, Set, Bytes, observe, default, Container, + Any, HasTraits, Unicode, Dict, Instance, List, Int, Set, Bytes, observe, default, Container, Undefined) from json import loads as jsonloads, dumps as jsondumps @@ -480,7 +479,7 @@ def get_view_spec(self): _view_count = Int(None, allow_none=True, 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) - comm = Instance('ipykernel.comm.Comm', allow_none=True) + comm = Any(None, allow_none=True) keys = List(help="The traits which are synced.") @@ -525,7 +524,15 @@ def open(self): if self._model_id is not None: args['comm_id'] = self._model_id - self.comm = Comm(**args) + try: + from comm import create_comm + except ImportError: + def create_comm(**kwargs): + from ipykernel.comm import Comm + + return Comm(**kwargs) + + self.comm = create_comm(**args) @observe('comm') def _comm_changed(self, change): @@ -686,7 +693,7 @@ def notify_change(self, change): # Send the state to the frontend before the user-registered callbacks # are called. name = change['name'] - if self.comm is not None and self.comm.kernel is not None: + if self.comm is not None: # Make sure this isn't information that the front-end just sent us. if name in self.keys and self._should_send_property(name, getattr(self, name)): # Send new state to front-end @@ -814,7 +821,7 @@ def _repr_mimebundle_(self, **kwargs): def _send(self, msg, buffers=None): """Sends a message to the model in the front-end.""" - if self.comm is not None and self.comm.kernel is not None: + if self.comm is not None: self.comm.send(data=msg, buffers=buffers) def _repr_keys(self): diff --git a/python/ipywidgets/ipywidgets/widgets/widget_output.py b/python/ipywidgets/ipywidgets/widgets/widget_output.py index 6d1bfdae4b..c5c3b072b4 100644 --- a/python/ipywidgets/ipywidgets/widgets/widget_output.py +++ b/python/ipywidgets/ipywidgets/widgets/widget_output.py @@ -111,9 +111,9 @@ def __enter__(self): kernel = None if ip and getattr(ip, "kernel", None) is not None: kernel = ip.kernel - elif self.comm is not None and self.comm.kernel is not None: + elif self.comm is not None and getattr(self.comm, "kernel", None) is not None: kernel = self.comm.kernel - + if kernel: parent = None if hasattr(kernel, "get_parent"): @@ -134,7 +134,9 @@ def __exit__(self, etype, evalue, tb): if ip: kernel = ip ip.showtraceback((etype, evalue, tb), tb_offset=0) - elif self.comm is not None and self.comm.kernel is not None: + elif (self.comm is not None and + getattr(self.comm, "kernel", None) is not None and + getattr(self.comm.kernel, "send_response", None) is not None): kernel = self.comm.kernel kernel.send_response(kernel.iopub_socket, u'error', diff --git a/python/ipywidgets/setup.cfg b/python/ipywidgets/setup.cfg index 8d60e3fcf2..0c63d8892e 100644 --- a/python/ipywidgets/setup.cfg +++ b/python/ipywidgets/setup.cfg @@ -34,7 +34,6 @@ zip_safe = False packages = find: install_requires = - ipykernel>=4.5.1 ipython>=6.1.0 traitlets>=4.3.1 widgetsnbextension~=4.0 @@ -43,6 +42,7 @@ install_requires = [options.extras_require] test = jsonschema + ipykernel pytest>=3.6.0 pytest-cov pytz